Merge pull request #2 from davidnewhall/dn2_bugs

Better errors, fix bugs, correct usg data.
This commit is contained in:
David Newhall II 2018-05-05 11:14:32 -07:00 committed by GitHub
commit 936191e464
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 3801 additions and 123 deletions

File diff suppressed because it is too large Load Diff

10
unidev/README.md Normal file
View File

@ -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!

View File

@ -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)

View File

@ -4,6 +4,8 @@ import (
"encoding/json"
"io/ioutil"
"log"
"github.com/pkg/errors"
)
// Debug ....
@ -28,17 +30,24 @@ 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
} else if err = json.Unmarshal(body, &response); err != nil {
return nil, err
} else if err = resp.Body.Close(); err != nil {
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, 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 {
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 := "<missing>"
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:

View File

@ -28,8 +28,6 @@ func (u USG) 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),
"guest_token": u.GuestToken,
"inform_ip": u.InformIP,
"last_seen": strconv.FormatFloat(u.LastSeen, 'f', 6, 64),
@ -41,7 +39,6 @@ func (u USG) Points() ([]*influx.Point, error) {
"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),
}
@ -50,6 +47,8 @@ func (u USG) Points() ([]*influx.Point, error) {
"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,9 +72,11 @@ 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. ;)
"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,
@ -100,6 +101,7 @@ func (u USG) Points() ([]*influx.Point, error) {
"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,
@ -108,6 +110,7 @@ func (u USG) Points() ([]*influx.Point, error) {
"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,
@ -117,9 +120,67 @@ func (u USG) Points() ([]*influx.Point, error) {
"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

View File

@ -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"`

View File

@ -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,

View File

@ -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"`