diff --git a/go.mod b/go.mod index ed518370a..1d46fb2b3 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/fabiolb/fabio require ( github.com/armon/go-proxyproto v0.0.0-20150207025855-609d6338d3a7 - github.com/circonus-labs/circonus-gometrics v0.0.0-20160830164725-f8c68ed96a06 + github.com/circonus-labs/circonus-gometrics v1.0.0 github.com/circonus-labs/circonusllhist v0.0.0-20160526043813-d724266ae527 // indirect github.com/cyberdelia/go-metrics-graphite v0.0.0-20150826032200-b8345b7f01d5 github.com/fatih/structs v0.0.0-20160601093117-5ada2f449b10 // indirect diff --git a/go.sum b/go.sum index 74da97231..eb2331859 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/armon/go-proxyproto v0.0.0-20150207025855-609d6338d3a7 h1:AuTQOaYRBRt github.com/armon/go-proxyproto v0.0.0-20150207025855-609d6338d3a7/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/circonus-labs/circonus-gometrics v0.0.0-20160830164725-f8c68ed96a06 h1:DHdPk46/oiLoYPQ7Dt10pRx74HaJVoyjS5Ea7eyROes= github.com/circonus-labs/circonus-gometrics v0.0.0-20160830164725-f8c68ed96a06/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonus-gometrics v1.0.0 h1:pMILaNgfEYrx8qdBukpsVU3muDkqfyPK/kmz1Ww4tQ4= +github.com/circonus-labs/circonus-gometrics v1.0.0/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.0.0-20160526043813-d724266ae527 h1:g5Ns3zGpLJ0T+1YJ0d3VVPrlMMyFHXV4x82xNgIT9z0= github.com/circonus-labs/circonusllhist v0.0.0-20160526043813-d724266ae527/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/cyberdelia/go-metrics-graphite v0.0.0-20150826032200-b8345b7f01d5 h1:TYSgSeiA3LfBi6son0KgHb4wcTXV1kB5wZtTorXQ914= diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md b/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md new file mode 100644 index 000000000..1cd78d19e --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/OPTIONS.md @@ -0,0 +1,109 @@ +## Circonus gometrics options + +### Example defaults +```go +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path" + + cgm "github.com/circonus-labs/circonus-gometrics" +) + +func main() { + cfg := &cgm.Config{} + + // Defaults + + // General + cfg.Debug = false + cfg.Log = log.New(ioutil.Discard, "", log.LstdFlags) + cfg.Interval = "10s" + cfg.ResetCounters = "true" + cfg.ResetGauges = "true" + cfg.ResetHistograms = "true" + cfg.ResetText = "true" + + // API + cfg.CheckManager.API.TokenKey = "" + cfg.CheckManager.API.TokenApp = "circonus-gometrics" + cfg.CheckManager.API.TokenURL = "https://api.circonus.com/v2" + cfg.CheckManager.API.CACert = nil + + // Check + _, an := path.Split(os.Args[0]) + hn, _ := os.Hostname() + cfg.CheckManager.Check.ID = "" + cfg.CheckManager.Check.SubmissionURL = "" + cfg.CheckManager.Check.InstanceID = fmt.Sprintf("%s:%s", hn, an) + cfg.CheckManager.Check.TargetHost = cfg.CheckManager.Check.InstanceID + cfg.CheckManager.Check.DisplayName = cfg.CheckManager.Check.InstanceID + cfg.CheckManager.Check.SearchTag = fmt.Sprintf("service:%s", an) + cfg.CheckManager.Check.Tags = "" + cfg.CheckManager.Check.Secret = "" // randomly generated sha256 hash + cfg.CheckManager.Check.MaxURLAge = "5m" + cfg.CheckManager.Check.ForceMetricActivation = "false" + + // Broker + cfg.CheckManager.Broker.ID = "" + cfg.CheckManager.Broker.SelectTag = "" + cfg.CheckManager.Broker.MaxResponseTime = "500ms" + + // create a new cgm instance and start sending metrics... + // see the complete example in the main README. +} +``` + +## Options +| Option | Default | Description | +| ------ | ------- | ----------- | +| General || +| `cfg.Log` | none | log.Logger instance to send logging messages. Default is to discard messages. If Debug is turned on and no instance is specified, messages will go to stderr. | +| `cfg.Debug` | false | Turn on debugging messages. | +| `cfg.Interval` | "10s" | Interval at which metrics are flushed and sent to Circonus. Set to "0s" to disable automatic flush (note, if disabled, `cgm.Flush()` must be called manually to send metrics to Circonus).| +| `cfg.ResetCounters` | "true" | Reset counter metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetGauges` | "true" | Reset gauge metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetHistograms` | "true" | Reset histogram metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +| `cfg.ResetText` | "true" | Reset text metrics after each submission. Change to "false" to retain (and continue submitting) the last value.| +|API|| +| `cfg.CheckManager.API.TokenKey` | "" | [Circonus API Token key](https://login.circonus.com/user/tokens) | +| `cfg.CheckManager.API.TokenApp` | "circonus-gometrics" | App associated with API token | +| `cfg.CheckManager.API.URL` | "https://api.circonus.com/v2" | Circonus API URL | +| `cfg.CheckManager.API.CACert` | nil | [*x509.CertPool](https://golang.org/pkg/crypto/x509/#CertPool) with CA Cert to validate API endpoint using internal CA or self-signed certificates | +|Check|| +| `cfg.CheckManager.Check.ID` | "" | Check ID of previously created check. (*Note: **check id** not **check bundle id**.*) | +| `cfg.CheckManager.Check.SubmissionURL` | "" | Submission URL of previously created check. | +| `cfg.CheckManager.Check.InstanceID` | hostname:program name | An identifier for the 'group of metrics emitted by this process or service'. | +| `cfg.CheckManager.Check.TargetHost` | InstanceID | Explicit setting of `check.target`. | +| `cfg.CheckManager.Check.DisplayName` | InstanceID | Custom `check.display_name`. Shows in UI check list. | +| `cfg.CheckManager.Check.SearchTag` | service:program name | Specific tag used to search for an existing check when neither SubmissionURL nor ID are provided. | +| `cfg.CheckManager.Check.Tags` | "" | List (comma separated) of tags to add to check when it is being created. The SearchTag will be added to the list. | +| `cfg.CheckManager.Check.Secret` | random generated | A secret to use for when creating an httptrap check. | +| `cfg.CheckManager.Check.MaxURLAge` | "5m" | Maximum amount of time to retry a [failing] submission URL before refreshing it. | +| `cfg.CheckManager.Check.ForceMetricActivation` | "false" | If a metric has been disabled via the UI the default behavior is to *not* re-activate the metric; this setting overrides the behavior and will re-activate the metric when it is encountered. | +|Broker|| +| `cfg.CheckManager.Broker.ID` | "" | ID of a specific broker to use when creating a check. Default is to use a random enterprise broker or the public Circonus default broker. | +| `cfg.CheckManager.Broker.SelectTag` | "" | Used to select a broker with the same tag(s). If more than one broker has the tag(s), one will be selected randomly from the resulting list. (e.g. could be used to select one from a list of brokers serving a specific colo/region. "dc:sfo", "loc:nyc,dc:nyc01", "zone:us-west") | +| `cfg.CheckManager.Broker.MaxResponseTime` | "500ms" | Maximum amount time to wait for a broker connection test to be considered valid. (if latency is > the broker will be considered invalid and not available for selection.) | + +## Notes: + +* All options are *strings* with the following exceptions: + * `cfg.Log` - an instance of [`log.Logger`](https://golang.org/pkg/log/#Logger) or something else (e.g. [logrus](https://github.com/Sirupsen/logrus)) which can be used to satisfy the interface requirements. + * `cfg.Debug` - a boolean true|false. +* At a minimum, one of either `API.TokenKey` or `Check.SubmissionURL` is **required** for cgm to function. +* Check management can be disabled by providing a `Check.SubmissionURL` without an `API.TokenKey`. Note: the supplied URL needs to be http or the broker needs to be running with a cert which can be verified. Otherwise, the `API.TokenKey` will be required to retrieve the correct CA certificate to validate the broker's cert for the SSL connection. +* A note on `Check.InstanceID`, the instance id is used to consistently identify a check. The display name can be changed in the UI. The hostname may be ephemeral. For metric continuity, the instance id is used to locate existing checks. Since the check.target is never actually used by an httptrap check it is more decorative than functional, a valid FQDN is not required for an httptrap check.target. But, using instance id as the target can pollute the Host list in the UI with host:application specific entries. +* Check identification precedence + 1. Check SubmissionURL + 2. Check ID + 3. Search + 1. Search for an active httptrap check for TargetHost which has the SearchTag + 2. Search for an active httptrap check which has the SearchTag and the InstanceID in the notes field + 3. Create a new check +* Broker selection + 1. If Broker.ID or Broker.SelectTag are not specified, a broker will be selected randomly from the list of brokers available to the API token. Enterprise brokers take precedence. A viable broker is "active", has the "httptrap" module enabled, and responds within Broker.MaxResponseTime. diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/README.md b/vendor/github.com/circonus-labs/circonus-gometrics/README.md index d245e40af..ca6cabb23 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/README.md +++ b/vendor/github.com/circonus-labs/circonus-gometrics/README.md @@ -1,156 +1,208 @@ # Circonus metrics tracking for Go applications -This library supports named counters, gauges and histograms. -It also provides convenience wrappers for registering latency -instrumented functions with Go's builtin http server. +This library supports named counters, gauges and histograms. It also provides convenience wrappers for registering latency instrumented functions with Go's builtin http server. -Initializing only requires setting an ApiToken. +Initializing only requires setting an [API Token](https://login.circonus.com/user/tokens) at a minimum. + +## Options + +See [OPTIONS.md](OPTIONS.md) for information on all of the available cgm options. ## Example -**rough and simple** +### Bare bones minimum + +A working cut-n-past example. Simply set the required environment variable `CIRCONUS_API_TOKEN` and run. ```go package main import ( - "log" - "math/rand" - "os" - "time" + "log" + "math/rand" + "os" + "os/signal" + "syscall" + "time" - cgm "github.com/circonus-labs/circonus-gometrics" + cgm "github.com/circonus-labs/circonus-gometrics" ) func main() { - log.Println("Configuring cgm") - - cmc := &cgm.Config{} - - // Interval at which metrics are submitted to Circonus, default: 10 seconds - cmc.Interval = "10s" // 10 seconds - // Enable debug messages, default: false - cmc.Debug = false - // Send debug messages to specific log.Logger instance - // default: if debug stderr, else, discard - //cmc.CheckManager.Log = ... - - // Circonus API configuration options - // - // Token, no default (blank disables check manager) - cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") - // App name, default: circonus-gometrics - cmc.CheckManager.API.TokenApp = os.Getenv("CIRCONUS_API_APP") - // URL, default: https://api.circonus.com/v2 - cmc.CheckManager.API.URL = os.Getenv("CIRCONUS_API_URL") - - // Check configuration options - // - // precedence 1 - explicit submission_url - // precedence 2 - specific check id (note: not a check bundle id) - // precedence 3 - search using instanceId and searchTag - // otherwise: if an applicable check is NOT specified or found, an - // attempt will be made to automatically create one - // - // Pre-existing httptrap check submission_url - cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISION_URL") - // Pre-existing httptrap check id (check not check bundle) - cmc.CheckManager.Check.ID = "" - // if neither a submission url nor check id are provided, an attempt will be made to find an existing - // httptrap check by using the circonus api to search for a check matching the following criteria: - // an active check, - // of type httptrap, - // where the target/host is equal to InstanceId - see below - // and the check has a tag equal to SearchTag - see below - // Instance ID - an identifier for the 'group of metrics emitted by this process or service' - // this is used as the value for check.target (aka host) - // default: 'hostname':'program name' - // note: for a persistent instance that is ephemeral or transient where metric continuity is - // desired set this explicitly so that the current hostname will not be used. - cmc.CheckManager.Check.InstanceID = "" - // Search tag - a specific tag which, when coupled with the instanceId serves to identify the - // origin and/or grouping of the metrics - // default: service:application name (e.g. service:consul) - cmc.CheckManager.Check.SearchTag = "" - // Check secret, default: generated when a check needs to be created - cmc.CheckManager.Check.Secret = "" - // Check tags, array of strings, additional tags to add to a new check, default: none - //cmc.CheckManager.Check.Tags = []string{"category:tagname"} - // max amount of time to to hold on to a submission url - // when a given submission fails (due to retries) if the - // time the url was last updated is > than this, the trap - // url will be refreshed (e.g. if the broker is changed - // in the UI) default 5 minutes - cmc.CheckManager.Check.MaxURLAge = "5m" - // custom display name for check, default: "InstanceId /cgm" - cmc.CheckManager.Check.DisplayName = "" - // force metric activation - if a metric has been disabled via the UI - // the default behavior is to *not* re-activate the metric; this setting - // overrides the behavior and will re-activate the metric when it is - // encountered. "(true|false)", default "false" - cmc.CheckManager.Check.ForceMetricActivation = "false" - - // Broker configuration options - // - // Broker ID of specific broker to use, default: random enterprise broker or - // Circonus default if no enterprise brokers are available. - // default: only used if set - cmc.CheckManager.Broker.ID = "" - // used to select a broker with the same tag (e.g. can be used to dictate that a broker - // serving a specific location should be used. "dc:sfo", "location:new_york", "zone:us-west") - // if more than one broker has the tag, one will be selected randomly from the resulting list - // default: not used unless != "" - cmc.CheckManager.Broker.SelectTag = "" - // longest time to wait for a broker connection (if latency is > the broker will - // be considered invalid and not available for selection.), default: 500 milliseconds - cmc.CheckManager.Broker.MaxResponseTime = "500ms" - // if broker Id or SelectTag are not specified, a broker will be selected randomly - // from the list of brokers available to the api token. enterprise brokers take precedence - // viable brokers are "active", have the "httptrap" module enabled, are reachable and respond - // within MaxResponseTime. - - log.Println("Creating new cgm instance") - - metrics, err := cgm.NewCirconusMetrics(cmc) - if err != nil { - panic(err) - } - - src := rand.NewSource(time.Now().UnixNano()) - rnd := rand.New(src) - - log.Println("Starting cgm internal auto-flush timer") - metrics.Start() - - log.Println("Starting to send metrics") - - // number of "sets" of metrics to send (a minute worth) - max := 60 - - for i := 1; i < max; i++ { - log.Printf("\tmetric set %d of %d", i, 60) - metrics.Timing("ding", rnd.Float64()*10) - metrics.Increment("dong") - metrics.Gauge("dang", 10) - time.Sleep(1000 * time.Millisecond) - } - - log.Println("Flushing any outstanding metrics manually") - metrics.Flush() + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Println("Configuring cgm") + + cmc := &cgm.Config{} + cmc.Debug = false // set to true for debug messages + cmc.Log = logger + + // Circonus API Token key (https://login.circonus.com/user/tokens) + cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") + + logger.Println("Creating new cgm instance") + + metrics, err := cgm.NewCirconusMetrics(cmc) + if err != nil { + logger.Println(err) + os.Exit(1) + } + src := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(src) + + logger.Println("Adding ctrl-c trap") + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + logger.Println("Received CTRL-C, flushing outstanding metrics before exit") + metrics.Flush() + os.Exit(0) + }() + + logger.Println("Starting to send metrics") + + // number of "sets" of metrics to send + max := 60 + + for i := 1; i < max; i++ { + logger.Printf("\tmetric set %d of %d", i, 60) + metrics.Timing("foo", rnd.Float64()*10) + metrics.Increment("bar") + metrics.Gauge("baz", 10) + time.Sleep(time.Second) + } + + metrics.SetText("fini", "complete") + + logger.Println("Flushing any outstanding metrics manually") + metrics.Flush() } ``` -### HTTP Handler wrapping +### A more complete example + +A working, cut-n-paste example with all options available for modification. Also, demonstrates metric tagging. + +```go +package main + +import ( + "log" + "math/rand" + "os" + "os/signal" + "syscall" + "time" + + cgm "github.com/circonus-labs/circonus-gometrics" +) + +func main() { + logger := log.New(os.Stdout, "", log.LstdFlags) + + logger.Println("Configuring cgm") + + cmc := &cgm.Config{} + + // General + + cmc.Interval = "10s" + cmc.Log = logger + cmc.Debug = false + cmc.ResetCounters = "true" + cmc.ResetGauges = "true" + cmc.ResetHistograms = "true" + cmc.ResetText = "true" + + // Circonus API configuration options + cmc.CheckManager.API.TokenKey = os.Getenv("CIRCONUS_API_TOKEN") + cmc.CheckManager.API.TokenApp = os.Getenv("CIRCONUS_API_APP") + cmc.CheckManager.API.URL = os.Getenv("CIRCONUS_API_URL") + + // Check configuration options + cmc.CheckManager.Check.SubmissionURL = os.Getenv("CIRCONUS_SUBMISSION_URL") + cmc.CheckManager.Check.ID = os.Getenv("CIRCONUS_CHECK_ID") + cmc.CheckManager.Check.InstanceID = "" + cmc.CheckManager.Check.DisplayName = "" + cmc.CheckManager.Check.TargetHost = "" + // if hn, err := os.Hostname(); err == nil { + // cmc.CheckManager.Check.TargetHost = hn + // } + cmc.CheckManager.Check.SearchTag = "" + cmc.CheckManager.Check.Secret = "" + cmc.CheckManager.Check.Tags = "" + cmc.CheckManager.Check.MaxURLAge = "5m" + cmc.CheckManager.Check.ForceMetricActivation = "false" + + // Broker configuration options + cmc.CheckManager.Broker.ID = "" + cmc.CheckManager.Broker.SelectTag = "" + cmc.CheckManager.Broker.MaxResponseTime = "500ms" + + logger.Println("Creating new cgm instance") + + metrics, err := cgm.NewCirconusMetrics(cmc) + if err != nil { + logger.Println(err) + os.Exit(1) + } + + src := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(src) + + logger.Println("Adding ctrl-c trap") + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + logger.Println("Received CTRL-C, flushing outstanding metrics before exit") + metrics.Flush() + os.Exit(0) + }() + + // Add metric tags (append to any existing tags on specified metric) + metrics.AddMetricTags("foo", []string{"cgm:test"}) + metrics.AddMetricTags("baz", []string{"cgm:test"}) + + logger.Println("Starting to send metrics") + + // number of "sets" of metrics to send + max := 60 + + for i := 1; i < max; i++ { + logger.Printf("\tmetric set %d of %d", i, 60) + + metrics.Timing("foo", rnd.Float64()*10) + metrics.Increment("bar") + metrics.Gauge("baz", 10) + + if i == 35 { + // Set metric tags (overwrite current tags on specified metric) + metrics.SetMetricTags("baz", []string{"cgm:reset_test", "cgm:test2"}) + } + + time.Sleep(time.Second) + } + + logger.Println("Flushing any outstanding metrics manually") + metrics.Flush() + +} ``` + +### HTTP Handler wrapping + +```go http.HandleFunc("/", metrics.TrackHTTPLatency("/", handler_func)) ``` ### HTTP latency example -``` +```go package main import ( @@ -168,7 +220,6 @@ func main() { if err != nil { panic(err) } - metrics.Start() http.HandleFunc("/", metrics.TrackHTTPLatency("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md b/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md new file mode 100644 index 000000000..8f286b79f --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/README.md @@ -0,0 +1,163 @@ +## Circonus API package + +Full api documentation (for using *this* package) is available at [godoc.org](https://godoc.org/github.com/circonus-labs/circonus-gometrics/api). Links in the lists below go directly to the generic Circonus API documentation for the endpoint. + +### Straight [raw] API access + +* Get +* Post (for creates) +* Put (for updates) +* Delete + +### Helpers for currently supported API endpoints + +> Note, these interfaces are still being actively developed. For example, many of the `New*` methods only return an empty struct; sensible defaults will be added going forward. Other, common helper methods for the various endpoints may be added as use cases emerge. The organization +of the API may change if common use contexts would benefit significantly. + +* [Account](https://login.circonus.com/resources/api/calls/account) + * FetchAccount + * FetchAccounts + * UpdateAccount + * SearchAccounts +* [Acknowledgement](https://login.circonus.com/resources/api/calls/acknowledgement) + * NewAcknowledgement + * FetchAcknowledgement + * FetchAcknowledgements + * UpdateAcknowledgement + * CreateAcknowledgement + * DeleteAcknowledgement + * DeleteAcknowledgementByCID + * SearchAcknowledgements +* [Alert](https://login.circonus.com/resources/api/calls/alert) + * FetchAlert + * FetchAlerts + * SearchAlerts +* [Annotation](https://login.circonus.com/resources/api/calls/annotation) + * NewAnnotation + * FetchAnnotation + * FetchAnnotations + * UpdateAnnotation + * CreateAnnotation + * DeleteAnnotation + * DeleteAnnotationByCID + * SearchAnnotations +* [Broker](https://login.circonus.com/resources/api/calls/broker) + * FetchBroker + * FetchBrokers + * SearchBrokers +* [Check Bundle](https://login.circonus.com/resources/api/calls/check_bundle) + * NewCheckBundle + * FetchCheckBundle + * FetchCheckBundles + * UpdateCheckBundle + * CreateCheckBundle + * DeleteCheckBundle + * DeleteCheckBundleByCID + * SearchCheckBundles +* [Check Bundle Metrics](https://login.circonus.com/resources/api/calls/check_bundle_metrics) + * FetchCheckBundleMetrics + * UpdateCheckBundleMetrics +* [Check](https://login.circonus.com/resources/api/calls/check) + * FetchCheck + * FetchChecks + * SearchChecks +* [Contact Group](https://login.circonus.com/resources/api/calls/contact_group) + * NewContactGroup + * FetchContactGroup + * FetchContactGroups + * UpdateContactGroup + * CreateContactGroup + * DeleteContactGroup + * DeleteContactGroupByCID + * SearchContactGroups +* [Dashboard](https://login.circonus.com/resources/api/calls/dashboard) -- note, this is a work in progress, the methods/types may still change + * NewDashboard + * FetchDashboard + * FetchDashboards + * UpdateDashboard + * CreateDashboard + * DeleteDashboard + * DeleteDashboardByCID + * SearchDashboards +* [Graph](https://login.circonus.com/resources/api/calls/graph) + * NewGraph + * FetchGraph + * FetchGraphs + * UpdateGraph + * CreateGraph + * DeleteGraph + * DeleteGraphByCID + * SearchGraphs +* [Metric Cluster](https://login.circonus.com/resources/api/calls/metric_cluster) + * NewMetricCluster + * FetchMetricCluster + * FetchMetricClusters + * UpdateMetricCluster + * CreateMetricCluster + * DeleteMetricCluster + * DeleteMetricClusterByCID + * SearchMetricClusters +* [Metric](https://login.circonus.com/resources/api/calls/metric) + * FetchMetric + * FetchMetrics + * UpdateMetric + * SearchMetrics +* [Maintenance window](https://login.circonus.com/resources/api/calls/maintenance) + * NewMaintenanceWindow + * FetchMaintenanceWindow + * FetchMaintenanceWindows + * UpdateMaintenanceWindow + * CreateMaintenanceWindow + * DeleteMaintenanceWindow + * DeleteMaintenanceWindowByCID + * SearchMaintenanceWindows +* [Outlier Report](https://login.circonus.com/resources/api/calls/outlier_report) + * NewOutlierReport + * FetchOutlierReport + * FetchOutlierReports + * UpdateOutlierReport + * CreateOutlierReport + * DeleteOutlierReport + * DeleteOutlierReportByCID + * SearchOutlierReports +* [Provision Broker](https://login.circonus.com/resources/api/calls/provision_broker) + * NewProvisionBroker + * FetchProvisionBroker + * UpdateProvisionBroker + * CreateProvisionBroker +* [Rule Set](https://login.circonus.com/resources/api/calls/rule_set) + * NewRuleset + * FetchRuleset + * FetchRulesets + * UpdateRuleset + * CreateRuleset + * DeleteRuleset + * DeleteRulesetByCID + * SearchRulesets +* [Rule Set Group](https://login.circonus.com/resources/api/calls/rule_set_group) + * NewRulesetGroup + * FetchRulesetGroup + * FetchRulesetGroups + * UpdateRulesetGroup + * CreateRulesetGroup + * DeleteRulesetGroup + * DeleteRulesetGroupByCID + * SearchRulesetGroups +* [User](https://login.circonus.com/resources/api/calls/user) + * FetchUser + * FetchUsers + * UpdateUser + * SearchUsers +* [Worksheet](https://login.circonus.com/resources/api/calls/worksheet) + * NewWorksheet + * FetchWorksheet + * FetchWorksheets + * UpdateWorksheet + * CreateWorksheet + * DeleteWorksheet + * DeleteWorksheetByCID + * SearchWorksheets + +--- + +Unless otherwise noted, the source files are distributed under the BSD-style license found in the LICENSE file. diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go new file mode 100644 index 000000000..dd8ff577d --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/account.go @@ -0,0 +1,181 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Account API support - Fetch and Update +// See: https://login.circonus.com/resources/api/calls/account +// Note: Create and Delete are not supported for Accounts via the API + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// AccountLimit defines a usage limit imposed on account +type AccountLimit struct { + Limit uint `json:"_limit,omitempty"` // uint >=0 + Type string `json:"_type,omitempty"` // string + Used uint `json:"_used,omitempty"` // uint >=0 +} + +// AccountInvite defines outstanding invites +type AccountInvite struct { + Email string `json:"email"` // string + Role string `json:"role"` // string +} + +// AccountUser defines current users +type AccountUser struct { + Role string `json:"role"` // string + UserCID string `json:"user"` // string +} + +// Account defines an account. See https://login.circonus.com/resources/api/calls/account for more information. +type Account struct { + Address1 *string `json:"address1,omitempty"` // string or null + Address2 *string `json:"address2,omitempty"` // string or null + CCEmail *string `json:"cc_email,omitempty"` // string or null + CID string `json:"_cid,omitempty"` // string + City *string `json:"city,omitempty"` // string or null + ContactGroups []string `json:"_contact_groups,omitempty"` // [] len >= 0 + Country string `json:"country_code,omitempty"` // string + Description *string `json:"description,omitempty"` // string or null + Invites []AccountInvite `json:"invites,omitempty"` // [] len >= 0 + Name string `json:"name,omitempty"` // string + OwnerCID string `json:"_owner,omitempty"` // string + StateProv *string `json:"state_prov,omitempty"` // string or null + Timezone string `json:"timezone,omitempty"` // string + UIBaseURL string `json:"_ui_base_url,omitempty"` // string + Usage []AccountLimit `json:"_usage,omitempty"` // [] len >= 0 + Users []AccountUser `json:"users,omitempty"` // [] len >= 0 +} + +// FetchAccount retrieves account with passed cid. Pass nil for '/account/current'. +func (a *API) FetchAccount(cid CIDType) (*Account, error) { + var accountCID string + + if cid == nil || *cid == "" { + accountCID = config.AccountPrefix + "/current" + } else { + accountCID = string(*cid) + } + + matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid account CID [%s]", accountCID) + } + + result, err := a.Get(accountCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] account fetch, received JSON: %s", string(result)) + } + + account := new(Account) + if err := json.Unmarshal(result, account); err != nil { + return nil, err + } + + return account, nil +} + +// FetchAccounts retrieves all accounts available to the API Token. +func (a *API) FetchAccounts() (*[]Account, error) { + result, err := a.Get(config.AccountPrefix) + if err != nil { + return nil, err + } + + var accounts []Account + if err := json.Unmarshal(result, &accounts); err != nil { + return nil, err + } + + return &accounts, nil +} + +// UpdateAccount updates passed account. +func (a *API) UpdateAccount(cfg *Account) (*Account, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid account config [nil]") + } + + accountCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.AccountCIDRegex, accountCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid account CID [%s]", accountCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] account update, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(accountCID, jsonCfg) + if err != nil { + return nil, err + } + + account := &Account{} + if err := json.Unmarshal(result, account); err != nil { + return nil, err + } + + return account, nil +} + +// SearchAccounts returns accounts matching a filter (search queries are not +// suppoted by the account endpoint). Pass nil as filter for all accounts the +// API Token can access. +func (a *API) SearchAccounts(filterCriteria *SearchFilterType) (*[]Account, error) { + q := url.Values{} + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchAccounts() + } + + reqURL := url.URL{ + Path: config.AccountPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var accounts []Account + if err := json.Unmarshal(result, &accounts); err != nil { + return nil, err + } + + return &accounts, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go new file mode 100644 index 000000000..f6da51d4d --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/acknowledgement.go @@ -0,0 +1,190 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Acknowledgement API support - Fetch, Create, Update, Delete*, and Search +// See: https://login.circonus.com/resources/api/calls/acknowledgement +// * : delete (cancel) by updating with AcknowledgedUntil set to 0 + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// Acknowledgement defines a acknowledgement. See https://login.circonus.com/resources/api/calls/acknowledgement for more information. +type Acknowledgement struct { + AcknowledgedBy string `json:"_acknowledged_by,omitempty"` // string + AcknowledgedOn uint `json:"_acknowledged_on,omitempty"` // uint + AcknowledgedUntil interface{} `json:"acknowledged_until,omitempty"` // NOTE received as uint; can be set using string or uint + Active bool `json:"_active,omitempty"` // bool + AlertCID string `json:"alert,omitempty"` // string + CID string `json:"_cid,omitempty"` // string + LastModified uint `json:"_last_modified,omitempty"` // uint + LastModifiedBy string `json:"_last_modified_by,omitempty"` // string + Notes string `json:"notes,omitempty"` // string +} + +// NewAcknowledgement returns new Acknowledgement (with defaults, if applicable). +func NewAcknowledgement() *Acknowledgement { + return &Acknowledgement{} +} + +// FetchAcknowledgement retrieves acknowledgement with passed cid. +func (a *API) FetchAcknowledgement(cid CIDType) (*Acknowledgement, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid acknowledgement CID [none]") + } + + acknowledgementCID := string(*cid) + + matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID) + } + + result, err := a.Get(acknowledgementCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] acknowledgement fetch, received JSON: %s", string(result)) + } + + acknowledgement := &Acknowledgement{} + if err := json.Unmarshal(result, acknowledgement); err != nil { + return nil, err + } + + return acknowledgement, nil +} + +// FetchAcknowledgements retrieves all acknowledgements available to the API Token. +func (a *API) FetchAcknowledgements() (*[]Acknowledgement, error) { + result, err := a.Get(config.AcknowledgementPrefix) + if err != nil { + return nil, err + } + + var acknowledgements []Acknowledgement + if err := json.Unmarshal(result, &acknowledgements); err != nil { + return nil, err + } + + return &acknowledgements, nil +} + +// UpdateAcknowledgement updates passed acknowledgement. +func (a *API) UpdateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid acknowledgement config [nil]") + } + + acknowledgementCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.AcknowledgementCIDRegex, acknowledgementCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid acknowledgement CID [%s]", acknowledgementCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] acknowledgement update, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(acknowledgementCID, jsonCfg) + if err != nil { + return nil, err + } + + acknowledgement := &Acknowledgement{} + if err := json.Unmarshal(result, acknowledgement); err != nil { + return nil, err + } + + return acknowledgement, nil +} + +// CreateAcknowledgement creates a new acknowledgement. +func (a *API) CreateAcknowledgement(cfg *Acknowledgement) (*Acknowledgement, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid acknowledgement config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + result, err := a.Post(config.AcknowledgementPrefix, jsonCfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] acknowledgement create, sending JSON: %s", string(jsonCfg)) + } + + acknowledgement := &Acknowledgement{} + if err := json.Unmarshal(result, acknowledgement); err != nil { + return nil, err + } + + return acknowledgement, nil +} + +// SearchAcknowledgements returns acknowledgements matching +// the specified search query and/or filter. If nil is passed for +// both parameters all acknowledgements will be returned. +func (a *API) SearchAcknowledgements(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Acknowledgement, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchAcknowledgements() + } + + reqURL := url.URL{ + Path: config.AcknowledgementPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var acknowledgements []Acknowledgement + if err := json.Unmarshal(result, &acknowledgements); err != nil { + return nil, err + } + + return &acknowledgements, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go new file mode 100644 index 000000000..a242d3d85 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/alert.go @@ -0,0 +1,131 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Alert API support - Fetch and Search +// See: https://login.circonus.com/resources/api/calls/alert + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// Alert defines a alert. See https://login.circonus.com/resources/api/calls/alert for more information. +type Alert struct { + AcknowledgementCID *string `json:"_acknowledgement,omitempty"` // string or null + AlertURL string `json:"_alert_url,omitempty"` // string + BrokerCID string `json:"_broker,omitempty"` // string + CheckCID string `json:"_check,omitempty"` // string + CheckName string `json:"_check_name,omitempty"` // string + CID string `json:"_cid,omitempty"` // string + ClearedOn *uint `json:"_cleared_on,omitempty"` // uint or null + ClearedValue *string `json:"_cleared_value,omitempty"` // string or null + Maintenance []string `json:"_maintenance,omitempty"` // [] len >= 0 + MetricLinkURL *string `json:"_metric_link,omitempty"` // string or null + MetricName string `json:"_metric_name,omitempty"` // string + MetricNotes *string `json:"_metric_notes,omitempty"` // string or null + OccurredOn uint `json:"_occurred_on,omitempty"` // uint + RuleSetCID string `json:"_rule_set,omitempty"` // string + Severity uint `json:"_severity,omitempty"` // uint + Tags []string `json:"_tags,omitempty"` // [] len >= 0 + Value string `json:"_value,omitempty"` // string +} + +// NewAlert returns a new alert (with defaults, if applicable) +func NewAlert() *Alert { + return &Alert{} +} + +// FetchAlert retrieves alert with passed cid. +func (a *API) FetchAlert(cid CIDType) (*Alert, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid alert CID [none]") + } + + alertCID := string(*cid) + + matched, err := regexp.MatchString(config.AlertCIDRegex, alertCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid alert CID [%s]", alertCID) + } + + result, err := a.Get(alertCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch alert, received JSON: %s", string(result)) + } + + alert := &Alert{} + if err := json.Unmarshal(result, alert); err != nil { + return nil, err + } + + return alert, nil +} + +// FetchAlerts retrieves all alerts available to the API Token. +func (a *API) FetchAlerts() (*[]Alert, error) { + result, err := a.Get(config.AlertPrefix) + if err != nil { + return nil, err + } + + var alerts []Alert + if err := json.Unmarshal(result, &alerts); err != nil { + return nil, err + } + + return &alerts, nil +} + +// SearchAlerts returns alerts matching the specified search query +// and/or filter. If nil is passed for both parameters all alerts +// will be returned. +func (a *API) SearchAlerts(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Alert, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchAlerts() + } + + reqURL := url.URL{ + Path: config.AlertPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var alerts []Alert + if err := json.Unmarshal(result, &alerts); err != nil { + return nil, err + } + + return &alerts, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go new file mode 100644 index 000000000..589ec6da9 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/annotation.go @@ -0,0 +1,223 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Annotation API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/annotation + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// Annotation defines a annotation. See https://login.circonus.com/resources/api/calls/annotation for more information. +type Annotation struct { + Category string `json:"category"` // string + CID string `json:"_cid,omitempty"` // string + Created uint `json:"_created,omitempty"` // uint + Description string `json:"description"` // string + LastModified uint `json:"_last_modified,omitempty"` // uint + LastModifiedBy string `json:"_last_modified_by,omitempty"` // string + RelatedMetrics []string `json:"rel_metrics"` // [] len >= 0 + Start uint `json:"start"` // uint + Stop uint `json:"stop"` // uint + Title string `json:"title"` // string +} + +// NewAnnotation returns a new Annotation (with defaults, if applicable) +func NewAnnotation() *Annotation { + return &Annotation{} +} + +// FetchAnnotation retrieves annotation with passed cid. +func (a *API) FetchAnnotation(cid CIDType) (*Annotation, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid annotation CID [none]") + } + + annotationCID := string(*cid) + + matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID) + } + + result, err := a.Get(annotationCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch annotation, received JSON: %s", string(result)) + } + + annotation := &Annotation{} + if err := json.Unmarshal(result, annotation); err != nil { + return nil, err + } + + return annotation, nil +} + +// FetchAnnotations retrieves all annotations available to the API Token. +func (a *API) FetchAnnotations() (*[]Annotation, error) { + result, err := a.Get(config.AnnotationPrefix) + if err != nil { + return nil, err + } + + var annotations []Annotation + if err := json.Unmarshal(result, &annotations); err != nil { + return nil, err + } + + return &annotations, nil +} + +// UpdateAnnotation updates passed annotation. +func (a *API) UpdateAnnotation(cfg *Annotation) (*Annotation, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid annotation config [nil]") + } + + annotationCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid annotation CID [%s]", annotationCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update annotation, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(annotationCID, jsonCfg) + if err != nil { + return nil, err + } + + annotation := &Annotation{} + if err := json.Unmarshal(result, annotation); err != nil { + return nil, err + } + + return annotation, nil +} + +// CreateAnnotation creates a new annotation. +func (a *API) CreateAnnotation(cfg *Annotation) (*Annotation, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid annotation config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.AnnotationPrefix, jsonCfg) + if err != nil { + return nil, err + } + + annotation := &Annotation{} + if err := json.Unmarshal(result, annotation); err != nil { + return nil, err + } + + return annotation, nil +} + +// DeleteAnnotation deletes passed annotation. +func (a *API) DeleteAnnotation(cfg *Annotation) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid annotation config [nil]") + } + + return a.DeleteAnnotationByCID(CIDType(&cfg.CID)) +} + +// DeleteAnnotationByCID deletes annotation with passed cid. +func (a *API) DeleteAnnotationByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid annotation CID [none]") + } + + annotationCID := string(*cid) + + matched, err := regexp.MatchString(config.AnnotationCIDRegex, annotationCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid annotation CID [%s]", annotationCID) + } + + _, err = a.Delete(annotationCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchAnnotations returns annotations matching the specified +// search query and/or filter. If nil is passed for both parameters +// all annotations will be returned. +func (a *API) SearchAnnotations(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Annotation, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchAnnotations() + } + + reqURL := url.URL{ + Path: config.AnnotationPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var annotations []Annotation + if err := json.Unmarshal(result, &annotations); err != nil { + return nil, err + } + + return &annotations, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go index 7a7f18708..598e20f2d 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/api.go @@ -2,31 +2,48 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package api provides methods for interacting with the Circonus API package api import ( "bytes" + crand "crypto/rand" + "crypto/tls" + "crypto/x509" "errors" "fmt" "io/ioutil" "log" + "math" + "math/big" + "math/rand" + "net" "net/http" "net/url" "os" + "regexp" "strings" + "sync" "time" "github.com/hashicorp/go-retryablehttp" ) +func init() { + n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + rand.Seed(time.Now().UTC().UnixNano()) + return + } + rand.Seed(n.Int64()) +} + const ( // a few sensible defaults defaultAPIURL = "https://api.circonus.com/v2" defaultAPIApp = "circonus-gometrics" - minRetryWait = 10 * time.Millisecond - maxRetryWait = 50 * time.Millisecond - maxRetries = 3 + minRetryWait = 1 * time.Second + maxRetryWait = 15 * time.Second + maxRetries = 4 // equating to 1 + maxRetries total attempts ) // TokenKeyType - Circonus API Token key @@ -35,41 +52,63 @@ type TokenKeyType string // TokenAppType - Circonus API Token app name type TokenAppType string -// IDType Circonus object id (numeric portion of cid) -type IDType int +// TokenAccountIDType - Circonus API Token account id +type TokenAccountIDType string // CIDType Circonus object cid -type CIDType string +type CIDType *string + +// IDType Circonus object id +type IDType int // URLType submission url type type URLType string -// SearchQueryType search query +// SearchQueryType search query (see: https://login.circonus.com/resources/api#searching) type SearchQueryType string -// SearchTagType search/select tag type -type SearchTagType string +// SearchFilterType search filter (see: https://login.circonus.com/resources/api#filtering) +type SearchFilterType map[string][]string + +// TagType search/select/custom tag(s) type +type TagType []string // Config options for Circonus API type Config struct { - URL string - TokenKey string - TokenApp string - Log *log.Logger - Debug bool + URL string + TokenKey string + TokenApp string + TokenAccountID string + CACert *x509.CertPool + Log *log.Logger + Debug bool } // API Circonus API type API struct { - apiURL *url.URL - key TokenKeyType - app TokenAppType - Debug bool - Log *log.Logger + apiURL *url.URL + key TokenKeyType + app TokenAppType + accountID TokenAccountIDType + caCert *x509.CertPool + Debug bool + Log *log.Logger + useExponentialBackoff bool + useExponentialBackoffmu sync.Mutex +} + +// NewClient returns a new Circonus API (alias for New) +func NewClient(ac *Config) (*API, error) { + return New(ac) } -// NewAPI returns a new Circonus API +// NewAPI returns a new Circonus API (alias for New) func NewAPI(ac *Config) (*API, error) { + return New(ac) +} + +// New returns a new Circonus API +func New(ac *Config) (*API, error) { if ac == nil { return nil, errors.New("Invalid API configuration (nil)") @@ -85,6 +124,8 @@ func NewAPI(ac *Config) (*API, error) { app = defaultAPIApp } + acctID := TokenAccountIDType(ac.TokenAccountID) + au := string(ac.URL) if au == "" { au = defaultAPIURL @@ -94,6 +135,7 @@ func NewAPI(ac *Config) (*API, error) { au = fmt.Sprintf("https://%s/v2", ac.URL) } if last := len(au) - 1; last >= 0 && au[last] == '/' { + // strip off trailing '/' au = au[:last] } apiURL, err := url.Parse(au) @@ -101,61 +143,127 @@ func NewAPI(ac *Config) (*API, error) { return nil, err } - a := &API{apiURL, key, app, ac.Debug, ac.Log} + a := &API{ + apiURL: apiURL, + key: key, + app: app, + accountID: acctID, + caCert: ac.CACert, + Debug: ac.Debug, + Log: ac.Log, + useExponentialBackoff: false, + } + a.Debug = ac.Debug + a.Log = ac.Log + if a.Debug && a.Log == nil { + a.Log = log.New(os.Stderr, "", log.LstdFlags) + } if a.Log == nil { - if a.Debug { - a.Log = log.New(os.Stderr, "", log.LstdFlags) - } else { - a.Log = log.New(ioutil.Discard, "", log.LstdFlags) - } + a.Log = log.New(ioutil.Discard, "", log.LstdFlags) } return a, nil } +// EnableExponentialBackoff enables use of exponential backoff for next API call(s) +// and use exponential backoff for all API calls until exponential backoff is disabled. +func (a *API) EnableExponentialBackoff() { + a.useExponentialBackoffmu.Lock() + a.useExponentialBackoff = true + a.useExponentialBackoffmu.Unlock() +} + +// DisableExponentialBackoff disables use of exponential backoff. If a request using +// exponential backoff is currently running, it will stop using exponential backoff +// on its next iteration (if needed). +func (a *API) DisableExponentialBackoff() { + a.useExponentialBackoffmu.Lock() + a.useExponentialBackoff = false + a.useExponentialBackoffmu.Unlock() +} + // Get API request func (a *API) Get(reqPath string) ([]byte, error) { - return a.apiCall("GET", reqPath, nil) + return a.apiRequest("GET", reqPath, nil) } // Delete API request func (a *API) Delete(reqPath string) ([]byte, error) { - return a.apiCall("DELETE", reqPath, nil) + return a.apiRequest("DELETE", reqPath, nil) } // Post API request func (a *API) Post(reqPath string, data []byte) ([]byte, error) { - return a.apiCall("POST", reqPath, data) + return a.apiRequest("POST", reqPath, data) } // Put API request func (a *API) Put(reqPath string, data []byte) ([]byte, error) { - return a.apiCall("PUT", reqPath, data) + return a.apiRequest("PUT", reqPath, data) +} + +func backoff(interval uint) float64 { + return math.Floor(((float64(interval) * (1 + rand.Float64())) / 2) + .5) +} + +// apiRequest manages retry strategy for exponential backoffs +func (a *API) apiRequest(reqMethod string, reqPath string, data []byte) ([]byte, error) { + backoffs := []uint{2, 4, 8, 16, 32} + attempts := 0 + success := false + + var result []byte + var err error + + for !success { + result, err = a.apiCall(reqMethod, reqPath, data) + if err == nil { + success = true + } + + // break and return error if not using exponential backoff + if err != nil { + if !a.useExponentialBackoff { + break + } + if matched, _ := regexp.MatchString("code 403", err.Error()); matched { + break + } + } + + if !success { + var wait float64 + if attempts >= len(backoffs) { + wait = backoff(backoffs[len(backoffs)-1]) + } else { + wait = backoff(backoffs[attempts]) + } + attempts++ + a.Log.Printf("[WARN] API call failed %s, retrying in %d seconds.\n", err.Error(), uint(wait)) + time.Sleep(time.Duration(wait) * time.Second) + } + } + + return result, err } // apiCall call Circonus API func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, error) { - dataReader := bytes.NewReader(data) reqURL := a.apiURL.String() + if reqPath == "" { + return nil, errors.New("Invalid URL path") + } if reqPath[:1] != "/" { reqURL += "/" } - if reqPath[:3] == "/v2" { - reqURL += reqPath[3:len(reqPath)] + if len(reqPath) >= 3 && reqPath[:3] == "/v2" { + reqURL += reqPath[3:] } else { reqURL += reqPath } - req, err := retryablehttp.NewRequest(reqMethod, reqURL, dataReader) - if err != nil { - return nil, fmt.Errorf("[ERROR] creating API request: %s %+v", reqURL, err) - } - req.Header.Add("Accept", "application/json") - req.Header.Add("X-Circonus-Auth-Token", string(a.key)) - req.Header.Add("X-Circonus-App-Name", string(a.app)) - // keep last HTTP error in the event of retry failure var lastHTTPError error retryPolicy := func(resp *http.Response, err error) (bool, error) { @@ -167,38 +275,98 @@ func (a *API) apiCall(reqMethod string, reqPath string, data []byte) ([]byte, er // the server time to recover, as 500's are typically not permanent // errors and may relate to outages on the server side. This will catch // invalid response codes as well, like 0 and 999. - if resp.StatusCode == 0 || resp.StatusCode >= 500 { - defer resp.Body.Close() + // Retry on 429 (rate limit) as well. + if resp.StatusCode == 0 || // wtf?! + resp.StatusCode >= 500 || // rutroh + resp.StatusCode == 429 { // rate limit body, readErr := ioutil.ReadAll(resp.Body) if readErr != nil { - lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr) + lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, readErr.Error()) } else { - lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body)) + lastHTTPError = fmt.Errorf("- response: %d %s", resp.StatusCode, strings.TrimSpace(string(body))) } return true, nil } return false, nil } + dataReader := bytes.NewReader(data) + + req, err := retryablehttp.NewRequest(reqMethod, reqURL, dataReader) + if err != nil { + return nil, fmt.Errorf("[ERROR] creating API request: %s %+v", reqURL, err) + } + req.Header.Add("Accept", "application/json") + req.Header.Add("X-Circonus-Auth-Token", string(a.key)) + req.Header.Add("X-Circonus-App-Name", string(a.app)) + if string(a.accountID) != "" { + req.Header.Add("X-Circonus-Account-ID", string(a.accountID)) + } + client := retryablehttp.NewClient() - client.RetryWaitMin = minRetryWait - client.RetryWaitMax = maxRetryWait - client.RetryMax = maxRetries - client.Logger = a.Log + if a.apiURL.Scheme == "https" && a.caCert != nil { + client.HTTPClient.Transport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + TLSClientConfig: &tls.Config{RootCAs: a.caCert}, + DisableKeepAlives: true, + MaxIdleConnsPerHost: -1, + DisableCompression: true, + } + } else { + client.HTTPClient.Transport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + DisableKeepAlives: true, + MaxIdleConnsPerHost: -1, + DisableCompression: true, + } + } + + a.useExponentialBackoffmu.Lock() + eb := a.useExponentialBackoff + a.useExponentialBackoffmu.Unlock() + + if eb { + // limit to one request if using exponential backoff + client.RetryWaitMin = 1 + client.RetryWaitMax = 2 + client.RetryMax = 0 + } else { + client.RetryWaitMin = minRetryWait + client.RetryWaitMax = maxRetryWait + client.RetryMax = maxRetries + } + + // retryablehttp only groks log or no log + if a.Debug { + client.Logger = a.Log + } else { + client.Logger = log.New(ioutil.Discard, "", log.LstdFlags) + } + client.CheckRetry = retryPolicy resp, err := client.Do(req) if err != nil { if lastHTTPError != nil { - return nil, fmt.Errorf("[ERROR] fetching: %+v %+v", err, lastHTTPError) + return nil, lastHTTPError } - return nil, fmt.Errorf("[ERROR] fetching %s: %+v", reqURL, err) + return nil, fmt.Errorf("[ERROR] %s: %+v", reqURL, err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("[ERROR] reading body %+v", err) + return nil, fmt.Errorf("[ERROR] reading response %+v", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go index 87e2b5e37..459fda6df 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/broker.go @@ -2,49 +2,70 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Broker API support - Fetch and Search +// See: https://login.circonus.com/resources/api/calls/broker + package api import ( "encoding/json" "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" ) -// BrokerDetail instance attributes +// BrokerDetail defines instance attributes type BrokerDetail struct { - CN string `json:"cn"` - IP string `json:"ipaddress"` - MinVer int `json:"minimum_version_required"` - Modules []string `json:"modules"` - Port int `json:"port"` - Skew string `json:"skew"` - Status string `json:"status"` - Version int `json:"version"` + CN string `json:"cn"` // string + ExternalHost *string `json:"external_host"` // string or null + ExternalPort uint16 `json:"external_port"` // uint16 + IP *string `json:"ipaddress"` // string or null + MinVer uint `json:"minimum_version_required"` // uint + Modules []string `json:"modules"` // [] len >= 0 + Port *uint16 `json:"port"` // uint16 or null + Skew *string `json:"skew"` // BUG doc: floating point number, api object: string or null + Status string `json:"status"` // string + Version *uint `json:"version"` // uint or null } -// Broker definition +// Broker defines a broker. See https://login.circonus.com/resources/api/calls/broker for more information. type Broker struct { - Cid string `json:"_cid"` - Details []BrokerDetail `json:"_details"` - Latitude string `json:"_latitude"` - Longitude string `json:"_longitude"` - Name string `json:"_name"` - Tags []string `json:"_tags"` - Type string `json:"_type"` + CID string `json:"_cid"` // string + Details []BrokerDetail `json:"_details"` // [] len >= 1 + Latitude *string `json:"_latitude"` // string or null + Longitude *string `json:"_longitude"` // string or null + Name string `json:"_name"` // string + Tags []string `json:"_tags"` // [] len >= 0 + Type string `json:"_type"` // string } -// FetchBrokerByID fetch a broker configuration by [group]id -func (a *API) FetchBrokerByID(id IDType) (*Broker, error) { - cid := CIDType(fmt.Sprintf("/broker/%d", id)) - return a.FetchBrokerByCID(cid) -} +// FetchBroker retrieves broker with passed cid. +func (a *API) FetchBroker(cid CIDType) (*Broker, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid broker CID [none]") + } + + brokerCID := string(*cid) + + matched, err := regexp.MatchString(config.BrokerCIDRegex, brokerCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid broker CID [%s]", brokerCID) + } -// FetchBrokerByCID fetch a broker configuration by cid -func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) { - result, err := a.Get(string(cid)) + result, err := a.Get(brokerCID) if err != nil { return nil, err } + if a.Debug { + a.Log.Printf("[DEBUG] fetch broker, received JSON: %s", string(result)) + } + response := new(Broker) if err := json.Unmarshal(result, &response); err != nil { return nil, err @@ -54,36 +75,57 @@ func (a *API) FetchBrokerByCID(cid CIDType) (*Broker, error) { } -// FetchBrokerListByTag return list of brokers with a specific tag -func (a *API) FetchBrokerListByTag(searchTag SearchTagType) ([]Broker, error) { - query := SearchQueryType(fmt.Sprintf("f__tags_has=%s", searchTag)) - return a.BrokerSearch(query) -} - -// BrokerSearch return a list of brokers matching a query/filter -func (a *API) BrokerSearch(query SearchQueryType) ([]Broker, error) { - queryURL := fmt.Sprintf("/broker?%s", string(query)) - - result, err := a.Get(queryURL) +// FetchBrokers returns all brokers available to the API Token. +func (a *API) FetchBrokers() (*[]Broker, error) { + result, err := a.Get(config.BrokerPrefix) if err != nil { return nil, err } - var brokers []Broker - json.Unmarshal(result, &brokers) + var response []Broker + if err := json.Unmarshal(result, &response); err != nil { + return nil, err + } - return brokers, nil + return &response, nil } -// FetchBrokerList return list of all brokers available to the api token/app -func (a *API) FetchBrokerList() ([]Broker, error) { - result, err := a.Get("/broker") +// SearchBrokers returns brokers matching the specified search +// query and/or filter. If nil is passed for both parameters +// all brokers will be returned. +func (a *API) SearchBrokers(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Broker, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchBrokers() + } + + reqURL := url.URL{ + Path: config.BrokerPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) if err != nil { - return nil, err + return nil, fmt.Errorf("[ERROR] API call error %+v", err) } - var response []Broker - json.Unmarshal(result, &response) + var brokers []Broker + if err := json.Unmarshal(result, &brokers); err != nil { + return nil, err + } - return response, nil + return &brokers, nil } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go index df0524f43..047d71935 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check.go @@ -2,112 +2,118 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Check API support - Fetch and Search +// See: https://login.circonus.com/resources/api/calls/check +// Notes: checks do not directly support create, update, and delete - see check bundle. + package api import ( "encoding/json" "fmt" "net/url" - "strings" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" ) -// CheckDetails is an arbitrary json structure, we would only care about submission_url -type CheckDetails struct { - SubmissionURL string `json:"submission_url"` -} +// CheckDetails contains [undocumented] check type specific information +type CheckDetails map[config.Key]string -// Check definition +// Check defines a check. See https://login.circonus.com/resources/api/calls/check for more information. type Check struct { - Cid string `json:"_cid"` - Active bool `json:"_active"` - BrokerCid string `json:"_broker"` - CheckBundleCid string `json:"_check_bundle"` - CheckUUID string `json:"_check_uuid"` - Details CheckDetails `json:"_details"` + Active bool `json:"_active"` // bool + BrokerCID string `json:"_broker"` // string + CheckBundleCID string `json:"_check_bundle"` // string + CheckUUID string `json:"_check_uuid"` // string + CID string `json:"_cid"` // string + Details CheckDetails `json:"_details"` // NOTE contents of details are check type specific, map len >= 0 } -// FetchCheckByID fetch a check configuration by id -func (a *API) FetchCheckByID(id IDType) (*Check, error) { - cid := CIDType(fmt.Sprintf("/check/%d", int(id))) - return a.FetchCheckByCID(cid) -} +// FetchCheck retrieves check with passed cid. +func (a *API) FetchCheck(cid CIDType) (*Check, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid check CID [none]") + } + + checkCID := string(*cid) -// FetchCheckByCID fetch a check configuration by cid -func (a *API) FetchCheckByCID(cid CIDType) (*Check, error) { - result, err := a.Get(string(cid)) + matched, err := regexp.MatchString(config.CheckCIDRegex, checkCID) if err != nil { return nil, err } + if !matched { + return nil, fmt.Errorf("Invalid check CID [%s]", checkCID) + } - check := new(Check) - json.Unmarshal(result, check) - - return check, nil -} - -// FetchCheckBySubmissionURL fetch a check configuration by submission_url -func (a *API) FetchCheckBySubmissionURL(submissionURL URLType) (*Check, error) { - - u, err := url.Parse(string(submissionURL)) + result, err := a.Get(checkCID) if err != nil { return nil, err } - // valid trap url: scheme://host[:port]/module/httptrap/UUID/secret - - // does it smell like a valid trap url path - if u.Path[:17] != "/module/httptrap/" { - return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', unrecognized path", submissionURL) + if a.Debug { + a.Log.Printf("[DEBUG] fetch check, received JSON: %s", string(result)) } - // extract uuid/secret - pathParts := strings.Split(u.Path[17:len(u.Path)], "/") - if len(pathParts) != 2 { - return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', UUID not where expected", submissionURL) + check := new(Check) + if err := json.Unmarshal(result, check); err != nil { + return nil, err } - uuid := pathParts[0] - - query := SearchQueryType(fmt.Sprintf("f__check_uuid=%s", uuid)) + return check, nil +} - checks, err := a.CheckSearch(query) +// FetchChecks retrieves all checks available to the API Token. +func (a *API) FetchChecks() (*[]Check, error) { + result, err := a.Get(config.CheckPrefix) if err != nil { return nil, err } - if len(checks) == 0 { - return nil, fmt.Errorf("[ERROR] No checks found with UUID %s", uuid) + var checks []Check + if err := json.Unmarshal(result, &checks); err != nil { + return nil, err } - numActive := 0 - checkID := -1 + return &checks, nil +} - for idx, check := range checks { - if check.Active { - numActive++ - checkID = idx - } - } +// SearchChecks returns checks matching the specified search query +// and/or filter. If nil is passed for both parameters all checks +// will be returned. +func (a *API) SearchChecks(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Check, error) { + q := url.Values{} - if numActive > 1 { - return nil, fmt.Errorf("[ERROR] Multiple checks with same UUID %s", uuid) + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) } - return &checks[checkID], nil + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } -} + if q.Encode() == "" { + return a.FetchChecks() + } -// CheckSearch returns a list of checks matching a query/filter -func (a *API) CheckSearch(query SearchQueryType) ([]Check, error) { - queryURL := fmt.Sprintf("/check?%s", string(query)) + reqURL := url.URL{ + Path: config.CheckPrefix, + RawQuery: q.Encode(), + } - result, err := a.Get(queryURL) + result, err := a.Get(reqURL.String()) if err != nil { return nil, err } var checks []Check - json.Unmarshal(result, &checks) + if err := json.Unmarshal(result, &checks); err != nil { + return nil, err + } - return checks, nil + return &checks, nil } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go new file mode 100644 index 000000000..c202853c2 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle.go @@ -0,0 +1,255 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check bundle API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/check_bundle + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// CheckBundleMetric individual metric configuration +type CheckBundleMetric struct { + Name string `json:"name"` // string + Result *string `json:"result,omitempty"` // string or null, NOTE not settable - return/information value only + Status string `json:"status,omitempty"` // string + Tags []string `json:"tags"` // [] len >= 0 + Type string `json:"type"` // string + Units *string `json:"units,omitempty"` // string or null + +} + +// CheckBundleConfig contains the check type specific configuration settings +// as k/v pairs (see https://login.circonus.com/resources/api/calls/check_bundle +// for the specific settings available for each distinct check type) +type CheckBundleConfig map[config.Key]string + +// CheckBundle defines a check bundle. See https://login.circonus.com/resources/api/calls/check_bundle for more information. +type CheckBundle struct { + Brokers []string `json:"brokers"` // [] len >= 0 + Checks []string `json:"_checks,omitempty"` // [] len >= 0 + CheckUUIDs []string `json:"_check_uuids,omitempty"` // [] len >= 0 + CID string `json:"_cid,omitempty"` // string + Config CheckBundleConfig `json:"config"` // NOTE contents of config are check type specific, map len >= 0 + Created uint `json:"_created,omitempty"` // uint + DisplayName string `json:"display_name"` // string + LastModifedBy string `json:"_last_modifed_by,omitempty"` // string + LastModified uint `json:"_last_modified,omitempty"` // uint + MetricLimit int `json:"metric_limit,omitempty"` // int + Metrics []CheckBundleMetric `json:"metrics"` // [] >= 0 + Notes *string `json:"notes,omitempty"` // string or null + Period uint `json:"period,omitempty"` // uint + ReverseConnectURLs []string `json:"_reverse_connection_urls,omitempty"` // [] len >= 0 + Status string `json:"status,omitempty"` // string + Tags []string `json:"tags,omitempty"` // [] len >= 0 + Target string `json:"target"` // string + Timeout float32 `json:"timeout,omitempty"` // float32 + Type string `json:"type"` // string +} + +// NewCheckBundle returns new CheckBundle (with defaults, if applicable) +func NewCheckBundle() *CheckBundle { + return &CheckBundle{ + Config: make(CheckBundleConfig, config.DefaultConfigOptionsSize), + MetricLimit: config.DefaultCheckBundleMetricLimit, + Period: config.DefaultCheckBundlePeriod, + Timeout: config.DefaultCheckBundleTimeout, + Status: config.DefaultCheckBundleStatus, + } +} + +// FetchCheckBundle retrieves check bundle with passed cid. +func (a *API) FetchCheckBundle(cid CIDType) (*CheckBundle, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid check bundle CID [none]") + } + + bundleCID := string(*cid) + + matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID) + } + + result, err := a.Get(bundleCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch check bundle, received JSON: %s", string(result)) + } + + checkBundle := &CheckBundle{} + if err := json.Unmarshal(result, checkBundle); err != nil { + return nil, err + } + + return checkBundle, nil +} + +// FetchCheckBundles retrieves all check bundles available to the API Token. +func (a *API) FetchCheckBundles() (*[]CheckBundle, error) { + result, err := a.Get(config.CheckBundlePrefix) + if err != nil { + return nil, err + } + + var checkBundles []CheckBundle + if err := json.Unmarshal(result, &checkBundles); err != nil { + return nil, err + } + + return &checkBundles, nil +} + +// UpdateCheckBundle updates passed check bundle. +func (a *API) UpdateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid check bundle config [nil]") + } + + bundleCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid check bundle CID [%s]", bundleCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update check bundle, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(bundleCID, jsonCfg) + if err != nil { + return nil, err + } + + checkBundle := &CheckBundle{} + if err := json.Unmarshal(result, checkBundle); err != nil { + return nil, err + } + + return checkBundle, nil +} + +// CreateCheckBundle creates a new check bundle (check). +func (a *API) CreateCheckBundle(cfg *CheckBundle) (*CheckBundle, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid check bundle config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create check bundle, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.CheckBundlePrefix, jsonCfg) + if err != nil { + return nil, err + } + + checkBundle := &CheckBundle{} + if err := json.Unmarshal(result, checkBundle); err != nil { + return nil, err + } + + return checkBundle, nil +} + +// DeleteCheckBundle deletes passed check bundle. +func (a *API) DeleteCheckBundle(cfg *CheckBundle) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid check bundle config [nil]") + } + return a.DeleteCheckBundleByCID(CIDType(&cfg.CID)) +} + +// DeleteCheckBundleByCID deletes check bundle with passed cid. +func (a *API) DeleteCheckBundleByCID(cid CIDType) (bool, error) { + + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid check bundle CID [none]") + } + + bundleCID := string(*cid) + + matched, err := regexp.MatchString(config.CheckBundleCIDRegex, bundleCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid check bundle CID [%v]", bundleCID) + } + + _, err = a.Delete(bundleCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchCheckBundles returns check bundles matching the specified +// search query and/or filter. If nil is passed for both parameters +// all check bundles will be returned. +func (a *API) SearchCheckBundles(searchCriteria *SearchQueryType, filterCriteria *map[string][]string) (*[]CheckBundle, error) { + + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchCheckBundles() + } + + reqURL := url.URL{ + Path: config.CheckBundlePrefix, + RawQuery: q.Encode(), + } + + resp, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var results []CheckBundle + if err := json.Unmarshal(resp, &results); err != nil { + return nil, err + } + + return &results, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go new file mode 100644 index 000000000..817c7b891 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/check_bundle_metrics.go @@ -0,0 +1,95 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// CheckBundleMetrics API support - Fetch, Create*, Update, and Delete** +// See: https://login.circonus.com/resources/api/calls/check_bundle_metrics +// * : create metrics by adding to array with a status of 'active' +// ** : delete (distable collection of) metrics by changing status from 'active' to 'available' + +package api + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// CheckBundleMetrics defines metrics for a specific check bundle. See https://login.circonus.com/resources/api/calls/check_bundle_metrics for more information. +type CheckBundleMetrics struct { + CID string `json:"_cid,omitempty"` // string + Metrics []CheckBundleMetric `json:"metrics"` // See check_bundle.go for CheckBundleMetric definition +} + +// FetchCheckBundleMetrics retrieves metrics for the check bundle with passed cid. +func (a *API) FetchCheckBundleMetrics(cid CIDType) (*CheckBundleMetrics, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid check bundle metrics CID [none]") + } + + metricsCID := string(*cid) + + matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID) + } + + result, err := a.Get(metricsCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch check bundle metrics, received JSON: %s", string(result)) + } + + metrics := &CheckBundleMetrics{} + if err := json.Unmarshal(result, metrics); err != nil { + return nil, err + } + + return metrics, nil +} + +// UpdateCheckBundleMetrics updates passed metrics. +func (a *API) UpdateCheckBundleMetrics(cfg *CheckBundleMetrics) (*CheckBundleMetrics, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid check bundle metrics config [nil]") + } + + metricsCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.CheckBundleMetricsCIDRegex, metricsCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid check bundle metrics CID [%s]", metricsCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update check bundle metrics, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(metricsCID, jsonCfg) + if err != nil { + return nil, err + } + + metrics := &CheckBundleMetrics{} + if err := json.Unmarshal(result, metrics); err != nil { + return nil, err + } + + return metrics, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go deleted file mode 100644 index 559df7ac0..000000000 --- a/vendor/github.com/circonus-labs/circonus-gometrics/api/checkbundle.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2016 Circonus, Inc. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package api - -import ( - "encoding/json" - "fmt" -) - -// CheckBundleConfig configuration specific to check type -type CheckBundleConfig struct { - AsyncMetrics bool `json:"async_metrics"` - Secret string `json:"secret"` - SubmissionURL string `json:"submission_url"` -} - -// CheckBundleMetric individual metric configuration -type CheckBundleMetric struct { - Name string `json:"name"` - Type string `json:"type"` - Units string `json:"units"` - Status string `json:"status"` -} - -// CheckBundle definition -type CheckBundle struct { - CheckUUIDs []string `json:"_check_uuids,omitempty"` - Checks []string `json:"_checks,omitempty"` - Cid string `json:"_cid,omitempty"` - Created int `json:"_created,omitempty"` - LastModified int `json:"_last_modified,omitempty"` - LastModifedBy string `json:"_last_modifed_by,omitempty"` - ReverseConnectUrls []string `json:"_reverse_connection_urls,omitempty"` - Brokers []string `json:"brokers"` - Config CheckBundleConfig `json:"config"` - DisplayName string `json:"display_name"` - Metrics []CheckBundleMetric `json:"metrics"` - MetricLimit int `json:"metric_limit"` - Notes string `json:"notes"` - Period int `json:"period"` - Status string `json:"status"` - Tags []string `json:"tags"` - Target string `json:"target"` - Timeout int `json:"timeout"` - Type string `json:"type"` -} - -// FetchCheckBundleByID fetch a check bundle configuration by id -func (a *API) FetchCheckBundleByID(id IDType) (*CheckBundle, error) { - cid := CIDType(fmt.Sprintf("/check_bundle/%d", id)) - return a.FetchCheckBundleByCID(cid) -} - -// FetchCheckBundleByCID fetch a check bundle configuration by id -func (a *API) FetchCheckBundleByCID(cid CIDType) (*CheckBundle, error) { - result, err := a.Get(string(cid)) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - json.Unmarshal(result, checkBundle) - - return checkBundle, nil -} - -// CheckBundleSearch returns list of check bundles matching a search query -// - a search query not a filter (see: https://login.circonus.com/resources/api#searching) -func (a *API) CheckBundleSearch(searchCriteria SearchQueryType) ([]CheckBundle, error) { - apiPath := fmt.Sprintf("/check_bundle?search=%s", searchCriteria) - - response, err := a.Get(apiPath) - if err != nil { - return nil, fmt.Errorf("[ERROR] API call error %+v", err) - } - - var results []CheckBundle - err = json.Unmarshal(response, &results) - if err != nil { - return nil, fmt.Errorf("[ERROR] Parsing JSON response %+v", err) - } - - return results, nil -} - -// CreateCheckBundle create a new check bundle (check) -func (a *API) CreateCheckBundle(config CheckBundle) (*CheckBundle, error) { - cfgJSON, err := json.Marshal(config) - if err != nil { - return nil, err - } - - response, err := a.Post("/check_bundle", cfgJSON) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - err = json.Unmarshal(response, checkBundle) - if err != nil { - return nil, err - } - - return checkBundle, nil -} - -// UpdateCheckBundle updates a check bundle configuration -func (a *API) UpdateCheckBundle(config *CheckBundle) (*CheckBundle, error) { - if a.Debug { - a.Log.Printf("[DEBUG] Updating check bundle with new metrics.") - } - - cfgJSON, err := json.Marshal(config) - if err != nil { - return nil, err - } - - response, err := a.Put(config.Cid, cfgJSON) - if err != nil { - return nil, err - } - - checkBundle := &CheckBundle{} - err = json.Unmarshal(response, checkBundle) - if err != nil { - return nil, err - } - - return checkBundle, nil -} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go new file mode 100644 index 000000000..bbca43d03 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/config/consts.go @@ -0,0 +1,538 @@ +package config + +// Key for CheckBundleConfig options and CheckDetails info +type Key string + +// Constants per type as defined in +// https://login.circonus.com/resources/api/calls/check_bundle +const ( + // + // default settings for api.NewCheckBundle() + // + DefaultCheckBundleMetricLimit = -1 // unlimited + DefaultCheckBundleStatus = "active" + DefaultCheckBundlePeriod = 60 + DefaultCheckBundleTimeout = 10 + DefaultConfigOptionsSize = 20 + + // + // common (apply to more than one check type) + // + AsyncMetrics = Key("asynch_metrics") + AuthMethod = Key("auth_method") + AuthPassword = Key("auth_password") + AuthUser = Key("auth_user") + BaseURL = Key("base_url") + CAChain = Key("ca_chain") + CertFile = Key("certificate_file") + Ciphers = Key("ciphers") + Command = Key("command") + DSN = Key("dsn") + HeaderPrefix = Key("header_") + HTTPVersion = Key("http_version") + KeyFile = Key("key_file") + Method = Key("method") + Password = Key("password") + Payload = Key("payload") + Port = Key("port") + Query = Key("query") + ReadLimit = Key("read_limit") + Secret = Key("secret") + SQL = Key("sql") + URI = Key("uri") + URL = Key("url") + Username = Key("username") + UseSSL = Key("use_ssl") + User = Key("user") + SASLAuthentication = Key("sasl_authentication") + SASLUser = Key("sasl_user") + SecurityLevel = Key("security_level") + Version = Key("version") + AppendColumnName = Key("append_column_name") + Database = Key("database") + JDBCPrefix = Key("jdbc_") + + // + // CAQL check + // + // Common items: + // Query + + // + // Circonus Windows Agent + // + // Common items: + // AuthPassword + // AuthUser + // Port + // URL + Calculated = Key("calculated") + Category = Key("category") + + // + // Cloudwatch + // + // Notes: + // DimPrefix is special because the actual key is dynamic and matches: `dim_(.+)` + // Common items: + // URL + // Version + APIKey = Key("api_key") + APISecret = Key("api_secret") + CloudwatchMetrics = Key("cloudwatch_metrics") + DimPrefix = Key("dim_") + Granularity = Key("granularity") + Namespace = Key("namespace") + Statistics = Key("statistics") + + // + // Collectd + // + // Common items: + // AsyncMetrics + // Username + // Secret + // SecurityLevel + + // + // Composite + // + CompositeMetricName = Key("composite_metric_name") + Formula = Key("formula") + + // + // DHCP + // + HardwareAddress = Key("hardware_addr") + HostIP = Key("host_ip") + RequestType = Key("request_type") + SendPort = Key("send_port") + + // + // DNS + // + // Common items: + // Query + CType = Key("ctype") + Nameserver = Key("nameserver") + RType = Key("rtype") + + // + // EC Console + // + // Common items: + // Command + // Port + // SASLAuthentication + // SASLUser + Objects = Key("objects") + XPath = Key("xpath") + + // + // Elastic Search + // + // Common items: + // Port + // URL + + // + // Ganglia + // + // Common items: + // AsyncMetrics + + // + // Google Analytics + // + // Common items: + // Password + // Username + OAuthToken = Key("oauth_token") + OAuthTokenSecret = Key("oauth_token_secret") + OAuthVersion = Key("oauth_version") + TableID = Key("table_id") + UseOAuth = Key("use_oauth") + + // + // HA Proxy + // + // Common items: + // AuthPassword + // AuthUser + // Port + // UseSSL + Host = Key("host") + Select = Key("select") + + // + // HTTP + // + // Notes: + // HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)` + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // KeyFile + // URL + // HeaderPrefix + // HTTPVersion + // Method + // Payload + // ReadLimit + Body = Key("body") + Code = Key("code") + Extract = Key("extract") + Redirects = Key("redirects") + + // + // HTTPTRAP + // + // Common items: + // AsyncMetrics + // Secret + + // + // IMAP + // + // Common items: + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // KeyFile + // Port + // UseSSL + Fetch = Key("fetch") + Folder = Key("folder") + HeaderHost = Key("header_Host") + Search = Key("search") + + // + // JMX + // + // Common items: + // Password + // Port + // URI + // Username + MbeanDomains = Key("mbean_domains") + + // + // JSON + // + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // HeaderPrefix + // HTTPVersion + // KeyFile + // Method + // Payload + // Port + // ReadLimit + // URL + + // + // Keynote + // + // Notes: + // SlotAliasPrefix is special because the actual key is dynamic and matches: `slot_alias_(\d+)` + // Common items: + // APIKey + // BaseURL + PageComponent = Key("pagecomponent") + SlotAliasPrefix = Key("slot_alias_") + SlotIDList = Key("slot_id_list") + TransPageList = Key("transpagelist") + + // + // Keynote Pulse + // + // Common items: + // BaseURL + // Password + // User + AgreementID = Key("agreement_id") + + // + // LDAP + // + // Common items: + // Password + // Port + AuthType = Key("authtype") + DN = Key("dn") + SecurityPrincipal = Key("security_principal") + + // + // Memcached + // + // Common items: + // Port + + // + // MongoDB + // + // Common items: + // Command + // Password + // Port + // Username + DBName = Key("dbname") + + // + // Munin + // + // Note: no configuration options + + // + // MySQL + // + // Common items: + // DSN + // SQL + + // + // Newrelic rpm + // + // Common items: + // APIKey + AccountID = Key("acct_id") + ApplicationID = Key("application_id") + LicenseKey = Key("license_key") + + // + // Nginx + // + // Common items: + // CAChain + // CertFile + // Ciphers + // KeyFile + // URL + + // + // NRPE + // + // Common items: + // Command + // Port + // UseSSL + AppendUnits = Key("append_uom") + + // + // NTP + // + // Common items: + // Port + Control = Key("control") + + // + // Oracle + // + // Notes: + // JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)` + // Common items: + // AppendColumnName + // Database + // JDBCPrefix + // Password + // Port + // SQL + // User + + // + // Ping ICMP + // + AvailNeeded = Key("avail_needed") + Count = Key("count") + Interval = Key("interval") + + // + // PostgreSQL + // + // Common items: + // DSN + // SQL + + // + // Redis + // + // Common items: + // Command + // Password + // Port + DBIndex = Key("dbindex") + + // + // Resmon + // + // Notes: + // HeaderPrefix is special because the actual key is dynamic and matches: `header_(\S+)` + // Common items: + // AuthMethod + // AuthPassword + // AuthUser + // CAChain + // CertFile + // Ciphers + // HeaderPrefix + // HTTPVersion + // KeyFile + // Method + // Payload + // Port + // ReadLimit + // URL + + // + // SMTP + // + // Common items: + // Payload + // Port + // SASLAuthentication + // SASLUser + EHLO = Key("ehlo") + From = Key("from") + SASLAuthID = Key("sasl_auth_id") + SASLPassword = Key("sasl_password") + StartTLS = Key("starttls") + To = Key("to") + + // + // SNMP + // + // Notes: + // OIDPrefix is special because the actual key is dynamic and matches: `oid_(.+)` + // TypePrefix is special because the actual key is dynamic and matches: `type_(.+)` + // Common items: + // Port + // SecurityLevel + // Version + AuthPassphrase = Key("auth_passphrase") + AuthProtocol = Key("auth_protocol") + Community = Key("community") + ContextEngine = Key("context_engine") + ContextName = Key("context_name") + OIDPrefix = Key("oid_") + PrivacyPassphrase = Key("privacy_passphrase") + PrivacyProtocol = Key("privacy_protocol") + SecurityEngine = Key("security_engine") + SecurityName = Key("security_name") + SeparateQueries = Key("separate_queries") + TypePrefix = Key("type_") + + // + // SQLServer + // + // Notes: + // JDBCPrefix is special because the actual key is dynamic and matches: `jdbc_(\S+)` + // Common items: + // AppendColumnName + // Database + // JDBCPrefix + // Password + // Port + // SQL + // User + + // + // SSH v2 + // + // Common items: + // Port + MethodCompCS = Key("method_comp_cs") + MethodCompSC = Key("method_comp_sc") + MethodCryptCS = Key("method_crypt_cs") + MethodCryptSC = Key("method_crypt_sc") + MethodHostKey = Key("method_hostkey") + MethodKeyExchange = Key("method_kex") + MethodMacCS = Key("method_mac_cs") + MethodMacSC = Key("method_mac_sc") + + // + // StatsD + // + // Note: no configuration options + + // + // TCP + // + // Common items: + // CAChain + // CertFile + // Ciphers + // KeyFile + // Port + // UseSSL + BannerMatch = Key("banner_match") + + // + // Varnish + // + // Note: no configuration options + + // + // reserved - config option(s) can't actually be set - here for r/o access + // + ReverseSecretKey = Key("reverse:secret_key") + SubmissionURL = Key("submission_url") + + // + // Endpoint prefix & cid regex + // + DefaultCIDRegex = "[0-9]+" + DefaultUUIDRegex = "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}" + AccountPrefix = "/account" + AccountCIDRegex = "^(" + AccountPrefix + "/(" + DefaultCIDRegex + "|current))$" + AcknowledgementPrefix = "/acknowledgement" + AcknowledgementCIDRegex = "^(" + AcknowledgementPrefix + "/(" + DefaultCIDRegex + "))$" + AlertPrefix = "/alert" + AlertCIDRegex = "^(" + AlertPrefix + "/(" + DefaultCIDRegex + "))$" + AnnotationPrefix = "/annotation" + AnnotationCIDRegex = "^(" + AnnotationPrefix + "/(" + DefaultCIDRegex + "))$" + BrokerPrefix = "/broker" + BrokerCIDRegex = "^(" + BrokerPrefix + "/(" + DefaultCIDRegex + "))$" + CheckBundleMetricsPrefix = "/check_bundle_metrics" + CheckBundleMetricsCIDRegex = "^(" + CheckBundleMetricsPrefix + "/(" + DefaultCIDRegex + "))$" + CheckBundlePrefix = "/check_bundle" + CheckBundleCIDRegex = "^(" + CheckBundlePrefix + "/(" + DefaultCIDRegex + "))$" + CheckPrefix = "/check" + CheckCIDRegex = "^(" + CheckPrefix + "/(" + DefaultCIDRegex + "))$" + ContactGroupPrefix = "/contact_group" + ContactGroupCIDRegex = "^(" + ContactGroupPrefix + "/(" + DefaultCIDRegex + "))$" + DashboardPrefix = "/dashboard" + DashboardCIDRegex = "^(" + DashboardPrefix + "/(" + DefaultCIDRegex + "))$" + GraphPrefix = "/graph" + GraphCIDRegex = "^(" + GraphPrefix + "/(" + DefaultUUIDRegex + "))$" + MaintenancePrefix = "/maintenance" + MaintenanceCIDRegex = "^(" + MaintenancePrefix + "/(" + DefaultCIDRegex + "))$" + MetricClusterPrefix = "/metric_cluster" + MetricClusterCIDRegex = "^(" + MetricClusterPrefix + "/(" + DefaultCIDRegex + "))$" + MetricPrefix = "/metric" + MetricCIDRegex = "^(" + MetricPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$" + OutlierReportPrefix = "/outlier_report" + OutlierReportCIDRegex = "^(" + OutlierReportPrefix + "/(" + DefaultCIDRegex + "))$" + ProvisionBrokerPrefix = "/provision_broker" + ProvisionBrokerCIDRegex = "^(" + ProvisionBrokerPrefix + "/([a-z0-9]+-[a-z0-9]+))$" + RuleSetGroupPrefix = "/rule_set_group" + RuleSetGroupCIDRegex = "^(" + RuleSetGroupPrefix + "/(" + DefaultCIDRegex + "))$" + RuleSetPrefix = "/rule_set" + RuleSetCIDRegex = "^(" + RuleSetPrefix + "/((" + DefaultCIDRegex + ")_([^[:space:]]+)))$" + UserPrefix = "/user" + UserCIDRegex = "^(" + UserPrefix + "/(" + DefaultCIDRegex + "|current))$" + WorksheetPrefix = "/worksheet" + WorksheetCIDRegex = "^(" + WorksheetPrefix + "/(" + DefaultUUIDRegex + "))$" + // contact group serverity levels + NumSeverityLevels = 5 +) diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go new file mode 100644 index 000000000..578a2e898 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/contact_group.go @@ -0,0 +1,263 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Contact Group API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/contact_group + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// ContactGroupAlertFormats define alert formats +type ContactGroupAlertFormats struct { + LongMessage *string `json:"long_message"` // string or null + LongSubject *string `json:"long_subject"` // string or null + LongSummary *string `json:"long_summary"` // string or null + ShortMessage *string `json:"short_message"` // string or null + ShortSummary *string `json:"short_summary"` // string or null +} + +// ContactGroupContactsExternal external contacts +type ContactGroupContactsExternal struct { + Info string `json:"contact_info"` // string + Method string `json:"method"` // string +} + +// ContactGroupContactsUser user contacts +type ContactGroupContactsUser struct { + Info string `json:"_contact_info,omitempty"` // string + Method string `json:"method"` // string + UserCID string `json:"user"` // string +} + +// ContactGroupContacts list of contacts +type ContactGroupContacts struct { + External []ContactGroupContactsExternal `json:"external"` // [] len >= 0 + Users []ContactGroupContactsUser `json:"users"` // [] len >= 0 +} + +// ContactGroupEscalation defines escalations for severity levels +type ContactGroupEscalation struct { + After uint `json:"after"` // uint + ContactGroupCID string `json:"contact_group"` // string +} + +// ContactGroup defines a contact group. See https://login.circonus.com/resources/api/calls/contact_group for more information. +type ContactGroup struct { + AggregationWindow uint `json:"aggregation_window,omitempty"` // uint + AlertFormats ContactGroupAlertFormats `json:"alert_formats,omitempty"` // ContactGroupAlertFormats + CID string `json:"_cid,omitempty"` // string + Contacts ContactGroupContacts `json:"contacts,omitempty"` // ContactGroupContacts + Escalations []*ContactGroupEscalation `json:"escalations,omitempty"` // [] len == 5, elements: ContactGroupEscalation or null + LastModified uint `json:"_last_modified,omitempty"` // uint + LastModifiedBy string `json:"_last_modified_by,omitempty"` // string + Name string `json:"name,omitempty"` // string + Reminders []uint `json:"reminders,omitempty"` // [] len == 5 + Tags []string `json:"tags,omitempty"` // [] len >= 0 +} + +// NewContactGroup returns a ContactGroup (with defaults, if applicable) +func NewContactGroup() *ContactGroup { + return &ContactGroup{ + Escalations: make([]*ContactGroupEscalation, config.NumSeverityLevels), + Reminders: make([]uint, config.NumSeverityLevels), + Contacts: ContactGroupContacts{ + External: []ContactGroupContactsExternal{}, + Users: []ContactGroupContactsUser{}, + }, + } +} + +// FetchContactGroup retrieves contact group with passed cid. +func (a *API) FetchContactGroup(cid CIDType) (*ContactGroup, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid contact group CID [none]") + } + + groupCID := string(*cid) + + matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID) + } + + result, err := a.Get(groupCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch contact group, received JSON: %s", string(result)) + } + + group := new(ContactGroup) + if err := json.Unmarshal(result, group); err != nil { + return nil, err + } + + return group, nil +} + +// FetchContactGroups retrieves all contact groups available to the API Token. +func (a *API) FetchContactGroups() (*[]ContactGroup, error) { + result, err := a.Get(config.ContactGroupPrefix) + if err != nil { + return nil, err + } + + var groups []ContactGroup + if err := json.Unmarshal(result, &groups); err != nil { + return nil, err + } + + return &groups, nil +} + +// UpdateContactGroup updates passed contact group. +func (a *API) UpdateContactGroup(cfg *ContactGroup) (*ContactGroup, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid contact group config [nil]") + } + + groupCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid contact group CID [%s]", groupCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update contact group, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(groupCID, jsonCfg) + if err != nil { + return nil, err + } + + group := &ContactGroup{} + if err := json.Unmarshal(result, group); err != nil { + return nil, err + } + + return group, nil +} + +// CreateContactGroup creates a new contact group. +func (a *API) CreateContactGroup(cfg *ContactGroup) (*ContactGroup, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid contact group config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create contact group, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.ContactGroupPrefix, jsonCfg) + if err != nil { + return nil, err + } + + group := &ContactGroup{} + if err := json.Unmarshal(result, group); err != nil { + return nil, err + } + + return group, nil +} + +// DeleteContactGroup deletes passed contact group. +func (a *API) DeleteContactGroup(cfg *ContactGroup) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid contact group config [nil]") + } + return a.DeleteContactGroupByCID(CIDType(&cfg.CID)) +} + +// DeleteContactGroupByCID deletes contact group with passed cid. +func (a *API) DeleteContactGroupByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid contact group CID [none]") + } + + groupCID := string(*cid) + + matched, err := regexp.MatchString(config.ContactGroupCIDRegex, groupCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid contact group CID [%s]", groupCID) + } + + _, err = a.Delete(groupCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchContactGroups returns contact groups matching the specified +// search query and/or filter. If nil is passed for both parameters +// all contact groups will be returned. +func (a *API) SearchContactGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]ContactGroup, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchContactGroups() + } + + reqURL := url.URL{ + Path: config.ContactGroupPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var groups []ContactGroup + if err := json.Unmarshal(result, &groups); err != nil { + return nil, err + } + + return &groups, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go new file mode 100644 index 000000000..b21987387 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/dashboard.go @@ -0,0 +1,399 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Dashboard API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/dashboard + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// DashboardGridLayout defines layout +type DashboardGridLayout struct { + Height uint `json:"height"` + Width uint `json:"width"` +} + +// DashboardAccessConfig defines access config +type DashboardAccessConfig struct { + BlackDash bool `json:"black_dash,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Fullscreen bool `json:"fullscreen,omitempty"` + FullscreenHideTitle bool `json:"fullscreen_hide_title,omitempty"` + Nickname string `json:"nickname,omitempty"` + ScaleText bool `json:"scale_text,omitempty"` + SharedID string `json:"shared_id,omitempty"` + TextSize uint `json:"text_size,omitempty"` +} + +// DashboardOptions defines options +type DashboardOptions struct { + AccessConfigs []DashboardAccessConfig `json:"access_configs,omitempty"` + FullscreenHideTitle bool `json:"fullscreen_hide_title,omitempty"` + HideGrid bool `json:"hide_grid,omitempty"` + Linkages [][]string `json:"linkages,omitempty"` + ScaleText bool `json:"scale_text,omitempty"` + TextSize uint `json:"text_size,omitempty"` +} + +// ChartTextWidgetDatapoint defines datapoints for charts +type ChartTextWidgetDatapoint struct { + AccountID string `json:"account_id,omitempty"` // metric cluster, metric + CheckID uint `json:"_check_id,omitempty"` // metric + ClusterID uint `json:"cluster_id,omitempty"` // metric cluster + ClusterTitle string `json:"_cluster_title,omitempty"` // metric cluster + Label string `json:"label,omitempty"` // metric + Label2 string `json:"_label,omitempty"` // metric cluster + Metric string `json:"metric,omitempty"` // metric + MetricType string `json:"_metric_type,omitempty"` // metric + NumericOnly bool `json:"numeric_only,omitempty"` // metric cluster +} + +// ChartWidgetDefinitionLegend defines chart widget definition legend +type ChartWidgetDefinitionLegend struct { + Show bool `json:"show,omitempty"` + Type string `json:"type,omitempty"` +} + +// ChartWidgetWedgeLabels defines chart widget wedge labels +type ChartWidgetWedgeLabels struct { + OnChart bool `json:"on_chart,omitempty"` + ToolTips bool `json:"tooltips,omitempty"` +} + +// ChartWidgetWedgeValues defines chart widget wedge values +type ChartWidgetWedgeValues struct { + Angle string `json:"angle,omitempty"` + Color string `json:"color,omitempty"` + Show bool `json:"show,omitempty"` +} + +// ChartWidgtDefinition defines chart widget definition +type ChartWidgtDefinition struct { + Datasource string `json:"datasource,omitempty"` + Derive string `json:"derive,omitempty"` + DisableAutoformat bool `json:"disable_autoformat,omitempty"` + Formula string `json:"formula,omitempty"` + Legend ChartWidgetDefinitionLegend `json:"legend,omitempty"` + Period uint `json:"period,omitempty"` + PopOnHover bool `json:"pop_onhover,omitempty"` + WedgeLabels ChartWidgetWedgeLabels `json:"wedge_labels,omitempty"` + WedgeValues ChartWidgetWedgeValues `json:"wedge_values,omitempty"` +} + +// ForecastGaugeWidgetThresholds defines forecast widget thresholds +type ForecastGaugeWidgetThresholds struct { + Colors []string `json:"colors,omitempty"` // forecasts, gauges + Flip bool `json:"flip,omitempty"` // gauges + Values []string `json:"values,omitempty"` // forecasts, gauges +} + +// StatusWidgetAgentStatusSettings defines agent status settings +type StatusWidgetAgentStatusSettings struct { + Search string `json:"search,omitempty"` + ShowAgentTypes string `json:"show_agent_types,omitempty"` + ShowContact bool `json:"show_contact,omitempty"` + ShowFeeds bool `json:"show_feeds,omitempty"` + ShowSetup bool `json:"show_setup,omitempty"` + ShowSkew bool `json:"show_skew,omitempty"` + ShowUpdates bool `json:"show_updates,omitempty"` +} + +// StatusWidgetHostStatusSettings defines host status settings +type StatusWidgetHostStatusSettings struct { + LayoutStyle string `json:"layout_style,omitempty"` + Search string `json:"search,omitempty"` + SortBy string `json:"sort_by,omitempty"` + TagFilterSet []string `json:"tag_filter_set,omitempty"` +} + +// DashboardWidgetSettings defines settings specific to widget +type DashboardWidgetSettings struct { + AccountID string `json:"account_id,omitempty"` // alerts, clusters, gauges, graphs, lists, status + Acknowledged string `json:"acknowledged,omitempty"` // alerts + AgentStatusSettings StatusWidgetAgentStatusSettings `json:"agent_status_settings,omitempty"` // status + Algorithm string `json:"algorithm,omitempty"` // clusters + Autoformat bool `json:"autoformat,omitempty"` // text + BodyFormat string `json:"body_format,omitempty"` // text + ChartType string `json:"chart_type,omitempty"` // charts + CheckUUID string `json:"check_uuid,omitempty"` // gauges + Cleared string `json:"cleared,omitempty"` // alerts + ClusterID uint `json:"cluster_id,omitempty"` // clusters + ClusterName string `json:"cluster_name,omitempty"` // clusters + ContactGroups []uint `json:"contact_groups,omitempty"` // alerts + ContentType string `json:"content_type,omitempty"` // status + Datapoints []ChartTextWidgetDatapoint `json:"datapoints,omitempty"` // charts, text + DateWindow string `json:"date_window,omitempty"` // graphs + Definition ChartWidgtDefinition `json:"definition,omitempty"` // charts + Dependents string `json:"dependents,omitempty"` // alerts + DisableAutoformat bool `json:"disable_autoformat,omitempty"` // gauges + Display string `json:"display,omitempty"` // alerts + Format string `json:"format,omitempty"` // forecasts + Formula string `json:"formula,omitempty"` // gauges + GraphUUID string `json:"graph_id,omitempty"` // graphs + HideXAxis bool `json:"hide_xaxis,omitempty"` // graphs + HideYAxis bool `json:"hide_yaxis,omitempty"` // graphs + HostStatusSettings StatusWidgetHostStatusSettings `json:"host_status_settings,omitempty"` // status + KeyInline bool `json:"key_inline,omitempty"` // graphs + KeyLoc string `json:"key_loc,omitempty"` // graphs + KeySize uint `json:"key_size,omitempty"` // graphs + KeyWrap bool `json:"key_wrap,omitempty"` // graphs + Label string `json:"label,omitempty"` // graphs + Layout string `json:"layout,omitempty"` // clusters + Limit uint `json:"limit,omitempty"` // lists + Maintenance string `json:"maintenance,omitempty"` // alerts + Markup string `json:"markup,omitempty"` // html + MetricDisplayName string `json:"metric_display_name,omitempty"` // gauges + MetricName string `json:"metric_name,omitempty"` // gauges + MinAge string `json:"min_age,omitempty"` // alerts + OffHours []uint `json:"off_hours,omitempty"` // alerts + OverlaySetID string `json:"overlay_set_id,omitempty"` // graphs + Period uint `json:"period,omitempty"` // gauges, text, graphs + RangeHigh int `json:"range_high,omitempty"` // gauges + RangeLow int `json:"range_low,omitempty"` // gauges + Realtime bool `json:"realtime,omitempty"` // graphs + ResourceLimit string `json:"resource_limit,omitempty"` // forecasts + ResourceUsage string `json:"resource_usage,omitempty"` // forecasts + Search string `json:"search,omitempty"` // alerts, lists + Severity string `json:"severity,omitempty"` // alerts + ShowFlags bool `json:"show_flags,omitempty"` // graphs + Size string `json:"size,omitempty"` // clusters + TagFilterSet []string `json:"tag_filter_set,omitempty"` // alerts + Threshold float32 `json:"threshold,omitempty"` // clusters + Thresholds ForecastGaugeWidgetThresholds `json:"thresholds,omitempty"` // forecasts, gauges + TimeWindow string `json:"time_window,omitempty"` // alerts + Title string `json:"title,omitempty"` // alerts, charts, forecasts, gauges, html + TitleFormat string `json:"title_format,omitempty"` // text + Trend string `json:"trend,omitempty"` // forecasts + Type string `json:"type,omitempty"` // gauges, lists + UseDefault bool `json:"use_default,omitempty"` // text + ValueType string `json:"value_type,omitempty"` // gauges, text + WeekDays []string `json:"weekdays,omitempty"` // alerts +} + +// DashboardWidget defines widget +type DashboardWidget struct { + Active bool `json:"active"` + Height uint `json:"height"` + Name string `json:"name"` + Origin string `json:"origin"` + Settings DashboardWidgetSettings `json:"settings"` + Type string `json:"type"` + WidgetID string `json:"widget_id"` + Width uint `json:"width"` +} + +// Dashboard defines a dashboard. See https://login.circonus.com/resources/api/calls/dashboard for more information. +type Dashboard struct { + AccountDefault bool `json:"account_default"` + Active bool `json:"_active,omitempty"` + CID string `json:"_cid,omitempty"` + Created uint `json:"_created,omitempty"` + CreatedBy string `json:"_created_by,omitempty"` + GridLayout DashboardGridLayout `json:"grid_layout"` + LastModified uint `json:"_last_modified,omitempty"` + Options DashboardOptions `json:"options"` + Shared bool `json:"shared"` + Title string `json:"title"` + UUID string `json:"_dashboard_uuid,omitempty"` + Widgets []DashboardWidget `json:"widgets"` +} + +// NewDashboard returns a new Dashboard (with defaults, if applicable) +func NewDashboard() *Dashboard { + return &Dashboard{} +} + +// FetchDashboard retrieves dashboard with passed cid. +func (a *API) FetchDashboard(cid CIDType) (*Dashboard, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid dashboard CID [none]") + } + + dashboardCID := string(*cid) + + matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID) + } + + result, err := a.Get(string(*cid)) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch dashboard, received JSON: %s", string(result)) + } + + dashboard := new(Dashboard) + if err := json.Unmarshal(result, dashboard); err != nil { + return nil, err + } + + return dashboard, nil +} + +// FetchDashboards retrieves all dashboards available to the API Token. +func (a *API) FetchDashboards() (*[]Dashboard, error) { + result, err := a.Get(config.DashboardPrefix) + if err != nil { + return nil, err + } + + var dashboards []Dashboard + if err := json.Unmarshal(result, &dashboards); err != nil { + return nil, err + } + + return &dashboards, nil +} + +// UpdateDashboard updates passed dashboard. +func (a *API) UpdateDashboard(cfg *Dashboard) (*Dashboard, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid dashboard config [nil]") + } + + dashboardCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update dashboard, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(dashboardCID, jsonCfg) + if err != nil { + return nil, err + } + + dashboard := &Dashboard{} + if err := json.Unmarshal(result, dashboard); err != nil { + return nil, err + } + + return dashboard, nil +} + +// CreateDashboard creates a new dashboard. +func (a *API) CreateDashboard(cfg *Dashboard) (*Dashboard, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid dashboard config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create dashboard, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.DashboardPrefix, jsonCfg) + if err != nil { + return nil, err + } + + dashboard := &Dashboard{} + if err := json.Unmarshal(result, dashboard); err != nil { + return nil, err + } + + return dashboard, nil +} + +// DeleteDashboard deletes passed dashboard. +func (a *API) DeleteDashboard(cfg *Dashboard) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid dashboard config [nil]") + } + return a.DeleteDashboardByCID(CIDType(&cfg.CID)) +} + +// DeleteDashboardByCID deletes dashboard with passed cid. +func (a *API) DeleteDashboardByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid dashboard CID [none]") + } + + dashboardCID := string(*cid) + + matched, err := regexp.MatchString(config.DashboardCIDRegex, dashboardCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid dashboard CID [%s]", dashboardCID) + } + + _, err = a.Delete(dashboardCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchDashboards returns dashboards matching the specified +// search query and/or filter. If nil is passed for both parameters +// all dashboards will be returned. +func (a *API) SearchDashboards(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Dashboard, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchDashboards() + } + + reqURL := url.URL{ + Path: config.DashboardPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var dashboards []Dashboard + if err := json.Unmarshal(result, &dashboards); err != nil { + return nil, err + } + + return &dashboards, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go new file mode 100644 index 000000000..63904d784 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/doc.go @@ -0,0 +1,63 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package api provides methods for interacting with the Circonus API. See the full Circonus API +Documentation at https://login.circonus.com/resources/api for more information. + +Raw REST methods + + Get - retrieve existing item(s) + Put - update an existing item + Post - create a new item + Delete - remove an existing item + +Endpoints (supported) + + Account https://login.circonus.com/resources/api/calls/account + Acknowledgement https://login.circonus.com/resources/api/calls/acknowledgement + Alert https://login.circonus.com/resources/api/calls/alert + Annotation https://login.circonus.com/resources/api/calls/annotation + Broker https://login.circonus.com/resources/api/calls/broker + Check https://login.circonus.com/resources/api/calls/check + Check Bundle https://login.circonus.com/resources/api/calls/check_bundle + Check Bundle Metrics https://login.circonus.com/resources/api/calls/check_bundle_metrics + Contact Group https://login.circonus.com/resources/api/calls/contact_group + Dashboard https://login.circonus.com/resources/api/calls/dashboard + Graph https://login.circonus.com/resources/api/calls/graph + Maintenance [window] https://login.circonus.com/resources/api/calls/maintenance + Metric https://login.circonus.com/resources/api/calls/metric + Metric Cluster https://login.circonus.com/resources/api/calls/metric_cluster + Outlier Report https://login.circonus.com/resources/api/calls/outlier_report + Provision Broker https://login.circonus.com/resources/api/calls/provision_broker + Rule Set https://login.circonus.com/resources/api/calls/rule_set + Rule Set Group https://login.circonus.com/resources/api/calls/rule_set_group + User https://login.circonus.com/resources/api/calls/user + Worksheet https://login.circonus.com/resources/api/calls/worksheet + +Endpoints (not supported) + + Support may be added for these endpoints in the future. These endpoints may currently be used + directly with the Raw REST methods above. + + CAQL https://login.circonus.com/resources/api/calls/caql + Check Move https://login.circonus.com/resources/api/calls/check_move + Data https://login.circonus.com/resources/api/calls/data + Snapshot https://login.circonus.com/resources/api/calls/snapshot + Tag https://login.circonus.com/resources/api/calls/tag + Template https://login.circonus.com/resources/api/calls/template + +Verbs + + Fetch singular/plural item(s) - e.g. FetchAnnotation, FetchAnnotations + Create create new item - e.g. CreateAnnotation + Update update an item - e.g. UpdateAnnotation + Delete remove an item - e.g. DeleteAnnotation, DeleteAnnotationByCID + Search search for item(s) - e.g. SearchAnnotations + New new item config - e.g. NewAnnotation (returns an empty item, + any applicable defautls defined) + + Not all endpoints support all verbs. +*/ +package api diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go new file mode 100644 index 000000000..2d2865327 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/graph.go @@ -0,0 +1,349 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Graph API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/graph + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// GraphAccessKey defines an access key for a graph +type GraphAccessKey struct { + Active bool `json:"active,omitempty"` // boolean + Height uint `json:"height,omitempty"` // uint + Key string `json:"key,omitempty"` // string + Legend bool `json:"legend,omitempty"` // boolean + LockDate bool `json:"lock_date,omitempty"` // boolean + LockMode string `json:"lock_mode,omitempty"` // string + LockRangeEnd uint `json:"lock_range_end,omitempty"` // uint + LockRangeStart uint `json:"lock_range_start,omitempty"` // uint + LockShowTimes bool `json:"lock_show_times,omitempty"` // boolean + LockZoom string `json:"lock_zoom,omitempty"` // string + Nickname string `json:"nickname,omitempty"` // string + Title bool `json:"title,omitempty"` // boolean + Width uint `json:"width,omitempty"` // uint + XLabels bool `json:"x_labels,omitempty"` // boolean + YLabels bool `json:"y_labels,omitempty"` // boolean +} + +// GraphComposite defines a composite +type GraphComposite struct { + Axis string `json:"axis,omitempty"` // string + Color string `json:"color,omitempty"` // string + DataFormula *string `json:"data_formula,omitempty"` // string or null + Hidden bool `json:"hidden,omitempty"` // boolean + LegendFormula *string `json:"legend_formula,omitempty"` // string or null + Name string `json:"name,omitempty"` // string + Stack *uint `json:"stack,omitempty"` // uint or null +} + +// GraphDatapoint defines a datapoint +type GraphDatapoint struct { + Alpha *float64 `json:"alpha,string,omitempty"` // float64 + Axis string `json:"axis,omitempty"` // string + CAQL *string `json:"caql,omitempty"` // string or null + CheckID uint `json:"check_id,omitempty"` // uint + Color *string `json:"color,omitempty"` // string + DataFormula *string `json:"data_formula"` // string or null + Derive interface{} `json:"derive,omitempty"` // BUG doc: string, api: string or boolean(for caql statements) + Hidden bool `json:"hidden"` // boolean + LegendFormula *string `json:"legend_formula"` // string or null + MetricName string `json:"metric_name,omitempty"` // string + MetricType string `json:"metric_type,omitempty"` // string + Name string `json:"name"` // string + Stack *uint `json:"stack"` // uint or null +} + +// GraphGuide defines a guide +type GraphGuide struct { + Color string `json:"color,omitempty"` // string + DataFormula *string `json:"data_formula,omitempty"` // string or null + Hidden bool `json:"hidden,omitempty"` // boolean + LegendFormula *string `json:"legend_formula,omitempty"` // string or null + Name string `json:"name,omitempty"` // string +} + +// GraphMetricCluster defines a metric cluster +type GraphMetricCluster struct { + AggregateFunc string `json:"aggregate_function,omitempty"` // string + Axis string `json:"axis,omitempty"` // string + Color *string `json:"color,omitempty"` // string + DataFormula *string `json:"data_formula"` // string or null + Hidden bool `json:"hidden"` // boolean + LegendFormula *string `json:"legend_formula"` // string or null + MetricCluster string `json:"metric_cluster,omitempty"` // string + Name string `json:"name,omitempty"` // string + Stack *uint `json:"stack"` // uint or null +} + +// OverlayDataOptions defines overlay options for data. Note, each overlay type requires +// a _subset_ of the options. See Graph API documentation (URL above) for details. +type OverlayDataOptions struct { + Alerts *int `json:"alerts,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + ArrayOutput *int `json:"array_output,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + BasePeriod *int `json:"base_period,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Delay *int `json:"delay,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Extension string `json:"extension,omitempty"` // string + GraphTitle string `json:"graph_title,omitempty"` // string + GraphUUID string `json:"graph_id,omitempty"` // string + InPercent *bool `json:"in_percent,string,omitempty"` // boolean encoded as string BUG doc: boolean, api: string + Inverse *int `json:"inverse,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Method string `json:"method,omitempty"` // string + Model string `json:"model,omitempty"` // string + ModelEnd string `json:"model_end,omitempty"` // string + ModelPeriod string `json:"model_period,omitempty"` // string + ModelRelative *int `json:"model_relative,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Out string `json:"out,omitempty"` // string + Prequel string `json:"prequel,omitempty"` // string + Presets string `json:"presets,omitempty"` // string + Quantiles string `json:"quantiles,omitempty"` // string + SeasonLength *int `json:"season_length,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Sensitivity *int `json:"sensitivity,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + SingleValue *int `json:"single_value,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + TargetPeriod string `json:"target_period,omitempty"` // string + TimeOffset string `json:"time_offset,omitempty"` // string + TimeShift *int `json:"time_shift,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Transform string `json:"transform,omitempty"` // string + Version *int `json:"version,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + Window *int `json:"window,string,omitempty"` // int encoded as string BUG doc: numeric, api: string + XShift string `json:"x_shift,omitempty"` // string +} + +// OverlayUISpecs defines UI specs for overlay +type OverlayUISpecs struct { + Decouple bool `json:"decouple,omitempty"` // boolean + ID string `json:"id,omitempty"` // string + Label string `json:"label,omitempty"` // string + Type string `json:"type,omitempty"` // string + Z *int `json:"z,string,omitempty"` // int encoded as string BUG doc: numeric, api: string +} + +// GraphOverlaySet defines overlays for graph +type GraphOverlaySet struct { + DataOpts OverlayDataOptions `json:"data_opts,omitempty"` // OverlayDataOptions + ID string `json:"id,omitempty"` // string + Title string `json:"title,omitempty"` // string + UISpecs OverlayUISpecs `json:"ui_specs,omitempty"` // OverlayUISpecs +} + +// Graph defines a graph. See https://login.circonus.com/resources/api/calls/graph for more information. +type Graph struct { + AccessKeys []GraphAccessKey `json:"access_keys,omitempty"` // [] len >= 0 + CID string `json:"_cid,omitempty"` // string + Composites []GraphComposite `json:"composites,omitempty"` // [] len >= 0 + Datapoints []GraphDatapoint `json:"datapoints,omitempt"` // [] len >= 0 + Description string `json:"description,omitempty"` // string + Guides []GraphGuide `json:"guides,omitempty"` // [] len >= 0 + LineStyle *string `json:"line_style"` // string or null + LogLeftY *int `json:"logarithmic_left_y,string,omitempty"` // int encoded as string or null BUG doc: number (not string) + LogRightY *int `json:"logarithmic_right_y,string,omitempty"` // int encoded as string or null BUG doc: number (not string) + MaxLeftY *float64 `json:"max_left_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string) + MaxRightY *float64 `json:"max_right_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string) + MetricClusters []GraphMetricCluster `json:"metric_clusters,omitempty"` // [] len >= 0 + MinLeftY *float64 `json:"min_left_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string) + MinRightY *float64 `json:"min_right_y,string,omitempty"` // float64 encoded as string or null BUG doc: number (not string) + Notes *string `json:"notes,omitempty"` // string or null + OverlaySets *map[string]GraphOverlaySet `json:"overlay_sets,omitempty"` // GroupOverLaySets or null + Style *string `json:"style"` // string or null + Tags []string `json:"tags,omitempty"` // [] len >= 0 + Title string `json:"title,omitempty"` // string +} + +// NewGraph returns a Graph (with defaults, if applicable) +func NewGraph() *Graph { + return &Graph{} +} + +// FetchGraph retrieves graph with passed cid. +func (a *API) FetchGraph(cid CIDType) (*Graph, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid graph CID [none]") + } + + graphCID := string(*cid) + + matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID) + } + + result, err := a.Get(graphCID) + if err != nil { + return nil, err + } + if a.Debug { + a.Log.Printf("[DEBUG] fetch graph, received JSON: %s", string(result)) + } + + graph := new(Graph) + if err := json.Unmarshal(result, graph); err != nil { + return nil, err + } + + return graph, nil +} + +// FetchGraphs retrieves all graphs available to the API Token. +func (a *API) FetchGraphs() (*[]Graph, error) { + result, err := a.Get(config.GraphPrefix) + if err != nil { + return nil, err + } + + var graphs []Graph + if err := json.Unmarshal(result, &graphs); err != nil { + return nil, err + } + + return &graphs, nil +} + +// UpdateGraph updates passed graph. +func (a *API) UpdateGraph(cfg *Graph) (*Graph, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid graph config [nil]") + } + + graphCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid graph CID [%s]", graphCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(graphCID, jsonCfg) + if err != nil { + return nil, err + } + + graph := &Graph{} + if err := json.Unmarshal(result, graph); err != nil { + return nil, err + } + + return graph, nil +} + +// CreateGraph creates a new graph. +func (a *API) CreateGraph(cfg *Graph) (*Graph, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid graph config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update graph, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.GraphPrefix, jsonCfg) + if err != nil { + return nil, err + } + + graph := &Graph{} + if err := json.Unmarshal(result, graph); err != nil { + return nil, err + } + + return graph, nil +} + +// DeleteGraph deletes passed graph. +func (a *API) DeleteGraph(cfg *Graph) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid graph config [nil]") + } + return a.DeleteGraphByCID(CIDType(&cfg.CID)) +} + +// DeleteGraphByCID deletes graph with passed cid. +func (a *API) DeleteGraphByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid graph CID [none]") + } + + graphCID := string(*cid) + + matched, err := regexp.MatchString(config.GraphCIDRegex, graphCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid graph CID [%s]", graphCID) + } + + _, err = a.Delete(graphCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchGraphs returns graphs matching the specified search query +// and/or filter. If nil is passed for both parameters all graphs +// will be returned. +func (a *API) SearchGraphs(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Graph, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchGraphs() + } + + reqURL := url.URL{ + Path: config.GraphPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var graphs []Graph + if err := json.Unmarshal(result, &graphs); err != nil { + return nil, err + } + + return &graphs, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go new file mode 100644 index 000000000..0e5e04729 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/maintenance.go @@ -0,0 +1,220 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Maintenance window API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/maintenance + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// Maintenance defines a maintenance window. See https://login.circonus.com/resources/api/calls/maintenance for more information. +type Maintenance struct { + CID string `json:"_cid,omitempty"` // string + Item string `json:"item,omitempty"` // string + Notes string `json:"notes,omitempty"` // string + Severities interface{} `json:"severities,omitempty"` // []string NOTE can be set with CSV string or []string + Start uint `json:"start,omitempty"` // uint + Stop uint `json:"stop,omitempty"` // uint + Tags []string `json:"tags,omitempty"` // [] len >= 0 + Type string `json:"type,omitempty"` // string +} + +// NewMaintenanceWindow returns a new Maintenance window (with defaults, if applicable) +func NewMaintenanceWindow() *Maintenance { + return &Maintenance{} +} + +// FetchMaintenanceWindow retrieves maintenance [window] with passed cid. +func (a *API) FetchMaintenanceWindow(cid CIDType) (*Maintenance, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid maintenance window CID [none]") + } + + maintenanceCID := string(*cid) + + matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID) + } + + result, err := a.Get(maintenanceCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch maintenance window, received JSON: %s", string(result)) + } + + window := &Maintenance{} + if err := json.Unmarshal(result, window); err != nil { + return nil, err + } + + return window, nil +} + +// FetchMaintenanceWindows retrieves all maintenance [windows] available to API Token. +func (a *API) FetchMaintenanceWindows() (*[]Maintenance, error) { + result, err := a.Get(config.MaintenancePrefix) + if err != nil { + return nil, err + } + + var windows []Maintenance + if err := json.Unmarshal(result, &windows); err != nil { + return nil, err + } + + return &windows, nil +} + +// UpdateMaintenanceWindow updates passed maintenance [window]. +func (a *API) UpdateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid maintenance window config [nil]") + } + + maintenanceCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update maintenance window, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(maintenanceCID, jsonCfg) + if err != nil { + return nil, err + } + + window := &Maintenance{} + if err := json.Unmarshal(result, window); err != nil { + return nil, err + } + + return window, nil +} + +// CreateMaintenanceWindow creates a new maintenance [window]. +func (a *API) CreateMaintenanceWindow(cfg *Maintenance) (*Maintenance, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid maintenance window config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create maintenance window, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.MaintenancePrefix, jsonCfg) + if err != nil { + return nil, err + } + + window := &Maintenance{} + if err := json.Unmarshal(result, window); err != nil { + return nil, err + } + + return window, nil +} + +// DeleteMaintenanceWindow deletes passed maintenance [window]. +func (a *API) DeleteMaintenanceWindow(cfg *Maintenance) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid maintenance window config [nil]") + } + return a.DeleteMaintenanceWindowByCID(CIDType(&cfg.CID)) +} + +// DeleteMaintenanceWindowByCID deletes maintenance [window] with passed cid. +func (a *API) DeleteMaintenanceWindowByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid maintenance window CID [none]") + } + + maintenanceCID := string(*cid) + + matched, err := regexp.MatchString(config.MaintenanceCIDRegex, maintenanceCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid maintenance window CID [%s]", maintenanceCID) + } + + _, err = a.Delete(maintenanceCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchMaintenanceWindows returns maintenance [windows] matching +// the specified search query and/or filter. If nil is passed for +// both parameters all maintenance [windows] will be returned. +func (a *API) SearchMaintenanceWindows(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Maintenance, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchMaintenanceWindows() + } + + reqURL := url.URL{ + Path: config.MaintenancePrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var windows []Maintenance + if err := json.Unmarshal(result, &windows); err != nil { + return nil, err + } + + return &windows, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go new file mode 100644 index 000000000..3608b06ff --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric.go @@ -0,0 +1,162 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Metric API support - Fetch, Create*, Update, Delete*, and Search +// See: https://login.circonus.com/resources/api/calls/metric +// * : create and delete are handled via check_bundle or check_bundle_metrics + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// Metric defines a metric. See https://login.circonus.com/resources/api/calls/metric for more information. +type Metric struct { + Active bool `json:"_active,omitempty"` // boolean + CheckActive bool `json:"_check_active,omitempty"` // boolean + CheckBundleCID string `json:"_check_bundle,omitempty"` // string + CheckCID string `json:"_check,omitempty"` // string + CheckTags []string `json:"_check_tags,omitempty"` // [] len >= 0 + CheckUUID string `json:"_check_uuid,omitempty"` // string + CID string `json:"_cid,omitempty"` // string + Histogram string `json:"_histogram,omitempty"` // string + Link *string `json:"link,omitempty"` // string or null + MetricName string `json:"_metric_name,omitempty"` // string + MetricType string `json:"_metric_type,omitempty"` // string + Notes *string `json:"notes,omitempty"` // string or null + Tags []string `json:"tags,omitempty"` // [] len >= 0 + Units *string `json:"units,omitempty"` // string or null +} + +// FetchMetric retrieves metric with passed cid. +func (a *API) FetchMetric(cid CIDType) (*Metric, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid metric CID [none]") + } + + metricCID := string(*cid) + + matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID) + } + + result, err := a.Get(metricCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch metric, received JSON: %s", string(result)) + } + + metric := &Metric{} + if err := json.Unmarshal(result, metric); err != nil { + return nil, err + } + + return metric, nil +} + +// FetchMetrics retrieves all metrics available to API Token. +func (a *API) FetchMetrics() (*[]Metric, error) { + result, err := a.Get(config.MetricPrefix) + if err != nil { + return nil, err + } + + var metrics []Metric + if err := json.Unmarshal(result, &metrics); err != nil { + return nil, err + } + + return &metrics, nil +} + +// UpdateMetric updates passed metric. +func (a *API) UpdateMetric(cfg *Metric) (*Metric, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid metric config [nil]") + } + + metricCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.MetricCIDRegex, metricCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid metric CID [%s]", metricCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update metric, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(metricCID, jsonCfg) + if err != nil { + return nil, err + } + + metric := &Metric{} + if err := json.Unmarshal(result, metric); err != nil { + return nil, err + } + + return metric, nil +} + +// SearchMetrics returns metrics matching the specified search query +// and/or filter. If nil is passed for both parameters all metrics +// will be returned. +func (a *API) SearchMetrics(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Metric, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchMetrics() + } + + reqURL := url.URL{ + Path: config.MetricPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var metrics []Metric + if err := json.Unmarshal(result, &metrics); err != nil { + return nil, err + } + + return &metrics, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go new file mode 100644 index 000000000..d29c5a674 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/metric_cluster.go @@ -0,0 +1,261 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Metric Cluster API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/metric_cluster + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// MetricQuery object +type MetricQuery struct { + Query string `json:"query"` + Type string `json:"type"` +} + +// MetricCluster defines a metric cluster. See https://login.circonus.com/resources/api/calls/metric_cluster for more information. +type MetricCluster struct { + CID string `json:"_cid,omitempty"` // string + Description string `json:"description"` // string + MatchingMetrics []string `json:"_matching_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set) + MatchingUUIDMetrics map[string][]string `json:"_matching_uuid_metrics,omitempty"` // [] len >= 1 (result info only, if query has extras - cannot be set) + Name string `json:"name"` // string + Queries []MetricQuery `json:"queries"` // [] len >= 1 + Tags []string `json:"tags"` // [] len >= 0 +} + +// NewMetricCluster returns a new MetricCluster (with defaults, if applicable) +func NewMetricCluster() *MetricCluster { + return &MetricCluster{} +} + +// FetchMetricCluster retrieves metric cluster with passed cid. +func (a *API) FetchMetricCluster(cid CIDType, extras string) (*MetricCluster, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid metric cluster CID [none]") + } + + clusterCID := string(*cid) + + matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID) + } + + reqURL := url.URL{ + Path: clusterCID, + } + + extra := "" + switch extras { + case "metrics": + extra = "_matching_metrics" + case "uuids": + extra = "_matching_uuid_metrics" + } + + if extra != "" { + q := url.Values{} + q.Set("extra", extra) + reqURL.RawQuery = q.Encode() + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch metric cluster, received JSON: %s", string(result)) + } + + cluster := &MetricCluster{} + if err := json.Unmarshal(result, cluster); err != nil { + return nil, err + } + + return cluster, nil +} + +// FetchMetricClusters retrieves all metric clusters available to API Token. +func (a *API) FetchMetricClusters(extras string) (*[]MetricCluster, error) { + reqURL := url.URL{ + Path: config.MetricClusterPrefix, + } + + extra := "" + switch extras { + case "metrics": + extra = "_matching_metrics" + case "uuids": + extra = "_matching_uuid_metrics" + } + + if extra != "" { + q := url.Values{} + q.Set("extra", extra) + reqURL.RawQuery = q.Encode() + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, err + } + + var clusters []MetricCluster + if err := json.Unmarshal(result, &clusters); err != nil { + return nil, err + } + + return &clusters, nil +} + +// UpdateMetricCluster updates passed metric cluster. +func (a *API) UpdateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid metric cluster config [nil]") + } + + clusterCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update metric cluster, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(clusterCID, jsonCfg) + if err != nil { + return nil, err + } + + cluster := &MetricCluster{} + if err := json.Unmarshal(result, cluster); err != nil { + return nil, err + } + + return cluster, nil +} + +// CreateMetricCluster creates a new metric cluster. +func (a *API) CreateMetricCluster(cfg *MetricCluster) (*MetricCluster, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid metric cluster config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create metric cluster, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.MetricClusterPrefix, jsonCfg) + if err != nil { + return nil, err + } + + cluster := &MetricCluster{} + if err := json.Unmarshal(result, cluster); err != nil { + return nil, err + } + + return cluster, nil +} + +// DeleteMetricCluster deletes passed metric cluster. +func (a *API) DeleteMetricCluster(cfg *MetricCluster) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid metric cluster config [nil]") + } + return a.DeleteMetricClusterByCID(CIDType(&cfg.CID)) +} + +// DeleteMetricClusterByCID deletes metric cluster with passed cid. +func (a *API) DeleteMetricClusterByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid metric cluster CID [none]") + } + + clusterCID := string(*cid) + + matched, err := regexp.MatchString(config.MetricClusterCIDRegex, clusterCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid metric cluster CID [%s]", clusterCID) + } + + _, err = a.Delete(clusterCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchMetricClusters returns metric clusters matching the specified +// search query and/or filter. If nil is passed for both parameters +// all metric clusters will be returned. +func (a *API) SearchMetricClusters(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]MetricCluster, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchMetricClusters("") + } + + reqURL := url.URL{ + Path: config.MetricClusterPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var clusters []MetricCluster + if err := json.Unmarshal(result, &clusters); err != nil { + return nil, err + } + + return &clusters, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go new file mode 100644 index 000000000..bc1a4d2b3 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/outlier_report.go @@ -0,0 +1,221 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// OutlierReport API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/report + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// OutlierReport defines a outlier report. See https://login.circonus.com/resources/api/calls/report for more information. +type OutlierReport struct { + CID string `json:"_cid,omitempty"` // string + Config string `json:"config,omitempty"` // string + Created uint `json:"_created,omitempty"` // uint + CreatedBy string `json:"_created_by,omitempty"` // string + LastModified uint `json:"_last_modified,omitempty"` // uint + LastModifiedBy string `json:"_last_modified_by,omitempty"` // string + MetricClusterCID string `json:"metric_cluster,omitempty"` // st ring + Tags []string `json:"tags,omitempty"` // [] len >= 0 + Title string `json:"title,omitempty"` // string +} + +// NewOutlierReport returns a new OutlierReport (with defaults, if applicable) +func NewOutlierReport() *OutlierReport { + return &OutlierReport{} +} + +// FetchOutlierReport retrieves outlier report with passed cid. +func (a *API) FetchOutlierReport(cid CIDType) (*OutlierReport, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid outlier report CID [none]") + } + + reportCID := string(*cid) + + matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID) + } + + result, err := a.Get(reportCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch outlier report, received JSON: %s", string(result)) + } + + report := &OutlierReport{} + if err := json.Unmarshal(result, report); err != nil { + return nil, err + } + + return report, nil +} + +// FetchOutlierReports retrieves all outlier reports available to API Token. +func (a *API) FetchOutlierReports() (*[]OutlierReport, error) { + result, err := a.Get(config.OutlierReportPrefix) + if err != nil { + return nil, err + } + + var reports []OutlierReport + if err := json.Unmarshal(result, &reports); err != nil { + return nil, err + } + + return &reports, nil +} + +// UpdateOutlierReport updates passed outlier report. +func (a *API) UpdateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid outlier report config [nil]") + } + + reportCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid outlier report CID [%s]", reportCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update outlier report, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(reportCID, jsonCfg) + if err != nil { + return nil, err + } + + report := &OutlierReport{} + if err := json.Unmarshal(result, report); err != nil { + return nil, err + } + + return report, nil +} + +// CreateOutlierReport creates a new outlier report. +func (a *API) CreateOutlierReport(cfg *OutlierReport) (*OutlierReport, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid outlier report config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create outlier report, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.OutlierReportPrefix, jsonCfg) + if err != nil { + return nil, err + } + + report := &OutlierReport{} + if err := json.Unmarshal(result, report); err != nil { + return nil, err + } + + return report, nil +} + +// DeleteOutlierReport deletes passed outlier report. +func (a *API) DeleteOutlierReport(cfg *OutlierReport) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid outlier report config [nil]") + } + return a.DeleteOutlierReportByCID(CIDType(&cfg.CID)) +} + +// DeleteOutlierReportByCID deletes outlier report with passed cid. +func (a *API) DeleteOutlierReportByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid outlier report CID [none]") + } + + reportCID := string(*cid) + + matched, err := regexp.MatchString(config.OutlierReportCIDRegex, reportCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid outlier report CID [%s]", reportCID) + } + + _, err = a.Delete(reportCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchOutlierReports returns outlier report matching the +// specified search query and/or filter. If nil is passed for +// both parameters all outlier report will be returned. +func (a *API) SearchOutlierReports(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]OutlierReport, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchOutlierReports() + } + + reqURL := url.URL{ + Path: config.OutlierReportPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var reports []OutlierReport + if err := json.Unmarshal(result, &reports); err != nil { + return nil, err + } + + return &reports, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go new file mode 100644 index 000000000..5b432a236 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/provision_broker.go @@ -0,0 +1,151 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// ProvisionBroker API support - Fetch, Create, and Update +// See: https://login.circonus.com/resources/api/calls/provision_broker +// Note that the provision_broker endpoint does not return standard cid format +// of '/object/item' (e.g. /provision_broker/abc-123) it just returns 'item' + +package api + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// BrokerStratcon defines stratcons for broker +type BrokerStratcon struct { + CN string `json:"cn,omitempty"` // string + Host string `json:"host,omitempty"` // string + Port string `json:"port,omitempty"` // string +} + +// ProvisionBroker defines a provision broker [request]. See https://login.circonus.com/resources/api/calls/provision_broker for more details. +type ProvisionBroker struct { + Cert string `json:"_cert,omitempty"` // string + CID string `json:"_cid,omitempty"` // string + CSR string `json:"_csr,omitempty"` // string + ExternalHost string `json:"external_host,omitempty"` // string + ExternalPort string `json:"external_port,omitempty"` // string + IPAddress string `json:"ipaddress,omitempty"` // string + Latitude string `json:"latitude,omitempty"` // string + Longitude string `json:"longitude,omitempty"` // string + Name string `json:"noit_name,omitempty"` // string + Port string `json:"port,omitempty"` // string + PreferReverseConnection bool `json:"prefer_reverse_connection,omitempty"` // boolean + Rebuild bool `json:"rebuild,omitempty"` // boolean + Stratcons []BrokerStratcon `json:"_stratcons,omitempty"` // [] len >= 1 + Tags []string `json:"tags,omitempty"` // [] len >= 0 +} + +// NewProvisionBroker returns a new ProvisionBroker (with defaults, if applicable) +func NewProvisionBroker() *ProvisionBroker { + return &ProvisionBroker{} +} + +// FetchProvisionBroker retrieves provision broker [request] with passed cid. +func (a *API) FetchProvisionBroker(cid CIDType) (*ProvisionBroker, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid provision broker request CID [none]") + } + + brokerCID := string(*cid) + + matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID) + } + + result, err := a.Get(brokerCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch broker provision request, received JSON: %s", string(result)) + } + + broker := &ProvisionBroker{} + if err := json.Unmarshal(result, broker); err != nil { + return nil, err + } + + return broker, nil +} + +// UpdateProvisionBroker updates a broker definition [request]. +func (a *API) UpdateProvisionBroker(cid CIDType, cfg *ProvisionBroker) (*ProvisionBroker, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid provision broker request config [nil]") + } + + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid provision broker request CID [none]") + } + + brokerCID := string(*cid) + + matched, err := regexp.MatchString(config.ProvisionBrokerCIDRegex, brokerCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid provision broker request CID [%s]", brokerCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update broker provision request, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(brokerCID, jsonCfg) + if err != nil { + return nil, err + } + + broker := &ProvisionBroker{} + if err := json.Unmarshal(result, broker); err != nil { + return nil, err + } + + return broker, nil +} + +// CreateProvisionBroker creates a new provison broker [request]. +func (a *API) CreateProvisionBroker(cfg *ProvisionBroker) (*ProvisionBroker, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid provision broker request config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create broker provision request, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.ProvisionBrokerPrefix, jsonCfg) + if err != nil { + return nil, err + } + + broker := &ProvisionBroker{} + if err := json.Unmarshal(result, broker); err != nil { + return nil, err + } + + return broker, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go new file mode 100644 index 000000000..3da0907f7 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set.go @@ -0,0 +1,234 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Rule Set API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/rule_set + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// RuleSetRule defines a ruleset rule +type RuleSetRule struct { + Criteria string `json:"criteria"` // string + Severity uint `json:"severity"` // uint + Value interface{} `json:"value"` // BUG doc: string, api: actual type returned switches based on Criteria + Wait uint `json:"wait"` // uint + WindowingDuration uint `json:"windowing_duration,omitempty"` // uint + WindowingFunction *string `json:"windowing_function,omitempty"` // string or null +} + +// RuleSet defines a ruleset. See https://login.circonus.com/resources/api/calls/rule_set for more information. +type RuleSet struct { + CheckCID string `json:"check"` // string + CID string `json:"_cid,omitempty"` // string + ContactGroups map[uint8][]string `json:"contact_groups"` // [] len 5 + Derive *string `json:"derive,omitempty"` // string or null + Link *string `json:"link"` // string or null + MetricName string `json:"metric_name"` // string + MetricTags []string `json:"metric_tags"` // [] len >= 0 + MetricType string `json:"metric_type"` // string + Notes *string `json:"notes"` // string or null + Parent *string `json:"parent,omitempty"` // string or null + Rules []RuleSetRule `json:"rules"` // [] len >= 1 + Tags []string `json:"tags"` // [] len >= 0 +} + +// NewRuleSet returns a new RuleSet (with defaults if applicable) +func NewRuleSet() *RuleSet { + return &RuleSet{} +} + +// FetchRuleSet retrieves rule set with passed cid. +func (a *API) FetchRuleSet(cid CIDType) (*RuleSet, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid rule set CID [none]") + } + + rulesetCID := string(*cid) + + matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID) + } + + result, err := a.Get(rulesetCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch rule set, received JSON: %s", string(result)) + } + + ruleset := &RuleSet{} + if err := json.Unmarshal(result, ruleset); err != nil { + return nil, err + } + + return ruleset, nil +} + +// FetchRuleSets retrieves all rule sets available to API Token. +func (a *API) FetchRuleSets() (*[]RuleSet, error) { + result, err := a.Get(config.RuleSetPrefix) + if err != nil { + return nil, err + } + + var rulesets []RuleSet + if err := json.Unmarshal(result, &rulesets); err != nil { + return nil, err + } + + return &rulesets, nil +} + +// UpdateRuleSet updates passed rule set. +func (a *API) UpdateRuleSet(cfg *RuleSet) (*RuleSet, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid rule set config [nil]") + } + + rulesetCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update rule set, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(rulesetCID, jsonCfg) + if err != nil { + return nil, err + } + + ruleset := &RuleSet{} + if err := json.Unmarshal(result, ruleset); err != nil { + return nil, err + } + + return ruleset, nil +} + +// CreateRuleSet creates a new rule set. +func (a *API) CreateRuleSet(cfg *RuleSet) (*RuleSet, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid rule set config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create rule set, sending JSON: %s", string(jsonCfg)) + } + + resp, err := a.Post(config.RuleSetPrefix, jsonCfg) + if err != nil { + return nil, err + } + + ruleset := &RuleSet{} + if err := json.Unmarshal(resp, ruleset); err != nil { + return nil, err + } + + return ruleset, nil +} + +// DeleteRuleSet deletes passed rule set. +func (a *API) DeleteRuleSet(cfg *RuleSet) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid rule set config [nil]") + } + return a.DeleteRuleSetByCID(CIDType(&cfg.CID)) +} + +// DeleteRuleSetByCID deletes rule set with passed cid. +func (a *API) DeleteRuleSetByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid rule set CID [none]") + } + + rulesetCID := string(*cid) + + matched, err := regexp.MatchString(config.RuleSetCIDRegex, rulesetCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid rule set CID [%s]", rulesetCID) + } + + _, err = a.Delete(rulesetCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchRuleSets returns rule sets matching the specified search +// query and/or filter. If nil is passed for both parameters all +// rule sets will be returned. +func (a *API) SearchRuleSets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSet, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchRuleSets() + } + + reqURL := url.URL{ + Path: config.RuleSetPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var rulesets []RuleSet + if err := json.Unmarshal(result, &rulesets); err != nil { + return nil, err + } + + return &rulesets, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go new file mode 100644 index 000000000..a15743061 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/rule_set_group.go @@ -0,0 +1,231 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// RuleSetGroup API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/rule_set_group + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// RuleSetGroupFormula defines a formula for raising alerts +type RuleSetGroupFormula struct { + Expression interface{} `json:"expression"` // string or uint BUG doc: string, api: string or numeric + RaiseSeverity uint `json:"raise_severity"` // uint + Wait uint `json:"wait"` // uint +} + +// RuleSetGroupCondition defines conditions for raising alerts +type RuleSetGroupCondition struct { + MatchingSeverities []string `json:"matching_serverities"` // [] len >= 1 + RuleSetCID string `json:"rule_set"` // string +} + +// RuleSetGroup defines a ruleset group. See https://login.circonus.com/resources/api/calls/rule_set_group for more information. +type RuleSetGroup struct { + CID string `json:"_cid,omitempty"` // string + ContactGroups map[uint8][]string `json:"contact_groups"` // [] len == 5 + Formulas []RuleSetGroupFormula `json:"formulas"` // [] len >= 0 + Name string `json:"name"` // string + RuleSetConditions []RuleSetGroupCondition `json:"rule_set_conditions"` // [] len >= 1 + Tags []string `json:"tags"` // [] len >= 0 +} + +// NewRuleSetGroup returns a new RuleSetGroup (with defaults, if applicable) +func NewRuleSetGroup() *RuleSetGroup { + return &RuleSetGroup{} +} + +// FetchRuleSetGroup retrieves rule set group with passed cid. +func (a *API) FetchRuleSetGroup(cid CIDType) (*RuleSetGroup, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid rule set group CID [none]") + } + + groupCID := string(*cid) + + matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID) + } + + result, err := a.Get(groupCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch rule set group, received JSON: %s", string(result)) + } + + rulesetGroup := &RuleSetGroup{} + if err := json.Unmarshal(result, rulesetGroup); err != nil { + return nil, err + } + + return rulesetGroup, nil +} + +// FetchRuleSetGroups retrieves all rule set groups available to API Token. +func (a *API) FetchRuleSetGroups() (*[]RuleSetGroup, error) { + result, err := a.Get(config.RuleSetGroupPrefix) + if err != nil { + return nil, err + } + + var rulesetGroups []RuleSetGroup + if err := json.Unmarshal(result, &rulesetGroups); err != nil { + return nil, err + } + + return &rulesetGroups, nil +} + +// UpdateRuleSetGroup updates passed rule set group. +func (a *API) UpdateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid rule set group config [nil]") + } + + groupCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid rule set group CID [%s]", groupCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update rule set group, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(groupCID, jsonCfg) + if err != nil { + return nil, err + } + + groups := &RuleSetGroup{} + if err := json.Unmarshal(result, groups); err != nil { + return nil, err + } + + return groups, nil +} + +// CreateRuleSetGroup creates a new rule set group. +func (a *API) CreateRuleSetGroup(cfg *RuleSetGroup) (*RuleSetGroup, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid rule set group config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create rule set group, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.RuleSetGroupPrefix, jsonCfg) + if err != nil { + return nil, err + } + + group := &RuleSetGroup{} + if err := json.Unmarshal(result, group); err != nil { + return nil, err + } + + return group, nil +} + +// DeleteRuleSetGroup deletes passed rule set group. +func (a *API) DeleteRuleSetGroup(cfg *RuleSetGroup) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid rule set group config [nil]") + } + return a.DeleteRuleSetGroupByCID(CIDType(&cfg.CID)) +} + +// DeleteRuleSetGroupByCID deletes rule set group wiht passed cid. +func (a *API) DeleteRuleSetGroupByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid rule set group CID [none]") + } + + groupCID := string(*cid) + + matched, err := regexp.MatchString(config.RuleSetGroupCIDRegex, groupCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid rule set group CID [%s]", groupCID) + } + + _, err = a.Delete(groupCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchRuleSetGroups returns rule set groups matching the +// specified search query and/or filter. If nil is passed for +// both parameters all rule set groups will be returned. +func (a *API) SearchRuleSetGroups(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]RuleSetGroup, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchRuleSetGroups() + } + + reqURL := url.URL{ + Path: config.RuleSetGroupPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var groups []RuleSetGroup + if err := json.Unmarshal(result, &groups); err != nil { + return nil, err + } + + return &groups, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go new file mode 100644 index 000000000..7771991d3 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/user.go @@ -0,0 +1,159 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// User API support - Fetch, Update, and Search +// See: https://login.circonus.com/resources/api/calls/user +// Note: Create and Delete are not supported directly via the User API +// endpoint. See the Account endpoint for inviting and removing users +// from specific accounts. + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// UserContactInfo defines known contact details +type UserContactInfo struct { + SMS string `json:"sms,omitempty"` // string + XMPP string `json:"xmpp,omitempty"` // string +} + +// User defines a user. See https://login.circonus.com/resources/api/calls/user for more information. +type User struct { + CID string `json:"_cid,omitempty"` // string + ContactInfo UserContactInfo `json:"contact_info,omitempty"` // UserContactInfo + Email string `json:"email"` // string + Firstname string `json:"firstname"` // string + Lastname string `json:"lastname"` // string +} + +// FetchUser retrieves user with passed cid. Pass nil for '/user/current'. +func (a *API) FetchUser(cid CIDType) (*User, error) { + var userCID string + + if cid == nil || *cid == "" { + userCID = config.UserPrefix + "/current" + } else { + userCID = string(*cid) + } + + matched, err := regexp.MatchString(config.UserCIDRegex, userCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid user CID [%s]", userCID) + } + + result, err := a.Get(userCID) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch user, received JSON: %s", string(result)) + } + + user := new(User) + if err := json.Unmarshal(result, user); err != nil { + return nil, err + } + + return user, nil +} + +// FetchUsers retrieves all users available to API Token. +func (a *API) FetchUsers() (*[]User, error) { + result, err := a.Get(config.UserPrefix) + if err != nil { + return nil, err + } + + var users []User + if err := json.Unmarshal(result, &users); err != nil { + return nil, err + } + + return &users, nil +} + +// UpdateUser updates passed user. +func (a *API) UpdateUser(cfg *User) (*User, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid user config [nil]") + } + + userCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.UserCIDRegex, userCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid user CID [%s]", userCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update user, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(userCID, jsonCfg) + if err != nil { + return nil, err + } + + user := &User{} + if err := json.Unmarshal(result, user); err != nil { + return nil, err + } + + return user, nil +} + +// SearchUsers returns users matching a filter (search queries +// are not suppoted by the user endpoint). Pass nil as filter for all +// users available to the API Token. +func (a *API) SearchUsers(filterCriteria *SearchFilterType) (*[]User, error) { + q := url.Values{} + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchUsers() + } + + reqURL := url.URL{ + Path: config.UserPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var users []User + if err := json.Unmarshal(result, &users); err != nil { + return nil, err + } + + return &users, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go b/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go new file mode 100644 index 000000000..0dd5e9373 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/api/worksheet.go @@ -0,0 +1,232 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Worksheet API support - Fetch, Create, Update, Delete, and Search +// See: https://login.circonus.com/resources/api/calls/worksheet + +package api + +import ( + "encoding/json" + "fmt" + "net/url" + "regexp" + + "github.com/circonus-labs/circonus-gometrics/api/config" +) + +// WorksheetGraph defines a worksheet cid to be include in the worksheet +type WorksheetGraph struct { + GraphCID string `json:"graph"` // string +} + +// WorksheetSmartQuery defines a query to include multiple worksheets +type WorksheetSmartQuery struct { + Name string `json:"name"` + Order []string `json:"order"` + Query string `json:"query"` +} + +// Worksheet defines a worksheet. See https://login.circonus.com/resources/api/calls/worksheet for more information. +type Worksheet struct { + CID string `json:"_cid,omitempty"` // string + Description *string `json:"description"` // string or null + Favorite bool `json:"favorite"` // boolean + Graphs []WorksheetGraph `json:"worksheets,omitempty"` // [] len >= 0 + Notes *string `json:"notes"` // string or null + SmartQueries []WorksheetSmartQuery `json:"smart_queries,omitempty"` // [] len >= 0 + Tags []string `json:"tags"` // [] len >= 0 + Title string `json:"title"` // string +} + +// NewWorksheet returns a new Worksheet (with defaults, if applicable) +func NewWorksheet() *Worksheet { + return &Worksheet{} +} + +// FetchWorksheet retrieves worksheet with passed cid. +func (a *API) FetchWorksheet(cid CIDType) (*Worksheet, error) { + if cid == nil || *cid == "" { + return nil, fmt.Errorf("Invalid worksheet CID [none]") + } + + worksheetCID := string(*cid) + + matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID) + } + + result, err := a.Get(string(*cid)) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] fetch worksheet, received JSON: %s", string(result)) + } + + worksheet := new(Worksheet) + if err := json.Unmarshal(result, worksheet); err != nil { + return nil, err + } + + return worksheet, nil +} + +// FetchWorksheets retrieves all worksheets available to API Token. +func (a *API) FetchWorksheets() (*[]Worksheet, error) { + result, err := a.Get(config.WorksheetPrefix) + if err != nil { + return nil, err + } + + var worksheets []Worksheet + if err := json.Unmarshal(result, &worksheets); err != nil { + return nil, err + } + + return &worksheets, nil +} + +// UpdateWorksheet updates passed worksheet. +func (a *API) UpdateWorksheet(cfg *Worksheet) (*Worksheet, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid worksheet config [nil]") + } + + worksheetCID := string(cfg.CID) + + matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID) + if err != nil { + return nil, err + } + if !matched { + return nil, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID) + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] update worksheet, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Put(worksheetCID, jsonCfg) + if err != nil { + return nil, err + } + + worksheet := &Worksheet{} + if err := json.Unmarshal(result, worksheet); err != nil { + return nil, err + } + + return worksheet, nil +} + +// CreateWorksheet creates a new worksheet. +func (a *API) CreateWorksheet(cfg *Worksheet) (*Worksheet, error) { + if cfg == nil { + return nil, fmt.Errorf("Invalid worksheet config [nil]") + } + + jsonCfg, err := json.Marshal(cfg) + if err != nil { + return nil, err + } + + if a.Debug { + a.Log.Printf("[DEBUG] create annotation, sending JSON: %s", string(jsonCfg)) + } + + result, err := a.Post(config.WorksheetPrefix, jsonCfg) + if err != nil { + return nil, err + } + + worksheet := &Worksheet{} + if err := json.Unmarshal(result, worksheet); err != nil { + return nil, err + } + + return worksheet, nil +} + +// DeleteWorksheet deletes passed worksheet. +func (a *API) DeleteWorksheet(cfg *Worksheet) (bool, error) { + if cfg == nil { + return false, fmt.Errorf("Invalid worksheet config [nil]") + } + return a.DeleteWorksheetByCID(CIDType(&cfg.CID)) +} + +// DeleteWorksheetByCID deletes worksheet with passed cid. +func (a *API) DeleteWorksheetByCID(cid CIDType) (bool, error) { + if cid == nil || *cid == "" { + return false, fmt.Errorf("Invalid worksheet CID [none]") + } + + worksheetCID := string(*cid) + + matched, err := regexp.MatchString(config.WorksheetCIDRegex, worksheetCID) + if err != nil { + return false, err + } + if !matched { + return false, fmt.Errorf("Invalid worksheet CID [%s]", worksheetCID) + } + + _, err = a.Delete(worksheetCID) + if err != nil { + return false, err + } + + return true, nil +} + +// SearchWorksheets returns worksheets matching the specified search +// query and/or filter. If nil is passed for both parameters all +// worksheets will be returned. +func (a *API) SearchWorksheets(searchCriteria *SearchQueryType, filterCriteria *SearchFilterType) (*[]Worksheet, error) { + q := url.Values{} + + if searchCriteria != nil && *searchCriteria != "" { + q.Set("search", string(*searchCriteria)) + } + + if filterCriteria != nil && len(*filterCriteria) > 0 { + for filter, criteria := range *filterCriteria { + for _, val := range criteria { + q.Add(filter, val) + } + } + } + + if q.Encode() == "" { + return a.FetchWorksheets() + } + + reqURL := url.URL{ + Path: config.WorksheetPrefix, + RawQuery: q.Encode(), + } + + result, err := a.Get(reqURL.String()) + if err != nil { + return nil, fmt.Errorf("[ERROR] API call error %+v", err) + } + + var worksheets []Worksheet + if err := json.Unmarshal(result, &worksheets); err != nil { + return nil, err + } + + return &worksheets, nil +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go index b23486ce2..a8413fb53 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/broker.go @@ -10,6 +10,7 @@ import ( "net" "net/url" "reflect" + "strconv" "strings" "time" @@ -23,7 +24,8 @@ func init() { // Get Broker to use when creating a check func (cm *CheckManager) getBroker() (*api.Broker, error) { if cm.brokerID != 0 { - broker, err := cm.apih.FetchBrokerByID(cm.brokerID) + cid := fmt.Sprintf("/broker/%d", cm.brokerID) + broker, err := cm.apih.FetchBroker(api.CIDType(&cid)) if err != nil { return nil, err } @@ -59,7 +61,7 @@ func (cm *CheckManager) getBrokerCN(broker *api.Broker, submissionURL api.URLTyp cn := "" for _, detail := range broker.Details { - if detail.IP == host { + if *detail.IP == host { cn = detail.CN break } @@ -76,31 +78,34 @@ func (cm *CheckManager) getBrokerCN(broker *api.Broker, submissionURL api.URLTyp // Select a broker for use when creating a check, if a specific broker // was not specified. func (cm *CheckManager) selectBroker() (*api.Broker, error) { - var brokerList []api.Broker + var brokerList *[]api.Broker var err error - if cm.brokerSelectTag != "" { - brokerList, err = cm.apih.FetchBrokerListByTag(cm.brokerSelectTag) + if len(cm.brokerSelectTag) > 0 { + filter := api.SearchFilterType{ + "f__tags_has": cm.brokerSelectTag, + } + brokerList, err = cm.apih.SearchBrokers(nil, &filter) if err != nil { return nil, err } } else { - brokerList, err = cm.apih.FetchBrokerList() + brokerList, err = cm.apih.FetchBrokers() if err != nil { return nil, err } } - if len(brokerList) == 0 { + if len(*brokerList) == 0 { return nil, fmt.Errorf("zero brokers found") } validBrokers := make(map[string]api.Broker) haveEnterprise := false - for _, broker := range brokerList { + for _, broker := range *brokerList { if cm.isValidBroker(&broker) { - validBrokers[broker.Cid] = broker + validBrokers[broker.CID] = broker if broker.Type == "enterprise" { haveEnterprise = true } @@ -116,7 +121,7 @@ func (cm *CheckManager) selectBroker() (*api.Broker, error) { } if len(validBrokers) == 0 { - return nil, fmt.Errorf("found %d broker(s), zero are valid", len(brokerList)) + return nil, fmt.Errorf("found %d broker(s), zero are valid", len(*brokerList)) } validBrokerKeys := reflect.ValueOf(validBrokers).MapKeys() @@ -133,8 +138,20 @@ func (cm *CheckManager) selectBroker() (*api.Broker, error) { // Verify broker supports the check type to be used func (cm *CheckManager) brokerSupportsCheckType(checkType CheckTypeType, details *api.BrokerDetail) bool { + baseType := string(checkType) + + for _, module := range details.Modules { + if module == baseType { + return true + } + } + + if idx := strings.Index(baseType, ":"); idx > 0 { + baseType = baseType[0:idx] + } + for _, module := range details.Modules { - if CheckTypeType(module) == checkType { + if module == baseType { return true } } @@ -145,10 +162,10 @@ func (cm *CheckManager) brokerSupportsCheckType(checkType CheckTypeType, details // Is the broker valid (active, supports check type, and reachable) func (cm *CheckManager) isValidBroker(broker *api.Broker) bool { - brokerPort := 0 + var brokerHost string + var brokerPort string valid := false for _, detail := range broker.Details { - brokerPort = 43191 // broker must be active if detail.Status != statusActive { @@ -166,34 +183,46 @@ func (cm *CheckManager) isValidBroker(broker *api.Broker) bool { continue } - // broker must be reachable and respond within designated time - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", detail.IP, brokerPort), cm.brokerMaxResponseTime) - if err != nil { - if detail.CN != "trap.noit.circonus.net" { - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' unable to connect, %v\n", broker.Name, err) - } - continue // not able to reach the broker (or respone slow enough for it to be considered not usable) - } - // if circonus trap broker, try port 443 - brokerPort = 443 - conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", detail.CN, brokerPort), cm.brokerMaxResponseTime) - if err != nil { - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' unable to connect %v\n", broker.Name, err) - } - continue // not able to reach the broker on 443 either (or respone slow enough for it to be considered not usable) + if detail.ExternalPort != 0 { + brokerPort = strconv.Itoa(int(detail.ExternalPort)) + } else { + if *detail.Port != 0 { + brokerPort = strconv.Itoa(int(*detail.Port)) + } else { + brokerPort = "43191" } } - conn.Close() - if cm.Debug { - cm.Log.Printf("[DEBUG] Broker '%s' is valid\n", broker.Name) + if detail.ExternalHost != nil && *detail.ExternalHost != "" { + brokerHost = *detail.ExternalHost + } else { + brokerHost = *detail.IP + } + + if brokerHost == "trap.noit.circonus.net" && brokerPort != "443" { + brokerPort = "443" } - valid = true - break + retries := 5 + for attempt := 1; attempt <= retries; attempt++ { + // broker must be reachable and respond within designated time + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", brokerHost, brokerPort), cm.brokerMaxResponseTime) + if err == nil { + conn.Close() + valid = true + break + } + + cm.Log.Printf("[WARN] Broker '%s' unable to connect, %v. Retrying in 2 seconds, attempt %d of %d.", broker.Name, err, attempt, retries) + time.Sleep(2 * time.Second) + } + if valid { + if cm.Debug { + cm.Log.Printf("[DEBUG] Broker '%s' is valid\n", broker.Name) + } + break + } } return valid } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go index 3f173bf5c..cbe3ba706 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/cert.go @@ -7,6 +7,7 @@ package checkmgr import ( "crypto/x509" "encoding/json" + "errors" "fmt" ) @@ -41,17 +42,22 @@ type CACert struct { } // loadCACert loads the CA cert for the broker designated by the submission url -func (cm *CheckManager) loadCACert() { +func (cm *CheckManager) loadCACert() error { if cm.certPool != nil { - return + return nil } cm.certPool = x509.NewCertPool() - cert, err := cm.fetchCert() - if err != nil { - if cm.Debug { - cm.Log.Printf("[DEBUG] Unable to fetch ca.crt, using default. %+v\n", err) + var cert []byte + var err error + + if cm.enabled { + // only attempt to retrieve broker CA cert if + // the check is being managed. + cert, err = cm.fetchCert() + if err != nil { + return err } } @@ -60,12 +66,14 @@ func (cm *CheckManager) loadCACert() { } cm.certPool.AppendCertsFromPEM(cert) + + return nil } // fetchCert fetches CA certificate using Circonus API func (cm *CheckManager) fetchCert() ([]byte, error) { if !cm.enabled { - return circonusCA, nil + return nil, errors.New("check manager is not enabled") } response, err := cm.apih.Get("/pki/ca.crt") @@ -74,8 +82,7 @@ func (cm *CheckManager) fetchCert() ([]byte, error) { } cadata := new(CACert) - err = json.Unmarshal(response, cadata) - if err != nil { + if err := json.Unmarshal(response, cadata); err != nil { return nil, err } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go index 56f6fed28..f9e9987de 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/check.go @@ -10,13 +10,80 @@ import ( "encoding/hex" "errors" "fmt" + "net/url" "strconv" "strings" "time" "github.com/circonus-labs/circonus-gometrics/api" + "github.com/circonus-labs/circonus-gometrics/api/config" ) +// UpdateCheck determines if the check needs to be updated (new metrics, tags, etc.) +func (cm *CheckManager) UpdateCheck(newMetrics map[string]*api.CheckBundleMetric) { + // only if check manager is enabled + if !cm.enabled { + return + } + + // only if checkBundle has been populated + if cm.checkBundle == nil { + return + } + + // only if there is *something* to update + if !cm.forceCheckUpdate && len(newMetrics) == 0 && len(cm.metricTags) == 0 { + return + } + + // refresh check bundle (in case there were changes made by other apps or in UI) + cid := cm.checkBundle.CID + checkBundle, err := cm.apih.FetchCheckBundle(api.CIDType(&cid)) + if err != nil { + cm.Log.Printf("[ERROR] unable to fetch up-to-date check bundle %v", err) + return + } + cm.cbmu.Lock() + cm.checkBundle = checkBundle + cm.cbmu.Unlock() + + // check metric_limit and see if it’s 0, if so, don't even bother to try to update the check. + + cm.addNewMetrics(newMetrics) + + if len(cm.metricTags) > 0 { + // note: if a tag has been added (queued) for a metric which never gets sent + // the tags will be discarded. (setting tags does not *create* metrics.) + for metricName, metricTags := range cm.metricTags { + for metricIdx, metric := range cm.checkBundle.Metrics { + if metric.Name == metricName { + cm.checkBundle.Metrics[metricIdx].Tags = metricTags + break + } + } + cm.mtmu.Lock() + delete(cm.metricTags, metricName) + cm.mtmu.Unlock() + } + cm.forceCheckUpdate = true + } + + if cm.forceCheckUpdate { + newCheckBundle, err := cm.apih.UpdateCheckBundle(cm.checkBundle) + if err != nil { + cm.Log.Printf("[ERROR] updating check bundle %v", err) + return + } + + cm.forceCheckUpdate = false + cm.cbmu.Lock() + cm.checkBundle = newCheckBundle + cm.cbmu.Unlock() + cm.inventoryMetrics() + } + +} + // Initialize CirconusMetrics instance. Attempt to find a check otherwise create one. // use cases: // @@ -32,6 +99,8 @@ func (cm *CheckManager) initializeTrapURL() error { cm.trapmu.Lock() defer cm.trapmu.Unlock() + // special case short-circuit: just send to a url, no check management + // up to user to ensure that if url is https that it will work (e.g. not self-signed) if cm.checkSubmissionURL != "" { if !cm.enabled { cm.trapURL = cm.checkSubmissionURL @@ -41,7 +110,7 @@ func (cm *CheckManager) initializeTrapURL() error { } if !cm.enabled { - return errors.New("Unable to initialize trap, check manager is disabled.") + return errors.New("unable to initialize trap, check manager is disabled") } var err error @@ -50,10 +119,13 @@ func (cm *CheckManager) initializeTrapURL() error { var broker *api.Broker if cm.checkSubmissionURL != "" { - check, err = cm.apih.FetchCheckBySubmissionURL(cm.checkSubmissionURL) + check, err = cm.fetchCheckBySubmissionURL(cm.checkSubmissionURL) if err != nil { return err } + if !check.Active { + return fmt.Errorf("[ERROR] Check ID %v is not active", check.CID) + } // extract check id from check object returned from looking up using submission url // set m.CheckId to the id // set m.SubmissionUrl to "" to prevent trying to search on it going forward @@ -61,27 +133,44 @@ func (cm *CheckManager) initializeTrapURL() error { // unless the new submission url can be fetched with the API (which is no // longer possible using the original submission url) var id int - id, err = strconv.Atoi(strings.Replace(check.Cid, "/check/", "", -1)) + id, err = strconv.Atoi(strings.Replace(check.CID, "/check/", "", -1)) if err == nil { cm.checkID = api.IDType(id) cm.checkSubmissionURL = "" } else { cm.Log.Printf( "[WARN] SubmissionUrl check to Check ID: unable to convert %s to int %q\n", - check.Cid, err) + check.CID, err) } } else if cm.checkID > 0 { - check, err = cm.apih.FetchCheckByID(cm.checkID) + cid := fmt.Sprintf("/check/%d", cm.checkID) + check, err = cm.apih.FetchCheck(api.CIDType(&cid)) if err != nil { return err } + if !check.Active { + return fmt.Errorf("[ERROR] Check ID %v is not active", check.CID) + } } else { - searchCriteria := fmt.Sprintf( - "(active:1)(host:\"%s\")(type:\"%s\")(tags:%s)", - cm.checkInstanceID, cm.checkType, cm.checkSearchTag) - checkBundle, err = cm.checkBundleSearch(searchCriteria) - if err != nil { - return err + if checkBundle == nil { + // old search (instanceid as check.target) + searchCriteria := fmt.Sprintf( + "(active:1)(type:\"%s\")(host:\"%s\")(tags:%s)", cm.checkType, cm.checkTarget, strings.Join(cm.checkSearchTag, ",")) + checkBundle, err = cm.checkBundleSearch(searchCriteria, map[string][]string{}) + if err != nil { + return err + } + } + + if checkBundle == nil { + // new search (check.target != instanceid, instanceid encoded in notes field) + searchCriteria := fmt.Sprintf( + "(active:1)(type:\"%s\")(tags:%s)", cm.checkType, strings.Join(cm.checkSearchTag, ",")) + filterCriteria := map[string][]string{"f_notes": []string{*cm.getNotes()}} + checkBundle, err = cm.checkBundleSearch(searchCriteria, filterCriteria) + if err != nil { + return err + } } if checkBundle == nil { @@ -96,7 +185,8 @@ func (cm *CheckManager) initializeTrapURL() error { if checkBundle == nil { if check != nil { - checkBundle, err = cm.apih.FetchCheckBundleByCID(api.CIDType(check.CheckBundleCid)) + cid := check.CheckBundleCID + checkBundle, err = cm.apih.FetchCheckBundle(api.CIDType(&cid)) if err != nil { return err } @@ -106,7 +196,8 @@ func (cm *CheckManager) initializeTrapURL() error { } if broker == nil { - broker, err = cm.apih.FetchBrokerByCID(api.CIDType(checkBundle.Brokers[0])) + cid := checkBundle.Brokers[0] + broker, err = cm.apih.FetchBroker(api.CIDType(&cid)) if err != nil { return err } @@ -116,8 +207,33 @@ func (cm *CheckManager) initializeTrapURL() error { cm.checkBundle = checkBundle cm.inventoryMetrics() - // url to which metrics should be PUT - cm.trapURL = api.URLType(checkBundle.Config.SubmissionURL) + // determine the trap url to which metrics should be PUT + if checkBundle.Type == "httptrap" { + if turl, found := checkBundle.Config[config.SubmissionURL]; found { + cm.trapURL = api.URLType(turl) + } else { + if cm.Debug { + cm.Log.Printf("Missing config.%s %+v", config.SubmissionURL, checkBundle) + } + return fmt.Errorf("[ERROR] Unable to use check, no %s in config", config.SubmissionURL) + } + } else { + // build a submission_url for non-httptrap checks out of mtev_reverse url + if len(checkBundle.ReverseConnectURLs) == 0 { + return fmt.Errorf("%s is not an HTTPTRAP check and no reverse connection urls found", checkBundle.Checks[0]) + } + mtevURL := checkBundle.ReverseConnectURLs[0] + mtevURL = strings.Replace(mtevURL, "mtev_reverse", "https", 1) + mtevURL = strings.Replace(mtevURL, "check", "module/httptrap", 1) + if rs, found := checkBundle.Config[config.ReverseSecretKey]; found { + cm.trapURL = api.URLType(fmt.Sprintf("%s/%s", mtevURL, rs)) + } else { + if cm.Debug { + cm.Log.Printf("Missing config.%s %+v", config.ReverseSecretKey, checkBundle) + } + return fmt.Errorf("[ERROR] Unable to use check, no %s in config", config.ReverseSecretKey) + } + } // used when sending as "ServerName" get around certs not having IP SANS // (cert created with server name as CN but IP used in trap url) @@ -127,26 +243,39 @@ func (cm *CheckManager) initializeTrapURL() error { } cm.trapCN = BrokerCNType(cn) + if cm.enabled { + u, err := url.Parse(string(cm.trapURL)) + if err != nil { + return err + } + if u.Scheme == "https" { + if err := cm.loadCACert(); err != nil { + return err + } + } + } + cm.trapLastUpdate = time.Now() return nil } // Search for a check bundle given a predetermined set of criteria -func (cm *CheckManager) checkBundleSearch(criteria string) (*api.CheckBundle, error) { - checkBundles, err := cm.apih.CheckBundleSearch(api.SearchQueryType(criteria)) +func (cm *CheckManager) checkBundleSearch(criteria string, filter map[string][]string) (*api.CheckBundle, error) { + search := api.SearchQueryType(criteria) + checkBundles, err := cm.apih.SearchCheckBundles(&search, &filter) if err != nil { return nil, err } - if len(checkBundles) == 0 { + if len(*checkBundles) == 0 { return nil, nil // trigger creation of a new check } numActive := 0 checkID := -1 - for idx, check := range checkBundles { + for idx, check := range *checkBundles { if check.Status == statusActive { numActive++ checkID = idx @@ -154,10 +283,12 @@ func (cm *CheckManager) checkBundleSearch(criteria string) (*api.CheckBundle, er } if numActive > 1 { - return nil, fmt.Errorf("[ERROR] Multiple possibilities multiple check bundles match criteria %s\n", criteria) + return nil, fmt.Errorf("[ERROR] multiple check bundles match criteria %s", criteria) } - return &checkBundles[checkID], nil + bundle := (*checkBundles)[checkID] + + return &bundle, nil } // Create a new check to receive metrics @@ -176,22 +307,39 @@ func (cm *CheckManager) createNewCheck() (*api.CheckBundle, *api.Broker, error) return nil, nil, err } - config := api.CheckBundle{ - Brokers: []string{broker.Cid}, - Config: api.CheckBundleConfig{AsyncMetrics: true, Secret: checkSecret}, + chkcfg := &api.CheckBundle{ + Brokers: []string{broker.CID}, + Config: make(map[config.Key]string), DisplayName: string(cm.checkDisplayName), Metrics: []api.CheckBundleMetric{}, - MetricLimit: 0, - Notes: "", + MetricLimit: config.DefaultCheckBundleMetricLimit, + Notes: cm.getNotes(), Period: 60, Status: statusActive, - Tags: append([]string{string(cm.checkSearchTag)}, cm.checkTags...), - Target: string(cm.checkInstanceID), + Tags: append(cm.checkSearchTag, cm.checkTags...), + Target: string(cm.checkTarget), Timeout: 10, Type: string(cm.checkType), } - checkBundle, err := cm.apih.CreateCheckBundle(config) + if len(cm.customConfigFields) > 0 { + for fld, val := range cm.customConfigFields { + chkcfg.Config[config.Key(fld)] = val + } + } + + // + // use the default config settings if these are NOT set by user configuration + // + if val, ok := chkcfg.Config[config.AsyncMetrics]; !ok || val == "" { + chkcfg.Config[config.AsyncMetrics] = "true" + } + + if val, ok := chkcfg.Config[config.Secret]; !ok || val == "" { + chkcfg.Config[config.Secret] = checkSecret + } + + checkBundle, err := cm.apih.CreateCheckBundle(chkcfg) if err != nil { return nil, nil, err } @@ -209,3 +357,64 @@ func (cm *CheckManager) makeSecret() (string, error) { hash.Write(x) return hex.EncodeToString(hash.Sum(nil))[0:16], nil } + +func (cm *CheckManager) getNotes() *string { + notes := fmt.Sprintf("cgm_instanceid|%s", cm.checkInstanceID) + return ¬es +} + +// FetchCheckBySubmissionURL fetch a check configuration by submission_url +func (cm *CheckManager) fetchCheckBySubmissionURL(submissionURL api.URLType) (*api.Check, error) { + if string(submissionURL) == "" { + return nil, errors.New("[ERROR] Invalid submission URL (blank)") + } + + u, err := url.Parse(string(submissionURL)) + if err != nil { + return nil, err + } + + // valid trap url: scheme://host[:port]/module/httptrap/UUID/secret + + // does it smell like a valid trap url path + if !strings.Contains(u.Path, "/module/httptrap/") { + return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', unrecognized path", submissionURL) + } + + // extract uuid + pathParts := strings.Split(strings.Replace(u.Path, "/module/httptrap/", "", 1), "/") + if len(pathParts) != 2 { + return nil, fmt.Errorf("[ERROR] Invalid submission URL '%s', UUID not where expected", submissionURL) + } + uuid := pathParts[0] + + filter := api.SearchFilterType{"f__check_uuid": []string{uuid}} + + checks, err := cm.apih.SearchChecks(nil, &filter) + if err != nil { + return nil, err + } + + if len(*checks) == 0 { + return nil, fmt.Errorf("[ERROR] No checks found with UUID %s", uuid) + } + + numActive := 0 + checkID := -1 + + for idx, check := range *checks { + if check.Active { + numActive++ + checkID = idx + } + } + + if numActive > 1 { + return nil, fmt.Errorf("[ERROR] Multiple checks with same UUID %s", uuid) + } + + check := (*checks)[checkID] + + return &check, nil + +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go index e23f00a57..f2ef7d6df 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/checkmgr.go @@ -16,6 +16,7 @@ import ( "os" "path" "strconv" + "strings" "sync" "time" @@ -58,17 +59,20 @@ type CheckConfig struct { // used to search for a check to use // used as check.target when creating a check InstanceID string - // unique check searching tag + // explicitly set check.target (default: instance id) + TargetHost string + // a custom display name for the check (as viewed in UI Checks) + // default: instance id + DisplayName string + // unique check searching tag (or tags) // used to search for a check to use (combined with instanceid) // used as a regular tag when creating a check SearchTag string - // a custom display name for the check (as viewed in UI Checks) - DisplayName string // httptrap check secret (for creating a check) Secret string // additional tags to add to a check (when creating a check) // these tags will not be added to an existing check - Tags []string + Tags string // max amount of time to to hold on to a submission url // when a given submission fails (due to retries) if the // time the url was last updated is > than this, the trap @@ -81,14 +85,18 @@ type CheckConfig struct { // overrides the behavior and will re-activate the metric when it is // encountered. "(true|false)", default "false" ForceMetricActivation string + // Type of check to use (default: httptrap) + Type string + // Custom check config fields (default: none) + CustomConfigFields map[string]string } // BrokerConfig options for broker type BrokerConfig struct { // a specific broker id (numeric portion of cid) ID string - // a tag that can be used to select 1-n brokers from which to select - // when creating a new check (e.g. datacenter:abc) + // one or more tags used to select 1-n brokers from which to select + // when creating a new check (e.g. datacenter:abc or loc:dfw,dc:abc) SelectTag string // for a broker to be considered viable it must respond to a // connection attempt within this amount of time e.g. 200ms, 2s, 1m @@ -114,11 +122,14 @@ type CheckTypeType string // CheckInstanceIDType check instance id type CheckInstanceIDType string +// CheckTargetType check target/host +type CheckTargetType string + // CheckSecretType check secret type CheckSecretType string // CheckTagsType check tags -type CheckTagsType []string +type CheckTagsType string // CheckDisplayNameType check display name type CheckDisplayNameType string @@ -133,31 +144,43 @@ type CheckManager struct { Debug bool apih *api.API + initialized bool + initializedmu sync.RWMutex + // check checkType CheckTypeType checkID api.IDType checkInstanceID CheckInstanceIDType - checkSearchTag api.SearchTagType + checkTarget CheckTargetType + checkSearchTag api.TagType checkSecret CheckSecretType - checkTags CheckTagsType + checkTags api.TagType + customConfigFields map[string]string checkSubmissionURL api.URLType checkDisplayName CheckDisplayNameType forceMetricActivation bool + forceCheckUpdate bool + + // metric tags + metricTags map[string][]string + mtmu sync.Mutex // broker brokerID api.IDType - brokerSelectTag api.SearchTagType + brokerSelectTag api.TagType brokerMaxResponseTime time.Duration // state - checkBundle *api.CheckBundle - availableMetrics map[string]bool - trapURL api.URLType - trapCN BrokerCNType - trapLastUpdate time.Time - trapMaxURLAge time.Duration - trapmu sync.Mutex - certPool *x509.CertPool + checkBundle *api.CheckBundle + cbmu sync.Mutex + availableMetrics map[string]bool + availableMetricsmu sync.Mutex + trapURL api.URLType + trapCN BrokerCNType + trapLastUpdate time.Time + trapMaxURLAge time.Duration + trapmu sync.Mutex + certPool *x509.CertPool } // Trap config @@ -168,58 +191,58 @@ type Trap struct { // NewCheckManager returns a new check manager func NewCheckManager(cfg *Config) (*CheckManager, error) { + return New(cfg) +} + +// New returns a new check manager +func New(cfg *Config) (*CheckManager, error) { if cfg == nil { - return nil, errors.New("Invalid Check Manager configuration (nil).") + return nil, errors.New("invalid Check Manager configuration (nil)") } - cm := &CheckManager{ - enabled: false, - } + cm := &CheckManager{enabled: true, initialized: false} + // Setup logging for check manager cm.Debug = cfg.Debug - cm.Log = cfg.Log + if cm.Debug && cm.Log == nil { + cm.Log = log.New(os.Stderr, "", log.LstdFlags) + } if cm.Log == nil { - if cm.Debug { - cm.Log = log.New(os.Stderr, "", log.LstdFlags) - } else { - cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) - } + cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) } if cfg.Check.SubmissionURL != "" { cm.checkSubmissionURL = api.URLType(cfg.Check.SubmissionURL) } + // Blank API Token *disables* check management if cfg.API.TokenKey == "" { - if cm.checkSubmissionURL == "" { - return nil, errors.New("Invalid check manager configuration (no API token AND no submission url).") - } - if err := cm.initializeTrapURL(); err != nil { - return nil, err - } - return cm, nil + cm.enabled = false } - // enable check manager - - cm.enabled = true - - // initialize api handle - - cfg.API.Debug = cm.Debug - cfg.API.Log = cm.Log + if !cm.enabled && cm.checkSubmissionURL == "" { + return nil, errors.New("invalid check manager configuration (no API token AND no submission url)") + } - apih, err := api.NewAPI(&cfg.API) - if err != nil { - return nil, err + if cm.enabled { + // initialize api handle + cfg.API.Debug = cm.Debug + cfg.API.Log = cm.Log + apih, err := api.New(&cfg.API) + if err != nil { + return nil, err + } + cm.apih = apih } - cm.apih = apih // initialize check related data - - cm.checkType = defaultCheckType + if cfg.Check.Type != "" { + cm.checkType = CheckTypeType(cfg.Check.Type) + } else { + cm.checkType = defaultCheckType + } idSetting := "0" if cfg.Check.ID != "" { @@ -232,10 +255,9 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { cm.checkID = api.IDType(id) cm.checkInstanceID = CheckInstanceIDType(cfg.Check.InstanceID) + cm.checkTarget = CheckTargetType(cfg.Check.TargetHost) cm.checkDisplayName = CheckDisplayNameType(cfg.Check.DisplayName) - cm.checkSearchTag = api.SearchTagType(cfg.Check.SearchTag) cm.checkSecret = CheckSecretType(cfg.Check.Secret) - cm.checkTags = cfg.Check.Tags fma := defaultForceMetricActivation if cfg.Check.ForceMetricActivation != "" { @@ -255,13 +277,28 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { if cm.checkInstanceID == "" { cm.checkInstanceID = CheckInstanceIDType(fmt.Sprintf("%s:%s", hn, an)) } + if cm.checkDisplayName == "" { + cm.checkDisplayName = CheckDisplayNameType(cm.checkInstanceID) + } + if cm.checkTarget == "" { + cm.checkTarget = CheckTargetType(cm.checkInstanceID) + } - if cm.checkSearchTag == "" { - cm.checkSearchTag = api.SearchTagType(fmt.Sprintf("service:%s", an)) + if cfg.Check.SearchTag == "" { + cm.checkSearchTag = []string{fmt.Sprintf("service:%s", an)} + } else { + cm.checkSearchTag = strings.Split(strings.Replace(cfg.Check.SearchTag, " ", "", -1), ",") } - if cm.checkDisplayName == "" { - cm.checkDisplayName = CheckDisplayNameType(fmt.Sprintf("%s /cgm", string(cm.checkInstanceID))) + if cfg.Check.Tags != "" { + cm.checkTags = strings.Split(strings.Replace(cfg.Check.Tags, " ", "", -1), ",") + } + + cm.customConfigFields = make(map[string]string) + if len(cfg.Check.CustomConfigFields) > 0 { + for fld, val := range cfg.Check.CustomConfigFields { + cm.customConfigFields[fld] = val + } } dur := cfg.Check.MaxURLAge @@ -275,7 +312,6 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { cm.trapMaxURLAge = maxDur // setup broker - idSetting = "0" if cfg.Broker.ID != "" { idSetting = cfg.Broker.ID @@ -286,7 +322,9 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { } cm.brokerID = api.IDType(id) - cm.brokerSelectTag = api.SearchTagType(cfg.Broker.SelectTag) + if cfg.Broker.SelectTag != "" { + cm.brokerSelectTag = strings.Split(strings.Replace(cfg.Broker.SelectTag, " ", "", -1), ",") + } dur = cfg.Broker.MaxResponseTime if dur == "" { @@ -300,20 +338,56 @@ func NewCheckManager(cfg *Config) (*CheckManager, error) { // metrics cm.availableMetrics = make(map[string]bool) + cm.metricTags = make(map[string][]string) - if err := cm.initializeTrapURL(); err != nil { - return nil, err + return cm, nil +} + +// Initialize for sending metrics +func (cm *CheckManager) Initialize() { + + // if not managing the check, quicker initialization + if !cm.enabled { + err := cm.initializeTrapURL() + if err == nil { + cm.initializedmu.Lock() + cm.initialized = true + cm.initializedmu.Unlock() + } else { + cm.Log.Printf("[WARN] error initializing trap %s", err.Error()) + } + return } - return cm, nil + // background initialization when we have to reach out to the api + go func() { + cm.apih.EnableExponentialBackoff() + err := cm.initializeTrapURL() + if err == nil { + cm.initializedmu.Lock() + cm.initialized = true + cm.initializedmu.Unlock() + } else { + cm.Log.Printf("[WARN] error initializing trap %s", err.Error()) + } + cm.apih.DisableExponentialBackoff() + }() +} + +// IsReady reflects if the check has been initialied and metrics can be sent to Circonus +func (cm *CheckManager) IsReady() bool { + cm.initializedmu.RLock() + defer cm.initializedmu.RUnlock() + return cm.initialized } -// GetTrap return the trap url -func (cm *CheckManager) GetTrap() (*Trap, error) { +// GetSubmissionURL returns submission url for circonus +func (cm *CheckManager) GetSubmissionURL() (*Trap, error) { if cm.trapURL == "" { - if err := cm.initializeTrapURL(); err != nil { - return nil, err - } + return nil, fmt.Errorf("[ERROR] no submission url currently available") + // if err := cm.initializeTrapURL(); err != nil { + // return nil, err + // } } trap := &Trap{} @@ -327,7 +401,9 @@ func (cm *CheckManager) GetTrap() (*Trap, error) { if u.Scheme == "https" { if cm.certPool == nil { - cm.loadCACert() + if err := cm.loadCACert(); err != nil { + return nil, err + } } t := &tls.Config{ RootCAs: cm.certPool, diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go index 8f03d4476..eb8603a84 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/checkmgr/metrics.go @@ -10,12 +10,18 @@ import ( // IsMetricActive checks whether a given metric name is currently active(enabled) func (cm *CheckManager) IsMetricActive(name string) bool { + cm.availableMetricsmu.Lock() + defer cm.availableMetricsmu.Unlock() + active, _ := cm.availableMetrics[name] return active } // ActivateMetric determines if a given metric should be activated func (cm *CheckManager) ActivateMetric(name string) bool { + cm.availableMetricsmu.Lock() + defer cm.availableMetricsmu.Unlock() + active, exists := cm.availableMetrics[name] if !exists { @@ -29,44 +35,101 @@ func (cm *CheckManager) ActivateMetric(name string) bool { return false } -// AddNewMetrics updates a check bundle with new metrics -func (cm *CheckManager) AddNewMetrics(newMetrics map[string]*api.CheckBundleMetric) { - // only if check manager is enabled - if !cm.enabled { - return +// AddMetricTags updates check bundle metrics with tags +func (cm *CheckManager) AddMetricTags(metricName string, tags []string, appendTags bool) bool { + tagsUpdated := false + + if appendTags && len(tags) == 0 { + return tagsUpdated + } + + currentTags, exists := cm.metricTags[metricName] + if !exists { + foundMetric := false + + if cm.checkBundle != nil { + for _, metric := range cm.checkBundle.Metrics { + if metric.Name == metricName { + foundMetric = true + currentTags = metric.Tags + break + } + } + } + + if !foundMetric { + currentTags = []string{} + } + } + + action := "" + if appendTags { + numNewTags := countNewTags(currentTags, tags) + if numNewTags > 0 { + action = "Added" + currentTags = append(currentTags, tags...) + tagsUpdated = true + } + } else { + if len(tags) != len(currentTags) { + action = "Set" + currentTags = tags + tagsUpdated = true + } else { + numNewTags := countNewTags(currentTags, tags) + if numNewTags > 0 { + action = "Set" + currentTags = tags + tagsUpdated = true + } + } + } + + if tagsUpdated { + cm.metricTags[metricName] = currentTags + } + + if cm.Debug && action != "" { + cm.Log.Printf("[DEBUG] %s metric tag(s) %s %v\n", action, metricName, tags) } - // only if checkBundle has been populated - if cm.checkBundle == nil { - return + return tagsUpdated +} + +// addNewMetrics updates a check bundle with new metrics +func (cm *CheckManager) addNewMetrics(newMetrics map[string]*api.CheckBundleMetric) bool { + updatedCheckBundle := false + + if cm.checkBundle == nil || len(newMetrics) == 0 { + return updatedCheckBundle } - newCheckBundle := cm.checkBundle - numCurrMetrics := len(newCheckBundle.Metrics) + cm.cbmu.Lock() + defer cm.cbmu.Unlock() + + numCurrMetrics := len(cm.checkBundle.Metrics) numNewMetrics := len(newMetrics) - if numCurrMetrics+numNewMetrics >= cap(newCheckBundle.Metrics) { + if numCurrMetrics+numNewMetrics >= cap(cm.checkBundle.Metrics) { nm := make([]api.CheckBundleMetric, numCurrMetrics+numNewMetrics) - copy(nm, newCheckBundle.Metrics) - newCheckBundle.Metrics = nm + copy(nm, cm.checkBundle.Metrics) + cm.checkBundle.Metrics = nm } - newCheckBundle.Metrics = newCheckBundle.Metrics[0 : numCurrMetrics+numNewMetrics] + cm.checkBundle.Metrics = cm.checkBundle.Metrics[0 : numCurrMetrics+numNewMetrics] i := 0 for _, metric := range newMetrics { - newCheckBundle.Metrics[numCurrMetrics+i] = *metric + cm.checkBundle.Metrics[numCurrMetrics+i] = *metric i++ + updatedCheckBundle = true } - checkBundle, err := cm.apih.UpdateCheckBundle(newCheckBundle) - if err != nil { - cm.Log.Printf("[ERROR] updating check bundle with new metrics %v", err) - return + if updatedCheckBundle { + cm.forceCheckUpdate = true } - cm.checkBundle = checkBundle - cm.inventoryMetrics() + return updatedCheckBundle } // inventoryMetrics creates list of active metrics in check bundle @@ -75,5 +138,35 @@ func (cm *CheckManager) inventoryMetrics() { for _, metric := range cm.checkBundle.Metrics { availableMetrics[metric.Name] = metric.Status == "active" } + cm.availableMetricsmu.Lock() cm.availableMetrics = availableMetrics + cm.availableMetricsmu.Unlock() +} + +// countNewTags returns a count of new tags which do not exist in the current list of tags +func countNewTags(currTags []string, newTags []string) int { + if len(newTags) == 0 { + return 0 + } + + if len(currTags) == 0 { + return len(newTags) + } + + newTagCount := 0 + + for _, newTag := range newTags { + found := false + for _, currTag := range currTags { + if newTag == currTag { + found = true + break + } + } + if !found { + newTagCount++ + } + } + + return newTagCount } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go index 34166c007..ba73ae625 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/circonus-gometrics.go @@ -34,6 +34,7 @@ import ( "io/ioutil" "log" "os" + "strconv" "sync" "time" @@ -47,24 +48,34 @@ const ( // Config options for circonus-gometrics type Config struct { - Log *log.Logger - Debug bool + Log *log.Logger + Debug bool + ResetCounters string // reset/delete counters on flush (default true) + ResetGauges string // reset/delete gauges on flush (default true) + ResetHistograms string // reset/delete histograms on flush (default true) + ResetText string // reset/delete text on flush (default true) // API, Check and Broker configuration options CheckManager checkmgr.Config - // how frequenly to submit metrics to Circonus, default 10 seconds + // how frequenly to submit metrics to Circonus, default 10 seconds. + // Set to 0 to disable automatic flushes and call Flush manually. Interval string } // CirconusMetrics state type CirconusMetrics struct { - Log *log.Logger - Debug bool - flushInterval time.Duration - flushing bool - flushmu sync.Mutex - check *checkmgr.CheckManager + Log *log.Logger + Debug bool + + resetCounters bool + resetGauges bool + resetHistograms bool + resetText bool + flushInterval time.Duration + flushing bool + flushmu sync.Mutex + check *checkmgr.CheckManager counters map[string]uint64 cm sync.Mutex @@ -90,9 +101,14 @@ type CirconusMetrics struct { // NewCirconusMetrics returns a CirconusMetrics instance func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) { + return New(cfg) +} + +// New returns a CirconusMetrics instance +func New(cfg *Config) (*CirconusMetrics, error) { if cfg == nil { - return nil, errors.New("Invalid configuration (nil).") + return nil, errors.New("invalid configuration (nil)") } cm := &CirconusMetrics{ @@ -105,55 +121,107 @@ func NewCirconusMetrics(cfg *Config) (*CirconusMetrics, error) { textFuncs: make(map[string]func() string), } - cm.Debug = cfg.Debug - if cm.Debug { - if cfg.Log == nil { + // Logging + { + cm.Debug = cfg.Debug + cm.Log = cfg.Log + + if cm.Debug && cm.Log == nil { cm.Log = log.New(os.Stderr, "", log.LstdFlags) - } else { - cm.Log = cfg.Log + } + if cm.Log == nil { + cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) } } - if cm.Log == nil { - cm.Log = log.New(ioutil.Discard, "", log.LstdFlags) + + // Flush Interval + { + fi := defaultFlushInterval + if cfg.Interval != "" { + fi = cfg.Interval + } + + dur, err := time.ParseDuration(fi) + if err != nil { + return nil, err + } + cm.flushInterval = dur } - fi := defaultFlushInterval - if cfg.Interval != "" { - fi = cfg.Interval + // metric resets + + cm.resetCounters = true + if cfg.ResetCounters != "" { + setting, err := strconv.ParseBool(cfg.ResetCounters) + if err != nil { + return nil, err + } + cm.resetCounters = setting } - dur, err := time.ParseDuration(fi) - if err != nil { - return nil, err + cm.resetGauges = true + if cfg.ResetGauges != "" { + setting, err := strconv.ParseBool(cfg.ResetGauges) + if err != nil { + return nil, err + } + cm.resetGauges = setting } - cm.flushInterval = dur - cfg.CheckManager.Debug = cm.Debug - cfg.CheckManager.Log = cm.Log + cm.resetHistograms = true + if cfg.ResetHistograms != "" { + setting, err := strconv.ParseBool(cfg.ResetHistograms) + if err != nil { + return nil, err + } + cm.resetHistograms = setting + } - check, err := checkmgr.NewCheckManager(&cfg.CheckManager) - if err != nil { - return nil, err + cm.resetText = true + if cfg.ResetText != "" { + setting, err := strconv.ParseBool(cfg.ResetText) + if err != nil { + return nil, err + } + cm.resetText = setting } - cm.check = check - if _, err := cm.check.GetTrap(); err != nil { - return nil, err + // check manager + { + cfg.CheckManager.Debug = cm.Debug + cfg.CheckManager.Log = cm.Log + + check, err := checkmgr.New(&cfg.CheckManager) + if err != nil { + return nil, err + } + cm.check = check + } + + // start background initialization + cm.check.Initialize() + + // if automatic flush is enabled, start it. + // note: submit will jettison metrics until initialization has completed. + if cm.flushInterval > time.Duration(0) { + go func() { + for range time.NewTicker(cm.flushInterval).C { + cm.Flush() + } + }() } return cm, nil } -// Start initializes the CirconusMetrics instance based on -// configuration settings and sets the httptrap check url to -// which metrics should be sent. It then starts a perdiodic -// submission process of all metrics collected. +// Start deprecated NOP, automatic flush is started in New if flush interval > 0. func (m *CirconusMetrics) Start() { - go func() { - for _ = range time.NewTicker(m.flushInterval).C { - m.Flush() - } - }() + return +} + +// Ready returns true or false indicating if the check is ready to accept metrics +func (m *CirconusMetrics) Ready() bool { + return m.check.IsReady() } // Flush metrics kicks off the process of sending metrics to Circonus @@ -186,7 +254,7 @@ func (m *CirconusMetrics) Flush() { } if send { output[name] = map[string]interface{}{ - "_type": "n", + "_type": "L", "_value": value, } } @@ -246,7 +314,13 @@ func (m *CirconusMetrics) Flush() { } } - m.submit(output, newMetrics) + if len(output) > 0 { + m.submit(output, newMetrics) + } else { + if m.Debug { + m.Log.Println("[DEBUG] No metrics to send, skipping") + } + } m.flushmu.Lock() m.flushing = false diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/counter.go b/vendor/github.com/circonus-labs/circonus-gometrics/counter.go index 2b34961f1..2311b0a41 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/counter.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/counter.go @@ -4,6 +4,8 @@ package circonusgometrics +import "fmt" + // A Counter is a monotonically increasing unsigned integer. // // Use a counter to derive rates (e.g., record total number of requests, derive @@ -40,6 +42,19 @@ func (m *CirconusMetrics) RemoveCounter(metric string) { delete(m.counters, metric) } +// GetCounterTest returns the current value for a counter. (note: it is a function specifically for "testing", disable automatic submission during testing.) +func (m *CirconusMetrics) GetCounterTest(metric string) (uint64, error) { + m.cm.Lock() + defer m.cm.Unlock() + + if val, ok := m.counters[metric]; ok { + return val, nil + } + + return 0, fmt.Errorf("Counter metric '%s' not found", metric) + +} + // SetCounterFunc set counter to a function [called at flush interval] func (m *CirconusMetrics) SetCounterFunc(metric string, fn func() uint64) { m.cfm.Lock() diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/gauge.go b/vendor/github.com/circonus-labs/circonus-gometrics/gauge.go index b44236959..9dce5cdf5 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/gauge.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/gauge.go @@ -32,6 +32,18 @@ func (m *CirconusMetrics) RemoveGauge(metric string) { delete(m.gauges, metric) } +// GetGaugeTest returns the current value for a gauge. (note: it is a function specifically for "testing", disable automatic submission during testing.) +func (m *CirconusMetrics) GetGaugeTest(metric string) (string, error) { + m.gm.Lock() + defer m.gm.Unlock() + + if val, ok := m.gauges[metric]; ok { + return val, nil + } + + return "", fmt.Errorf("Gauge metric '%s' not found", metric) +} + // SetGaugeFunc sets a gauge to a function [called at flush interval] func (m *CirconusMetrics) SetGaugeFunc(metric string, fn func() int64) { m.gfm.Lock() diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/histogram.go b/vendor/github.com/circonus-labs/circonus-gometrics/histogram.go index 59d19e20f..71a05054e 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/histogram.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/histogram.go @@ -5,6 +5,7 @@ package circonusgometrics import ( + "fmt" "sync" "github.com/circonus-labs/circonusllhist" @@ -29,19 +30,32 @@ func (m *CirconusMetrics) RecordValue(metric string, val float64) { // SetHistogramValue adds a value to a histogram func (m *CirconusMetrics) SetHistogramValue(metric string, val float64) { - m.NewHistogram(metric) + hist := m.NewHistogram(metric) - m.histograms[metric].rw.Lock() - defer m.histograms[metric].rw.Unlock() + m.hm.Lock() + hist.rw.Lock() + hist.hist.RecordValue(val) + hist.rw.Unlock() + m.hm.Unlock() +} + +// GetHistogramTest returns the current value for a gauge. (note: it is a function specifically for "testing", disable automatic submission during testing.) +func (m *CirconusMetrics) GetHistogramTest(metric string) ([]string, error) { + m.hm.Lock() + defer m.hm.Unlock() - m.histograms[metric].hist.RecordValue(val) + if hist, ok := m.histograms[metric]; ok { + return hist.hist.DecStrings(), nil + } + + return []string{""}, fmt.Errorf("Histogram metric '%s' not found", metric) } // RemoveHistogram removes a histogram func (m *CirconusMetrics) RemoveHistogram(metric string) { m.hm.Lock() - defer m.hm.Unlock() delete(m.histograms, metric) + m.hm.Unlock() } // NewHistogram returns a histogram instance. @@ -71,7 +85,6 @@ func (h *Histogram) Name() string { // RecordValue records the given value to a histogram instance func (h *Histogram) RecordValue(v float64) { h.rw.Lock() - defer h.rw.Unlock() - h.hist.RecordValue(v) + h.rw.Unlock() } diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/metrics.go b/vendor/github.com/circonus-labs/circonus-gometrics/metrics.go new file mode 100644 index 000000000..85812f195 --- /dev/null +++ b/vendor/github.com/circonus-labs/circonus-gometrics/metrics.go @@ -0,0 +1,15 @@ +// Copyright 2016 Circonus, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package circonusgometrics + +// SetMetricTags sets the tags for the named metric and flags a check update is needed +func (m *CirconusMetrics) SetMetricTags(name string, tags []string) bool { + return m.check.AddMetricTags(name, tags, false) +} + +// AddMetricTags appends tags to any existing tags for the named metric and flags a check update is needed +func (m *CirconusMetrics) AddMetricTags(name string, tags []string) bool { + return m.check.AddMetricTags(name, tags, true) +} diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/submit.go b/vendor/github.com/circonus-labs/circonus-gometrics/submit.go index abeb73006..3b0c0e0df 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/submit.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/submit.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "io/ioutil" "log" "net" @@ -20,13 +21,19 @@ import ( ) func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[string]*api.CheckBundleMetric) { - if len(newMetrics) > 0 { - m.check.AddNewMetrics(newMetrics) + + // if there is nowhere to send metrics to, just return. + if !m.check.IsReady() { + m.Log.Printf("[WARN] check not ready, skipping metric submission") + return } + // update check if there are any new metrics or, if metric tags have been added since last submit + m.check.UpdateCheck(newMetrics) + str, err := json.Marshal(output) if err != nil { - m.Log.Printf("[ERROR] marshling output %+v", err) + m.Log.Printf("[ERROR] marshaling output %+v", err) return } @@ -42,7 +49,7 @@ func (m *CirconusMetrics) submit(output map[string]interface{}, newMetrics map[s } func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { - trap, err := m.check.GetTrap() + trap, err := m.check.GetSubmissionURL() if err != nil { return 0, err } @@ -53,8 +60,32 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { if err != nil { return 0, err } + req.Header.Add("Content-Type", "application/json") req.Header.Add("Accept", "application/json") + // keep last HTTP error in the event of retry failure + var lastHTTPError error + retryPolicy := func(resp *http.Response, err error) (bool, error) { + if err != nil { + lastHTTPError = err + return true, err + } + // Check the response code. We retry on 500-range responses to allow + // the server time to recover, as 500's are typically not permanent + // errors and may relate to outages on the server side. This will catch + // invalid response codes as well, like 0 and 999. + if resp.StatusCode == 0 || resp.StatusCode >= 500 { + body, readErr := ioutil.ReadAll(resp.Body) + if readErr != nil { + lastHTTPError = fmt.Errorf("- last HTTP error: %d %+v", resp.StatusCode, readErr) + } else { + lastHTTPError = fmt.Errorf("- last HTTP error: %d %s", resp.StatusCode, string(body)) + } + return true, nil + } + return false, nil + } + client := retryablehttp.NewClient() if trap.URL.Scheme == "https" { client.HTTPClient.Transport = &http.Transport{ @@ -82,10 +113,17 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { DisableCompression: true, } } - client.RetryWaitMin = 10 * time.Millisecond - client.RetryWaitMax = 50 * time.Millisecond + client.RetryWaitMin = 1 * time.Second + client.RetryWaitMax = 5 * time.Second client.RetryMax = 3 - client.Logger = m.Log + // retryablehttp only groks log or no log + // but, outputs everything as [DEBUG] messages + if m.Debug { + client.Logger = m.Log + } else { + client.Logger = log.New(ioutil.Discard, "", log.LstdFlags) + } + client.CheckRetry = retryPolicy attempts := -1 client.RequestLogHook = func(logger *log.Logger, req *http.Request, retryNumber int) { @@ -94,6 +132,9 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { resp, err := client.Do(req) if err != nil { + if lastHTTPError != nil { + return 0, fmt.Errorf("[ERROR] submitting: %+v %+v", err, lastHTTPError) + } if attempts == client.RetryMax { m.check.RefreshTrap() } @@ -107,9 +148,8 @@ func (m *CirconusMetrics) trapCall(payload []byte) (int, error) { } var response map[string]interface{} - err = json.Unmarshal(body, &response) - if err != nil { - m.Log.Printf("[ERROR] parsing body, proceeding. %s\n", err) + if err := json.Unmarshal(body, &response); err != nil { + m.Log.Printf("[ERROR] parsing body, proceeding. %v (%s)\n", err, body) } if resp.StatusCode != 200 { diff --git a/vendor/github.com/circonus-labs/circonus-gometrics/util.go b/vendor/github.com/circonus-labs/circonus-gometrics/util.go index d5cc7d56c..4428e8985 100644 --- a/vendor/github.com/circonus-labs/circonus-gometrics/util.go +++ b/vendor/github.com/circonus-labs/circonus-gometrics/util.go @@ -42,59 +42,103 @@ func (m *CirconusMetrics) Reset() { // snapshot returns a copy of the values of all registered counters and gauges. func (m *CirconusMetrics) snapshot() (c map[string]uint64, g map[string]string, h map[string]*circonusllhist.Histogram, t map[string]string) { + c = m.snapCounters() + g = m.snapGauges() + h = m.snapHistograms() + t = m.snapText() + + return +} + +func (m *CirconusMetrics) snapCounters() map[string]uint64 { m.cm.Lock() defer m.cm.Unlock() - m.cfm.Lock() defer m.cfm.Unlock() - m.gm.Lock() - defer m.gm.Unlock() - - m.gfm.Lock() - defer m.gfm.Unlock() - - m.hm.Lock() - defer m.hm.Unlock() + c := make(map[string]uint64, len(m.counters)+len(m.counterFuncs)) - m.tm.Lock() - defer m.tm.Unlock() - - m.tfm.Lock() - defer m.tfm.Unlock() - - c = make(map[string]uint64, len(m.counters)+len(m.counterFuncs)) for n, v := range m.counters { c[n] = v } + if m.resetCounters && len(c) > 0 { + m.counters = make(map[string]uint64) + } for n, f := range m.counterFuncs { c[n] = f() } + if m.resetCounters && len(c) > 0 { + m.counterFuncs = make(map[string]func() uint64) + } + + return c +} + +func (m *CirconusMetrics) snapGauges() map[string]string { + m.gm.Lock() + defer m.gm.Unlock() + m.gfm.Lock() + defer m.gfm.Unlock() + + g := make(map[string]string, len(m.gauges)+len(m.gaugeFuncs)) - //g = make(map[string]int64, len(m.gauges)+len(m.gaugeFuncs)) - g = make(map[string]string, len(m.gauges)+len(m.gaugeFuncs)) for n, v := range m.gauges { g[n] = v } + if m.resetGauges && len(g) > 0 { + m.gauges = make(map[string]string) + } for n, f := range m.gaugeFuncs { g[n] = m.gaugeValString(f()) } + if m.resetGauges && len(g) > 0 { + m.gaugeFuncs = make(map[string]func() int64) + } + + return g +} + +func (m *CirconusMetrics) snapHistograms() map[string]*circonusllhist.Histogram { + m.hm.Lock() + defer m.hm.Unlock() + + h := make(map[string]*circonusllhist.Histogram, len(m.histograms)) - h = make(map[string]*circonusllhist.Histogram, len(m.histograms)) for n, hist := range m.histograms { + hist.rw.Lock() h[n] = hist.hist.CopyAndReset() + hist.rw.Unlock() } + if m.resetHistograms && len(h) > 0 { + m.histograms = make(map[string]*Histogram) + } + + return h +} + +func (m *CirconusMetrics) snapText() map[string]string { + m.tm.Lock() + defer m.tm.Unlock() + m.tfm.Lock() + defer m.tfm.Unlock() + + t := make(map[string]string, len(m.text)+len(m.textFuncs)) - t = make(map[string]string, len(m.text)+len(m.textFuncs)) for n, v := range m.text { t[n] = v } + if m.resetText && len(t) > 0 { + m.text = make(map[string]string) + } for n, f := range m.textFuncs { t[n] = f() } + if m.resetText && len(t) > 0 { + m.textFuncs = make(map[string]func() string) + } - return + return t } diff --git a/vendor/modules.txt b/vendor/modules.txt index b5ee9a945..47c0970bc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,9 +1,10 @@ # github.com/armon/go-proxyproto v0.0.0-20150207025855-609d6338d3a7 github.com/armon/go-proxyproto -# github.com/circonus-labs/circonus-gometrics v0.0.0-20160830164725-f8c68ed96a06 +# github.com/circonus-labs/circonus-gometrics v1.0.0 github.com/circonus-labs/circonus-gometrics github.com/circonus-labs/circonus-gometrics/api github.com/circonus-labs/circonus-gometrics/checkmgr +github.com/circonus-labs/circonus-gometrics/api/config # github.com/circonus-labs/circonusllhist v0.0.0-20160526043813-d724266ae527 github.com/circonus-labs/circonusllhist # github.com/cyberdelia/go-metrics-graphite v0.0.0-20150826032200-b8345b7f01d5