diff --git a/integrations/influxunifi/Gopkg.lock b/integrations/influxunifi/Gopkg.lock index 5f537b36..c037c0da 100644 --- a/integrations/influxunifi/Gopkg.lock +++ b/integrations/influxunifi/Gopkg.lock @@ -2,59 +2,68 @@ [[projects]] + digest = "1:e7f0acf99afe9a7b03d270164bd2976b687e1aef02ab3a0abd8db0b4de44b817" name = "github.com/golift/unifi" packages = ["."] - revision = "24c7eb106b3c9ff4260c88b54c02f0f86301fa75" - version = "0.9.0" + pruneopts = "UT" + revision = "f8fec42fbe169dceb69f15276a2323fb007a4539" + version = "v1.0.0" [[projects]] - name = "github.com/influxdata/influxdb" - packages = [ - "client/v2", - "models", - "pkg/escape" - ] - revision = "698dbc789aff13c2678357a6b93ff73dd7136571" - version = "v1.7.3" - -[[projects]] - name = "github.com/influxdata/platform" + branch = "master" + digest = "1:50708c8fc92aec981df5c446581cf9f90ba9e2a5692118e0ce75d4534aaa14a2" + name = "github.com/influxdata/influxdb1-client" packages = [ "models", - "pkg/escape" + "pkg/escape", + "v2", ] - revision = "0f79e4ea3248354c789cba274542e0a8e55971db" + pruneopts = "UT" + revision = "16c852ea613fa2d42fcdccc9a8b0802a8bdc6140" [[projects]] + digest = "1:b56c589214f01a5601e0821387db484617392d0042f26234bf2da853a2f498a1" name = "github.com/naoina/go-stringutil" packages = ["."] + pruneopts = "UT" revision = "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b" version = "v0.1.0" [[projects]] + digest = "1:f58c3d0e46b64878d00652fedba24ee879725191ab919dca7b62586859281c04" name = "github.com/naoina/toml" packages = [ ".", - "ast" + "ast", ] + pruneopts = "UT" revision = "e6f5723bf2a66af014955e0888881314cf294129" version = "v0.1.1" [[projects]] + digest = "1:8fd3a15613c7e70cceff3aa03dd57560dba87c4868864e397d5eb2f14addd3f5" name = "github.com/ogier/pflag" packages = ["."] + pruneopts = "UT" revision = "32a05c62658bd1d7c7e75cbc8195de5d585fde0f" version = "v0.0.1" [[projects]] + digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "UT" revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" version = "v0.8.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "5fa0fad44ae6516c3c699a60210634ddcc7d36e6b8a10bae25d0efcc001bb768" + input-imports = [ + "github.com/golift/unifi", + "github.com/influxdata/influxdb1-client/v2", + "github.com/naoina/toml", + "github.com/ogier/pflag", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/integrations/influxunifi/Gopkg.toml b/integrations/influxunifi/Gopkg.toml index 9a133e8e..a63bab83 100644 --- a/integrations/influxunifi/Gopkg.toml +++ b/integrations/influxunifi/Gopkg.toml @@ -24,15 +24,6 @@ # go-tests = true # unused-packages = true - -[[constraint]] - branch = "master" - name = "github.com/golift/unifi" - -[[constraint]] - name = "github.com/influxdata/influxdb" - version = "1.7.3" - [[constraint]] name = "github.com/naoina/toml" version = "0.1.1" diff --git a/integrations/influxunifi/Makefile b/integrations/influxunifi/Makefile index 08c692bf..d4fffac1 100644 --- a/integrations/influxunifi/Makefile +++ b/integrations/influxunifi/Makefile @@ -1,5 +1,5 @@ PACKAGES=`find ./cmd -mindepth 1 -maxdepth 1 -type d` -LIBRARYS=./unidev +BINARY=unifi-poller all: clean man build @@ -14,30 +14,45 @@ linux: install: man test build @echo "If you get errors, you may need sudo." + # Install binary. binary. GOBIN=/usr/local/bin go install -ldflags "-w -s" ./... - mkdir -p /usr/local/etc/unifi-poller /usr/local/share/man/man1 - test -f /usr/local/etc/unifi-poller/up.conf || cp up.conf.example /usr/local/etc/unifi-poller/up.conf - test -d ~/Library/LaunchAgents && cp startup/launchd/com.github.davidnewhall.unifi-poller.plist ~/Library/LaunchAgents || true - test -d /etc/systemd/system && cp startup/systemd/unifi-poller.service /etc/systemd/system || true + # Making config folders and installing man page. + mkdir -p /usr/local/etc/$(BINARY) /usr/local/share/man/man1 mv *.1.gz /usr/local/share/man/man1 + # Installing config file, man page and launch agent or systemd unit file. + test -f /usr/local/etc/$(BINARY)/up.conf || cp up.conf.example /usr/local/etc/$(BINARY)/up.conf + test -d ~/Library/LaunchAgents && cp startup/launchd/com.github.davidnewhall.$(BINARY).plist ~/Library/LaunchAgents || true + test -d /etc/systemd/system && cp startup/systemd/$(BINARY).service /etc/systemd/system || true + # Making systemd happy by telling it to reload. + test -x /bin/systemctl && /bin/systemctl --system daemon-reload || true + @echo + @echo "Installation Complete. Edit the config file @ /usr/local/etc/$(BINARY)/up.conf " + @echo "Then start the daemon with:" + @test -d ~/Library/LaunchAgents && echo " launchctl load ~/Library/LaunchAgents/com.github.davidnewhall.$(BINARY).plist" || true + @test -d /etc/systemd/system && echo " sudo /bin/systemctl start $(BINARY)" || true + @echo "Examine the log file at: /usr/local/var/log/$(BINARY).log (logs may go elsewhere on linux, check syslog)" uninstall: @echo "If you get errors, you may need sudo." - test -f ~/Library/LaunchAgents/com.github.davidnewhall.unifi-poller.plist && launchctl unload ~/Library/LaunchAgents/com.github.davidnewhall.unifi-poller.plist || true - test -f /etc/systemd/system/unifi-poller.service && systemctl stop unifi-poller || true - rm -rf /usr/local/{etc,bin}/unifi-poller /usr/local/share/man/man1/unifi-poller.1.gz - rm -f ~/Library/LaunchAgents/com.github.davidnewhall.unifi-poller.plist - rm -f /etc/systemd/system/unifi-poller.service + # Stopping the daemon + test -x /bin/systemctl && /bin/systemctl stop $(BINARY) || true + test -x /bin/launchctl && /bin/launchctl unload ~/Library/LaunchAgents/com.github.davidnewhall.$(BINARY).plist || true + # Deleting config file, binary, man page, launch agent or unit file. + rm -rf /usr/local/{etc,bin}/$(BINARY) /usr/local/share/man/man1/$(BINARY).1.gz + rm -f ~/Library/LaunchAgents/com.github.davidnewhall.$(BINARY).plist + rm -f /etc/systemd/system/$(BINARY).service + # Making systemd happy by telling it to reload. + test -x /bin/systemctl && /bin/systemctl --system daemon-reload || true test: lint for p in $(PACKAGES) $(LIBRARYS); do go test -race -covermode=atomic $${p}; done lint: - goimports -l $(PACKAGES) $(LIBRARYS) - gofmt -l $(PACKAGES) $(LIBRARYS) - errcheck $(PACKAGES) $(LIBRARYS) - golint $(PACKAGES) $(LIBRARYS) - go vet $(PACKAGES) $(LIBRARYS) + goimports -l $(PACKAGES) + gofmt -l $(PACKAGES) + errcheck $(PACKAGES) + golint $(PACKAGES) + go vet $(PACKAGES) man: script/build_manpages.sh ./ diff --git a/integrations/influxunifi/README.md b/integrations/influxunifi/README.md index 6ad0f622..6621065c 100644 --- a/integrations/influxunifi/README.md +++ b/integrations/influxunifi/README.md @@ -1,7 +1,7 @@ -# Unifi +# Unifi Poller Collect your Unifi Controller Data and send it to an InfluxDB instance. -Grafana dashboards included. +Grafana dashboards included. Updated 2019. ## Installation @@ -37,19 +37,18 @@ for making this dashboard; it gave me a fantastic start to making my own. # What now... +- Better Linux support and testing + +I only, personally, run this on a Mac 10.13.something. I know others are using +Linux and it's working, but I need more feedback. Does the unit file work? Are +you able to stop and start the service? Does the Makefile do the right things? + - I probably suck at InfluxDB. I don't know what should be a tag and what should be a field. I think I did my best, but there's certainly room for improvements in both -the data input and the Grafana graphs (output). - - -- The USW and USG code needs love. - -Up to this point, my focus has been on UAP. I have only included dashboards -that focus on UAP. I am still working on the other two, but it may be a while -before I get around to publishing them. Help is appreciated. - +the data input and the Grafana graphs (output). I'm always iterating, but +if you find a deficiency or something that can be improved, let me know. - Are there other devices that need to be included? @@ -60,25 +59,27 @@ ports, some switches have 10Gb, etc. These are things I do not have data on to write code for. If you have these devices, and want them graphed, open an Issue and lets discuss. - - Better Installation instructions. If you're a nerd you can probably figure it out. I'd still like some pretty pictures and maybe even a Twitch VOD. - - Sanity Checking Did I actually graph the right data in the right way? Some validation would be nice. - - Radios, Frequencies, Interfaces, vAPs My access points only seem to have two radios, one interface and vAP per radio. I'm not sure if the graphs, as-is, provide enough insight into APs with other configurations. Help me figure that out? +- It possibly loses access to the controller at some point. + +I noticed metrics stop updating after a while. I think the new code will help +isolate why this happens. We may need to issue a reconnect and get a new cookie. + # What's it look like? Here's a picture of the Client dashboard. diff --git a/integrations/influxunifi/cmd/unifi-poller/README.md b/integrations/influxunifi/cmd/unifi-poller/README.md index e28fbeee..b506d037 100644 --- a/integrations/influxunifi/cmd/unifi-poller/README.md +++ b/integrations/influxunifi/cmd/unifi-poller/README.md @@ -13,9 +13,9 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB ## OPTIONS -`unifi-poller [-c ] [-h] [-v]` +`unifi-poller [-c ] [-h] [-v]` - -c, --config + -c, --config Provide a configuration file (instead of the default). -v, --version @@ -24,6 +24,51 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB -h, --help Display usage and exit. +## CONFIGURATION + +* Config File Default Location: /usr/local/etc/unifi-poller/up.conf + +`Config File Parameters` + + `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. + + `debug` default: false + This turns on time stamps and line numbers in logs, outputs a few extra + lines of information while processing. + + `quiet` default: false + Setting this to true will turn off per-device and per-interval logs. Only + errors will be logged. Using this with debug=true adds line numbers to + any error logs. + + `influx_url` default: http://127.0.0.1:8086 + This is the URL where the Influx web server is available. + + `influx_user` default: unifi + Username used to authenticate with InfluxDB. Many servers do not use auth. + + `influx_pass` default: unifi + Password used to authenticate with InfluxDB. + + `influx_db` default: unifi + Custom database created in InfluxDB to use with this application. + + `unifi_url` default: https://127.0.0.1:8443 + This is the URL where the Unifi Controller is available. + + `unifi_user` default: influxdb + Username used to authenticate with Unifi controller. This should be a + special service account created on the control with read-only access. + + `unifi_user` no default ENV: UNIFI_PASSWORD + Password used to authenticate with Unifi controller. This can also be + set in an environment variable instead of a configuration file. + + `verify_ssl` default: false + If your Unifi controller has a valid SSL certificate, you can enable + this option to validate it. Otherwise, any SSL certificate is valid. ## GO DURATION @@ -49,4 +94,5 @@ Example Use: `1m`, `5h`, `100ms`, `17s`, `1s45ms`, `1m3s` * https://github.com/davidnewhall/unifi-poller * /usr/local/bin/unifi-poller +* config-file: /usr/local/etc/unifi-poller/up.conf * previously: https://github.com/dewski/unifi diff --git a/integrations/influxunifi/cmd/unifi-poller/config.go b/integrations/influxunifi/cmd/unifi-poller/config.go index cd20c8fa..9b69ad86 100644 --- a/integrations/influxunifi/cmd/unifi-poller/config.go +++ b/integrations/influxunifi/cmd/unifi-poller/config.go @@ -3,7 +3,7 @@ package main import "time" // Version is loosely followed. -var Version = "v0.3.0" +var Version = "v1.0.0" const ( // App defaults in case they're missing from the config. diff --git a/integrations/influxunifi/cmd/unifi-poller/main.go b/integrations/influxunifi/cmd/unifi-poller/main.go index cc10df24..a6514334 100644 --- a/integrations/influxunifi/cmd/unifi-poller/main.go +++ b/integrations/influxunifi/cmd/unifi-poller/main.go @@ -8,31 +8,37 @@ import ( "time" "github.com/golift/unifi" - influx "github.com/influxdata/influxdb/client/v2" + influx "github.com/influxdata/influxdb1-client/v2" "github.com/naoina/toml" flag "github.com/ogier/pflag" ) -func main() { +// Asset is used to give all devices and clients a common interface. +type Asset interface { + Points() ([]*influx.Point, error) +} +func main() { configFile := parseFlags() log.Println("Unifi-Poller Starting Up! PID:", os.Getpid()) config, err := GetConfig(configFile) if err != nil { flag.Usage() log.Fatalf("Config Error '%v': %v", configFile, err) - } else if log.SetFlags(0); config.Debug { - log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate) - log.Println("Debug Logging Enabled") } - log.Println("Loaded Configuration:", configFile) // Create an authenticated session to the Unifi Controller. - device, err := unifi.AuthController(config.UnifiUser, config.UnifiPass, config.UnifiBase, config.VerifySSL) + controller, err := unifi.GetController(config.UnifiUser, config.UnifiPass, config.UnifiBase, config.VerifySSL) if err != nil { log.Fatalln("Unifi Controller Error:", err) } else if !config.Quiet { log.Println("Authenticated to Unifi Controller @", config.UnifiBase, "as user", config.UnifiUser) } + controller.ErrorLog = log.Printf + if log.SetFlags(0); config.Debug { + controller.DebugLog = log.Printf + log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate) + log.Println("Debug Logging Enabled") + } infdb, err := influx.NewHTTPClient(influx.HTTPConfig{ Addr: config.InfluxURL, Username: config.InfluxUser, @@ -40,15 +46,15 @@ func main() { }) if err != nil { log.Fatalln("InfluxDB Error:", err) - } else if config.Quiet { + } + if config.Quiet { // Doing it this way allows debug error logs (line numbers, etc) - unifi.Debug = false + 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.Println("Everyting checks out! Beginning Poller Routine.") - config.PollUnifiController(infdb, device, config.Quiet) + config.PollUnifiController(controller, infdb) } func parseFlags() string { @@ -87,39 +93,72 @@ func GetConfig(configFile string) (Config, error) { } else if err := toml.Unmarshal(buf, &config); err != nil { return config, err } + log.Println("Loaded Configuration:", configFile) return config, nil } // PollUnifiController runs forever, polling and pushing. -func (c *Config) PollUnifiController(infdb influx.Client, device *unifi.AuthedReq, quiet bool) { +func (c *Config) PollUnifiController(controller *unifi.Unifi, infdb influx.Client) { + log.Println("[INFO] Everyting checks out! Beginning Poller Routine.") ticker := time.NewTicker(c.Interval.value) for range ticker.C { - var clients, devices []unifi.Asset - var bp influx.BatchPoints - var err error - if clients, err = device.GetUnifiClientAssets(); err != nil { - log.Println("ERROR unifi.GetUnifiClientsAssets():", err) - } else if devices, err = device.GetUnifiDeviceAssets(); err != nil { - log.Println("ERROR unifi.GetUnifiDeviceAssets():", err) - } else if bp, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: c.InfluxDB}); err != nil { - log.Println("ERROR influx.NewBatchPoints:", err) - } - if err != nil { - continue - } - for _, asset := range append(clients, devices...) { - if pt, errr := asset.Points(); errr != nil { - log.Println("ERROR asset.Points():", errr) - } else { - bp.AddPoints(pt) - } - } - if err = infdb.Write(bp); err != nil { - log.Println("ERROR infdb.Write(bp):", err) - continue - } - if !quiet { - log.Println("Logged Unifi States. Clients:", len(clients), "- Devices:", len(devices)) + if clients, err := controller.GetClients(); err != nil { + logErrors([]error{err}, "uni.GetClients()") + } else if devices, err := controller.GetDevices(); 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") + } else if errs := batchPoints(devices, clients, bp); errs != nil && hasErr(errs) { + logErrors(errs, "asset.Points()") + } 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)) + } + } +} + +// 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 { + influxPoints, err := asset.Points() + if err != nil { + return err + } + batchPoints.AddPoints(influxPoints) + return nil + } + for _, asset := range devices.UAPs { + errs = append(errs, process(asset)) + } + for _, asset := range devices.USGs { + errs = append(errs, process(asset)) + } + for _, asset := range devices.USWs { + errs = append(errs, process(asset)) + } + for _, asset := range clients.UCLs { + errs = append(errs, process(asset)) + } + return +} + +// hasErr checks a list of errors for a non-nil. +func hasErr(errs []error) bool { + for _, err := range errs { + if err != nil { + return true + } + } + return false +} + +// logErrors writes a slice of errors, with a prefix, to log-out. +func logErrors(errs []error, prefix string) { + for _, err := range errs { + if err != nil { + log.Println("[ERROR]", prefix+":", err.Error()) } } } diff --git a/integrations/influxunifi/script/build_manpages.sh b/integrations/influxunifi/script/build_manpages.sh index 6273888a..19493dd9 100755 --- a/integrations/influxunifi/script/build_manpages.sh +++ b/integrations/influxunifi/script/build_manpages.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -o pipefail + OUTPUT=$1 # This requires the installation of `ronn`: sudo gem install ronn @@ -8,6 +10,6 @@ for f in cmd/*/README.md;do PKGNOCMD="${f#cmd/}" PKG="${PKGNOCMD%/README.md}" echo "Creating Man Page: ${f} -> ${OUTPUT}${PKG}.1.gz" - echo "If this produces an error. Install ronn; something like: sudo gem install ronn" - ronn < "$f" | gzip -9 > "${OUTPUT}${PKG}.1.gz" + ronn < "$f" | gzip -9 > "${OUTPUT}${PKG}.1.gz" || \ + echo "If this produces an error. Install ronn; something like: sudo gem install ronn" done