diff --git a/metrics-operator/controllers/common/providers/common.go b/metrics-operator/controllers/common/providers/common.go index 12b47b57f6..701712feb4 100644 --- a/metrics-operator/controllers/common/providers/common.go +++ b/metrics-operator/controllers/common/providers/common.go @@ -4,3 +4,10 @@ const DynatraceProviderType = "dynatrace" const DynatraceDQLProviderType = "dql" const PrometheusProviderType = "prometheus" const DataDogProviderType = "datadog" + +var SupportedProviders = []string{ + DynatraceProviderType, + DynatraceDQLProviderType, + PrometheusProviderType, + DataDogProviderType, +} diff --git a/metrics-operator/controllers/common/providers/datadog/datadog.go b/metrics-operator/controllers/common/providers/datadog/datadog.go index 52287dfe1d..b230d4f8c3 100644 --- a/metrics-operator/controllers/common/providers/datadog/datadog.go +++ b/metrics-operator/controllers/common/providers/datadog/datadog.go @@ -26,6 +26,12 @@ type KeptnDataDogProvider struct { K8sClient client.Client } +func (d *KeptnDataDogProvider) FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, []byte, error) { + ctx, cancel := context.WithTimeout(ctx, 20*time.Second) + defer cancel() + return d.query(ctx, query, *provider, spec.From.Unix(), spec.To.Unix()) +} + // 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) @@ -35,7 +41,11 @@ func (d *KeptnDataDogProvider) EvaluateQuery(ctx context.Context, metric metrics 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) + return d.query(ctx, metric.Spec.Query, provider, fromTime, toTime) +} + +func (d *KeptnDataDogProvider) query(ctx context.Context, query string, provider metricsapi.KeptnMetricsProvider, fromTime int64, toTime int64) (string, []byte, error) { + qURL := provider.Spec.TargetServer + "/api/v1/query?from=" + strconv.Itoa(int(fromTime)) + "&to=" + strconv.Itoa(int(toTime)) + "&query=" + url.QueryEscape(query) apiKeyVal, appKeyVal, err := getDDSecret(ctx, provider, d.K8sClient) if err != nil { return "", nil, err diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go index 886d031369..36a1d5d739 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace.go @@ -38,6 +38,13 @@ type DynatraceData struct { Values []*float64 `json:"values"` } +func (d *KeptnDynatraceProvider) FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, []byte, error) { + baseURL := d.normalizeAPIURL(provider.Spec.TargetServer) + escapedQ := urlEncodeQuery(query) + qURL := baseURL + "v2/metrics/query?metricSelector=" + escapedQ + "&from=" + spec.From.String() + "&to=" + spec.To.String() + return d.runQuery(ctx, qURL, *provider) +} + // EvaluateQuery fetches the SLI values from dynatrace provider func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) { baseURL := d.normalizeAPIURL(provider.Spec.TargetServer) @@ -52,6 +59,10 @@ func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, metric metri qURL = urlEncodeQuery(qURL) qURL = baseURL + "v2/metrics/query?" + qURL + return d.runQuery(ctx, qURL, provider) +} + +func (d *KeptnDynatraceProvider) runQuery(ctx context.Context, qURL string, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) { d.Log.Info("Running query: " + qURL) ctx, cancel := context.WithTimeout(ctx, 20*time.Second) defer cancel() diff --git a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go index 6e6fca2c02..d6c22fdab2 100644 --- a/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go +++ b/metrics-operator/controllers/common/providers/dynatrace/dynatrace_dql.go @@ -31,6 +31,11 @@ type keptnDynatraceDQLProvider struct { clock clock.Clock } +func (d *keptnDynatraceDQLProvider) FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, []byte, error) { + //TODO implement me + panic("implement me") +} + type DynatraceDQLHandler struct { RequestToken string `json:"requestToken"` } diff --git a/metrics-operator/controllers/common/providers/prometheus/prometheus.go b/metrics-operator/controllers/common/providers/prometheus/prometheus.go index 6d1da44185..dbd6ca4a12 100644 --- a/metrics-operator/controllers/common/providers/prometheus/prometheus.go +++ b/metrics-operator/controllers/common/providers/prometheus/prometheus.go @@ -23,6 +23,40 @@ type KeptnPrometheusProvider struct { HttpClient http.Client } +func (r *KeptnPrometheusProvider) FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, []byte, error) { + ctx, cancel := context.WithTimeout(ctx, 20*time.Second) + defer cancel() + + client, err := promapi.NewClient(promapi.Config{Address: provider.Spec.TargetServer, Client: &r.HttpClient}) + if err != nil { + return "", nil, err + } + api := prometheus.NewAPI(client) + r.Log.Info(fmt.Sprintf( + "Running query: /api/v1/query_range?query=%s&start=%d&end=%d", + query, + spec.From.Unix(), spec.To.Unix(), + )) + queryRange := prometheus.Range{ + Start: spec.From.Time, + End: spec.To.Time, + } + result, warnings, err := api.QueryRange( + ctx, + query, + queryRange, + []prometheus.Option{}..., + ) + + if err != nil { + return "", nil, err + } + if len(warnings) != 0 { + r.Log.Info("Prometheus API returned warnings: " + warnings[0]) + } + return getResultForMatrix(result) +} + // EvaluateQuery fetches the SLI values from prometheus provider func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) { ctx, cancel := context.WithTimeout(ctx, 20*time.Second) diff --git a/metrics-operator/controllers/common/providers/prometheus/prometheus_test.go b/metrics-operator/controllers/common/providers/prometheus/prometheus_test.go index a18cb74d64..8c0558cdd4 100644 --- a/metrics-operator/controllers/common/providers/prometheus/prometheus_test.go +++ b/metrics-operator/controllers/common/providers/prometheus/prometheus_test.go @@ -5,11 +5,13 @@ import ( "net/http" "net/http/httptest" "testing" + "time" metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" ) @@ -314,3 +316,53 @@ func Test_resultsForMatrix(t *testing.T) { }) } } + +func TestFetchAnalysisValue(t *testing.T) { + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte(promPayloadWithRangeAndStep)) + require.Nil(t, err) + })) + defer svr.Close() + + // Create a mock KeptnMetricsProvider + mockProvider := &metricsapi.KeptnMetricsProvider{ + Spec: metricsapi.KeptnMetricsProviderSpec{ + SecretKeyRef: v1.SecretKeySelector{ + LocalObjectReference: v1.LocalObjectReference{ + Name: "myapitoken", + }, + Key: "mykey", + }, + TargetServer: svr.URL, + }, + } + + // Create your KeptnPrometheusProvider instance + provider := KeptnPrometheusProvider{ + HttpClient: http.Client{}, + Log: ctrl.Log.WithName("testytest"), + } + + // Prepare the analysis spec + now := time.Now() + analysisSpec := metricsapi.AnalysisSpec{ + Timeframe: metricsapi.Timeframe{ + From: metav1.Time{ + Time: now.Add(-time.Hour), + }, + To: metav1.Time{ + Time: now, + }}, + } + + // Prepare the expected result + expectedResult := "1" + + // Call the function + result, _, err := provider.FetchAnalysisValue(context.Background(), "your_query_string_here", analysisSpec, mockProvider) + + // Assertions + require.NoError(t, err) + require.Equal(t, expectedResult, result) +} diff --git a/metrics-operator/controllers/common/providers/provider.go b/metrics-operator/controllers/common/providers/provider.go index 61cc818746..779d75eb86 100644 --- a/metrics-operator/controllers/common/providers/provider.go +++ b/metrics-operator/controllers/common/providers/provider.go @@ -17,6 +17,7 @@ import ( // KeptnSLIProvider is the interface that describes the operations that an SLI provider must implement type KeptnSLIProvider interface { EvaluateQuery(ctx context.Context, metric metricsapi.KeptnMetric, provider metricsapi.KeptnMetricsProvider) (string, []byte, error) + FetchAnalysisValue(ctx context.Context, query string, spec metricsapi.AnalysisSpec, provider *metricsapi.KeptnMetricsProvider) (string, []byte, error) } // NewProvider is a factory method that chooses the right implementation of KeptnSLIProvider