diff --git a/core/unifi/README.md b/core/unifi/README.md index 3325c94a..5ea77454 100644 --- a/core/unifi/README.md +++ b/core/unifi/README.md @@ -8,6 +8,8 @@ data. The data is provided in a large struct you can consume in your application This library also contains methods to export the Unifi data in InfluxDB format, and this can be used as an example to base your own metrics collection methods. +If more features are requested, I'll certainly consider them. Do you need to do +more than just collect data? [Let me know](https://github.com/golift/unifi/issues/new)! Pull requests and feedback are welcomed! Here's a working example: @@ -21,25 +23,32 @@ func main() { username := "admin" password := "superSecret1234" URL := "https://127.0.0.1:8443/" - uni, err := unifi.GetController(username, password, URL, false) + uni, err := unifi.NewUnifi(username, password, URL, false) if err != nil { log.Fatalln("Error:", err) } // Log with log.Printf or make your own interface that accepts (msg, fmt) uni.ErrorLog = log.Printf uni.DebugLog = log.Printf - clients, err := uni.GetClients() + + sites, err := uni.GetSites() if err != nil { log.Fatalln("Error:", err) } + clients, err := uni.GetClients(sites) + if err != nil { + log.Fatalln("Error:", err) + } + devices, err := uni.GetDevices(sites) + if err != nil { + log.Fatalln("Error:", err) + } + log.Println(len(clients.UCLs), "Clients connected:") for i, client := range clients.UCLs { log.Println(i+1, client.ID, client.Hostname, client.IP, client.Name, client.LastSeen) } - devices, err := uni.GetDevices() - if err != nil { - log.Fatalln("Error:", err) - } + log.Println(len(devices.USWs), "Unifi Switches Found") log.Println(len(devices.USGs), "Unifi Gateways Found") @@ -48,5 +57,4 @@ func main() { log.Println(i+1, uap.Name, uap.IP) } } - ``` diff --git a/core/unifi/clients.go b/core/unifi/clients_influx.go similarity index 94% rename from core/unifi/clients.go rename to core/unifi/clients_influx.go index 51e0c8e2..70adf58f 100644 --- a/core/unifi/clients.go +++ b/core/unifi/clients_influx.go @@ -10,15 +10,15 @@ import ( // Points generates Unifi Client datapoints for InfluxDB. // These points can be passed directly to influx. func (c UCL) Points() ([]*influx.Point, error) { - var points []*influx.Point // Fix name and hostname fields. Sometimes one or the other is blank. - if c.Name == "" && c.Hostname != "" { - c.Name = c.Hostname - } else if c.Hostname == "" && c.Name != "" { - c.Hostname = c.Name - } else if c.Hostname == "" && c.Name == "" { + switch { + case c.Hostname == "" && c.Name == "": c.Hostname = "-no-name-" c.Name = "-no-name-" + case c.Hostname == "" && c.Name != "": + c.Hostname = c.Name + case c.Name == "" && c.Hostname != "": + c.Name = c.Hostname } tags := map[string]string{ "id": c.ID, @@ -100,7 +100,7 @@ func (c UCL) Points() ([]*influx.Point, error) { } pt, err := influx.NewPoint("clients", tags, fields, time.Now()) if err == nil { - points = append(points, pt) + return nil, err } - return points, err + return []*influx.Point{pt}, nil } diff --git a/core/unifi/parsers.go b/core/unifi/parsers.go new file mode 100644 index 00000000..70c2d193 --- /dev/null +++ b/core/unifi/parsers.go @@ -0,0 +1,49 @@ +package unifi + +import "encoding/json" + +// parseDevices parses the raw JSON from the Unifi Controller into device structures. +func (u *Unifi) parseDevices(data []json.RawMessage) *Devices { + devices := new(Devices) + for _, r := range data { + // Loop each item in the raw JSON message, detect its type and unmarshal it. + assetType := "" + if o := make(map[string]interface{}); u.unmarshalDevice("map", r, &o) != nil { + continue + } else if t, ok := o["type"].(string); ok { + assetType = t + } + u.dLogf("Unmarshalling Device Type: %v", assetType) + // Choose which type to unmarshal into based on the "type" json key. + switch assetType { // Unmarshal again into the correct type.. + case "uap": + if uap := (UAP{}); u.unmarshalDevice(assetType, r, &uap) == nil { + devices.UAPs = append(devices.UAPs, uap) + } + case "ugw", "usg": // in case they ever fix the name in the api. + if usg := (USG{}); u.unmarshalDevice(assetType, r, &usg) == nil { + devices.USGs = append(devices.USGs, usg) + } + case "usw": + if usw := (USW{}); u.unmarshalDevice(assetType, r, &usw) == nil { + devices.USWs = append(devices.USWs, usw) + } + default: + u.eLogf("unknown asset type - %v - skipping", assetType) + } + } + return devices +} + +// unmarshalDevice handles logging for the unmarshal operations in parseDevices(). +func (u *Unifi) unmarshalDevice(dev string, data json.RawMessage, v interface{}) (err error) { + if err = json.Unmarshal(data, v); err != nil { + u.eLogf("json.Unmarshal(%v): %v", dev, err) + u.eLogf("Enable Debug Logging to output the failed payload.") + json, err := data.MarshalJSON() + u.dLogf("Failed Payload: %s (marshal err: %v)", json, err) + u.dLogf("The above payload can prove useful during torubleshooting when you open an Issue:") + u.dLogf("==- https://github.com/golift/unifi/issues/new -==") + } + return err +} diff --git a/core/unifi/types.go b/core/unifi/types.go new file mode 100644 index 00000000..61266609 --- /dev/null +++ b/core/unifi/types.go @@ -0,0 +1,120 @@ +package unifi + +import ( + "encoding/json" + "net/http" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// This is a list of unifi API paths. +// The %s in each string must be replaced with a Site.Name. +const ( + // SiteList is the path to the api site list. + SiteList string = "/api/self/sites" + // ClientPath is Unifi Clients API Path + ClientPath string = "/api/s/%s/stat/sta" + // DevicePath is where we get data about Unifi devices. + DevicePath string = "/api/s/%s/stat/device" + // NetworkPath contains network-configuration data. Not really graphable. + NetworkPath string = "/api/s/%s/rest/networkconf" + // UserGroupPath contains usergroup configurations. + UserGroupPath string = "/api/s/%s/rest/usergroup" + // LoginPath is Unifi Controller Login API Path + LoginPath string = "/api/login" +) + +// Logger is a base type to deal with changing log outputs. Create a logger +// that matches this interface to capture debug and error logs. +type Logger func(msg string, fmt ...interface{}) + +// dLogf logs a debug message. +func (u *Unifi) dLogf(msg string, v ...interface{}) { + if u.DebugLog != nil { + u.DebugLog("[DEBUG] "+msg, v...) + } +} + +// dLogf logs an error message. +func (u *Unifi) eLogf(msg string, v ...interface{}) { + if u.ErrorLog != nil { + u.ErrorLog("[ERROR] "+msg, v...) + } +} + +// Devices contains a list of all the unifi devices from a controller. +// Contains Access points, security gateways and switches. +type Devices struct { + UAPs []UAP + USGs []USG + 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. +type Unifi struct { + *http.Client + baseURL string + ErrorLog Logger + 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"` +} + +// FlexInt provides a container and unmarshalling for fields that may be +// numbers or strings in the Unifi API. +type FlexInt struct { + Val float64 + Txt string +} + +// UnmarshalJSON converts a string or number to an integer. +// Generally, do call this directly, it's used in the json interface. +func (f *FlexInt) UnmarshalJSON(b []byte) error { + var unk interface{} + if err := json.Unmarshal(b, &unk); err != nil { + return err + } + switch i := unk.(type) { + case float64: + f.Val = i + f.Txt = strconv.FormatFloat(i, 'f', -1, 64) + return nil + case string: + f.Txt = i + f.Val, _ = strconv.ParseFloat(i, 64) + return nil + default: + return errors.New("Cannot unmarshal to FlexInt") + } +} + +// FlexBool provides a container and unmarshalling for fields that may be +// boolean or strings in the Unifi API. +type FlexBool struct { + Val bool + 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. +func (f *FlexBool) UnmarshalJSON(b []byte) error { + f.Txt = strings.Trim(string(b), `"`) + 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") + return nil +} diff --git a/core/unifi/types_test.go b/core/unifi/types_test.go new file mode 100644 index 00000000..2b62cfcc --- /dev/null +++ b/core/unifi/types_test.go @@ -0,0 +1,36 @@ +package unifi + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlexInt(t *testing.T) { + t.Parallel() + a := assert.New(t) + type testReply struct { + Five FlexInt `json:"five"` + Seven FlexInt `json:"seven"` + Auto FlexInt `json:"auto"` + Channel FlexInt `json:"channel"` + } + var r testReply + // test unmarshalling the custom type three times with different values. + a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto"}`), &r)) + + // test number in string. + a.EqualValues(5, r.Five.Val) + a.EqualValues("5", r.Five.Txt) + // test number. + a.EqualValues(7, r.Seven.Val) + a.EqualValues("7", r.Seven.Txt) + // test string. + a.EqualValues(0, r.Auto.Val) + a.EqualValues("auto", r.Auto.Txt) + // test (error) struct. + a.NotNil(json.Unmarshal([]byte(`{"channel": {}}`), &r), + "a non-string and non-number must produce an error.") + a.EqualValues(0, r.Channel.Val) +} diff --git a/core/unifi/uap.go b/core/unifi/uap_influx.go similarity index 97% rename from core/unifi/uap.go rename to core/unifi/uap_influx.go index edd1fc54..31b3f128 100644 --- a/core/unifi/uap.go +++ b/core/unifi/uap_influx.go @@ -13,7 +13,6 @@ func (u UAP) Points() ([]*influx.Point, error) { /* I generally suck at InfluxDB, so if I got the tags/fields wrong, please send me a PR or open an Issue to address my faults. Thanks! */ - var points []*influx.Point tags := map[string]string{ "id": u.ID, "mac": u.Mac, @@ -22,7 +21,7 @@ func (u UAP) Points() ([]*influx.Point, error) { "device_ap": u.Stat.Ap, "site_id": u.SiteID, "name": u.Name, - "addopted": strconv.FormatBool(u.Adopted), + "adopted": strconv.FormatBool(u.Adopted), "bandsteering_mode": u.BandsteeringMode, "board_rev": strconv.Itoa(u.BoardRev), "cfgversion": u.Cfgversion, @@ -63,7 +62,7 @@ func (u UAP) Points() ([]*influx.Point, error) { "rx_bytes-d": u.RxBytesD, "tx_bytes": u.TxBytes, "tx_bytes-d": u.TxBytesD, - "uptime": u.Uptime.Number, + "uptime": u.Uptime.Val, "considered_lost_at": u.ConsideredLostAt, "next_heartbeat_at": u.NextHeartbeatAt, "scanning": u.Scanning, @@ -174,7 +173,7 @@ func (u UAP) Points() ([]*influx.Point, error) { if err != nil { return nil, err } - points = append(points, pt) + points := []*influx.Point{pt} for _, p := range u.RadioTable { tags := map[string]string{ "device_name": u.Name, @@ -182,7 +181,7 @@ func (u UAP) Points() ([]*influx.Point, error) { "device_mac": u.Mac, "name": p.Name, "wlangroup_id": p.WlangroupID, - "channel": p.Channel.String, + "channel": p.Channel.Txt, "radio": p.Radio, } fields := map[string]interface{}{ @@ -197,7 +196,7 @@ func (u UAP) Points() ([]*influx.Point, error) { "min_txpower": p.MinTxpower, "nss": p.Nss, "radio_caps": p.RadioCaps, - "tx_power": p.TxPower.Number, + "tx_power": p.TxPower.Val, "tx_power_mode": p.TxPowerMode, } @@ -219,7 +218,7 @@ func (u UAP) Points() ([]*influx.Point, error) { fields["radio"] = s.Radio fields["state"] = s.State fields["radio_tx_packets"] = s.TxPackets - fields["radio_tx_power"] = s.TxPower.Number + fields["radio_tx_power"] = s.TxPower.Val fields["radio_tx_retries"] = s.TxRetries fields["user-num_sta"] = s.UserNumSta break @@ -253,7 +252,7 @@ func (u UAP) Points() ([]*influx.Point, error) { fields["vap_tx_latency_max"] = s.TxLatencyMax fields["vap_tx_latency_min"] = s.TxLatencyMin fields["vap_tx_packets"] = s.TxPackets - fields["vap_tx_power"] = s.TxPower.Number + fields["vap_tx_power"] = s.TxPower.Val fields["vap_tx_retries"] = s.TxRetries fields["usage"] = s.Usage break diff --git a/core/unifi/uap_type.go b/core/unifi/uap_type.go index cf930920..a6116eb6 100644 --- a/core/unifi/uap_type.go +++ b/core/unifi/uap_type.go @@ -1,6 +1,6 @@ package unifi -// UAP represents all the data from the Ubiquit Controller for a Unifi Access Point. +// UAP represents all the data from the Ubiquiti Controller for a Unifi Access Point. type UAP struct { /* This was auto generated and then slowly edited by hand to get all the data types right and graphable. diff --git a/core/unifi/unidev.go b/core/unifi/unidev.go deleted file mode 100644 index 2eb9629f..00000000 --- a/core/unifi/unidev.go +++ /dev/null @@ -1,159 +0,0 @@ -package unifi - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "net/http" - "net/http/cookiejar" - "strconv" - "strings" - - "github.com/pkg/errors" -) - -const ( - // SiteList is the path to the api site list. - SiteList string = "/api/self/sites" - // ClientPath is Unifi Clients API Path - ClientPath string = "/api/s/%s/stat/sta" - // DevicePath is where we get data about Unifi devices. - DevicePath string = "/api/s/%s/stat/device" - // NetworkPath contains network-configuration data. Not really graphable. - NetworkPath string = "/api/s/%s/rest/networkconf" - // UserGroupPath contains usergroup configurations. - UserGroupPath string = "/api/s/%s/rest/usergroup" - // LoginPath is Unifi Controller Login API Path - LoginPath string = "/api/login" -) - -// Logger is a base type to deal with changing log outputs. -type Logger func(msg string, fmt ...interface{}) - -// Devices contains a list of all the unifi devices from a controller. -type Devices struct { - UAPs []UAP - USGs []USG - USWs []USW -} - -// Clients conptains a list of all the unifi clients from a controller. -type Clients struct { - UCLs []UCL -} - -// Unifi is what you get in return for providing a password! -type Unifi struct { - *http.Client - baseURL string - ErrorLog Logger - DebugLog Logger -} - -// FlexInt provides a container and unmarshalling for fields that may be -// numbers or strings in the Unifi API -type FlexInt struct { - Number float64 - String string -} - -// UnmarshalJSON converts a string or number to an integer. -func (f *FlexInt) UnmarshalJSON(b []byte) error { - var unk interface{} - if err := json.Unmarshal(b, &unk); err != nil { - return err - } - switch i := unk.(type) { - case float64: - f.Number = i - f.String = strconv.FormatFloat(i, 'f', -1, 64) - return nil - case string: - f.String = i - f.Number, _ = strconv.ParseFloat(i, 64) - return nil - default: - return errors.New("Cannot unmarshal to FlexInt") - } -} - -// FlexBool provides a container and unmarshalling for fields that may be -// boolean or strings in the Unifi API -type FlexBool struct { - Bool bool - String string -} - -// UnmarshalJSON 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. -func (f *FlexBool) UnmarshalJSON(b []byte) error { - f.String = strings.Trim(string(b), `"`) - f.Bool = f.String == "1" || strings.EqualFold(f.String, "true") || strings.EqualFold(f.String, "yes") || - strings.EqualFold(f.String, "t") || strings.EqualFold(f.String, "armed") || strings.EqualFold(f.String, "active") || - strings.EqualFold(f.String, "enabled") || strings.EqualFold(f.String, "ready") || strings.EqualFold(f.String, "up") - return nil -} - -// GetController creates a http.Client with authenticated cookies. -// Used to make additional, authenticated requests to the APIs. -func GetController(user, pass, url string, verifySSL bool) (*Unifi, error) { - json := `{"username": "` + user + `","password": "` + pass + `"}` - jar, err := cookiejar.New(nil) - if err != nil { - return nil, errors.Wrap(err, "cookiejar.New(nil)") - } - u := &Unifi{ - Client: &http.Client{ - Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}}, - Jar: jar, - }, - } - if u.baseURL = url; strings.HasSuffix(url, "/") { - u.baseURL = url[:len(url)-1] - } - req, err := u.UniReq(LoginPath, json) - if err != nil { - return u, errors.Wrap(err, "UniReq(LoginPath, json)") - } - resp, err := u.Do(req) - if err != nil { - return u, errors.Wrap(err, "authReq.Do(req)") - } - defer func() { - _ = resp.Body.Close() - }() - if resp.StatusCode != http.StatusOK { - return u, errors.Errorf("authentication failed (%v): %v (status: %v/%v)", - user, url+LoginPath, resp.StatusCode, resp.Status) - } - return u, nil -} - -// UniReq is a small helper function that adds an Accept header. -// Use this if you're unmarshalling Unifi data into custom types. -// And you're doing that... sumbut a pull request with your new struct. :) -func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) { - if params != "" { - req, err = http.NewRequest("POST", u.baseURL+apiPath, bytes.NewBufferString(params)) - } else { - req, err = http.NewRequest("GET", u.baseURL+apiPath, nil) - } - if err == nil { - req.Header.Add("Accept", "application/json") - } - return -} - -// dLogf logs a debug message. -func (u *Unifi) dLogf(msg string, v ...interface{}) { - if u.DebugLog != nil { - u.DebugLog("[DEBUG] "+msg, v...) - } -} - -// dLogf logs an error message. -func (u *Unifi) eLogf(msg string, v ...interface{}) { - if u.ErrorLog != nil { - u.ErrorLog("[ERROR] "+msg, v...) - } -} diff --git a/core/unifi/unifi.go b/core/unifi/unifi.go index 1cc11bb6..0422a529 100644 --- a/core/unifi/unifi.go +++ b/core/unifi/unifi.go @@ -1,14 +1,63 @@ +// Package unifi provides a set of types to unload (unmarshal) Unifi Ubiquiti +// controller data. Also provided are methods to easily get data for devices - +// things like access points and switches, and for clients - the things +// connected to those access points and switches. As a bonus, each device and +// client type provided has an attached method to create InfluxDB datapoints. package unifi import ( + "bytes" + "crypto/tls" "encoding/json" "fmt" + "io" "io/ioutil" + "net/http" + "net/http/cookiejar" "strings" "github.com/pkg/errors" ) +// NewUnifi creates a http.Client with authenticated cookies. +// Used to make additional, authenticated requests to the APIs. +// Start here. +func NewUnifi(user, pass, url string, verifySSL bool) (*Unifi, error) { + jar, err := cookiejar.New(nil) + if err != nil { + return nil, errors.Wrap(err, "cookiejar.New(nil)") + } + u := &Unifi{baseURL: strings.TrimRight(url, "/"), + Client: &http.Client{ + Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: !verifySSL}}, + Jar: jar, + }, + } + return u, u.getController(user, pass) +} + +// getController is a helper method to make testsing a bit easier. +func (u *Unifi) getController(user, pass string) error { + // magic login. + req, err := u.UniReq(LoginPath, `{"username": "`+user+`","password": "`+pass+`"}`) + if err != nil { + return errors.Wrap(err, "UniReq(LoginPath, json)") + } + resp, err := u.Do(req) + if err != nil { + return errors.Wrap(err, "authReq.Do(req)") + } + defer func() { + _, _ = io.Copy(ioutil.Discard, resp.Body) // avoid leaking. + _ = resp.Body.Close() + }() + if resp.StatusCode != http.StatusOK { + return errors.Errorf("authentication failed (user: %s): %s (status: %d/%s)", + user, u.baseURL+LoginPath, resp.StatusCode, resp.Status) + } + return nil +} + // GetClients returns a response full of clients' data from the Unifi Controller. func (u *Unifi) GetClients(sites []Site) (*Clients, error) { var data []UCL @@ -44,12 +93,6 @@ func (u *Unifi) GetDevices(sites []Site) (*Devices, error) { return u.parseDevices(data), nil } -// Site represents a site's data. There are more pieces to this. -type Site struct { - Name string `json:"name"` - Desc string `json:"desc"` -} - // GetSites returns a list of configured sites on the Unifi controller. func (u *Unifi) GetSites() ([]Site, error) { var response struct { @@ -87,55 +130,18 @@ func (u *Unifi) GetData(methodPath string, v interface{}) error { return nil } -// parseDevices parses the raw JSON from the Unifi Controller into device structures. -func (u *Unifi) parseDevices(data []json.RawMessage) *Devices { - devices := new(Devices) - for _, r := range data { - // Loop each item in the raw JSON message, detect its type and unmarshal it. - var obj map[string]interface{} - var uap UAP - var usg USG - var usw USW - - if u.unmarshalDevice("interface{}", &obj, r) != nil { - continue - } - assetType := "" - if t, ok := obj["type"].(string); ok { - assetType = t - } - u.dLogf("Unmarshalling Device Type: %v", assetType) - switch assetType { // Unmarshal again into the correct type.. - case "uap": - if u.unmarshalDevice(assetType, &uap, r) == nil { - devices.UAPs = append(devices.UAPs, uap) - } - case "ugw", "usg": // in case they ever fix the name in the api. - if u.unmarshalDevice(assetType, &usg, r) == nil { - devices.USGs = append(devices.USGs, usg) - } - case "usw": - if u.unmarshalDevice(assetType, &usw, r) == nil { - devices.USWs = append(devices.USWs, usw) - } - default: - u.eLogf("unknown asset type - %v - skipping", assetType) - continue - } +// UniReq is a small helper function that adds an Accept header. +// Use this if you're unmarshalling Unifi data into custom types. +// And if you're doing that... sumbut a pull request with your new struct. :) +// This is a helper method that is exposed for convenience. +func (u *Unifi) UniReq(apiPath string, params string) (req *http.Request, err error) { + if params != "" { + req, err = http.NewRequest("POST", u.baseURL+apiPath, bytes.NewBufferString(params)) + } else { + req, err = http.NewRequest("GET", u.baseURL+apiPath, nil) } - return devices -} - -// unmarshalDevice handles logging for the unmarshal operations in parseDevices(). -func (u *Unifi) unmarshalDevice(device string, ptr interface{}, payload json.RawMessage) error { - err := json.Unmarshal(payload, ptr) - if err != nil { - u.eLogf("json.Unmarshal(%v): %v", device, err) - u.eLogf("Enable Debug Logging to output the failed payload.") - json, err := payload.MarshalJSON() - u.dLogf("Failed Payload: %s (marshal err: %v)", json, err) - u.dLogf("The above payload can prove useful during torubleshooting when you open an Issue:") - u.dLogf("==- https://github.com/golift/unifi/issues/new -==") + if err == nil { + req.Header.Add("Accept", "application/json") } - return err + return } diff --git a/core/unifi/unidev_test.go b/core/unifi/unifi_test.go similarity index 66% rename from core/unifi/unidev_test.go rename to core/unifi/unifi_test.go index 35b31704..dff7687d 100644 --- a/core/unifi/unidev_test.go +++ b/core/unifi/unifi_test.go @@ -1,7 +1,6 @@ package unifi import ( - "encoding/json" "io/ioutil" "net/http" "testing" @@ -9,32 +8,17 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFlexInt(t *testing.T) { +func TestNewUnifi(t *testing.T) { t.Parallel() a := assert.New(t) - type testReply struct { - Five FlexInt `json:"five"` - Seven FlexInt `json:"seven"` - Auto FlexInt `json:"auto"` - Channel FlexInt `json:"channel"` - } - var r testReply - // test unmarshalling the custom type three times with different values. - a.Nil(json.Unmarshal([]byte(`{"five": "5", "seven": 7, "auto": "auto"}`), &r)) - - // test number in string. - a.EqualValues(5, r.Five.Number) - a.EqualValues("5", r.Five.String) - // test number. - a.EqualValues(7, r.Seven.Number) - a.EqualValues("7", r.Seven.String) - // test string. - a.EqualValues(0, r.Auto.Number) - a.EqualValues("auto", r.Auto.String) - // test (error) struct. - a.NotNil(json.Unmarshal([]byte(`{"channel": {}}`), &r), - "a non-string and non-number must produce an error.") - a.EqualValues(0, r.Channel.Number) + url := "http://127.0.0.1:64431" + authReq, err := NewUnifi("user1", "pass2", url, false) + a.NotNil(err) + a.EqualValues(url, authReq.baseURL) + a.Contains(err.Error(), "authReq.Do(req):", "an invalid destination should product a .Do(req) error.") + /* TODO: OPEN web server, check parameters posted, more. This test is incomplete. + a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), "user/pass json parameters improperly encoded") + */ } func TestUniReq(t *testing.T) { @@ -67,16 +51,3 @@ func TestUniReq(t *testing.T) { a.Nil(err, "problem reading request body, POST parameters may be malformed") a.EqualValues(p, string(d), "POST parameters improperly encoded") } - -func TestAuthController(t *testing.T) { - t.Parallel() - a := assert.New(t) - url := "http://127.0.0.1:64431" - authReq, err := GetController("user1", "pass2", url, false) - a.NotNil(err) - a.EqualValues(url, authReq.baseURL) - a.Contains(err.Error(), "authReq.Do(req):", "an invalid destination should product a .Do(req) error.") - /* TODO: OPEN web server, check parameters posted, more. This test is incomplete. - a.EqualValues(`{"username": "user1","password": "pass2"}`, string(post_params), "user/pass json parameters improperly encoded") - */ -} diff --git a/core/unifi/usg.go b/core/unifi/usg_influx.go similarity index 97% rename from core/unifi/usg.go rename to core/unifi/usg_influx.go index 894f33b4..a2a52779 100644 --- a/core/unifi/usg.go +++ b/core/unifi/usg_influx.go @@ -10,14 +10,13 @@ import ( // Points generates Unifi Gateway datapoints for InfluxDB. // These points can be passed directly to influx. 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), + "adopted": strconv.FormatBool(u.Adopted), "name": u.Name, "adopt_ip": u.AdoptIP, "adopt_url": u.AdoptURL, @@ -92,7 +91,7 @@ func (u USG) Points() ([]*influx.Point, error) { "wan1_rx_packets": u.Wan1.RxPackets, "wan1_type": u.Wan1.Type, "wan1_speed": u.Wan1.Speed, - "wan1_up": u.Wan1.Up.Bool, + "wan1_up": u.Wan1.Up.Val, "wan1_tx_bytes": u.Wan1.TxBytes, "wan1_tx_bytes-r": u.Wan1.TxBytesR, "wan1_tx_dropped": u.Wan1.TxDropped, @@ -130,7 +129,7 @@ func (u USG) Points() ([]*influx.Point, error) { if err != nil { return nil, err } - points = append(points, pt) + points := []*influx.Point{pt} for _, p := range u.NetworkTable { tags := map[string]string{ "device_name": u.Name, @@ -170,7 +169,7 @@ func (u USG) Points() ([]*influx.Point, error) { "rx_packets": p.RxPackets, "tx_bytes": p.TxBytes, "tx_packets": p.TxPackets, - "up": p.Up.String, + "up": p.Up.Txt, "vlan": p.Vlan, "dhcpd_ntp_1": p.DhcpdNtp1, "dhcpd_unifi_controller": p.DhcpdUnifiController, @@ -183,5 +182,5 @@ func (u USG) Points() ([]*influx.Point, error) { } points = append(points, pt) } - return points, err + return points, nil } diff --git a/core/unifi/usg_type.go b/core/unifi/usg_type.go index 3b29103a..e8bc453f 100644 --- a/core/unifi/usg_type.go +++ b/core/unifi/usg_type.go @@ -2,7 +2,7 @@ package unifi import "encoding/json" -// USG represents all the data from the Ubiquit Controller for a Unifi Security Gateway. +// USG represents all the data from the Ubiquiti Controller for a Unifi Security Gateway. type USG struct { ID string `json:"_id"` UUptime float64 `json:"_uptime"` diff --git a/core/unifi/usw.go b/core/unifi/usw_influx.go similarity index 96% rename from core/unifi/usw.go rename to core/unifi/usw_influx.go index 760ba833..810148de 100644 --- a/core/unifi/usw.go +++ b/core/unifi/usw_influx.go @@ -10,7 +10,6 @@ import ( // Points generates Unifi Switch datapoints for InfluxDB. // These points can be passed directly to influx. func (u USW) Points() ([]*influx.Point, error) { - var points []*influx.Point tags := map[string]string{ "id": u.ID, "mac": u.Mac, @@ -18,7 +17,7 @@ func (u USW) Points() ([]*influx.Point, error) { "device_oid": u.Stat.Oid, "site_id": u.SiteID, "name": u.Name, - "addopted": strconv.FormatBool(u.Adopted), + "adopted": strconv.FormatBool(u.Adopted), "adopt_ip": u.AdoptIP, "adopt_url": u.AdoptURL, "cfgversion": u.Cfgversion, @@ -112,8 +111,8 @@ func (u USW) Points() ([]*influx.Point, error) { // Add the port stats too. } pt, err := influx.NewPoint("usw", tags, fields, time.Now()) - if err == nil { - points = append(points, pt) + if err != nil { + return nil, err } - return points, err + return []*influx.Point{pt}, nil } diff --git a/core/unifi/usw_type.go b/core/unifi/usw_type.go index 7fa68de4..a0d6485c 100644 --- a/core/unifi/usw_type.go +++ b/core/unifi/usw_type.go @@ -1,6 +1,6 @@ package unifi -// USW represents all the data from the Ubiquit Controller for a Unifi Switch. +// USW represents all the data from the Ubiquiti Controller for a Unifi Switch. type USW struct { ID string `json:"_id"` UUptime float64 `json:"_uptime"`