make dumper work

This commit is contained in:
davidnewhall2 2019-12-17 01:31:30 -08:00
parent aec4b12d3c
commit d3d420597e
11 changed files with 178 additions and 152 deletions

4
Gopkg.lock generated
View File

@ -107,11 +107,11 @@
[[projects]]
branch = "master"
digest = "1:d54a8d89f95a4d2a5a24ce63cb1835ccdff337fde7776c87ceacb6fdbe4349ae"
digest = "1:7a90fad47972b5ae06013d4685eb2f3007e7c92609a1399d2adf59fe04cd9b63"
name = "golift.io/config"
packages = ["."]
pruneopts = "UT"
revision = "fd8ffb02173aad2183e5555a03b1d1f909aca930"
revision = "fe642c8392dc00d72ddcc47f05a06096bd5d054b"
[[projects]]
digest = "1:2883cea734f2766f41ff9c9d4aefccccc53e3d44f5c8b08893b9c218cf666722"

View File

@ -24,7 +24,7 @@
<verify_ssl>false</verify_ssl>
</influxdb>
<unifi>
<unifi disable="false">
<!-- Repeat this stanza to poll additional controllers. -->
<controller name="">
<site>all</site>

View File

@ -24,6 +24,7 @@ influxdb:
verify_ssl: false
unifi:
disable: false
controllers:
- name: ""
user: "influx"

View File

@ -24,7 +24,7 @@ const (
// Config defines the data needed to store metrics in InfluxDB
type Config struct {
Interval config.Duration `json:"interval,omitempty" toml:"interval,omitempty" xml:"interval" yaml:"interval"`
Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
Disable bool `json:"disable" toml:"disable" xml:"disable,attr" yaml:"disable"`
VerifySSL bool `json:"verify_ssl" toml:"verify_ssl" xml:"verify_ssl" yaml:"verify_ssl"`
URL string `json:"url,omitempty" toml:"url,omitempty" xml:"url" yaml:"url"`
User string `json:"user,omitempty" toml:"user,omitempty" xml:"user" yaml:"user"`

View File

@ -8,14 +8,14 @@ import (
"golift.io/unifi"
)
func (u *InputUnifi) isNill(c Controller) bool {
func (u *InputUnifi) isNill(c *Controller) bool {
u.Config.RLock()
defer u.Config.RUnlock()
return c.Unifi == nil
}
func (u *InputUnifi) collectController(c Controller) (*poller.Metrics, error) {
func (u *InputUnifi) collectController(c *Controller) (*poller.Metrics, error) {
if u.isNill(c) {
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
@ -32,7 +32,7 @@ func (u *InputUnifi) collectController(c Controller) (*poller.Metrics, error) {
return u.pollController(c)
}
func (u *InputUnifi) pollController(c Controller) (*poller.Metrics, error) {
func (u *InputUnifi) pollController(c *Controller) (*poller.Metrics, error) {
var err error
u.Config.RLock()
@ -67,7 +67,7 @@ func (u *InputUnifi) pollController(c Controller) (*poller.Metrics, error) {
// augmentMetrics is our middleware layer between collecting metrics and writing them.
// This is where we can manipuate the returned data or make arbitrary decisions.
// This function currently adds parent device names to client metrics.
func (u *InputUnifi) augmentMetrics(c Controller, metrics *poller.Metrics) *poller.Metrics {
func (u *InputUnifi) augmentMetrics(c *Controller, metrics *poller.Metrics) *poller.Metrics {
if metrics == nil || metrics.Devices == nil || metrics.Clients == nil {
return metrics
}
@ -113,22 +113,22 @@ func (u *InputUnifi) augmentMetrics(c Controller, metrics *poller.Metrics) *poll
// getFilteredSites returns a list of sites to fetch data for.
// Omits requested but unconfigured sites. Grabs the full list from the
// controller and returns the sites provided in the config file.
func (u *InputUnifi) getFilteredSites(c Controller) (unifi.Sites, error) {
func (u *InputUnifi) getFilteredSites(c *Controller) (unifi.Sites, error) {
u.Config.RLock()
defer u.Config.RUnlock()
sites, err := c.Unifi.GetSites()
if err != nil {
return nil, err
} else if len(c.Sites) < 1 || poller.StringInSlice("all", c.Sites) {
} else if len(c.Sites) < 1 || StringInSlice("all", c.Sites) {
return sites, nil
}
var i int
i := 0
for _, s := range sites {
// Only include valid sites in the request filter.
if poller.StringInSlice(s.Name, c.Sites) {
if StringInSlice(s.Name, c.Sites) {
sites[i] = s
i++
}

View File

@ -4,6 +4,8 @@ package inputunifi
import (
"fmt"
"os"
"strings"
"sync"
@ -13,7 +15,7 @@ import (
// InputUnifi contains the running data.
type InputUnifi struct {
Config Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
Config *Config `json:"unifi" toml:"unifi" xml:"unifi" yaml:"unifi"`
poller.Logger
}
@ -35,7 +37,7 @@ type Controller struct {
type Config struct {
sync.RWMutex // locks the Unifi struct member when re-authing to unifi.
Disable bool `json:"disable" toml:"disable" xml:"disable" yaml:"disable"`
Controllers []Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"`
Controllers []*Controller `json:"controllers" toml:"controller" xml:"controller" yaml:"controllers"`
}
func init() {
@ -48,7 +50,7 @@ func init() {
}
// getUnifi (re-)authenticates to a unifi controller.
func (u *InputUnifi) getUnifi(c Controller) error {
func (u *InputUnifi) getUnifi(c *Controller) error {
var err error
u.Config.Lock()
@ -76,3 +78,74 @@ func (u *InputUnifi) getUnifi(c Controller) error {
return nil
}
// checkSites makes sure the list of provided sites exists on the controller.
// This only runs once during initialization.
func (u *InputUnifi) checkSites(c *Controller) error {
u.Config.RLock()
defer u.Config.RUnlock()
if len(c.Sites) < 1 || c.Sites[0] == "" {
c.Sites = []string{"all"}
}
u.LogDebugf("Checking Controller Sites List")
sites, err := c.Unifi.GetSites()
if err != nil {
return err
}
msg := []string{}
for _, site := range sites {
msg = append(msg, site.Name+" ("+site.Desc+")")
}
u.Logf("Found %d site(s) on controller %s: %v", len(msg), c.Name, strings.Join(msg, ", "))
if StringInSlice("all", c.Sites) {
c.Sites = []string{"all"}
return nil
}
FIRST:
for _, s := range c.Sites {
for _, site := range sites {
if s == site.Name {
continue FIRST
}
}
return fmt.Errorf("configured site not found on controller: %v", s)
}
return nil
}
func (u *InputUnifi) dumpSitesJSON(c *Controller, path, name string, sites unifi.Sites) ([]byte, error) {
allJSON := []byte{}
for _, s := range sites {
apiPath := fmt.Sprintf(path, s.Name)
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n", name, apiPath, s.Desc, s.Name)
body, err := c.Unifi.GetJSON(apiPath)
if err != nil {
return allJSON, err
}
allJSON = append(allJSON, body...)
}
return allJSON, nil
}
// StringInSlice returns true if a string is in a slice.
func StringInSlice(str string, slice []string) bool {
for _, s := range slice {
if strings.EqualFold(s, str) {
return true
}
}
return false
}

View File

@ -1,13 +1,51 @@
package inputunifi
/* This file contains the three poller.Input interface methods. */
import (
"fmt"
"os"
"strings"
"github.com/davidnewhall/unifi-poller/pkg/poller"
"golift.io/unifi"
)
// Initialize gets called one time when starting up.
// Satisfies poller.Input interface.
func (u *InputUnifi) Initialize(l poller.Logger) error {
if u.Config.Disable {
l.Logf("unifi input disabled")
return nil
}
if len(u.Config.Controllers) < 1 {
return fmt.Errorf("no unifi controllers defined for unifi input")
}
u.Logger = l
for i, c := range u.Config.Controllers {
if c.Name == "" {
u.Config.Controllers[i].Name = c.URL
}
switch err := u.getUnifi(c); err {
case nil:
if err := u.checkSites(c); err != nil {
u.LogErrorf("checking sites on %s: %v", c.Name, err)
}
u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
c.URL, c.Unifi.ServerVersion, c.User, c.Sites)
default:
u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Name, err)
}
}
return nil
}
// Metrics grabs all the measurements from a UniFi controller and returns them.
func (u *InputUnifi) Metrics() (*poller.Metrics, error) {
if u.Config.Disable {
@ -52,75 +90,35 @@ func (u *InputUnifi) Metrics() (*poller.Metrics, error) {
return metrics, nil
}
// Initialize gets called one time when starting up.
// Satisfies poller.Input interface.
func (u *InputUnifi) Initialize(l poller.Logger) error {
if u.Config.Disable {
l.Logf("unifi input disabled")
return nil
// RawMetrics returns API output from the first configured unifi controller.
func (u *InputUnifi) RawMetrics(filter poller.Filter) ([]byte, error) {
c := u.Config.Controllers[0] // We could pull the controller number from the filter.
if u.isNill(c) {
u.Logf("Re-authenticating to UniFi Controller: %s", c.URL)
if err := u.getUnifi(c); err != nil {
return nil, fmt.Errorf("re-authenticating to %s: %v", c.Name, err)
}
}
if len(u.Config.Controllers) < 1 {
return fmt.Errorf("no unifi controllers defined for unifi input")
}
u.Logger = l
for i, c := range u.Config.Controllers {
if c.Name == "" {
u.Config.Controllers[i].Name = c.URL
}
switch err := u.getUnifi(c); err {
case nil:
if err := u.checkSites(c); err != nil {
u.LogErrorf("checking sites on %s: %v", c.Name, err)
return nil, err
}
u.Logf("Polling UniFi Controller at %s v%s as user %s. Sites: %v",
c.URL, c.Unifi.ServerVersion, c.User, c.Sites)
default:
u.LogErrorf("Controller Auth or Connection failed, but continuing to retry! %s: %v", c.Name, err)
}
}
return nil
}
// checkSites makes sure the list of provided sites exists on the controller.
// This only runs once during initialization.
func (u *InputUnifi) checkSites(c Controller) error {
u.Config.RLock()
defer u.Config.RUnlock()
u.LogDebugf("Checking Controller Sites List")
sites, err := c.Unifi.GetSites()
sites, err := u.getFilteredSites(c)
if err != nil {
return err
return nil, err
}
msg := []string{}
for _, site := range sites {
msg = append(msg, site.Name+" ("+site.Desc+")")
}
u.Logf("Found %d site(s) on controller: %v", len(msg), strings.Join(msg, ", "))
if poller.StringInSlice("all", c.Sites) {
c.Sites = []string{"all"}
return nil
}
FIRST:
for _, s := range c.Sites {
for _, site := range sites {
if s == site.Name {
continue FIRST
switch filter.Type {
case "d", "device", "devices":
return u.dumpSitesJSON(c, unifi.APIDevicePath, "Devices", sites)
case "client", "clients", "c":
return u.dumpSitesJSON(c, unifi.APIClientPath, "Clients", sites)
case "other", "o":
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", filter.Term)
return c.Unifi.GetJSON(filter.Term)
default:
return []byte{}, fmt.Errorf("must provide filter: devices, clients, other")
}
}
return fmt.Errorf("configured site not found on controller: %v", s)
}
return nil
}

View File

@ -1,85 +1,28 @@
package poller
import (
"fmt"
"strings"
)
// DumpJSONPayload prints raw json from the UniFi Controller.
// This only works with controller 0 (first one) in the config.
func (u *UnifiPoller) DumpJSONPayload() (err error) {
if true {
return nil
}
/*
u.Config.Quiet = true
config := u.Config.Controllers[0]
config.Unifi, err = unifi.NewUnifi(&unifi.Config{
User: config.User,
Pass: config.Pass,
URL: config.URL,
VerifySSL: config.VerifySSL,
})
split := strings.SplitN(u.Flags.DumpJSON, " ", 2)
filter := Filter{Type: split[0]}
if len(split) > 1 {
filter.Term = split[1]
}
m, err := inputs[0].RawMetrics(filter)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "[INFO] Authenticated to UniFi Controller @ %v as user %v", config.URL, config.User)
fmt.Println(string(m))
if err := u.CheckSites(config); err != nil {
return err
}
config.Unifi.ErrorLog = func(m string, v ...interface{}) {
fmt.Fprintf(os.Stderr, "[ERROR] "+m, v...)
} // Log all errors to stderr.
switch sites, err := u.GetFilteredSites(config); {
case err != nil:
return err
case StringInSlice(u.Flags.DumpJSON, []string{"d", "device", "devices"}):
return u.dumpSitesJSON(config, unifi.APIDevicePath, "Devices", sites)
case StringInSlice(u.Flags.DumpJSON, []string{"client", "clients", "c"}):
return u.dumpSitesJSON(config, unifi.APIClientPath, "Clients", sites)
case strings.HasPrefix(u.Flags.DumpJSON, "other "):
apiPath := strings.SplitN(u.Flags.DumpJSON, " ", 2)[1]
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping Path '%s':\n", apiPath)
return u.PrintRawAPIJSON(config, apiPath)
default:
return fmt.Errorf("must provide filter: devices, clients, other")
}
*/
return nil
}
/*
func (u *UnifiPoller) dumpSitesJSON(c Controller, path, name string, sites unifi.Sites) error {
for _, s := range sites {
apiPath := fmt.Sprintf(path, s.Name)
_, _ = fmt.Fprintf(os.Stderr, "[INFO] Dumping %s: '%s' JSON for site: %s (%s):\n",
name, apiPath, s.Desc, s.Name)
if err := u.PrintRawAPIJSON(c, apiPath); err != nil {
return err
}
}
return nil
}
// PrintRawAPIJSON prints the raw json for a user-provided path on a UniFi Controller.
func (u *UnifiPoller) PrintRawAPIJSON(c Controller, apiPath string) error {
body, err := c.Unifi.GetJSON(apiPath)
fmt.Println(string(body))
return err
}
*/
// StringInSlice returns true if a string is in a slice.
func StringInSlice(str string, slice []string) bool {
for _, s := range slice {
if strings.EqualFold(s, str) {
return true
}
}
return false
}

View File

@ -17,6 +17,7 @@ var (
type Input interface {
Initialize(Logger) error // Called once on startup to initialize the plugin.
Metrics() (*Metrics, error) // Called every time new metrics are requested.
RawMetrics(Filter) ([]byte, error)
}
// InputPlugin describes an input plugin's consumable interface.
@ -25,6 +26,12 @@ type InputPlugin struct {
Input
}
// Filter is used for raw metrics filters.
type Filter struct {
Type string
Term string
}
// NewInput creates a metric input. This should be called by input plugins
// init() functions.
func NewInput(i *InputPlugin) {

View File

@ -16,14 +16,14 @@ type Logger interface {
// Logf prints a log entry if quiet is false.
func (u *UnifiPoller) Logf(m string, v ...interface{}) {
if !u.Config.Quiet {
if !u.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[INFO] "+m, v...))
}
}
// LogDebugf prints a debug log entry if debug is true and quite is false
func (u *UnifiPoller) LogDebugf(m string, v ...interface{}) {
if u.Config.Debug && !u.Config.Quiet {
if u.Debug && !u.Quiet {
_ = log.Output(callDepth, fmt.Sprintf("[DEBUG] "+m, v...))
}
}

View File

@ -61,10 +61,14 @@ func (f *Flags) Parse(args []string) {
// 3. Start a web server and wait for Prometheus to poll the application for metrics.
func (u *UnifiPoller) Run() error {
if u.Flags.DumpJSON != "" {
if err := u.InitializeInputs(); err != nil {
return err
}
return u.DumpJSONPayload()
}
if u.Config.Debug {
if u.Debug {
log.SetFlags(log.Lshortfile | log.Lmicroseconds | log.Ldate)
u.LogDebugf("Debug Logging Enabled")
}