Merge pull request #13 from golift/dn2_sites

Provide Site metrics
This commit is contained in:
David Newhall II 2019-06-14 21:09:48 -07:00 committed by GitHub
commit 4e061b82f3
9 changed files with 182 additions and 31 deletions

View File

@ -7,7 +7,7 @@ before_install:
- curl -sLo $GOPATH/bin/dep https://github.com/golang/dep/releases/download/v0.5.3/dep-linux-amd64
- chmod +x $GOPATH/bin/dep
# download super-linter: golangci-lint
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin latest
- curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.16.0
install:
- dep ensure
script:

View File

@ -45,8 +45,8 @@ func main() {
}
log.Println(len(sites), "Unifi Sites Found: ", sites)
log.Println(len(clients.UCLs), "Clients connected:")
for i, client := range clients.UCLs {
log.Println(len(clients), "Clients connected:")
for i, client := range clients {
log.Println(i+1, client.ID, client.Hostname, client.IP, client.Name, client.LastSeen)
}

View File

@ -9,7 +9,7 @@ import (
// Points generates Unifi Client datapoints for InfluxDB.
// These points can be passed directly to influx.
func (c UCL) Points() ([]*influx.Point, error) {
func (c Client) Points() ([]*influx.Point, error) {
// Fix name and hostname fields. Sometimes one or the other is blank.
switch {
case c.Hostname == "" && c.Name == "":

View File

@ -1,7 +1,10 @@
package unifi
// UCL defines all the data a connected-network client contains.
type UCL struct {
// Clients contains a list that contains all of the unifi clients from a controller.
type Clients []Client
// Client defines all the data a connected-network client contains.
type Client struct {
ID string `json:"_id"`
IsGuestByUAP FlexBool `json:"_is_guest_by_uap"`
IsGuestByUGW FlexBool `json:"_is_guest_by_ugw"`

View File

@ -13,7 +13,7 @@ func (u *Unifi) parseDevices(data []json.RawMessage, siteName string) *Devices {
} else if t, ok := o["type"].(string); ok {
assetType = t
}
u.dLogf("Unmarshalling Device Type: %v", assetType)
u.dLogf("Unmarshalling Device Type: %v, site %s ", assetType, siteName)
// Choose which type to unmarshal into based on the "type" json key.
switch assetType { // Unmarshal again into the correct type..
case "uap":

81
core/unifi/site_influx.go Normal file
View File

@ -0,0 +1,81 @@
package unifi
import (
"strings"
"time"
influx "github.com/influxdata/influxdb1-client/v2"
)
// Points generates Unifi Sites' datapoints for InfluxDB.
// These points can be passed directly to influx.
func (u Site) Points() ([]*influx.Point, error) {
points := []*influx.Point{}
for _, s := range u.Health {
tags := map[string]string{
"id": u.ID,
"name": u.Name,
"desc": u.Desc,
"status": s.Status,
"subsystem": s.Subsystem,
"wan_ip": s.WanIP,
"netmask": s.Netmask,
"gw_name": s.GwName,
"gw_mac": s.GwMac,
"gw_version": s.GwVersion,
"speedtest_status": s.SpeedtestStatus,
"lan_ip": s.LanIP,
"remote_user_enabled": s.RemoteUserEnabled.Txt,
"site_to_site_enabled": s.SiteToSiteEnabled.Txt,
"nameservers": strings.Join(s.Nameservers, ","),
"gateways": strings.Join(s.Gateways, ","),
"num_new_alarms": u.NumNewAlarms.Txt,
"attr_hidden_id": u.AttrHiddenID,
"attr_no_delete": u.AttrNoDelete.Txt,
}
fields := map[string]interface{}{
"attr_hidden_id": u.AttrHiddenID,
"attr_no_delete": u.AttrNoDelete.Val,
"num_user": s.NumUser.Val,
"num_guest": s.NumGuest.Val,
"num_iot": s.NumIot.Val,
"tx_bytes-r": s.TxBytesR.Val,
"rx_bytes-r": s.RxBytesR.Val,
"status": s.Status,
"num_ap": s.NumAp.Val,
"num_adopted": s.NumAdopted.Val,
"num_disabled": s.NumDisabled.Val,
"num_disconnected": s.NumDisconnected.Val,
"num_pending": s.NumPending.Val,
"num_gw": s.NumGw.Val,
"wan_ip": s.WanIP,
"num_sta": s.NumSta.Val,
"gw_cpu": s.GwSystemStats.CPU.Val,
"gw_mem": s.GwSystemStats.Mem.Val,
"gw_uptime": s.GwSystemStats.Uptime.Val,
"latency": s.Latency.Val,
"uptime": s.Uptime.Val,
"drops": s.Drops.Val,
"xput_up": s.XputUp.Val,
"xput_down": s.XputDown.Val,
"speedtest_ping": s.SpeedtestPing.Val,
"speedtest_lastrun": s.SpeedtestLastrun.Val,
"num_sw": s.NumSw.Val,
"remote_user_num_active": s.RemoteUserNumActive.Val,
"remote_user_num_inactive": s.RemoteUserNumInactive.Val,
"remote_user_rx_bytes": s.RemoteUserRxBytes.Val,
"remote_user_tx_bytes": s.RemoteUserTxBytes.Val,
"remote_user_rx_packets": s.RemoteUserRxPackets.Val,
"remote_user_tx_packets": s.RemoteUserTxPackets.Val,
"num_new_alarms": u.NumNewAlarms.Val,
"nameservers": len(s.Nameservers),
"gateways": len(s.Gateways),
}
pt, err := influx.NewPoint("subsystems", tags, fields, time.Now())
if err != nil {
return points, err
}
points = append(points, pt)
}
return points, nil
}

60
core/unifi/site_type.go Normal file
View File

@ -0,0 +1,60 @@
package unifi
// Sites is a struct to match Devices and Clients.
type Sites []Site
// Site represents a site's data.
type Site struct {
ID string `json:"_id"`
Name string `json:"name"`
Desc string `json:"desc"`
AttrHiddenID string `json:"attr_hidden_id"`
AttrNoDelete FlexBool `json:"attr_no_delete"`
Health []struct {
Subsystem string `json:"subsystem"`
NumUser FlexInt `json:"num_user,omitempty"`
NumGuest FlexInt `json:"num_guest,omitempty"`
NumIot FlexInt `json:"num_iot,omitempty"`
TxBytesR FlexInt `json:"tx_bytes-r,omitempty"`
RxBytesR FlexInt `json:"rx_bytes-r,omitempty"`
Status string `json:"status"`
NumAp FlexInt `json:"num_ap,omitempty"`
NumAdopted FlexInt `json:"num_adopted,omitempty"`
NumDisabled FlexInt `json:"num_disabled,omitempty"`
NumDisconnected FlexInt `json:"num_disconnected,omitempty"`
NumPending FlexInt `json:"num_pending,omitempty"`
NumGw FlexInt `json:"num_gw,omitempty"`
WanIP string `json:"wan_ip,omitempty"`
Gateways []string `json:"gateways,omitempty"`
Netmask string `json:"netmask,omitempty"`
Nameservers []string `json:"nameservers,omitempty"`
NumSta FlexInt `json:"num_sta,omitempty"`
GwMac string `json:"gw_mac,omitempty"`
GwName string `json:"gw_name,omitempty"`
GwSystemStats struct {
CPU FlexInt `json:"cpu"`
Mem FlexInt `json:"mem"`
Uptime FlexInt `json:"uptime"`
} `json:"gw_system-stats,omitempty"`
GwVersion string `json:"gw_version,omitempty"`
Latency FlexInt `json:"latency,omitempty"`
Uptime FlexInt `json:"uptime,omitempty"`
Drops FlexInt `json:"drops,omitempty"`
XputUp FlexInt `json:"xput_up,omitempty"`
XputDown FlexInt `json:"xput_down,omitempty"`
SpeedtestStatus string `json:"speedtest_status,omitempty"`
SpeedtestLastrun FlexInt `json:"speedtest_lastrun,omitempty"`
SpeedtestPing FlexInt `json:"speedtest_ping,omitempty"`
LanIP string `json:"lan_ip,omitempty"`
NumSw FlexInt `json:"num_sw,omitempty"`
RemoteUserEnabled FlexBool `json:"remote_user_enabled,omitempty"`
RemoteUserNumActive FlexInt `json:"remote_user_num_active,omitempty"`
RemoteUserNumInactive FlexInt `json:"remote_user_num_inactive,omitempty"`
RemoteUserRxBytes FlexInt `json:"remote_user_rx_bytes,omitempty"`
RemoteUserTxBytes FlexInt `json:"remote_user_tx_bytes,omitempty"`
RemoteUserRxPackets FlexInt `json:"remote_user_rx_packets,omitempty"`
RemoteUserTxPackets FlexInt `json:"remote_user_tx_packets,omitempty"`
SiteToSiteEnabled FlexBool `json:"site_to_site_enabled,omitempty"`
} `json:"health"`
NumNewAlarms FlexInt `json:"num_new_alarms"`
}

View File

@ -12,8 +12,10 @@ import (
// This is a list of unifi API paths.
// The %s in each string must be replaced with a Site.Name.
const (
// StatusPath shows Controller version.
StatusPath string = "/status"
// SiteList is the path to the api site list.
SiteList string = "/api/self/sites"
SiteList string = "/api/stat/sites"
// ClientPath is Unifi Clients API Path
ClientPath string = "/api/s/%s/stat/sta"
// DevicePath is where we get data about Unifi devices.
@ -52,11 +54,6 @@ type Devices struct {
USWs []USW
}
// Clients contains a list that contains all of the unifi clients from a controller.
type Clients struct {
UCLs []UCL
}
// Unifi is what you get in return for providing a password! Unifi represents
// a controller that you can make authenticated requests to. Use this to make
// additional requests for devices, clients or other custom data.
@ -67,11 +64,11 @@ type Unifi struct {
DebugLog Logger
}
// Site represents a site's data. There are more pieces to this, but this is
// all we expose.
type Site struct {
Name string `json:"name"`
Desc string `json:"desc"`
// Server is the /status endpoint from the Unifi controller.
type Server struct {
Up FlexBool `json:"up"`
ServerVersion string `json:"server_version"`
UUID string `json:"uuid"`
}
// FlexInt provides a container and unmarshalling for fields that may be
@ -109,12 +106,15 @@ type FlexBool struct {
Txt string
}
// UnmarshalJSO method converts armed/disarmed, yes/no, active/inactive or 0/1 to true/false.
// Really it converts ready, up, t, armed, yes, active, enabled, 1, true to true. Anything else is false.
// UnmarshalJSON method converts armed/disarmed, yes/no, active/inactive or 0/1 to true/false.
// Really it converts ready, ok, up, t, armed, yes, active, enabled, 1, true to true. Anything else is false.
func (f *FlexBool) UnmarshalJSON(b []byte) error {
f.Txt = strings.Trim(string(b), `"`)
if f.Txt = strings.Trim(string(b), `"`); f.Txt == "" {
f.Txt = "false"
}
f.Val = f.Txt == "1" || strings.EqualFold(f.Txt, "true") || strings.EqualFold(f.Txt, "yes") ||
strings.EqualFold(f.Txt, "t") || strings.EqualFold(f.Txt, "armed") || strings.EqualFold(f.Txt, "active") ||
strings.EqualFold(f.Txt, "enabled") || strings.EqualFold(f.Txt, "ready") || strings.EqualFold(f.Txt, "up")
strings.EqualFold(f.Txt, "enabled") || strings.EqualFold(f.Txt, "ready") || strings.EqualFold(f.Txt, "up") ||
strings.EqualFold(f.Txt, "ok")
return nil
}

View File

@ -58,15 +58,23 @@ func (u *Unifi) getController(user, pass string) error {
return nil
}
// GetServer returns the controller's version and UUID.
func (u *Unifi) GetServer() (Server, error) {
var response struct {
Data Server `json:"meta"`
}
err := u.GetData(StatusPath, &response)
return response.Data, err
}
// GetClients returns a response full of clients' data from the Unifi Controller.
func (u *Unifi) GetClients(sites []Site) (*Clients, error) {
data := make([]UCL, 0)
func (u *Unifi) GetClients(sites []Site) (Clients, error) {
data := make([]Client, 0)
for _, site := range sites {
var response struct {
Data []UCL `json:"data"`
Data []Client `json:"data"`
}
u.dLogf("Polling Site '%s' (%s) Clients", site.Name, site.Desc)
u.dLogf("Unmarshalling Device Type: ucl")
u.dLogf("Polling Controller, retreiving Unifi Clients, site %s (%s) ", site.Name, site.Desc)
clientPath := fmt.Sprintf(ClientPath, site.Name)
if err := u.GetData(clientPath, &response); err != nil {
return nil, err
@ -76,14 +84,13 @@ func (u *Unifi) GetClients(sites []Site) (*Clients, error) {
}
data = append(data, response.Data...)
}
return &Clients{UCLs: data}, nil
return data, nil
}
// GetDevices returns a response full of devices' data from the Unifi Controller.
func (u *Unifi) GetDevices(sites []Site) (*Devices, error) {
devices := new(Devices)
for _, site := range sites {
u.dLogf("Polling Site '%s' (%s) Devices", site.Name, site.Desc)
var response struct {
Data []json.RawMessage `json:"data"`
}
@ -116,7 +123,7 @@ func (u *Unifi) GetDevices(sites []Site) (*Devices, error) {
}
// GetSites returns a list of configured sites on the Unifi controller.
func (u *Unifi) GetSites() ([]Site, error) {
func (u *Unifi) GetSites() (Sites, error) {
var response struct {
Data []Site `json:"data"`
}
@ -127,7 +134,7 @@ func (u *Unifi) GetSites() ([]Site, error) {
for i := range response.Data {
sites = append(sites, response.Data[i].Name)
}
u.dLogf("Found %d sites: %s", len(sites), strings.Join(sites, ","))
u.dLogf("Found %d site(s): %s", len(sites), strings.Join(sites, ","))
return response.Data, nil
}