diff --git a/examples/up.conf.example b/examples/up.conf.example index 942c53cd..bb0f09dd 100644 --- a/examples/up.conf.example +++ b/examples/up.conf.example @@ -55,7 +55,7 @@ # provide one and dynamic is disabled. In other words, you can just add your # controller here and delete the following section. Either works. [unifi.defaults] - name = "https://127.0.0.1:8443" + role = "https://127.0.0.1:8443" url = "https://127.0.0.1:8443" user = "unifipoller" pass = "unifipoller" @@ -68,7 +68,8 @@ [[unifi.controller]] # Friendly name used in dashboards. Uses URL if left empty; which is fine. # Avoid changing this later because it will live forever in your database. - name = "" + # Multiple controllers may share a role. This allows grouping during scrapes. + role = "" url = "https://127.0.0.1:8443" # Make a read-only user in the UniFi Admin Settings. diff --git a/examples/up.json.example b/examples/up.json.example index a4e0a401..d523eee2 100644 --- a/examples/up.json.example +++ b/examples/up.json.example @@ -24,7 +24,7 @@ "unifi": { "dynamic": false, "defaults": { - "name": "https://127.0.0.1:8443", + "role": "https://127.0.0.1:8443", "user": "unifipoller", "pass": "unifipoller", "url": "https://127.0.0.1:8443", @@ -35,7 +35,7 @@ }, "controllers": [ { - "name": "", + "role": "", "user": "unifipoller", "pass": "unifipoller", "url": "https://127.0.0.1:8443", diff --git a/examples/up.xml.example b/examples/up.xml.example index 094f8347..d7caacc7 100644 --- a/examples/up.xml.example +++ b/examples/up.xml.example @@ -25,7 +25,7 @@ - + all unifipoller unifipoller @@ -36,7 +36,7 @@ - + all unifipoller unifipoller diff --git a/examples/up.yaml.example b/examples/up.yaml.example index edf08506..a675fd8e 100644 --- a/examples/up.yaml.example +++ b/examples/up.yaml.example @@ -26,7 +26,7 @@ influxdb: unifi: dynamic: false defaults: - name: "https://127.0.0.1:8443" + role: "https://127.0.0.1:8443" user: "unifipoller" pass: "unifipoller" url: "https://127.0.0.1:8443" @@ -36,8 +36,10 @@ unifi: save_ids: false save_sites: true + controllers: - - name: "" + # Repeat the following stanza to poll more controllers. + - role: "" user: "unifipoller" pass: "unifipoller" url: "https://127.0.0.1:8443" diff --git a/pkg/inputunifi/collector.go b/pkg/inputunifi/collector.go index 90f1feb4..85941fee 100644 --- a/pkg/inputunifi/collector.go +++ b/pkg/inputunifi/collector.go @@ -10,8 +10,8 @@ import ( ) func (u *InputUnifi) isNill(c *Controller) bool { - u.Config.RLock() - defer u.Config.RUnlock() + u.RLock() + defer u.RUnlock() return c.Unifi == nil } @@ -28,10 +28,10 @@ func (u *InputUnifi) newDynamicCntrlr(url string) (bool, *Controller) { return false, c } - ccopy := u.Config.Default // copy defaults into new controller + ccopy := u.Default // copy defaults into new controller c = &ccopy u.dynamic[url] = c - c.Name = url + c.Role = url c.URL = url return true, c @@ -60,7 +60,7 @@ func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) { u.Logf("Re-authenticating to UniFi Controller: %s", c.URL) if err := u.getUnifi(c); err != nil { - return nil, fmt.Errorf("re-authenticating to %s: %v", c.Name, err) + return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err) } } @@ -70,8 +70,8 @@ func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) { func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) { var err error - u.Config.RLock() - defer u.Config.RUnlock() + u.RLock() + defer u.RUnlock() m := &poller.Metrics{TS: time.Now()} // At this point, it's the Current Check. @@ -149,8 +149,8 @@ func (u *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *pol // Omits requested but unconfigured sites. Grabs the full list from the // controller and returns the sites provided in the config file. func (u *InputUnifi) getFilteredSites(c *Controller) (unifi.Sites, error) { - u.Config.RLock() - defer u.Config.RUnlock() + u.RLock() + defer u.RUnlock() sites, err := c.Unifi.GetSites() if err != nil { diff --git a/pkg/inputunifi/input.go b/pkg/inputunifi/input.go index a540964d..45d3e4e9 100644 --- a/pkg/inputunifi/input.go +++ b/pkg/inputunifi/input.go @@ -22,7 +22,7 @@ const ( // InputUnifi contains the running data. type InputUnifi struct { - Config *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"` + *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"` dynamic map[string]*Controller sync.Mutex // to lock the map above. poller.Logger @@ -34,7 +34,7 @@ type Controller struct { VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` SaveIDS bool `json:"save_ids" toml:"save_ids" xml:"save_ids" yaml:"save_ids"` SaveSites *bool `json:"save_sites" toml:"save_sites" xml:"save_sites" yaml:"save_sites"` - Name string `json:"name" toml:"name" xml:"name,attr" yaml:"name"` + Role string `json:"role" toml:"role" xml:"role,attr" yaml:"role"` User string `json:"user" toml:"user" xml:"user" yaml:"user"` Pass string `json:"pass" toml:"pass" xml:"pass" yaml:"pass"` URL string `json:"url" toml:"url" xml:"url" yaml:"url"` @@ -65,8 +65,8 @@ func init() { func (u *InputUnifi) getUnifi(c *Controller) error { var err error - u.Config.Lock() - defer u.Config.Unlock() + u.Lock() + defer u.Unlock() if c.Unifi != nil { c.Unifi.CloseIdleConnections() @@ -94,8 +94,8 @@ func (u *InputUnifi) getUnifi(c *Controller) error { // checkSites makes sure the list of provided sites exists on the controller. // This only runs once during initialization. func (u *InputUnifi) checkSites(c *Controller) error { - u.Config.RLock() - defer u.Config.RUnlock() + u.RLock() + defer u.RUnlock() if len(c.Sites) < 1 || c.Sites[0] == "" { c.Sites = []string{"all"} @@ -113,7 +113,7 @@ func (u *InputUnifi) checkSites(c *Controller) error { msg = append(msg, site.Name+" ("+site.Desc+")") } - u.Logf("Found %d site(s) on controller %s: %v", len(msg), c.Name, strings.Join(msg, ", ")) + u.Logf("Found %d site(s) on controller %s: %v", len(msg), c.Role, strings.Join(msg, ", ")) if StringInSlice("all", c.Sites) { c.Sites = []string{"all"} @@ -130,7 +130,7 @@ FIRST: continue FIRST } } - u.LogErrorf("Configured site not found on controller %s: %v", c.Name, s) + u.LogErrorf("Configured site not found on controller %s: %v", c.Role, s) } if c.Sites = keep; len(keep) < 1 { @@ -168,8 +168,8 @@ func (u *InputUnifi) setDefaults(c *Controller) { c.URL = defaultURL } - if c.Name == "" { - c.Name = c.URL + if c.Role == "" { + c.Role = c.URL } if c.Pass == "" { diff --git a/pkg/inputunifi/interface.go b/pkg/inputunifi/interface.go index 42745687..daf21ae7 100644 --- a/pkg/inputunifi/interface.go +++ b/pkg/inputunifi/interface.go @@ -14,36 +14,36 @@ import ( // Initialize gets called one time when starting up. // Satisfies poller.Input interface. func (u *InputUnifi) Initialize(l poller.Logger) error { - if u.Config.Disable { + if u.Disable { l.Logf("UniFi input plugin disabled!") return nil } - if u.setDefaults(&u.Config.Default); len(u.Config.Controllers) < 1 && !u.Config.Dynamic { - new := u.Config.Default // copy defaults. - u.Config.Controllers = []*Controller{&new} + if u.setDefaults(&u.Default); len(u.Controllers) < 1 && !u.Dynamic { + new := u.Default // copy defaults. + u.Controllers = []*Controller{&new} } - if len(u.Config.Controllers) < 1 { + if len(u.Controllers) < 1 { l.Logf("No controllers configured. Polling dynamic controllers only!") } u.dynamic = make(map[string]*Controller) u.Logger = l - for _, c := range u.Config.Controllers { + for _, c := range u.Controllers { u.setDefaults(c) switch err := u.getUnifi(c); err { case nil: if err := u.checkSites(c); err != nil { - u.LogErrorf("checking sites on %s: %v", c.Name, err) + u.LogErrorf("checking sites on %s: %v", c.Role, err) } u.Logf("Configured UniFi Controller at %s v%s as user %s. Sites: %v", c.URL, c.Unifi.ServerVersion, c.User, c.Sites) default: - u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Name, err) + u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Role, err) } } @@ -57,7 +57,7 @@ func (u *InputUnifi) Metrics() (*poller.Metrics, bool, error) { // MetricsFrom grabs all the measurements from a UniFi controller and returns them. func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool, error) { - if u.Config.Disable { + if u.Disable { return nil, false, nil } @@ -65,9 +65,20 @@ func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool, metrics := &poller.Metrics{} ok := false + if filter != nil && filter.Path != "" { + if !u.Dynamic { + return metrics, false, fmt.Errorf("filter path requested but dynamic lookups disabled") + } + + // Attempt a dynamic metrics fetch from an unconfigured controller. + m, err := u.dynamicController(filter.Path) + + return m, err == nil && m != nil, err + } + // Check if the request is for an existing, configured controller. - for _, c := range u.Config.Controllers { - if filter != nil && !strings.EqualFold(c.Name, filter.Term) { + for _, c := range u.Controllers { + if filter != nil && !strings.EqualFold(c.Role, filter.Role) { continue } @@ -88,32 +99,21 @@ func (u *InputUnifi) MetricsFrom(filter *poller.Filter) (*poller.Metrics, bool, return metrics, ok, fmt.Errorf(strings.Join(errs, ", ")) } - if ok { - return metrics, true, nil - } - - if filter != nil && !u.Config.Dynamic { - return metrics, false, fmt.Errorf("scrape filter match failed and dynamic lookups disabled") - } - - // Attempt a dynamic metrics fetch from an unconfigured controller. - m, err := u.dynamicController(filter.Term) - - return m, err == nil && m != nil, err + return metrics, ok, nil } // RawMetrics returns API output from the first configured unifi controller. func (u *InputUnifi) RawMetrics(filter *poller.Filter) ([]byte, error) { - if l := len(u.Config.Controllers); filter.Unit >= l { + if l := len(u.Controllers); filter.Unit >= l { return nil, fmt.Errorf("control number %d not found, %d controller(s) configured (0 index)", filter.Unit, l) } - c := u.Config.Controllers[filter.Unit] + c := u.Controllers[filter.Unit] if u.isNill(c) { u.Logf("Re-authenticating to UniFi Controller: %s", c.URL) if err := u.getUnifi(c); err != nil { - return nil, fmt.Errorf("re-authenticating to %s: %v", c.Name, err) + return nil, fmt.Errorf("re-authenticating to %s: %v", c.Role, err) } } diff --git a/pkg/promunifi/collector.go b/pkg/promunifi/collector.go index bbfbda25..353bed01 100644 --- a/pkg/promunifi/collector.go +++ b/pkg/promunifi/collector.go @@ -129,17 +129,21 @@ func (u *promUnifi) Run(c poller.Collect) error { // ScrapeHandler allows prometheus to scrape a single source, instead of all sources. func (u *promUnifi) ScrapeHandler(w http.ResponseWriter, r *http.Request) { - t := &target{u: u, Filter: &poller.Filter{}} - if t.Name = r.URL.Query().Get("input"); t.Name == "" { + t := &target{u: u, Filter: &poller.Filter{ + Name: r.URL.Query().Get("input"), // "unifi" + Path: r.URL.Query().Get("path"), // url: "https://127.0.0.1:8443" + Role: r.URL.Query().Get("role"), // configured role in up.conf. + }} + if t.Name == "" { u.Collector.LogErrorf("input parameter missing on scrape from %v", r.RemoteAddr) http.Error(w, `'input' parameter must be specified (try "unifi")`, 400) return } - if t.Term = r.URL.Query().Get("target"); t.Term == "" { - u.Collector.LogErrorf("target parameter missing on scrape from %v", r.RemoteAddr) - http.Error(w, "'target' parameter must be specified, configured name, or unconfigured url", 400) + if t.Role == "" && t.Path == "" { + u.Collector.LogErrorf("role and path parameters missing on scrape from %v", r.RemoteAddr) + http.Error(w, "'role' OR 'path' parameter must be specified: configured role OR unconfigured url", 400) return }