diff --git a/metrics-operator/controllers/analysis/controller.go b/metrics-operator/controllers/analysis/controller.go index 0b7357db48..7ae537156f 100644 --- a/metrics-operator/controllers/analysis/controller.go +++ b/metrics-operator/controllers/analysis/controller.go @@ -121,6 +121,12 @@ func (a *AnalysisReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c err = a.evaluateObjectives(ctx, res, analysisDef, analysis) + // if evaluation was successful remove the stored values + if err == nil { + analysis.Status.StoredValues = nil + err = a.updateStatus(ctx, analysis) + } + return ctrl.Result{}, err } diff --git a/metrics-operator/controllers/analysis/controller_test.go b/metrics-operator/controllers/analysis/controller_test.go index be7d072354..922dd461d1 100644 --- a/metrics-operator/controllers/analysis/controller_test.go +++ b/metrics-operator/controllers/analysis/controller_test.go @@ -101,6 +101,61 @@ func TestAnalysisReconciler_Reconcile_BasicControlLoop(t *testing.T) { } } +func TestAnalysisReconciler_ExistingAnalysisStatusIsFlushedWhenEvaluationFinishes(t *testing.T) { + analysis, analysisDef, template, _ := getTestCRDs() + + analysis.Status = metricsapi.AnalysisStatus{ + StoredValues: map[string]metricsapi.ProviderResult{ + "default": { + Objective: metricsapi.ObjectReference{ + Name: "my-analysis-def", + Namespace: "default", + }, + Value: "1", + }, + }, + } + + mockFactory := func(ctx context.Context, analysisMoqParam *metricsapi.Analysis, obj []metricsapi.Objective, numWorkers int, c client.Client, log logr.Logger, namespace string) (context.Context, IAnalysisPool) { + mymock := fake.IAnalysisPoolMock{ + DispatchAndCollectFunc: func(ctx context.Context) (map[string]metricsapi.ProviderResult, error) { + return map[string]metricsapi.ProviderResult{}, nil + }, + } + return ctx, &mymock + } + + fclient := fake2.NewClient(&analysis, &analysisDef, &template) + a := &AnalysisReconciler{ + Client: fclient, + Scheme: fclient.Scheme(), + Log: testr.New(t), + MaxWorkers: 2, + NewWorkersPoolFactory: mockFactory, + IAnalysisEvaluator: &fakeEvaluator.IAnalysisEvaluatorMock{ + EvaluateFunc: func(values map[string]metricsapi.ProviderResult, ad *metricsapi.AnalysisDefinition) metricstypes.AnalysisResult { + return metricstypes.AnalysisResult{Pass: true} + }}, + } + + req := controllerruntime.Request{ + NamespacedName: types.NamespacedName{Namespace: "default", Name: "my-analysis"}, + } + + status := &metricsapi.AnalysisStatus{Raw: "{\"objectiveResults\":null,\"totalScore\":0,\"maximumScore\":0,\"pass\":true,\"warning\":false}", Pass: true} + + got, err := a.Reconcile(context.TODO(), req) + + require.Nil(t, err) + require.Equal(t, controllerruntime.Result{}, got) + resAnalysis := metricsapi.Analysis{} + err = fclient.Get(context.TODO(), req.NamespacedName, &resAnalysis) + require.Nil(t, err) + require.Nil(t, resAnalysis.Status.StoredValues) + require.Equal(t, *status, resAnalysis.Status) + +} + func getTestCRDs() (metricsapi.Analysis, metricsapi.AnalysisDefinition, metricsapi.AnalysisValueTemplate, metricsapi.KeptnMetricsProvider) { analysis := metricsapi.Analysis{ ObjectMeta: metav1.ObjectMeta{ diff --git a/test/integration/analysis-controller-existing-status/00-assert.yaml b/test/integration/analysis-controller-existing-status/00-assert.yaml new file mode 100644 index 0000000000..9506857208 --- /dev/null +++ b/test/integration/analysis-controller-existing-status/00-assert.yaml @@ -0,0 +1,14 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: Analysis +metadata: + name: analysis-sample + namespace: testy +spec: + analysisDefinition: + name: ed-my-proj-dev-svc1 +status: + storedValues: + ready-testy: + objectiveReference: + name: ready + namespace: testy diff --git a/test/integration/analysis-controller-existing-status/00-install.yaml b/test/integration/analysis-controller-existing-status/00-install.yaml new file mode 100644 index 0000000000..5a75c37dc8 --- /dev/null +++ b/test/integration/analysis-controller-existing-status/00-install.yaml @@ -0,0 +1,70 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: testy +--- +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisValueTemplate +metadata: + name: ready + namespace: testy +spec: + provider: + name: my-mocked-provider + namespace: testy + query: 'sum(kube_pod_container_status_ready{namespace="{{.ns}}"})' +--- +apiVersion: metrics.keptn.sh/v1alpha3 +kind: AnalysisDefinition +metadata: + name: ed-my-proj-dev-svc1 + namespace: testy +spec: + objectives: + - analysisValueTemplateRef: + name: ready + namespace: testy + target: + failure: + lessThan: + fixedValue: 2 + warning: + lessThan: + fixedValue: 3 + weight: 1 + keyObjective: false + totalScore: + passPercentage: 90 + warningPercentage: 75 +--- +apiVersion: metrics.keptn.sh/v1alpha3 +kind: Analysis +metadata: + name: analysis-sample + namespace: testy +spec: + timeframe: + from: 2023-09-14T07:33:19Z + to: 2023-09-14T07:33:19Z + args: + "ns": "keptn-lifecycle-toolkit-system" + analysisDefinition: + name: ed-my-proj-dev-svc1 + namespace: testy +status: + storedValues: + my-provider-query-1: + objectiveReference: + name: objective-template-1 + value: 1 + errMsg: "" + +--- +apiVersion: metrics.keptn.sh/v1alpha3 +kind: KeptnMetricsProvider +metadata: + name: my-mocked-provider + namespace: testy +spec: + type: prometheus + targetServer: "http://mockserver.testy.svc.cluster.local:1080" diff --git a/test/integration/analysis-controller-existing-status/01-assert.yaml b/test/integration/analysis-controller-existing-status/01-assert.yaml new file mode 100644 index 0000000000..e3baae607b --- /dev/null +++ b/test/integration/analysis-controller-existing-status/01-assert.yaml @@ -0,0 +1,12 @@ +apiVersion: metrics.keptn.sh/v1alpha3 +kind: Analysis +metadata: + name: analysis-sample + namespace: testy +spec: + analysisDefinition: + name: ed-my-proj-dev-svc1 +status: + pass: true + # yamllint disable-line rule:line-length + raw: '{"objectiveResults":[{"result":{"failResult":{"operator":{"lessThan":{"fixedValue":"2"}},"fulfilled":false},"warnResult":{"operator":{"lessThan":{"fixedValue":"3"}},"fulfilled":false},"warning":false,"pass":true},"value":4,"score":1}],"totalScore":1,"maximumScore":1,"pass":true,"warning":false}' diff --git a/test/integration/analysis-controller-existing-status/01-install.yaml b/test/integration/analysis-controller-existing-status/01-install.yaml new file mode 100644 index 0000000000..cd82cd0761 --- /dev/null +++ b/test/integration/analysis-controller-existing-status/01-install.yaml @@ -0,0 +1,143 @@ +apiVersion: v1 +kind: Service +metadata: + name: mockserver + namespace: testy +spec: + ports: + - name: serviceport + port: 1080 + protocol: TCP + targetPort: serviceport + selector: + app: mockserver + sessionAffinity: None + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: mockserver + name: mockserver + namespace: testy +spec: + replicas: 1 + selector: + matchLabels: + app: mockserver + template: + metadata: + labels: + app: mockserver + name: mockserver + spec: + containers: + - env: + - name: MOCKSERVER_LOG_LEVEL + value: INFO + - name: SERVER_PORT + value: "1080" + image: mockserver/mockserver:mockserver-5.13.0 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 10 + initialDelaySeconds: 10 + periodSeconds: 5 + successThreshold: 1 + tcpSocket: + port: serviceport + timeoutSeconds: 1 + name: mockserver + ports: + - containerPort: 1080 + name: serviceport + protocol: TCP + readinessProbe: + failureThreshold: 10 + initialDelaySeconds: 2 + periodSeconds: 2 + successThreshold: 1 + tcpSocket: + port: serviceport + timeoutSeconds: 1 + volumeMounts: + - mountPath: /config + name: config-volume + - mountPath: /libs + name: libs-volume + terminationGracePeriodSeconds: 30 + volumes: + - configMap: + defaultMode: 420 + name: mockserver-config + optional: true + name: config-volume + - configMap: + defaultMode: 420 + name: mockserver-config + optional: true + name: libs-volume +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: mockserver-config + namespace: testy +data: + initializerJson.json: |- + [ + { + "httpRequest": { + "path": "/api/v1/query_range", + "method": "POST" + }, + "httpResponse": { + "body": { + "status": "success", + "data": { + "resultType": "matrix", + "result": [ + { + "metric": { + "__name__": "metric-name", + "job": "", + "instance": "" + }, + "values": [[1669714193.275, "4"]] + } + ] + } + }, + "statusCode": 200 + } + } + ] + mockserver.properties: |- + ############################### + # MockServer & Proxy Settings # + ############################### + # Socket & Port Settings + # socket timeout in milliseconds (default 120000) + mockserver.maxSocketTimeout=120000 + # Certificate Generation + # dynamically generated CA key pair (if they don't already exist in + specified directory) + mockserver.dynamicallyCreateCertificateAuthorityCertificate=true + # save dynamically generated CA key pair in working directory + mockserver.directoryToSaveDynamicSSLCertificate=. + # certificate domain name (default "localhost") + mockserver.sslCertificateDomainName=localhost + # comma separated list of ip addresses for Subject Alternative Name domain + names (default empty list) + mockserver.sslSubjectAlternativeNameDomains=www.example.com,www.another.com + # comma separated list of ip addresses for Subject Alternative Name ips + (default empty list) + mockserver.sslSubjectAlternativeNameIps=127.0.0.1 + # CORS + # enable CORS for MockServer REST API + mockserver.enableCORSForAPI=true + # enable CORS for all responses + mockserver.enableCORSForAllResponses=true + # Json Initialization + mockserver.initializationJsonPath=/config/initializerJson.json diff --git a/test/integration/analysis-controller-existing-status/02-namespacedelete.yaml b/test/integration/analysis-controller-existing-status/02-namespacedelete.yaml new file mode 100644 index 0000000000..de2c1eecac --- /dev/null +++ b/test/integration/analysis-controller-existing-status/02-namespacedelete.yaml @@ -0,0 +1,4 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl delete ns testy