Skip to content

Commit

Permalink
feat: update Datadog API to query metrics for range (#1615)
Browse files Browse the repository at this point in the history
Signed-off-by: Rakshit Gondwal <rakshitgondwal3@gmail.com>
Co-authored-by: Florian Bacher <florian.bacher@dynatrace.com>
  • Loading branch information
rakshitgondwal and bacherfl committed Jul 25, 2023
1 parent eb7c9b2 commit 3b370ab
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 57 deletions.
23 changes: 18 additions & 5 deletions metrics-operator/controllers/common/providers/datadog/datadog.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ func (d *KeptnDataDogProvider) EvaluateQuery(ctx context.Context, metric metrics
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()

// Assumed default metric duration as 5 minutes
// Think a better way to handle this
intervalInMin := 5
fromTime := time.Now().Add(time.Duration(-intervalInMin) * time.Minute).Unix()
toTime := time.Now().Unix()
fromTime, toTime, err := getTimeRange(metric)
if err != nil {
return "", nil, err
}
qURL := provider.Spec.TargetServer + "/api/v1/query?from=" + strconv.Itoa(int(fromTime)) + "&to=" + strconv.Itoa(int(toTime)) + "&query=" + url.QueryEscape(metric.Spec.Query)
req, err := http.NewRequestWithContext(ctx, "GET", qURL, nil)
if err != nil {
Expand Down Expand Up @@ -105,3 +104,17 @@ func (d *KeptnDataDogProvider) getSingleValue(points [][]*float64) float64 {
}
return sum / float64(count)
}

func getTimeRange(metric metricsapi.KeptnMetric) (int64, int64, error) {
var intervalInMin string
if metric.Spec.Range != nil {
intervalInMin = metric.Spec.Range.Interval
} else {
intervalInMin = "5m"
}
intervalDuration, err := time.ParseDuration(intervalInMin)
if err != nil {
return 0, 0, err
}
return time.Now().Add(-intervalDuration).Unix(), time.Now().Unix(), nil
}
213 changes: 161 additions & 52 deletions metrics-operator/controllers/common/providers/datadog/datadog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,17 @@ func TestEvaluateQuery_APIError(t *testing.T) {
},
}
kdd := setupTest(apiToken)
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
Expand All @@ -63,12 +71,13 @@ func TestEvaluateQuery_APIError(t *testing.T) {
TargetServer: svr.URL,
},
}

r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Error(t, e)
require.Contains(t, e.Error(), "Token is missing required scope")
require.Equal(t, []byte(ddErrorPayload), raw)
require.Empty(t, r)
for _, metric := range metrics {
r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Error(t, e)
require.Contains(t, e.Error(), "Token is missing required scope")
require.Equal(t, []byte(ddErrorPayload), raw)
require.Empty(t, r)
}
}

func TestEvaluateQuery_HappyPath(t *testing.T) {
Expand All @@ -92,9 +101,17 @@ func TestEvaluateQuery_HappyPath(t *testing.T) {
},
}
kdd := setupTest(apiToken)
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
Expand All @@ -109,11 +126,12 @@ func TestEvaluateQuery_HappyPath(t *testing.T) {
TargetServer: svr.URL,
},
}

r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Nil(t, e)
require.Equal(t, []byte(ddPayload), raw)
require.Equal(t, fmt.Sprintf("%.3f", 89.116), r)
for _, metric := range metrics {
r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Nil(t, e)
require.Equal(t, []byte(ddPayload), raw)
require.Equal(t, fmt.Sprintf("%.3f", 89.116), r)
}
}
func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -136,9 +154,17 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) {
},
}
kdd := setupTest(apiToken)
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
Expand All @@ -153,11 +179,12 @@ func TestEvaluateQuery_WrongPayloadHandling(t *testing.T) {
TargetServer: svr.URL,
},
}

r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Equal(t, "", r)
require.Equal(t, []byte("garbage"), raw)
require.NotNil(t, e)
for _, metric := range metrics {
r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.Equal(t, "", r)
require.Equal(t, []byte("garbage"), raw)
require.NotNil(t, e)
}
}
func TestEvaluateQuery_MissingSecret(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -167,20 +194,29 @@ func TestEvaluateQuery_MissingSecret(t *testing.T) {
defer svr.Close()

kdd := setupTest()
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
TargetServer: svr.URL,
},
}

_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.ErrorIs(t, e, ErrSecretKeyRefNotDefined)
for _, metric := range metrics {
_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.ErrorIs(t, e, ErrSecretKeyRefNotDefined)
}
}
func TestEvaluateQuery_SecretNotFound(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -192,9 +228,17 @@ func TestEvaluateQuery_SecretNotFound(t *testing.T) {
secretName := "datadogSecret"

kdd := setupTest()
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
Expand All @@ -209,10 +253,11 @@ func TestEvaluateQuery_SecretNotFound(t *testing.T) {
TargetServer: svr.URL,
},
}

_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.True(t, errors.IsNotFound(e))
for _, metric := range metrics {
_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.True(t, errors.IsNotFound(e))
}
}
func TestEvaluateQuery_RefNonExistingKey(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -233,9 +278,17 @@ func TestEvaluateQuery_RefNonExistingKey(t *testing.T) {
},
}
kdd := setupTest(apiToken)
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
Expand All @@ -250,10 +303,11 @@ func TestEvaluateQuery_RefNonExistingKey(t *testing.T) {
TargetServer: svr.URL,
},
}

_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.True(t, strings.Contains(e.Error(), "secret does not contain DD_CLIENT_API_KEY or DD_CLIENT_APP_KEY"))
for _, metric := range metrics {
_, _, e := kdd.EvaluateQuery(context.TODO(), metric, p)
require.NotNil(t, e)
require.True(t, strings.Contains(e.Error(), "secret does not contain DD_CLIENT_API_KEY or DD_CLIENT_APP_KEY"))
}
}
func TestEvaluateQuery_EmptyPayload(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -262,6 +316,61 @@ func TestEvaluateQuery_EmptyPayload(t *testing.T) {
}))
defer svr.Close()

secretName := "datadogSecret"
apiKey, apiKeyValue := "DD_CLIENT_API_KEY", "fake-api-key"
appKey, appKeyValue := "DD_CLIENT_APP_KEY", "fake-app-key"
apiToken := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: "",
},
Data: map[string][]byte{
apiKey: []byte(apiKeyValue),
appKey: []byte(appKeyValue),
},
}
kdd := setupTest(apiToken)
metrics := []metricsapi.KeptnMetric{
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
},
},
{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5m"},
},
},
}
b := true
p := metricsapi.KeptnMetricsProvider{
Spec: metricsapi.KeptnMetricsProviderSpec{
SecretKeyRef: v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: secretName,
},
Optional: &b,
},
TargetServer: svr.URL,
},
}
for _, metric := range metrics {
r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
t.Log(string(raw))
require.Nil(t, raw)
require.Equal(t, "", r)
require.True(t, strings.Contains(e.Error(), "no values in query result"))
}
}

func TestEvaluateQuery_WrongInterval(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(ddPayload))
require.Nil(t, err)
}))
defer svr.Close()

secretName := "datadogSecret"
apiKey, apiKeyValue := "DD_CLIENT_API_KEY", "fake-api-key"
appKey, appKeyValue := "DD_CLIENT_APP_KEY", "fake-app-key"
Expand All @@ -279,6 +388,7 @@ func TestEvaluateQuery_EmptyPayload(t *testing.T) {
metric := metricsapi.KeptnMetric{
Spec: metricsapi.KeptnMetricSpec{
Query: "system.cpu.idle{*}",
Range: &metricsapi.RangeSpec{Interval: "5mins"},
},
}
b := true
Expand All @@ -293,14 +403,13 @@ func TestEvaluateQuery_EmptyPayload(t *testing.T) {
TargetServer: svr.URL,
},
}

r, raw, e := kdd.EvaluateQuery(context.TODO(), metric, p)
t.Log(string(raw))
require.Nil(t, raw)
require.Equal(t, "", r)
require.True(t, strings.Contains(e.Error(), "no values in query result"))

require.Error(t, e)
require.Contains(t, e.Error(), "unknown unit \"mins\" in duration \"5mins\"")
require.Equal(t, []byte(nil), raw)
require.Empty(t, r)
}

func TestGetSingleValue_EmptyPoints(t *testing.T) {
kdd := setupTest()
var points [][]*float64
Expand Down

0 comments on commit 3b370ab

Please sign in to comment.