From 530c3bff570d13e98802f0eb85734bb45f2096f6 Mon Sep 17 00:00:00 2001 From: DN2 Date: Sun, 29 Apr 2018 01:35:18 -0700 Subject: [PATCH] Better errors, fix bugs, correct usg data. --- integrations/inputunifi/unidev/README.md | 10 + integrations/inputunifi/unidev/unidev.go | 9 +- integrations/inputunifi/unidev/unifi.go | 68 ++++--- integrations/inputunifi/unidev/usg.go | 217 +++++++++++++-------- integrations/inputunifi/unidev/usg_type.go | 14 +- integrations/inputunifi/unidev/usw.go | 6 +- integrations/inputunifi/unidev/usw_type.go | 12 +- 7 files changed, 213 insertions(+), 123 deletions(-) create mode 100644 integrations/inputunifi/unidev/README.md diff --git a/integrations/inputunifi/unidev/README.md b/integrations/inputunifi/unidev/README.md new file mode 100644 index 00000000..a5195c1e --- /dev/null +++ b/integrations/inputunifi/unidev/README.md @@ -0,0 +1,10 @@ +# Go Library: `unidev` + +It connects to a Unifi Controller, given a url, username and password. Returns +an authenticated http Client you may use to query the device for data. Also +contains some built-in methods for de-serializing common client and device +data. The included asset interface currently only works for InfluxDB but could +probably be modified to support other output mechanisms; not sure really. + +This lib is rudimentary and gets a job done for the tool at hand. It could be +used to base your own library. Good luck! diff --git a/integrations/inputunifi/unidev/unidev.go b/integrations/inputunifi/unidev/unidev.go index 7cba8d98..f9399263 100644 --- a/integrations/inputunifi/unidev/unidev.go +++ b/integrations/inputunifi/unidev/unidev.go @@ -3,10 +3,11 @@ package unidev import ( "bytes" "crypto/tls" - influx "github.com/influxdata/influxdb/client/v2" - "github.com/pkg/errors" "net/http" "net/http/cookiejar" + + influx "github.com/influxdata/influxdb/client/v2" + "github.com/pkg/errors" ) // LoginPath is Unifi Controller Login API Path @@ -41,9 +42,9 @@ func AuthController(user, pass, url string) (*AuthedReq, error) { Jar: jar, }, url} if req, err := authReq.UniReq(LoginPath, json); err != nil { - return nil, errors.Wrap(err, "uniRequest(LoginPath, json)") + return nil, errors.Wrap(err, "UniReq(LoginPath, json)") } else if resp, err := authReq.Do(req); err != nil { - return nil, errors.Wrap(err, "c.uniClient.Do(req)") + return nil, errors.Wrap(err, "authReq.Do(req)") } else if resp.StatusCode != http.StatusOK { return nil, errors.Errorf("authentication failed (%v): %v (status: %v/%v)", user, url+LoginPath, resp.StatusCode, resp.Status) diff --git a/integrations/inputunifi/unidev/unifi.go b/integrations/inputunifi/unidev/unifi.go index 8239e620..e1f1e6f2 100644 --- a/integrations/inputunifi/unidev/unifi.go +++ b/integrations/inputunifi/unidev/unifi.go @@ -4,6 +4,8 @@ import ( "encoding/json" "io/ioutil" "log" + + "github.com/pkg/errors" ) // Debug .... @@ -28,16 +30,23 @@ func (c *AuthedReq) GetUnifiClients() ([]UCL, error) { Rc string `json:"rc"` } `json:"meta"` } - if req, err := c.UniReq(ClientPath, ""); err != nil { - return nil, err - } else if resp, err := c.Do(req); err != nil { - return nil, err - } else if body, err := ioutil.ReadAll(resp.Body); err != nil { - return nil, err + req, err := c.UniReq(ClientPath, "") + if err != nil { + return nil, errors.Wrap(err, "c.UniReq(ClientPath)") + } + resp, err := c.Do(req) + if err != nil { + return nil, errors.Wrap(err, "c.Do(req)") + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + } + }() + if body, err := ioutil.ReadAll(resp.Body); err != nil { + return nil, errors.Wrap(err, "ioutil.ReadAll(resp.Body)") } else if err = json.Unmarshal(body, &response); err != nil { - return nil, err - } else if err = resp.Body.Close(); err != nil { - log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + return nil, errors.Wrap(err, "json.Unmarshal([]UCL)") } return response.Clients, nil } @@ -62,30 +71,39 @@ func (c *AuthedReq) GetUnifiDevices() ([]USG, []USW, []UAP, error) { Rc string `json:"rc"` } `json:"meta"` } - if req, err := c.UniReq(DevicePath, ""); err != nil { - return nil, nil, nil, err - } else if resp, err := c.Do(req); err != nil { - return nil, nil, nil, err - } else if body, err := ioutil.ReadAll(resp.Body); err != nil { - return nil, nil, nil, err - } else if err = json.Unmarshal(body, &parsed); err != nil { - return nil, nil, nil, err - } else if err = resp.Body.Close(); err != nil { - log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + req, err := c.UniReq(DevicePath, "") + if err != nil { + return nil, nil, nil, errors.Wrap(err, "c.UniReq(DevicePath)") } + resp, err := c.Do(req) + if err != nil { + return nil, nil, nil, errors.Wrap(err, "c.Do(req)") + } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Println("resp.Body.Close():", err) // Not fatal? Just log it. + } + }() + if body, err := ioutil.ReadAll(resp.Body); err != nil { + return nil, nil, nil, errors.Wrap(err, "ioutil.ReadAll(resp.Body)") + } else if err = json.Unmarshal(body, &parsed); err != nil { + return nil, nil, nil, errors.Wrap(err, "json.Unmarshal([]json.RawMessage)") + } + var usgs []USG var usws []USW var uaps []UAP - for _, r := range parsed.Data { + // Loop each item in the raw JSON message, detect its type and unmarshal it. + for i, r := range parsed.Data { var usg USG var usw USW var uap UAP // Unamrshal into a map and check "type" var obj map[string]interface{} if err := json.Unmarshal(r, &obj); err != nil { - return nil, nil, nil, err + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal(interfce{})", i) } - assetType := "- missing -" + assetType := "" if t, ok := obj["type"].(string); ok { assetType = t } @@ -96,17 +114,17 @@ func (c *AuthedReq) GetUnifiDevices() ([]USG, []USW, []UAP, error) { switch assetType { case "uap": if err := json.Unmarshal(r, &uap); err != nil { - return nil, nil, nil, err + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]UAP)", i) } uaps = append(uaps, uap) case "ugw", "usg": // in case they ever fix the name in the api. if err := json.Unmarshal(r, &usg); err != nil { - return nil, nil, nil, err + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]USG)", i) } usgs = append(usgs, usg) case "usw": if err := json.Unmarshal(r, &usw); err != nil { - return nil, nil, nil, err + return nil, nil, nil, errors.Wrapf(err, "[%d] json.Unmarshal([]USW)", i) } usws = append(usws, usw) default: diff --git a/integrations/inputunifi/unidev/usg.go b/integrations/inputunifi/unidev/usg.go index f904f37c..86efb7ab 100644 --- a/integrations/inputunifi/unidev/usg.go +++ b/integrations/inputunifi/unidev/usg.go @@ -11,45 +11,44 @@ import ( func (u USG) Points() ([]*influx.Point, error) { var points []*influx.Point tags := map[string]string{ - "id": u.ID, - "mac": u.Mac, - "device_type": u.Stat.O, - "device_oid": u.Stat.Oid, - "site_id": u.SiteID, - "addopted": strconv.FormatBool(u.Adopted), - "name": u.Name, - "adopt_ip": u.AdoptIP, - "adopt_url": u.AdoptURL, - "cfgversion": u.Cfgversion, - "config_network_ip": u.ConfigNetwork.IP, - "config_network_type": u.ConfigNetwork.Type, - "connect_request_ip": u.ConnectRequestIP, - "connect_request_port": u.ConnectRequestPort, - "default": strconv.FormatBool(u.Default), - "device_id": u.DeviceID, - "discovered_via": u.DiscoveredVia, - "fw_caps": strconv.FormatFloat(u.FwCaps, 'f', 6, 64), - "guest-num_sta": strconv.FormatFloat(u.GuestNumSta, 'f', 6, 64), - "guest_token": u.GuestToken, - "inform_ip": u.InformIP, - "last_seen": strconv.FormatFloat(u.LastSeen, 'f', 6, 64), - "known_cfgversion": u.KnownCfgversion, - "led_override": u.LedOverride, - "locating": strconv.FormatBool(u.Locating), - "model": u.Model, - "outdoor_mode_override": u.OutdoorModeOverride, - "serial": u.Serial, - "type": u.Type, - "version_incompatible": strconv.FormatBool(u.VersionIncompatible), - "config_network_wan_type": u.ConfigNetworkWan.Type, - "usg_caps": strconv.FormatFloat(u.UsgCaps, 'f', 6, 64), - "speedtest-status-saved": strconv.FormatBool(u.SpeedtestStatusSaved), + "id": u.ID, + "mac": u.Mac, + "device_type": u.Stat.O, + "device_oid": u.Stat.Oid, + "site_id": u.SiteID, + "addopted": strconv.FormatBool(u.Adopted), + "name": u.Name, + "adopt_ip": u.AdoptIP, + "adopt_url": u.AdoptURL, + "cfgversion": u.Cfgversion, + "config_network_ip": u.ConfigNetwork.IP, + "config_network_type": u.ConfigNetwork.Type, + "connect_request_ip": u.ConnectRequestIP, + "connect_request_port": u.ConnectRequestPort, + "default": strconv.FormatBool(u.Default), + "device_id": u.DeviceID, + "discovered_via": u.DiscoveredVia, + "guest_token": u.GuestToken, + "inform_ip": u.InformIP, + "last_seen": strconv.FormatFloat(u.LastSeen, 'f', 6, 64), + "known_cfgversion": u.KnownCfgversion, + "led_override": u.LedOverride, + "locating": strconv.FormatBool(u.Locating), + "model": u.Model, + "outdoor_mode_override": u.OutdoorModeOverride, + "serial": u.Serial, + "type": u.Type, + "version_incompatible": strconv.FormatBool(u.VersionIncompatible), + "usg_caps": strconv.FormatFloat(u.UsgCaps, 'f', 6, 64), + "speedtest-status-saved": strconv.FormatBool(u.SpeedtestStatusSaved), } fields := map[string]interface{}{ "ip": u.IP, "bytes": u.Bytes, "last_seen": u.LastSeen, "license_state": u.LicenseState, + "fw_caps": u.FwCaps, + "guest-num_sta": u.GuestNumSta, "rx_bytes": u.RxBytes, "tx_bytes": u.TxBytes, "uptime": u.Uptime, @@ -73,53 +72,115 @@ func (u USG) Points() ([]*influx.Point, error) { "speedtest-status_xput_download": u.SpeedtestStatus.XputDownload, "speedtest-status_xput_upload": u.SpeedtestStatus.XputUpload, // have two WANs? mmmm, go ahead and add it. ;) - "wan1_bytes-r": u.Wan1.BytesR, - "wan1_enable": u.Wan1.Enable, - "wan1_full_duplex": u.Wan1.FullDuplex, - "wan1_gateway": u.Wan1.Gateway, - "wan1_ifname": u.Wan1.Ifname, - "wan1_ip": u.Wan1.IP, - "wan1_mac": u.Wan1.Mac, - "wan1_max_speed": u.Wan1.MaxSpeed, - "wan1_name": u.Wan1.Name, - "wan1_netmask": u.Wan1.Netmask, - "wan1_rx_bytes": u.Wan1.RxBytes, - "wan1_rx_bytes-r": u.Wan1.RxBytesR, - "wan1_rx_dropped": u.Wan1.RxDropped, - "wan1_rx_errors": u.Wan1.RxErrors, - "wan1_rx_multicast": u.Wan1.RxMulticast, - "wan1_rx_packets": u.Wan1.RxPackets, - "wan1_type": u.Wan1.Type, - "wan1_speed": u.Wan1.Speed, - "wan1_up": u.Wan1.Up, - "wan1_tx_bytes": u.Wan1.TxBytes, - "wan1_tx_bytes-r": u.Wan1.TxBytesR, - "wan1_tx_dropped": u.Wan1.TxDropped, - "wan1_tx_errors": u.Wan1.TxErrors, - "wan1_tx_packets": u.Wan1.TxPackets, - "loadavg_1": u.SysStats.Loadavg1, - "loadavg_5": u.SysStats.Loadavg5, - "loadavg_15": u.SysStats.Loadavg15, - "mem_buffer": u.SysStats.MemBuffer, - "mem_total": u.SysStats.MemTotal, - "cpu": u.SystemStats.CPU, - "mem": u.SystemStats.Mem, - "system_uptime": u.SystemStats.Uptime, - "stat_duration": u.Stat.Duration, - "stat_datetime": u.Stat.Datetime, - "gw": u.Stat.Gw, - "lan-rx_bytes": u.Stat.LanRxBytes, - "lan-rx_packets": u.Stat.LanRxPackets, - "lan-tx_bytes": u.Stat.LanTxBytes, - "lan-tx_packets": u.Stat.LanTxPackets, - "wan-rx_bytes": u.Stat.WanRxBytes, - "wan-rx_dropped": u.Stat.WanRxDropped, - "wan-rx_packets": u.Stat.WanRxPackets, - "wan-tx_bytes": u.Stat.WanTxBytes, - "wan-tx_packets": u.Stat.WanTxPackets, + "config_network_wan_type": u.ConfigNetworkWan.Type, + "wan1_bytes-r": u.Wan1.BytesR, + "wan1_enable": u.Wan1.Enable, + "wan1_full_duplex": u.Wan1.FullDuplex, + "wan1_purpose": "uplink", // because it should have a purpose. + "wan1_gateway": u.Wan1.Gateway, + "wan1_ifname": u.Wan1.Ifname, + "wan1_ip": u.Wan1.IP, + "wan1_mac": u.Wan1.Mac, + "wan1_max_speed": u.Wan1.MaxSpeed, + "wan1_name": u.Wan1.Name, + "wan1_netmask": u.Wan1.Netmask, + "wan1_rx_bytes": u.Wan1.RxBytes, + "wan1_rx_bytes-r": u.Wan1.RxBytesR, + "wan1_rx_dropped": u.Wan1.RxDropped, + "wan1_rx_errors": u.Wan1.RxErrors, + "wan1_rx_multicast": u.Wan1.RxMulticast, + "wan1_rx_packets": u.Wan1.RxPackets, + "wan1_type": u.Wan1.Type, + "wan1_speed": u.Wan1.Speed, + "wan1_up": u.Wan1.Up, + "wan1_tx_bytes": u.Wan1.TxBytes, + "wan1_tx_bytes-r": u.Wan1.TxBytesR, + "wan1_tx_dropped": u.Wan1.TxDropped, + "wan1_tx_errors": u.Wan1.TxErrors, + "wan1_tx_packets": u.Wan1.TxPackets, + "loadavg_1": u.SysStats.Loadavg1, + "loadavg_5": u.SysStats.Loadavg5, + "loadavg_15": u.SysStats.Loadavg15, + "mem_used": u.SysStats.MemUsed, + "mem_buffer": u.SysStats.MemBuffer, + "mem_total": u.SysStats.MemTotal, + "cpu": u.SystemStats.CPU, + "mem": u.SystemStats.Mem, + "system_uptime": u.SystemStats.Uptime, + "stat_duration": u.Stat.Duration, + "stat_datetime": u.Stat.Datetime, + "gw": u.Stat.Gw, + "false": "false", // to fill holes in graphs. + "lan-rx_bytes": u.Stat.LanRxBytes, + "lan-rx_packets": u.Stat.LanRxPackets, + "lan-tx_bytes": u.Stat.LanTxBytes, + "lan-tx_packets": u.Stat.LanTxPackets, + "wan-rx_bytes": u.Stat.WanRxBytes, + "wan-rx_dropped": u.Stat.WanRxDropped, + "wan-rx_packets": u.Stat.WanRxPackets, + "wan-tx_bytes": u.Stat.WanTxBytes, + "wan-tx_packets": u.Stat.WanTxPackets, + "uplink_name": u.Uplink.Name, + "uplink_latency": u.Uplink.Latency, + "uplink_speed": u.Uplink.Speed, + "uplink_num_ports": u.Uplink.NumPort, + "uplink_max_speed": u.Uplink.MaxSpeed, } pt, err := influx.NewPoint("usg", tags, fields, time.Now()) - if err == nil { + if err != nil { + return nil, err + } + points = append(points, pt) + for _, p := range u.NetworkTable { + tags := map[string]string{ + "device_name": u.Name, + "device_id": u.ID, + "device_mac": u.Mac, + "name": p.Name, + "dhcpd_dns_enabled": strconv.FormatBool(p.DhcpdDNSEnabled), + "dhcpd_enabled": strconv.FormatBool(p.DhcpdEnabled), + "dhcpd_ntp_enabled": strconv.FormatBool(p.DhcpdNtpEnabled), + "dhcpd_time_offset_enabled": strconv.FormatBool(p.DhcpdTimeOffsetEnabled), + "dhcp_relay_enabledy": strconv.FormatBool(p.DhcpRelayEnabled), + "dhcpd_gateway_enabled": strconv.FormatBool(p.DhcpdGatewayEnabled), + "dhcpd_wins_enabled": strconv.FormatBool(p.DhcpdWinsEnabled), + "dhcpguard_enabled": strconv.FormatBool(p.DhcpguardEnabled), + "enabled": strconv.FormatBool(p.Enabled), + "vlan_enabled": strconv.FormatBool(p.VlanEnabled), + "attr_no_delete": strconv.FormatBool(p.AttrNoDelete), + "upnp_lan_enabled": strconv.FormatBool(p.UpnpLanEnabled), + "igmp_snooping": strconv.FormatBool(p.IgmpSnooping), + "is_guest": strconv.FormatBool(p.IsGuest), + "is_nat": strconv.FormatBool(p.IsNat), + "networkgroup": p.Networkgroup, + "site_id": p.SiteID, + } + fields := map[string]interface{}{ + "dhcpd_ip_1": p.DhcpdIP1, + "domain_name": p.DomainName, + "dhcpd_start": p.DhcpdStart, + "dhcpd_stop": p.DhcpdStop, + "ip": p.IP, + "ip_subnet": p.IPSubnet, + "mac": p.Mac, + "name": p.Name, + "num_sta": p.NumSta, + "purpose": p.Purpose, + "rx_bytes": p.RxBytes, + "rx_packets": p.RxPackets, + "tx_bytes": p.TxBytes, + "tx_packets": p.TxPackets, + "up": p.Up, + "vlan": p.Vlan, + "dhcpd_ntp_1": p.DhcpdNtp1, + "dhcpd_unifi_controller": p.DhcpdUnifiController, + "ipv6_interface_type": p.Ipv6InterfaceType, + "attr_hidden_id": p.AttrHiddenID, + } + pt, err = influx.NewPoint("usg_networks", tags, fields, time.Now()) + if err != nil { + return points, err + } points = append(points, pt) } return points, err diff --git a/integrations/inputunifi/unidev/usg_type.go b/integrations/inputunifi/unidev/usg_type.go index 8d6b4d82..d5a7d15a 100644 --- a/integrations/inputunifi/unidev/usg_type.go +++ b/integrations/inputunifi/unidev/usg_type.go @@ -79,7 +79,7 @@ type USG struct { DhcpdNtpEnabled bool `json:"dhcpd_ntp_enabled,omitempty"` DhcpdTimeOffsetEnabled bool `json:"dhcpd_time_offset_enabled,omitempty"` DhcpdUnifiController string `json:"dhcpd_unifi_controller,omitempty"` - Ipv6float64erfaceType string `json:"ipv6_float64erface_type,omitempty"` + Ipv6InterfaceType string `json:"ipv6_interface_type,omitempty"` AttrHiddenID string `json:"attr_hidden_id,omitempty"` AttrNoDelete bool `json:"attr_no_delete,omitempty"` UpnpLanEnabled bool `json:"upnp_lan_enabled,omitempty"` @@ -148,17 +148,17 @@ type USG struct { } `json:"stat"` State float64 `json:"state"` SysStats struct { - Loadavg1 string `json:"loadavg_1"` - Loadavg15 string `json:"loadavg_15"` - Loadavg5 string `json:"loadavg_5"` + Loadavg1 float64 `json:"loadavg_1,string"` + Loadavg15 float64 `json:"loadavg_15,string"` + Loadavg5 float64 `json:"loadavg_5,string"` MemBuffer float64 `json:"mem_buffer"` MemTotal float64 `json:"mem_total"` MemUsed float64 `json:"mem_used"` } `json:"sys_stats"` SystemStats struct { - CPU string `json:"cpu"` - Mem string `json:"mem"` - Uptime string `json:"uptime"` + CPU float64 `json:"cpu,string"` + Mem float64 `json:"mem,string"` + Uptime float64 `json:"uptime,string"` } `json:"system-stats"` TxBytes float64 `json:"tx_bytes"` Type string `json:"type"` diff --git a/integrations/inputunifi/unidev/usw.go b/integrations/inputunifi/unidev/usw.go index bbcb1dc1..8211ca2b 100644 --- a/integrations/inputunifi/unidev/usw.go +++ b/integrations/inputunifi/unidev/usw.go @@ -28,10 +28,7 @@ func (u USW) Points() ([]*influx.Point, error) { "default": strconv.FormatBool(u.Default), "device_id": u.DeviceID, "discovered_via": u.DiscoveredVia, - "fw_caps": strconv.FormatFloat(u.FwCaps, 'f', 6, 64), - "guest-num_sta": strconv.FormatFloat(u.GuestNumSta, 'f', 6, 64), "inform_ip": u.InformIP, - "last_seen": strconv.FormatFloat(u.LastSeen, 'f', 6, 64), "last_uplink_mac": u.LastUplink.UplinkMac, "known_cfgversion": u.KnownCfgversion, "led_override": u.LedOverride, @@ -51,6 +48,8 @@ func (u USW) Points() ([]*influx.Point, error) { "uplink_depth": strconv.FormatFloat(u.UplinkDepth, 'f', 6, 64), } fields := map[string]interface{}{ + "fw_caps": u.FwCaps, + "guest-num_sta": u.GuestNumSta, "ip": u.IP, "bytes": u.Bytes, "fan_level": u.FanLevel, @@ -72,6 +71,7 @@ func (u USW) Points() ([]*influx.Point, error) { "loadavg_5": u.SysStats.Loadavg5, "loadavg_15": u.SysStats.Loadavg15, "mem_buffer": u.SysStats.MemBuffer, + "mem_used": u.SysStats.MemUsed, "mem_total": u.SysStats.MemTotal, "cpu": u.SystemStats.CPU, "mem": u.SystemStats.Mem, diff --git a/integrations/inputunifi/unidev/usw_type.go b/integrations/inputunifi/unidev/usw_type.go index 5ed47065..0b61ce17 100644 --- a/integrations/inputunifi/unidev/usw_type.go +++ b/integrations/inputunifi/unidev/usw_type.go @@ -366,17 +366,17 @@ type USW struct { StpPriority string `json:"stp_priority"` StpVersion string `json:"stp_version"` SysStats struct { - Loadavg1 string `json:"loadavg_1"` - Loadavg15 string `json:"loadavg_15"` - Loadavg5 string `json:"loadavg_5"` + Loadavg1 float64 `json:"loadavg_1,string"` + Loadavg15 float64 `json:"loadavg_15,string"` + Loadavg5 float64 `json:"loadavg_5,string"` MemBuffer float64 `json:"mem_buffer"` MemTotal float64 `json:"mem_total"` MemUsed float64 `json:"mem_used"` } `json:"sys_stats"` SystemStats struct { - CPU string `json:"cpu"` - Mem string `json:"mem"` - Uptime string `json:"uptime"` + CPU float64 `json:"cpu,string"` + Mem float64 `json:"mem,string"` + Uptime float64 `json:"uptime,string"` } `json:"system-stats"` TxBytes float64 `json:"tx_bytes"` Type string `json:"type"`