From 82a7092f7ee2bfe6a7c6e6bd75117129733761de Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 15 Apr 2019 01:26:24 -0700 Subject: [PATCH 1/5] Add support for multiple sites. --- Gopkg.lock | 6 +++--- cmd/unifi-poller/README.md | 4 ++++ cmd/unifi-poller/config.go | 23 +++++++++++---------- cmd/unifi-poller/main.go | 41 ++++++++++++++++++++++++++++++++++---- up.conf.example | 4 ++++ 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c037c0da..7a4f5692 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,12 +2,12 @@ [[projects]] - digest = "1:e7f0acf99afe9a7b03d270164bd2976b687e1aef02ab3a0abd8db0b4de44b817" + digest = "1:9543804b8019077cb769bc3b15bfb0640fc732f8b061df323a973171b979d02f" name = "github.com/golift/unifi" packages = ["."] pruneopts = "UT" - revision = "f8fec42fbe169dceb69f15276a2323fb007a4539" - version = "v1.0.0" + revision = "1779d2c07abd748d0d23868356ef517ac2e00e7e" + version = "v1.0.1" [[projects]] branch = "master" diff --git a/cmd/unifi-poller/README.md b/cmd/unifi-poller/README.md index b506d037..31b75051 100644 --- a/cmd/unifi-poller/README.md +++ b/cmd/unifi-poller/README.md @@ -30,6 +30,10 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB `Config File Parameters` + `sites` default: ["default"] + This list of strings should represent the names of sites on the unifi + controller that should be polled for data. + `interval` default: 30s How often to poll the controller for updated client and device data. The Unifi Controller only updates traffic stats about every 30 seconds. diff --git a/cmd/unifi-poller/config.go b/cmd/unifi-poller/config.go index 9b69ad86..68b8e217 100644 --- a/cmd/unifi-poller/config.go +++ b/cmd/unifi-poller/config.go @@ -22,17 +22,18 @@ const ( // Config represents the data needed to poll a controller and report to influxdb. type Config struct { - Interval Dur `json:"interval" toml:"interval" xml:"interval" yaml:"interval"` - Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"` - Quiet bool `json:"quiet" toml:"quiet" xml:"quiet" yaml:"quiet"` - VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` - InfluxURL string `json:"influx_url" toml:"influx_url" xml:"influx_url" yaml:"influx_url"` - InfluxUser string `json:"influx_user" toml:"influx_user" xml:"influx_user" yaml:"influx_user"` - InfluxPass string `json:"influx_pass" toml:"influx_pass" xml:"influx_pass" yaml:"influx_pass"` - InfluxDB string `json:"influx_db" toml:"influx_db" xml:"influx_db" yaml:"influx_db"` - UnifiUser string `json:"unifi_user" toml:"unifi_user" xml:"unifi_user" yaml:"unifi_user"` - UnifiPass string `json:"unifi_pass" toml:"unifi_pass" xml:"unifi_pass" yaml:"unifi_pass"` - UnifiBase string `json:"unifi_url" toml:"unifi_url" xml:"unifi_url" yaml:"unifi_url"` + Interval Dur `json:"interval" toml:"interval" xml:"interval" yaml:"interval"` + Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"` + Quiet bool `json:"quiet" toml:"quiet" xml:"quiet" yaml:"quiet"` + VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` + InfluxURL string `json:"influx_url" toml:"influx_url" xml:"influx_url" yaml:"influx_url"` + InfluxUser string `json:"influx_user" toml:"influx_user" xml:"influx_user" yaml:"influx_user"` + InfluxPass string `json:"influx_pass" toml:"influx_pass" xml:"influx_pass" yaml:"influx_pass"` + InfluxDB string `json:"influx_db" toml:"influx_db" xml:"influx_db" yaml:"influx_db"` + UnifiUser string `json:"unifi_user" toml:"unifi_user" xml:"unifi_user" yaml:"unifi_user"` + UnifiPass string `json:"unifi_pass" toml:"unifi_pass" xml:"unifi_pass" yaml:"unifi_pass"` + UnifiBase string `json:"unifi_url" toml:"unifi_url" xml:"unifi_url" yaml:"unifi_url"` + Sites []string `json:"sites" toml:"sites" xml:"sites" yaml:"sites"` } // Dur is used to UnmarshalTOML into a time.Duration value. diff --git a/cmd/unifi-poller/main.go b/cmd/unifi-poller/main.go index a6514334..51c88a95 100644 --- a/cmd/unifi-poller/main.go +++ b/cmd/unifi-poller/main.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "log" "os" + "strings" "time" "github.com/golift/unifi" @@ -86,6 +87,7 @@ func GetConfig(configFile string) (Config, error) { Debug: defaultDebug, Quiet: defaultQuiet, Interval: Dur{value: defaultInterval}, + Sites: []string{"default"}, } if buf, err := ioutil.ReadFile(configFile); err != nil { return config, err @@ -102,9 +104,11 @@ func (c *Config) PollUnifiController(controller *unifi.Unifi, infdb influx.Clien log.Println("[INFO] Everyting checks out! Beginning Poller Routine.") ticker := time.NewTicker(c.Interval.value) for range ticker.C { - if clients, err := controller.GetClients(); err != nil { + if sites, err := filterSites(controller, c.Sites); err != nil { + logErrors([]error{err}, "uni.GetSites()") + } else if clients, err := controller.GetClients(sites); err != nil { logErrors([]error{err}, "uni.GetClients()") - } else if devices, err := controller.GetDevices(); err != nil { + } else if devices, err := controller.GetDevices(sites); err != nil { logErrors([]error{err}, "uni.GetDevices()") } else if bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{Database: c.InfluxDB}); err != nil { logErrors([]error{err}, "influx.NewBatchPoints") @@ -113,12 +117,31 @@ func (c *Config) PollUnifiController(controller *unifi.Unifi, infdb influx.Clien } else if err := infdb.Write(bp); err != nil { logErrors([]error{err}, "infdb.Write(bp)") } else if !c.Quiet { - log.Println("[INFO] Logged Unifi States. Clients:", len(clients.UCLs), "- Wireless APs:", - len(devices.UAPs), "Gateways:", len(devices.USGs), "Switches:", len(devices.USWs)) + log.Printf("[INFO] Logged Unifi States. Sites: %d Clients: %d, Wireless APs: %d, Gateways: %d, Switches: %d", + len(sites), len(clients.UCLs), len(devices.UAPs), len(devices.USGs), len(devices.USWs)) } } } +// filterSites returns a list of sites to fetch data for. +// Omits requested but unconfigured sites. +func filterSites(controller *unifi.Unifi, filter []string) ([]string, error) { + sites, err := controller.GetSites() + if err != nil { + return filter, err + } else if len(filter) < 1 || StringInSlice("all", filter) { + return sites, nil + } + output := []string{} + for _, s := range filter { + // Do not return requested sites that are not configured. + if StringInSlice(s, sites) { + output = append(output, s) + } + } + return output, nil +} + // batchPoints combines all device and client data into influxdb data points. func batchPoints(devices *unifi.Devices, clients *unifi.Clients, batchPoints influx.BatchPoints) (errs []error) { process := func(asset Asset) error { @@ -162,3 +185,13 @@ func logErrors(errs []error, prefix string) { } } } + +// StringInSlice returns true if a string is in a slice. +func StringInSlice(str string, slc []string) bool { + for _, s := range slc { + if strings.EqualFold(s, str) { + return true + } + } + return false +} diff --git a/up.conf.example b/up.conf.example index 30f411f8..723779e1 100644 --- a/up.conf.example +++ b/up.conf.example @@ -3,6 +3,10 @@ # commented lines are defaults, uncomment to change. # ########################################################## +# If the controller has more than one site, specify which sites to poll here. +# If only one site, "default" is likely the correct name. +#sites = ["default"] + # The Unifi Controller only updates traffic stats about every 30 seconds. # Setting this to something lower may lead to "zeros" in your data. You've been warned. #interval = "30s" From 651d8acd07084d54b1f04439a2a8ae95839845e5 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 15 Apr 2019 01:33:20 -0700 Subject: [PATCH 2/5] Update readme and example config --- cmd/unifi-poller/README.md | 3 ++- up.conf.example | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/unifi-poller/README.md b/cmd/unifi-poller/README.md index 31b75051..691b7de3 100644 --- a/cmd/unifi-poller/README.md +++ b/cmd/unifi-poller/README.md @@ -32,7 +32,8 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB `sites` default: ["default"] This list of strings should represent the names of sites on the unifi - controller that should be polled for data. + controller that will be polled for data. Pass `all` in the list to + poll all sites. `interval` default: 30s How often to poll the controller for updated client and device data. diff --git a/up.conf.example b/up.conf.example index 723779e1..c1c13b18 100644 --- a/up.conf.example +++ b/up.conf.example @@ -5,6 +5,7 @@ # If the controller has more than one site, specify which sites to poll here. # If only one site, "default" is likely the correct name. +# Set this to ["all"] to poll all sites, no matter their names. #sites = ["default"] # The Unifi Controller only updates traffic stats about every 30 seconds. From f823c451856eb7afed87095a06638d8f4af7b2bb Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 15 Apr 2019 02:26:19 -0700 Subject: [PATCH 3/5] Use []Site type --- cmd/unifi-poller/main.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cmd/unifi-poller/main.go b/cmd/unifi-poller/main.go index 51c88a95..4d4b8f2c 100644 --- a/cmd/unifi-poller/main.go +++ b/cmd/unifi-poller/main.go @@ -125,21 +125,22 @@ func (c *Config) PollUnifiController(controller *unifi.Unifi, infdb influx.Clien // filterSites returns a list of sites to fetch data for. // Omits requested but unconfigured sites. -func filterSites(controller *unifi.Unifi, filter []string) ([]string, error) { +func filterSites(controller *unifi.Unifi, filter []string) ([]unifi.Site, error) { sites, err := controller.GetSites() if err != nil { - return filter, err + return []unifi.Site{{Name: "default", Desc: "DEFAULT"}}, err } else if len(filter) < 1 || StringInSlice("all", filter) { return sites, nil } - output := []string{} - for _, s := range filter { - // Do not return requested sites that are not configured. - if StringInSlice(s, sites) { - output = append(output, s) + var i int + for _, s := range sites { + // Only include valid sites in the request filter. + if StringInSlice(s.Name, filter) { + sites[i] = s + i++ } } - return output, nil + return sites[:i], nil } // batchPoints combines all device and client data into influxdb data points. From 84e96a860c1e2355dadfbee82209ab75ea4c5ef7 Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Mon, 15 Apr 2019 02:30:09 -0700 Subject: [PATCH 4/5] dont do this.. --- cmd/unifi-poller/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/unifi-poller/main.go b/cmd/unifi-poller/main.go index 4d4b8f2c..087d3cd4 100644 --- a/cmd/unifi-poller/main.go +++ b/cmd/unifi-poller/main.go @@ -128,7 +128,7 @@ func (c *Config) PollUnifiController(controller *unifi.Unifi, infdb influx.Clien func filterSites(controller *unifi.Unifi, filter []string) ([]unifi.Site, error) { sites, err := controller.GetSites() if err != nil { - return []unifi.Site{{Name: "default", Desc: "DEFAULT"}}, err + return nil, err } else if len(filter) < 1 || StringInSlice("all", filter) { return sites, nil } From 2d8160212e09c04452f67a063c9a31370dda970e Mon Sep 17 00:00:00 2001 From: David Newhall II Date: Thu, 30 May 2019 01:57:47 -0700 Subject: [PATCH 5/5] Fixes. --- Gopkg.lock | 8 ++-- cmd/unifi-poller/config.go | 37 +++++++++---------- cmd/unifi-poller/main.go | 7 +--- .../unifi-clients-grafana-dash.json | 2 +- .../unifi-uap-grafana-dash.json | 2 +- .../unifi-usg-grafana-dash.json | 2 +- 6 files changed, 26 insertions(+), 32 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 7a4f5692..b847cff4 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,12 +2,12 @@ [[projects]] - digest = "1:9543804b8019077cb769bc3b15bfb0640fc732f8b061df323a973171b979d02f" + digest = "1:dbd1c953926f2aca29aeb24f13af5875a3b31f09ed7093cb18a5409d4b9fc9f1" name = "github.com/golift/unifi" packages = ["."] pruneopts = "UT" - revision = "1779d2c07abd748d0d23868356ef517ac2e00e7e" - version = "v1.0.1" + revision = "034a62e1cb6ef13e724cfab9b8f87dbb130d493c" + version = "v2.0.0" [[projects]] branch = "master" @@ -19,7 +19,7 @@ "v2", ] pruneopts = "UT" - revision = "16c852ea613fa2d42fcdccc9a8b0802a8bdc6140" + revision = "8ff2fc3824fcb533795f9a2f233275f0bb18d6c5" [[projects]] digest = "1:b56c589214f01a5601e0821387db484617392d0042f26234bf2da853a2f498a1" diff --git a/cmd/unifi-poller/config.go b/cmd/unifi-poller/config.go index 68b8e217..bc27d9d4 100644 --- a/cmd/unifi-poller/config.go +++ b/cmd/unifi-poller/config.go @@ -7,33 +7,30 @@ var Version = "v1.0.0" const ( // App defaults in case they're missing from the config. - defaultConfFile = "/usr/local/etc/unifi-poller/up.conf" - defaultInterval = 30 * time.Second - defaultInfxDb = "unifi" - defaultInfxUser = "unifi" - defaultInfxPass = "unifi" - defaultInfxURL = "http://127.0.0.1:8086" - defaultUnifUser = "influx" - defaultUnifURL = "https://127.0.0.1:8443" - defaultVerifySSL = false - defaultDebug = false - defaultQuiet = false + defaultConfFile = "/usr/local/etc/unifi-poller/up.conf" + defaultInterval = 30 * time.Second + defaultInfxDb = "unifi" + defaultInfxUser = "unifi" + defaultInfxPass = "unifi" + defaultInfxURL = "http://127.0.0.1:8086" + defaultUnifUser = "influx" + defaultUnifURL = "https://127.0.0.1:8443" ) // Config represents the data needed to poll a controller and report to influxdb. type Config struct { - Interval Dur `json:"interval" toml:"interval" xml:"interval" yaml:"interval"` + Interval Dur `json:"interval,_omitempty" toml:"interval,_omitempty" xml:"interval" yaml:"interval"` Debug bool `json:"debug" toml:"debug" xml:"debug" yaml:"debug"` Quiet bool `json:"quiet" toml:"quiet" xml:"quiet" yaml:"quiet"` VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"` - InfluxURL string `json:"influx_url" toml:"influx_url" xml:"influx_url" yaml:"influx_url"` - InfluxUser string `json:"influx_user" toml:"influx_user" xml:"influx_user" yaml:"influx_user"` - InfluxPass string `json:"influx_pass" toml:"influx_pass" xml:"influx_pass" yaml:"influx_pass"` - InfluxDB string `json:"influx_db" toml:"influx_db" xml:"influx_db" yaml:"influx_db"` - UnifiUser string `json:"unifi_user" toml:"unifi_user" xml:"unifi_user" yaml:"unifi_user"` - UnifiPass string `json:"unifi_pass" toml:"unifi_pass" xml:"unifi_pass" yaml:"unifi_pass"` - UnifiBase string `json:"unifi_url" toml:"unifi_url" xml:"unifi_url" yaml:"unifi_url"` - Sites []string `json:"sites" toml:"sites" xml:"sites" yaml:"sites"` + InfluxURL string `json:"influx_url,_omitempty" toml:"influx_url,_omitempty" xml:"influx_url" yaml:"influx_url"` + InfluxUser string `json:"influx_user,_omitempty" toml:"influx_user,_omitempty" xml:"influx_user" yaml:"influx_user"` + InfluxPass string `json:"influx_pass,_omitempty" toml:"influx_pass,_omitempty" xml:"influx_pass" yaml:"influx_pass"` + InfluxDB string `json:"influx_db,_omitempty" toml:"influx_db,_omitempty" xml:"influx_db" yaml:"influx_db"` + UnifiUser string `json:"unifi_user,_omitempty" toml:"unifi_user,_omitempty" xml:"unifi_user" yaml:"unifi_user"` + UnifiPass string `json:"unifi_pass,_omitempty" toml:"unifi_pass,_omitempty" xml:"unifi_pass" yaml:"unifi_pass"` + UnifiBase string `json:"unifi_url,_omitempty" toml:"unifi_url,_omitempty" xml:"unifi_url" yaml:"unifi_url"` + Sites []string `json:"sites,_omitempty" toml:"sites,_omitempty" xml:"sites" yaml:"sites"` } // Dur is used to UnmarshalTOML into a time.Duration value. diff --git a/cmd/unifi-poller/main.go b/cmd/unifi-poller/main.go index 087d3cd4..a7092015 100644 --- a/cmd/unifi-poller/main.go +++ b/cmd/unifi-poller/main.go @@ -28,7 +28,7 @@ func main() { log.Fatalf("Config Error '%v': %v", configFile, err) } // Create an authenticated session to the Unifi Controller. - controller, err := unifi.GetController(config.UnifiUser, config.UnifiPass, config.UnifiBase, config.VerifySSL) + controller, err := unifi.NewUnifi(config.UnifiUser, config.UnifiPass, config.UnifiBase, config.VerifySSL) if err != nil { log.Fatalln("Unifi Controller Error:", err) } else if !config.Quiet { @@ -53,7 +53,7 @@ func main() { controller.DebugLog = nil } else { log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser) - log.Println("Polling Unifi Controller, interval:", config.Interval.value) + log.Printf("Polling Unifi Controller (sites %v), interval: %v", config.Sites, config.Interval.value) } config.PollUnifiController(controller, infdb) } @@ -83,9 +83,6 @@ func GetConfig(configFile string) (Config, error) { UnifiUser: defaultUnifUser, UnifiPass: os.Getenv("UNIFI_PASSWORD"), UnifiBase: defaultUnifURL, - VerifySSL: defaultVerifySSL, - Debug: defaultDebug, - Quiet: defaultQuiet, Interval: Dur{value: defaultInterval}, Sites: []string{"default"}, } diff --git a/grafana-dashboards/unifi-clients-grafana-dash.json b/grafana-dashboards/unifi-clients-grafana-dash.json index af41b0bb..7befce75 100644 --- a/grafana-dashboards/unifi-clients-grafana-dash.json +++ b/grafana-dashboards/unifi-clients-grafana-dash.json @@ -3,7 +3,7 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": "Unifi", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", diff --git a/grafana-dashboards/unifi-uap-grafana-dash.json b/grafana-dashboards/unifi-uap-grafana-dash.json index 3fdf2626..7d7e7ecb 100644 --- a/grafana-dashboards/unifi-uap-grafana-dash.json +++ b/grafana-dashboards/unifi-uap-grafana-dash.json @@ -57,7 +57,7 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": "${DS_UNIFI}", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", diff --git a/grafana-dashboards/unifi-usg-grafana-dash.json b/grafana-dashboards/unifi-usg-grafana-dash.json index f43847b9..26d01dfc 100644 --- a/grafana-dashboards/unifi-usg-grafana-dash.json +++ b/grafana-dashboards/unifi-usg-grafana-dash.json @@ -57,7 +57,7 @@ "list": [ { "builtIn": 1, - "datasource": "-- Grafana --", + "datasource": "${DS_UNIFI}", "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)",