-
Notifications
You must be signed in to change notification settings - Fork 129
feat: datadog metric provider for KLT #948
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
thisthat
merged 17 commits into
keptn:main
from
sudiptob2:feat/554/datadog-metric-provider
Mar 20, 2023
Merged
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
94ed95f
feat: implemented datadog provider with httpclient instead of dd-go-c…
sudiptob2 6a87fca
test: happy path unit test added
sudiptob2 f03326e
feat: red datadog secrets from kubernetes secret
sudiptob2 a63fd24
feat: implemented getting secrets from kubernetes secret and secret v…
sudiptob2 7be4ac9
test: provider updated - Key of the SecretKeyRef made optional
sudiptob2 2701b3b
feat: linting error fixed
sudiptob2 19dc66a
feat: apiKey and appKey defined as const
sudiptob2 e6c6386
feat: linting error fixed
sudiptob2 d408124
feat: added KeptnDataDogProvider in the provider_test
sudiptob2 d9c6aa0
feat: unittest for wrong payload
sudiptob2 48d052d
feat: added some unittest for datadog.go
sudiptob2 cd23dba
feat: updated error message
sudiptob2 5bc7127
feat: updated lint
sudiptob2 69ed689
feat: added unittests common.go
sudiptob2 4301f0a
feat: defualt duration of the datadog query is set to 5 min
sudiptob2 b04da55
feat: nolint comments removed
sudiptob2 a30c132
feat: implemented avg for getting single value from the metric.
sudiptob2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
metrics-operator/controllers/common/providers/datadog/common.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package datadog | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" | ||
| corev1 "k8s.io/api/core/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| ) | ||
|
|
||
| const apiKey, appKey = "DD_CLIENT_API_KEY", "DD_CLIENT_APP_KEY" | ||
|
|
||
| var ErrSecretKeyRefNotDefined = errors.New("the SecretKeyRef property with the DataDog API Key is missing") | ||
|
|
||
| func hasDDSecretDefined(spec metricsapi.KeptnMetricsProviderSpec) bool { | ||
| if spec.SecretKeyRef == (corev1.SecretKeySelector{}) { | ||
| return false | ||
| } | ||
| if strings.TrimSpace(spec.SecretKeyRef.Name) == "" { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func getDDSecret(ctx context.Context, provider metricsapi.KeptnMetricsProvider, k8sClient client.Client) (string, string, error) { | ||
| if !hasDDSecretDefined(provider.Spec) { | ||
| return "", "", ErrSecretKeyRefNotDefined | ||
| } | ||
| ddCredsSecret := &corev1.Secret{} | ||
| if err := k8sClient.Get(ctx, types.NamespacedName{Name: provider.Spec.SecretKeyRef.Name, Namespace: provider.Namespace}, ddCredsSecret); err != nil { | ||
| return "", "", err | ||
| } | ||
|
|
||
| apiKeyVal := ddCredsSecret.Data[apiKey] | ||
| appKeyVal := ddCredsSecret.Data[appKey] | ||
| if len(apiKeyVal) == 0 || len(appKeyVal) == 0 { | ||
| return "", "", fmt.Errorf("secret does not contain %s or %s", apiKey, appKey) | ||
| } | ||
| return string(apiKeyVal), string(appKeyVal), nil | ||
| } | ||
120 changes: 120 additions & 0 deletions
120
metrics-operator/controllers/common/providers/datadog/common_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| package datadog | ||
|
|
||
| import ( | ||
| "context" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "strings" | ||
| "testing" | ||
|
|
||
| metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" | ||
| "github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/fake" | ||
| "github.com/stretchr/testify/require" | ||
| corev1 "k8s.io/api/core/v1" | ||
| v1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| ) | ||
|
|
||
| func TestGetSecret_NoKeyDefined(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() | ||
| fakeClient := fake.NewClient() | ||
|
|
||
| p := metricsapi.KeptnMetricsProvider{ | ||
| Spec: metricsapi.KeptnMetricsProviderSpec{ | ||
| TargetServer: svr.URL, | ||
| }, | ||
| } | ||
| r1, r2, e := getDDSecret(context.TODO(), p, fakeClient) | ||
| require.NotNil(t, e) | ||
| require.ErrorIs(t, e, ErrSecretKeyRefNotDefined) | ||
| require.Empty(t, r1) | ||
| require.Empty(t, r2) | ||
|
|
||
| } | ||
|
|
||
| func TestGetSecret_NoSecretDefined(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" | ||
| apiToken := &corev1.Secret{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: "garbage", | ||
| Namespace: "", | ||
| }, | ||
| Data: map[string][]byte{ | ||
| apiKey: []byte(apiKeyValue), | ||
| appKey: []byte(appKeyValue), | ||
| }, | ||
| } | ||
| fakeClient := fake.NewClient(apiToken) | ||
|
|
||
| b := true | ||
| p := metricsapi.KeptnMetricsProvider{ | ||
| Spec: metricsapi.KeptnMetricsProviderSpec{ | ||
| SecretKeyRef: v1.SecretKeySelector{ | ||
| LocalObjectReference: v1.LocalObjectReference{ | ||
| Name: secretName, | ||
| }, | ||
| Optional: &b, | ||
| }, | ||
| TargetServer: svr.URL, | ||
| }, | ||
| } | ||
| r1, r2, e := getDDSecret(context.TODO(), p, fakeClient) | ||
| require.NotNil(t, e) | ||
| require.True(t, strings.Contains(e.Error(), "secrets \""+secretName+"\" not found")) | ||
| require.Empty(t, r1) | ||
| require.Empty(t, r2) | ||
|
|
||
| } | ||
|
|
||
| func TestGetSecret_HappyPath(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" | ||
| apiToken := &corev1.Secret{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: secretName, | ||
| Namespace: "", | ||
| }, | ||
| Data: map[string][]byte{ | ||
| apiKey: []byte(apiKeyValue), | ||
| appKey: []byte(appKeyValue), | ||
| }, | ||
| } | ||
| fakeClient := fake.NewClient(apiToken) | ||
|
|
||
| b := true | ||
| p := metricsapi.KeptnMetricsProvider{ | ||
| Spec: metricsapi.KeptnMetricsProviderSpec{ | ||
| SecretKeyRef: v1.SecretKeySelector{ | ||
| LocalObjectReference: v1.LocalObjectReference{ | ||
| Name: secretName, | ||
| }, | ||
| Optional: &b, | ||
| }, | ||
| TargetServer: svr.URL, | ||
| }, | ||
| } | ||
| r1, r2, e := getDDSecret(context.TODO(), p, fakeClient) | ||
| require.Nil(t, e) | ||
| require.Equal(t, apiKeyValue, r1) | ||
| require.Equal(t, appKeyValue, r2) | ||
|
|
||
| } |
79 changes: 79 additions & 0 deletions
79
metrics-operator/controllers/common/providers/datadog/datadog.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package datadog | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
| "net/url" | ||
| "strconv" | ||
| "time" | ||
|
|
||
| "github.com/DataDog/datadog-api-client-go/v2/api/datadogV1" | ||
| "github.com/go-logr/logr" | ||
| metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| ) | ||
|
|
||
| type KeptnDataDogProvider struct { | ||
| Log logr.Logger | ||
| HttpClient http.Client | ||
| K8sClient client.Client | ||
| } | ||
|
|
||
| // EvaluateQuery fetches the SLI values from datadog provider | ||
| func (d *KeptnDataDogProvider) EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) { | ||
| 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() | ||
| 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 { | ||
| d.Log.Error(err, "Error while creating request") | ||
| return "", nil, err | ||
| } | ||
|
|
||
| apiKeyVal, appKeyVal, err := getDDSecret(ctx, provider, d.K8sClient) | ||
| if err != nil { | ||
| return "", nil, err | ||
| } | ||
|
|
||
| req.Header.Set("Accept", "application/json") | ||
| req.Header.Set("Dd-Api-Key", apiKeyVal) | ||
| req.Header.Set("Dd-Application-Key", appKeyVal) | ||
|
|
||
| res, err := d.HttpClient.Do(req) | ||
| if err != nil { | ||
| d.Log.Error(err, "Error while creating request") | ||
| return "", nil, err | ||
| } | ||
| defer func() { | ||
| err := res.Body.Close() | ||
| if err != nil { | ||
| d.Log.Error(err, "Could not close request body") | ||
| } | ||
| }() | ||
|
|
||
| b, _ := io.ReadAll(res.Body) | ||
| result := datadogV1.MetricsQueryResponse{} | ||
| err = json.Unmarshal(b, &result) | ||
| if err != nil { | ||
| d.Log.Error(err, "Error while parsing response") | ||
| return "", nil, err | ||
| } | ||
|
|
||
| if len(result.Series) == 0 { | ||
| d.Log.Info("No values in query result") | ||
| return "", nil, fmt.Errorf("no values in query result") | ||
| } | ||
|
|
||
| points := (result.Series)[0].Pointlist | ||
| value := strconv.FormatFloat(*points[len(points)-1][1], 'g', 5, 64) | ||
thisthat marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return value, b, nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.