[datadog] add support for new host delay to the datadog_monitor resource (#11975)

* [datadog] Update go-datadog-api library

Involves one breaking API change. Also some `gofmt`ing.

* [datadog] Add support for new_host_delay to the datadog_monitor resource

New API parameter that Datadog added for monitors to ignore new hosts
for the specified time period in monitor evaluation.
This commit is contained in:
Jesse Szwedko 2017-02-17 07:08:31 -08:00 committed by Paul Stack
parent 2075ea84e8
commit 5b7e3701cb
12 changed files with 140 additions and 23 deletions

View File

@ -1,8 +1,9 @@
package datadog
import (
"github.com/hashicorp/terraform/helper/resource"
"testing"
"github.com/hashicorp/terraform/helper/resource"
)
func TestDatadogMonitor_import(t *testing.T) {
@ -41,6 +42,7 @@ resource "datadog_monitor" "foo" {
}
notify_no_data = false
new_host_delay = 600
renotify_interval = 60
notify_audit = false

View File

@ -80,6 +80,11 @@ func resourceDatadogMonitor() *schema.Resource {
Optional: true,
Default: false,
},
"new_host_delay": {
Type: schema.TypeInt,
Computed: true,
Optional: true,
},
"no_data_timeframe": {
Type: schema.TypeInt,
Optional: true,
@ -153,6 +158,12 @@ func buildMonitorStruct(d *schema.ResourceData) *datadog.Monitor {
}
o.Silenced = s
}
if attr, ok := d.GetOk("notify_no_data"); ok {
o.NotifyNoData = attr.(bool)
}
if attr, ok := d.GetOk("new_host_delay"); ok {
o.NewHostDelay = datadog.Int(attr.(int))
}
if attr, ok := d.GetOk("no_data_timeframe"); ok {
o.NoDataTimeframe = datadog.NoDataTimeframe(attr.(int))
}
@ -268,6 +279,8 @@ func resourceDatadogMonitorRead(d *schema.ResourceData, meta interface{}) error
d.Set("query", m.Query)
d.Set("type", m.Type)
d.Set("thresholds", thresholds)
d.Set("new_host_delay", m.Options.NewHostDelay)
d.Set("notify_no_data", m.Options.NotifyNoData)
d.Set("no_data_timeframe", m.Options.NoDataTimeframe)
d.Set("renotify_interval", m.Options.RenotifyInterval)
@ -328,6 +341,12 @@ func resourceDatadogMonitorUpdate(d *schema.ResourceData, meta interface{}) erro
}
}
if attr, ok := d.GetOk("notify_no_data"); ok {
o.NotifyNoData = attr.(bool)
}
if attr, ok := d.GetOk("new_host_delay"); ok {
o.NewHostDelay = datadog.Int(attr.(int))
}
if attr, ok := d.GetOk("no_data_timeframe"); ok {
o.NoDataTimeframe = datadog.NoDataTimeframe(attr.(int))
}

View File

@ -31,6 +31,8 @@ func TestAccDatadogMonitor_Basic(t *testing.T) {
"datadog_monitor.foo", "query", "avg(last_1h):avg:aws.ec2.cpu{environment:foo,host:foo} by {host} > 2"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "new_host_delay", "600"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "renotify_interval", "60"),
resource.TestCheckResourceAttr(
@ -109,6 +111,8 @@ func TestAccDatadogMonitor_Updated(t *testing.T) {
"datadog_monitor.foo", "type", "metric alert"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "notify_no_data", "false"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "new_host_delay", "600"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "renotify_interval", "60"),
resource.TestCheckResourceAttr(
@ -147,6 +151,8 @@ func TestAccDatadogMonitor_Updated(t *testing.T) {
"datadog_monitor.foo", "type", "metric alert"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "notify_no_data", "true"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "new_host_delay", "900"),
resource.TestCheckResourceAttr(
"datadog_monitor.foo", "no_data_timeframe", "20"),
resource.TestCheckResourceAttr(
@ -281,6 +287,7 @@ resource "datadog_monitor" "foo" {
notify_audit = false
timeout_h = 60
new_host_delay = 600
include_tags = true
require_full_window = true
locked = false
@ -378,6 +385,7 @@ resource "datadog_monitor" "foo" {
}
notify_no_data = true
new_host_delay = 900
no_data_timeframe = 20
renotify_interval = 40
escalation_message = "the situation has escalated! @pagerduty"

View File

@ -322,11 +322,7 @@ func appendRequests(datadogGraph *datadog.Graph, terraformRequests *[]interface{
if style, ok := t["style"]; ok {
s, _ := style.(map[string]interface{})
style := struct {
Palette *string `json:"palette,omitempty"`
Width *string `json:"width,omitempty"`
Type *string `json:"type,omitempty"`
}{}
style := &datadog.GraphDefinitionRequestStyle{}
if palette_, ok := s["palette"]; ok {
palette := palette_.(string)
@ -343,7 +339,7 @@ func appendRequests(datadogGraph *datadog.Graph, terraformRequests *[]interface{
style.Type = &style_type
}
d.Style = &style
d.Style = style
}
if changeType, ok := t["change_type"]; ok {

View File

@ -14,6 +14,7 @@ import (
"io"
"io/ioutil"
"net/http"
"time"
)
// Client is the object that handles talking to the Datadog API. This maintains
@ -22,7 +23,8 @@ type Client struct {
apiKey, appKey string
//The Http Client that is used to make requests
HttpClient *http.Client
HttpClient *http.Client
RetryTimeout time.Duration
}
// valid is the struct to unmarshal validation endpoint responses into.
@ -35,9 +37,10 @@ type valid struct {
// methods. The expected argument is the API key.
func NewClient(apiKey, appKey string) *Client {
return &Client{
apiKey: apiKey,
appKey: appKey,
HttpClient: http.DefaultClient,
apiKey: apiKey,
appKey: appKey,
HttpClient: http.DefaultClient,
RetryTimeout: time.Duration(60 * time.Second),
}
}

View File

@ -0,0 +1,15 @@
package datadog
// Int is a helper routine that allocates a new int value
// to store v and returns a pointer to it.
func Int(v int) *int { return &v }
// GetInt is a helper routine that returns a boolean representing
// if a value was set, and if so, dereferences the pointer to it.
func GetInt(v *int) (int, bool) {
if v != nil {
return *v, true
}
return 0, false
}

View File

@ -13,18 +13,21 @@ import (
"fmt"
)
// GraphDefinitionRequestStyle represents the graph style attributes
type GraphDefinitionRequestStyle struct {
Palette *string `json:"palette,omitempty"`
Width *string `json:"width,omitempty"`
Type *string `json:"type,omitempty"`
}
// GraphDefinitionRequest represents the requests passed into each graph.
type GraphDefinitionRequest struct {
Query string `json:"q"`
Stacked bool `json:"stacked"`
Aggregator string
Query string `json:"q"`
Stacked bool `json:"stacked"`
Aggregator string `json:"aggregator"`
ConditionalFormats []DashboardConditionalFormat `json:"conditional_formats,omitempty"`
Type string `json:"type,omitempty"`
Style *struct {
Palette *string `json:"palette,omitempty"`
Width *string `json:"width,omitempty"`
Type *string `json:"type,omitempty"`
} `json:"style,omitempty"`
Style *GraphDefinitionRequestStyle `json:"style,omitempty"`
// For change type graphs
ChangeType string `json:"change_type,omitempty"`

View File

@ -42,6 +42,7 @@ type Options struct {
NoDataTimeframe NoDataTimeframe `json:"no_data_timeframe,omitempty"`
NotifyAudit bool `json:"notify_audit,omitempty"`
NotifyNoData bool `json:"notify_no_data,omitempty"`
NewHostDelay *int `json:"new_host_delay,omitempty"`
RenotifyInterval int `json:"renotify_interval,omitempty"`
Silenced map[string]int `json:"silenced,omitempty"`
TimeoutH int `json:"timeout_h,omitempty"`

26
vendor/github.com/zorkian/go-datadog-api/ratelimit.go generated vendored Normal file
View File

@ -0,0 +1,26 @@
package datadog
import (
"fmt"
"time"
)
type RateLimit struct {
Limit int
Period time.Duration
Remaining int
Reset time.Duration
}
func (r *RateLimit) Error() string {
return fmt.Sprintf("Rate limiting: Limit %d, Period %d, Remaining %d, Reset in %d", r.Limit, r.Period, r.Remaining, r.Reset)
}
func NewRateLimit(limit, period, remaining, reset int) *RateLimit {
return &RateLimit{
Limit: limit,
Period: time.Duration(period) * time.Second,
Remaining: remaining,
Reset: time.Duration(reset) * time.Second,
}
}

View File

@ -17,12 +17,20 @@ import (
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/cenkalti/backoff"
)
const (
RateLimitHeader = "X-RateLimit-Limit"
RatePeriodHeader = "X-RateLimit-Period"
RateRemainingHeader = "X-RateLimit-Remaining"
RateResetHeader = "X-RateLimit-Reset"
)
// uriForAPI is to be called with something like "/v1/events" and it will give
// the proper request URI to be posted to.
func (client *Client) uriForAPI(api string) string {
@ -66,13 +74,19 @@ func (client *Client) doJsonRequest(method, api string,
if method == "POST" {
resp, err = client.HttpClient.Do(req)
} else {
resp, err = client.doRequestWithRetries(req, 60*time.Second)
resp, err = client.doRequestWithRetries(req, client.RetryTimeout)
}
if err != nil {
return err
}
defer resp.Body.Close()
if err := client.getRateLimit(resp); err != nil {
if err.(*RateLimit).Remaining == 0 {
return err
}
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
@ -104,6 +118,27 @@ func (client *Client) doJsonRequest(method, api string,
return nil
}
func (client *Client) getRateLimit(response *http.Response) error {
ratelimit := response.Header.Get(RateLimitHeader)
if ratelimit == "" {
return nil
}
rateperiod := response.Header.Get(RatePeriodHeader)
rateremaining := response.Header.Get(RateRemainingHeader)
ratereset := response.Header.Get(RateResetHeader)
limit, _ := strconv.Atoi(ratelimit)
period, _ := strconv.Atoi(rateperiod)
remaining, _ := strconv.Atoi(rateremaining)
reset, _ := strconv.Atoi(ratereset)
ratelimiterror := NewRateLimit(limit, period, remaining, reset)
return error(ratelimiterror)
}
// doRequestWithRetries performs an HTTP request repeatedly for maxTime or until
// no error and no HTTP response code higher than 299 is returned.
func (client *Client) doRequestWithRetries(req *http.Request, maxTime time.Duration) (*http.Response, error) {
@ -120,6 +155,12 @@ func (client *Client) doRequestWithRetries(req *http.Request, maxTime time.Durat
return err
}
if err := client.getRateLimit(resp); err != nil {
if err.(*RateLimit).Remaining == 0 {
return err
}
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
return errors.New("API error: " + resp.Status)
}

6
vendor/vendor.json vendored
View File

@ -2647,10 +2647,10 @@
"revision": "75ce5fbba34b1912a3641adbd58cf317d7315821"
},
{
"checksumSHA1": "yMIu8wtilcyADHouhDllFm+kovE=",
"checksumSHA1": "uynUzdKeOsfi7flpYWFx835Nafo=",
"path": "github.com/zorkian/go-datadog-api",
"revision": "a0a72fc5e4cae721b5144ba785f07f4edcf2cd47",
"revisionTime": "2016-12-07T17:41:01Z"
"revision": "6308094e4aca46eb16a227b50be29772242bf3aa",
"revisionTime": "2017-02-02T00:47:50Z"
},
{
"checksumSHA1": "9x64JhJGo6z8TS0Q33cGcR64kjA=",

View File

@ -80,6 +80,9 @@ The following arguments are supported:
* `notify_no_data` (Optional) A boolean indicating whether this monitor will notify when data stops reporting. Defaults
to true.
* `new_host_delay` (Optional) Time (in seconds) to allow a host to boot and
applications to fully start before starting the evaluation of monitor
results. Should be a non negative integer. Defaults to 300.
* `no_data_timeframe` (Optional) The number of minutes before a monitor will notify when data stops reporting. Must be at
least 2x the monitor timeframe for metric alerts or 2 minutes for service checks. Default: 2x timeframe for
metric alerts, 2 minutes for service checks.