Merge pull request #11 from davidnewhall/dn2_docs

Update libraries, fix Makefile bugs.
This commit is contained in:
David Newhall II 2019-01-26 16:09:33 -08:00 committed by GitHub
commit d8aaf69c35
8 changed files with 201 additions and 98 deletions

View File

@ -2,59 +2,68 @@
[[projects]] [[projects]]
digest = "1:e7f0acf99afe9a7b03d270164bd2976b687e1aef02ab3a0abd8db0b4de44b817"
name = "github.com/golift/unifi" name = "github.com/golift/unifi"
packages = ["."] packages = ["."]
revision = "24c7eb106b3c9ff4260c88b54c02f0f86301fa75" pruneopts = "UT"
version = "0.9.0" revision = "f8fec42fbe169dceb69f15276a2323fb007a4539"
version = "v1.0.0"
[[projects]] [[projects]]
name = "github.com/influxdata/influxdb" branch = "master"
packages = [ digest = "1:50708c8fc92aec981df5c446581cf9f90ba9e2a5692118e0ce75d4534aaa14a2"
"client/v2", name = "github.com/influxdata/influxdb1-client"
"models",
"pkg/escape"
]
revision = "698dbc789aff13c2678357a6b93ff73dd7136571"
version = "v1.7.3"
[[projects]]
name = "github.com/influxdata/platform"
packages = [ packages = [
"models", "models",
"pkg/escape" "pkg/escape",
"v2",
] ]
revision = "0f79e4ea3248354c789cba274542e0a8e55971db" pruneopts = "UT"
revision = "16c852ea613fa2d42fcdccc9a8b0802a8bdc6140"
[[projects]] [[projects]]
digest = "1:b56c589214f01a5601e0821387db484617392d0042f26234bf2da853a2f498a1"
name = "github.com/naoina/go-stringutil" name = "github.com/naoina/go-stringutil"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b" revision = "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b"
version = "v0.1.0" version = "v0.1.0"
[[projects]] [[projects]]
digest = "1:f58c3d0e46b64878d00652fedba24ee879725191ab919dca7b62586859281c04"
name = "github.com/naoina/toml" name = "github.com/naoina/toml"
packages = [ packages = [
".", ".",
"ast" "ast",
] ]
pruneopts = "UT"
revision = "e6f5723bf2a66af014955e0888881314cf294129" revision = "e6f5723bf2a66af014955e0888881314cf294129"
version = "v0.1.1" version = "v0.1.1"
[[projects]] [[projects]]
digest = "1:8fd3a15613c7e70cceff3aa03dd57560dba87c4868864e397d5eb2f14addd3f5"
name = "github.com/ogier/pflag" name = "github.com/ogier/pflag"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "32a05c62658bd1d7c7e75cbc8195de5d585fde0f" revision = "32a05c62658bd1d7c7e75cbc8195de5d585fde0f"
version = "v0.0.1" version = "v0.0.1"
[[projects]] [[projects]]
digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b"
name = "github.com/pkg/errors" name = "github.com/pkg/errors"
packages = ["."] packages = ["."]
pruneopts = "UT"
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
version = "v0.8.1" version = "v0.8.1"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 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-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -24,15 +24,6 @@
# go-tests = true # go-tests = true
# unused-packages = true # unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/golift/unifi"
[[constraint]]
name = "github.com/influxdata/influxdb"
version = "1.7.3"
[[constraint]] [[constraint]]
name = "github.com/naoina/toml" name = "github.com/naoina/toml"
version = "0.1.1" version = "0.1.1"

View File

@ -1,5 +1,5 @@
PACKAGES=`find ./cmd -mindepth 1 -maxdepth 1 -type d` PACKAGES=`find ./cmd -mindepth 1 -maxdepth 1 -type d`
LIBRARYS=./unidev BINARY=unifi-poller
all: clean man build all: clean man build
@ -14,30 +14,45 @@ linux:
install: man test build install: man test build
@echo "If you get errors, you may need sudo." @echo "If you get errors, you may need sudo."
# Install binary. binary.
GOBIN=/usr/local/bin go install -ldflags "-w -s" ./... GOBIN=/usr/local/bin go install -ldflags "-w -s" ./...
mkdir -p /usr/local/etc/unifi-poller /usr/local/share/man/man1 # Making config folders and installing man page.
test -f /usr/local/etc/unifi-poller/up.conf || cp up.conf.example /usr/local/etc/unifi-poller/up.conf mkdir -p /usr/local/etc/$(BINARY) /usr/local/share/man/man1
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
mv *.1.gz /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: uninstall:
@echo "If you get errors, you may need sudo." @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 # Stopping the daemon
test -f /etc/systemd/system/unifi-poller.service && systemctl stop unifi-poller || true test -x /bin/systemctl && /bin/systemctl stop $(BINARY) || true
rm -rf /usr/local/{etc,bin}/unifi-poller /usr/local/share/man/man1/unifi-poller.1.gz test -x /bin/launchctl && /bin/launchctl unload ~/Library/LaunchAgents/com.github.davidnewhall.$(BINARY).plist || true
rm -f ~/Library/LaunchAgents/com.github.davidnewhall.unifi-poller.plist # Deleting config file, binary, man page, launch agent or unit file.
rm -f /etc/systemd/system/unifi-poller.service 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 test: lint
for p in $(PACKAGES) $(LIBRARYS); do go test -race -covermode=atomic $${p}; done for p in $(PACKAGES) $(LIBRARYS); do go test -race -covermode=atomic $${p}; done
lint: lint:
goimports -l $(PACKAGES) $(LIBRARYS) goimports -l $(PACKAGES)
gofmt -l $(PACKAGES) $(LIBRARYS) gofmt -l $(PACKAGES)
errcheck $(PACKAGES) $(LIBRARYS) errcheck $(PACKAGES)
golint $(PACKAGES) $(LIBRARYS) golint $(PACKAGES)
go vet $(PACKAGES) $(LIBRARYS) go vet $(PACKAGES)
man: man:
script/build_manpages.sh ./ script/build_manpages.sh ./

View File

@ -1,7 +1,7 @@
# Unifi # Unifi Poller
Collect your Unifi Controller Data and send it to an InfluxDB instance. Collect your Unifi Controller Data and send it to an InfluxDB instance.
Grafana dashboards included. Grafana dashboards included. Updated 2019.
## Installation ## Installation
@ -37,19 +37,18 @@ for making this dashboard; it gave me a fantastic start to making my own.
# What now... # 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 probably suck at InfluxDB.
I don't know what should be a tag and what should be a field. I think 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 I did my best, but there's certainly room for improvements in both
the data input and the Grafana graphs (output). 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.
- 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.
- Are there other devices that need to be included? - 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 to write code for. If you have these devices, and want them graphed, open an
Issue and lets discuss. Issue and lets discuss.
- Better Installation instructions. - Better Installation instructions.
If you're a nerd you can probably figure it out. I'd still like some pretty If you're a nerd you can probably figure it out. I'd still like some pretty
pictures and maybe even a Twitch VOD. pictures and maybe even a Twitch VOD.
- Sanity Checking - Sanity Checking
Did I actually graph the right data in the right way? Some validation would Did I actually graph the right data in the right way? Some validation would
be nice. be nice.
- Radios, Frequencies, Interfaces, vAPs - Radios, Frequencies, Interfaces, vAPs
My access points only seem to have two radios, one interface and vAP per radio. 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 I'm not sure if the graphs, as-is, provide enough insight into APs with other
configurations. Help me figure that out? 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? # What's it look like?
Here's a picture of the Client dashboard. Here's a picture of the Client dashboard.

View File

@ -13,9 +13,9 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB
## OPTIONS ## OPTIONS
`unifi-poller [-c <config file>] [-h] [-v]` `unifi-poller [-c <config-file>] [-h] [-v]`
-c, --config <file_path> -c, --config <config-file>
Provide a configuration file (instead of the default). Provide a configuration file (instead of the default).
-v, --version -v, --version
@ -24,6 +24,51 @@ unifi-poller(1) -- Utility to poll Unifi Metrics and drop them into InfluxDB
-h, --help -h, --help
Display usage and exit. 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 ## GO DURATION
@ -49,4 +94,5 @@ Example Use: `1m`, `5h`, `100ms`, `17s`, `1s45ms`, `1m3s`
* https://github.com/davidnewhall/unifi-poller * https://github.com/davidnewhall/unifi-poller
* /usr/local/bin/unifi-poller * /usr/local/bin/unifi-poller
* config-file: /usr/local/etc/unifi-poller/up.conf
* previously: https://github.com/dewski/unifi * previously: https://github.com/dewski/unifi

View File

@ -3,7 +3,7 @@ package main
import "time" import "time"
// Version is loosely followed. // Version is loosely followed.
var Version = "v0.3.0" var Version = "v1.0.0"
const ( const (
// App defaults in case they're missing from the config. // App defaults in case they're missing from the config.

View File

@ -8,31 +8,37 @@ import (
"time" "time"
"github.com/golift/unifi" "github.com/golift/unifi"
influx "github.com/influxdata/influxdb/client/v2" influx "github.com/influxdata/influxdb1-client/v2"
"github.com/naoina/toml" "github.com/naoina/toml"
flag "github.com/ogier/pflag" 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() configFile := parseFlags()
log.Println("Unifi-Poller Starting Up! PID:", os.Getpid()) log.Println("Unifi-Poller Starting Up! PID:", os.Getpid())
config, err := GetConfig(configFile) config, err := GetConfig(configFile)
if err != nil { if err != nil {
flag.Usage() flag.Usage()
log.Fatalf("Config Error '%v': %v", configFile, err) 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. // 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 { if err != nil {
log.Fatalln("Unifi Controller Error:", err) log.Fatalln("Unifi Controller Error:", err)
} else if !config.Quiet { } else if !config.Quiet {
log.Println("Authenticated to Unifi Controller @", config.UnifiBase, "as user", config.UnifiUser) 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{ infdb, err := influx.NewHTTPClient(influx.HTTPConfig{
Addr: config.InfluxURL, Addr: config.InfluxURL,
Username: config.InfluxUser, Username: config.InfluxUser,
@ -40,15 +46,15 @@ func main() {
}) })
if err != nil { if err != nil {
log.Fatalln("InfluxDB Error:", err) log.Fatalln("InfluxDB Error:", err)
} else if config.Quiet { }
if config.Quiet {
// Doing it this way allows debug error logs (line numbers, etc) // Doing it this way allows debug error logs (line numbers, etc)
unifi.Debug = false controller.DebugLog = nil
} else { } else {
log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser) log.Println("Logging Unifi Metrics to InfluXDB @", config.InfluxURL, "as user", config.InfluxUser)
log.Println("Polling Unifi Controller, interval:", config.Interval.value) log.Println("Polling Unifi Controller, interval:", config.Interval.value)
} }
log.Println("Everyting checks out! Beginning Poller Routine.") config.PollUnifiController(controller, infdb)
config.PollUnifiController(infdb, device, config.Quiet)
} }
func parseFlags() string { func parseFlags() string {
@ -87,39 +93,72 @@ func GetConfig(configFile string) (Config, error) {
} else if err := toml.Unmarshal(buf, &config); err != nil { } else if err := toml.Unmarshal(buf, &config); err != nil {
return config, err return config, err
} }
log.Println("Loaded Configuration:", configFile)
return config, nil return config, nil
} }
// PollUnifiController runs forever, polling and pushing. // 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) ticker := time.NewTicker(c.Interval.value)
for range ticker.C { for range ticker.C {
var clients, devices []unifi.Asset if clients, err := controller.GetClients(); err != nil {
var bp influx.BatchPoints logErrors([]error{err}, "uni.GetClients()")
var err error } else if devices, err := controller.GetDevices(); err != nil {
if clients, err = device.GetUnifiClientAssets(); err != nil { logErrors([]error{err}, "uni.GetDevices()")
log.Println("ERROR unifi.GetUnifiClientsAssets():", err) } else if bp, err := influx.NewBatchPoints(influx.BatchPointsConfig{Database: c.InfluxDB}); err != nil {
} else if devices, err = device.GetUnifiDeviceAssets(); err != nil { logErrors([]error{err}, "influx.NewBatchPoints")
log.Println("ERROR unifi.GetUnifiDeviceAssets():", err) } else if errs := batchPoints(devices, clients, bp); errs != nil && hasErr(errs) {
} else if bp, err = influx.NewBatchPoints(influx.BatchPointsConfig{Database: c.InfluxDB}); err != nil { logErrors(errs, "asset.Points()")
log.Println("ERROR influx.NewBatchPoints:", err) } else if err := infdb.Write(bp); err != nil {
} logErrors([]error{err}, "infdb.Write(bp)")
if err != nil { } else if !c.Quiet {
continue log.Println("[INFO] Logged Unifi States. Clients:", len(clients.UCLs), "- Wireless APs:",
} len(devices.UAPs), "Gateways:", len(devices.USGs), "Switches:", len(devices.USWs))
for _, asset := range append(clients, devices...) { }
if pt, errr := asset.Points(); errr != nil { }
log.Println("ERROR asset.Points():", errr) }
} else {
bp.AddPoints(pt) // 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 {
if err = infdb.Write(bp); err != nil { influxPoints, err := asset.Points()
log.Println("ERROR infdb.Write(bp):", err) if err != nil {
continue return err
} }
if !quiet { batchPoints.AddPoints(influxPoints)
log.Println("Logged Unifi States. Clients:", len(clients), "- Devices:", len(devices)) 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())
} }
} }
} }

View File

@ -1,5 +1,7 @@
#!/bin/bash #!/bin/bash
set -o pipefail
OUTPUT=$1 OUTPUT=$1
# This requires the installation of `ronn`: sudo gem install ronn # This requires the installation of `ronn`: sudo gem install ronn
@ -8,6 +10,6 @@ for f in cmd/*/README.md;do
PKGNOCMD="${f#cmd/}" PKGNOCMD="${f#cmd/}"
PKG="${PKGNOCMD%/README.md}" PKG="${PKGNOCMD%/README.md}"
echo "Creating Man Page: ${f} -> ${OUTPUT}${PKG}.1.gz" 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 done