242 lines
5.6 KiB
Go
242 lines
5.6 KiB
Go
package poller
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"golift.io/unifi"
|
|
)
|
|
|
|
// GetUnifi returns a UniFi controller interface.
|
|
func (u *UnifiPoller) GetUnifi(c Controller) error {
|
|
var err error
|
|
|
|
u.Lock()
|
|
defer u.Unlock()
|
|
|
|
if c.Unifi != nil {
|
|
c.Unifi.CloseIdleConnections()
|
|
}
|
|
// Create an authenticated session to the Unifi Controller.
|
|
c.Unifi, err = unifi.NewUnifi(&unifi.Config{
|
|
User: c.User,
|
|
Pass: c.Pass,
|
|
URL: c.URL,
|
|
VerifySSL: c.VerifySSL,
|
|
ErrorLog: u.LogErrorf, // Log all errors.
|
|
DebugLog: u.LogDebugf, // Log debug messages.
|
|
})
|
|
|
|
if err != nil {
|
|
c.Unifi = nil
|
|
return fmt.Errorf("unifi controller: %v", err)
|
|
}
|
|
|
|
u.LogDebugf("Authenticated with controller successfully, %s", c.URL)
|
|
|
|
return u.CheckSites(c)
|
|
}
|
|
|
|
// CheckSites makes sure the list of provided sites exists on the controller.
|
|
// This does not run in Lambda (run-once) mode.
|
|
func (u *UnifiPoller) CheckSites(c Controller) error {
|
|
u.LogDebugf("Checking Controller Sites List")
|
|
|
|
sites, err := c.Unifi.GetSites()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msg := []string{}
|
|
|
|
for _, site := range sites {
|
|
msg = append(msg, site.Name+" ("+site.Desc+")")
|
|
}
|
|
|
|
u.Logf("Found %d site(s) on controller: %v", len(msg), strings.Join(msg, ", "))
|
|
|
|
if StringInSlice("all", c.Sites) {
|
|
c.Sites = []string{"all"}
|
|
return nil
|
|
}
|
|
|
|
FIRST:
|
|
for _, s := range c.Sites {
|
|
for _, site := range sites {
|
|
if s == site.Name {
|
|
continue FIRST
|
|
}
|
|
}
|
|
return fmt.Errorf("configured site not found on controller: %v", s)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Metrics grabs all the measurements from a UniFi controller and returns them.
|
|
func (u *UnifiPoller) Metrics() (*Metrics, error) {
|
|
errs := []string{}
|
|
metrics := &Metrics{}
|
|
|
|
for _, c := range u.Config.Controllers {
|
|
m, err := u.checkAndPollController(c)
|
|
if err != nil {
|
|
errs = append(errs, err.Error())
|
|
}
|
|
|
|
if m == nil {
|
|
continue
|
|
}
|
|
|
|
metrics.Sites = append(metrics.Sites, m.Sites...)
|
|
metrics.Clients = append(metrics.Clients, m.Clients...)
|
|
metrics.IDSList = append(metrics.IDSList, m.IDSList...)
|
|
|
|
if m.Devices == nil {
|
|
continue
|
|
}
|
|
|
|
if metrics.Devices == nil {
|
|
metrics.Devices = &unifi.Devices{}
|
|
}
|
|
|
|
metrics.UAPs = append(metrics.UAPs, m.UAPs...)
|
|
metrics.USGs = append(metrics.USGs, m.USGs...)
|
|
metrics.USWs = append(metrics.USWs, m.USWs...)
|
|
metrics.UDMs = append(metrics.UDMs, m.UDMs...)
|
|
}
|
|
|
|
var err error
|
|
|
|
if len(errs) > 0 {
|
|
err = fmt.Errorf(strings.Join(errs, ", "))
|
|
}
|
|
|
|
return metrics, err
|
|
}
|
|
|
|
func (u *UnifiPoller) checkAndPollController(c Controller) (*Metrics, error) {
|
|
if c.Unifi == nil {
|
|
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
|
|
|
|
if err := u.GetUnifi(c); err != nil {
|
|
u.LogErrorf("re-authenticating to %s: %v", c.URL, err)
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
m, err := u.collectController(c)
|
|
if err == nil {
|
|
return m, nil
|
|
}
|
|
|
|
u.LogErrorf("collecting metrics %v", err)
|
|
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
|
|
|
|
if err := u.GetUnifi(c); err != nil {
|
|
u.LogErrorf("re-authenticating to %s: %v", c.URL, err)
|
|
return nil, err
|
|
}
|
|
|
|
return u.collectController(c)
|
|
}
|
|
|
|
func (u *UnifiPoller) collectController(c Controller) (*Metrics, error) {
|
|
var err error
|
|
|
|
m := &Metrics{TS: time.Now()} // At this point, it's the Current Check.
|
|
|
|
// Get the sites we care about.
|
|
if m.Sites, err = u.GetFilteredSites(c); err != nil {
|
|
return m, fmt.Errorf("unifi.GetSites(%v): %v", c.URL, err)
|
|
}
|
|
|
|
if c.SaveIDS {
|
|
m.IDSList, err = c.Unifi.GetIDS(m.Sites, time.Now().Add(2*time.Minute), time.Now())
|
|
if err != nil {
|
|
return m, fmt.Errorf("unifi.GetIDS(%v): %v", c.URL, err)
|
|
}
|
|
}
|
|
|
|
// Get all the points.
|
|
if m.Clients, err = c.Unifi.GetClients(m.Sites); err != nil {
|
|
return m, fmt.Errorf("unifi.GetClients(%v): %v", c.URL, err)
|
|
}
|
|
|
|
if m.Devices, err = c.Unifi.GetDevices(m.Sites); err != nil {
|
|
return m, fmt.Errorf("unifi.GetDevices(%v): %v", c.URL, err)
|
|
}
|
|
|
|
return u.augmentMetrics(c, m), nil
|
|
}
|
|
|
|
// augmentMetrics is our middleware layer between collecting metrics and writing them.
|
|
// This is where we can manipuate the returned data or make arbitrary decisions.
|
|
// This function currently adds parent device names to client metrics.
|
|
func (u *UnifiPoller) augmentMetrics(c Controller, metrics *Metrics) *Metrics {
|
|
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
|
|
return metrics
|
|
}
|
|
|
|
devices := make(map[string]string)
|
|
bssdIDs := make(map[string]string)
|
|
|
|
for _, r := range metrics.UAPs {
|
|
devices[r.Mac] = r.Name
|
|
for _, v := range r.VapTable {
|
|
bssdIDs[v.Bssid] = fmt.Sprintf("%s %s %s:", r.Name, v.Radio, v.RadioName)
|
|
}
|
|
}
|
|
|
|
for _, r := range metrics.USGs {
|
|
devices[r.Mac] = r.Name
|
|
}
|
|
|
|
for _, r := range metrics.USWs {
|
|
devices[r.Mac] = r.Name
|
|
}
|
|
|
|
for _, r := range metrics.UDMs {
|
|
devices[r.Mac] = r.Name
|
|
}
|
|
|
|
// These come blank, so set them here.
|
|
for i, c := range metrics.Clients {
|
|
metrics.Clients[i].SwName = devices[c.SwMac]
|
|
metrics.Clients[i].ApName = devices[c.ApMac]
|
|
metrics.Clients[i].GwName = devices[c.GwMac]
|
|
metrics.Clients[i].RadioDescription = bssdIDs[metrics.Clients[i].Bssid] + metrics.Clients[i].RadioProto
|
|
}
|
|
|
|
if !c.SaveSites {
|
|
metrics.Sites = nil
|
|
}
|
|
|
|
return metrics
|
|
}
|
|
|
|
// GetFilteredSites returns a list of sites to fetch data for.
|
|
// Omits requested but unconfigured sites. Grabs the full list from the
|
|
// controller and returns the sites provided in the config file.
|
|
func (u *UnifiPoller) GetFilteredSites(c Controller) (unifi.Sites, error) {
|
|
var i int
|
|
|
|
sites, err := c.Unifi.GetSites()
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(c.Sites) < 1 || StringInSlice("all", c.Sites) {
|
|
return sites, nil
|
|
}
|
|
|
|
for _, s := range sites {
|
|
// Only include valid sites in the request filter.
|
|
if StringInSlice(s.Name, c.Sites) {
|
|
sites[i] = s
|
|
i++
|
|
}
|
|
}
|
|
|
|
return sites[:i], nil
|
|
}
|