Skip to content

Commit

Permalink
feat(operator): add information about evaluation target in status (#1341
Browse files Browse the repository at this point in the history
)

Signed-off-by: Florian Bacher <florian.bacher@dynatrace.com>
  • Loading branch information
bacherfl committed May 2, 2023
1 parent d576a33 commit cc03a85
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 18 deletions.
34 changes: 18 additions & 16 deletions operator/controllers/lifecycle/keptnevaluation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package keptnevaluation

import (
"context"
"fmt"
"time"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -66,10 +67,6 @@ type KeptnEvaluationReconciler struct {

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the KeptnEvaluation object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.12.2/pkg/reconcile
Expand Down Expand Up @@ -196,17 +193,17 @@ func (r *KeptnEvaluationReconciler) performEvaluation(ctx context.Context, evalu
return evaluation
}

func (r *KeptnEvaluationReconciler) evaluateObjective(ctx context.Context, evaluation *klcv1alpha3.KeptnEvaluation, statusSummary apicommon.StatusSummary, newStatus map[string]klcv1alpha3.EvaluationStatusItem, query klcv1alpha3.Objective, provider *keptnmetric.KeptnMetricProvider) (map[string]klcv1alpha3.EvaluationStatusItem, apicommon.StatusSummary) {
if _, ok := evaluation.Status.EvaluationStatus[query.KeptnMetricRef.Name]; !ok {
evaluation.AddEvaluationStatus(query)
func (r *KeptnEvaluationReconciler) evaluateObjective(ctx context.Context, evaluation *klcv1alpha3.KeptnEvaluation, statusSummary apicommon.StatusSummary, newStatus map[string]klcv1alpha3.EvaluationStatusItem, objective klcv1alpha3.Objective, provider *keptnmetric.KeptnMetricProvider) (map[string]klcv1alpha3.EvaluationStatusItem, apicommon.StatusSummary) {
if _, ok := evaluation.Status.EvaluationStatus[objective.KeptnMetricRef.Name]; !ok {
evaluation.AddEvaluationStatus(objective)
}
if evaluation.Status.EvaluationStatus[query.KeptnMetricRef.Name].Status.IsSucceeded() {
if evaluation.Status.EvaluationStatus[objective.KeptnMetricRef.Name].Status.IsSucceeded() {
statusSummary = apicommon.UpdateStatusSummary(apicommon.StateSucceeded, statusSummary)
newStatus[query.KeptnMetricRef.Name] = evaluation.Status.EvaluationStatus[query.KeptnMetricRef.Name]
newStatus[objective.KeptnMetricRef.Name] = evaluation.Status.EvaluationStatus[objective.KeptnMetricRef.Name]
return newStatus, statusSummary
}
// resolving the SLI value
value, _, err := provider.FetchData(ctx, query, evaluation.Namespace)
value, _, err := provider.FetchData(ctx, objective, evaluation.Namespace)
statusItem := &klcv1alpha3.EvaluationStatusItem{
Value: value,
Status: apicommon.StateFailed,
Expand All @@ -215,16 +212,21 @@ func (r *KeptnEvaluationReconciler) evaluateObjective(ctx context.Context, evalu
statusItem.Message = err.Error()
}
// Evaluating SLO
check, err := checkValue(query, statusItem)
check, err := checkValue(objective, statusItem)
if err != nil {
statusItem.Message = err.Error()
r.Log.Error(err, "Could not check query result")
}
if check {
statusItem.Status = apicommon.StateSucceeded
r.Log.Error(err, "Could not check objective result")
} else {
// if there is no error, we set the message depending on if the value passed the objective, or not
if check {
statusItem.Status = apicommon.StateSucceeded
statusItem.Message = fmt.Sprintf("value '%s' met objective '%s'", value, objective.EvaluationTarget)
} else {
statusItem.Message = fmt.Sprintf("value '%s' did not meet objective '%s'", value, objective.EvaluationTarget)
}
}
statusSummary = apicommon.UpdateStatusSummary(statusItem.Status, statusSummary)
newStatus[query.KeptnMetricRef.Name] = *statusItem
newStatus[objective.KeptnMetricRef.Name] = *statusItem

return newStatus, statusSummary
}
Expand Down
183 changes: 183 additions & 0 deletions operator/controllers/lifecycle/keptnevaluation/controller_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
//nolint:dupl
package keptnevaluation

import (
"context"
"testing"

"github.com/go-logr/logr"
"github.com/go-logr/logr/testr"
metricsapi "github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha2"
klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3"
"github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common"
controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common"
"github.com/keptn/lifecycle-toolkit/operator/controllers/common/fake"
"github.com/keptn/lifecycle-toolkit/operator/controllers/common/providers"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
)

const KltNamespace = "klt-namespace"
Expand Down Expand Up @@ -122,3 +133,175 @@ func setupProviders() (*metricsapi.KeptnMetricsProvider, *metricsapi.KeptnMetric

return DTProv, PromProv
}

func TestKeptnEvaluationReconciler_Reconcile_FailEvaluation(t *testing.T) {

const namespace = "my-namespace"
metric := &metricsapi.KeptnMetric{
ObjectMeta: metav1.ObjectMeta{
Name: "my-metric",
Namespace: namespace,
},
Status: metricsapi.KeptnMetricStatus{
Value: "10",
},
}

evaluationDefinition := &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "my-definition",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationDefinitionSpec{
Objectives: []klcv1alpha3.Objective{
{
KeptnMetricRef: klcv1alpha3.KeptnMetricReference{
Name: metric.Name,
Namespace: namespace,
},
EvaluationTarget: "<5",
},
},
},
}

evaluation := &klcv1alpha3.KeptnEvaluation{
ObjectMeta: metav1.ObjectMeta{
Name: "my-evaluation",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationSpec{
EvaluationDefinition: evaluationDefinition.Name,
Retries: 1,
},
}

reconciler, fakeClient := setupReconcilerAndClient(t, metric, evaluationDefinition, evaluation)

request := controllerruntime.Request{
NamespacedName: types.NamespacedName{
Namespace: namespace,
Name: evaluation.Name,
},
}

reconcile, err := reconciler.Reconcile(context.TODO(), request)

require.Nil(t, err)
require.True(t, reconcile.Requeue)

updatedEvaluation := &klcv1alpha3.KeptnEvaluation{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{
Namespace: namespace,
Name: evaluation.Name,
}, updatedEvaluation)

require.Nil(t, err)

require.Equal(t, common.StateFailed, updatedEvaluation.Status.EvaluationStatus[metric.Name].Status)
require.Equal(t, "value '10' did not meet objective '<5'", updatedEvaluation.Status.EvaluationStatus[metric.Name].Message)
}

func TestKeptnEvaluationReconciler_Reconcile_SucceedEvaluation(t *testing.T) {

const namespace = "my-namespace"
metric := &metricsapi.KeptnMetric{
ObjectMeta: metav1.ObjectMeta{
Name: "my-metric",
Namespace: namespace,
},
Status: metricsapi.KeptnMetricStatus{
Value: "10",
},
}

evaluationDefinition := &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "my-definition",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationDefinitionSpec{
Objectives: []klcv1alpha3.Objective{
{
KeptnMetricRef: klcv1alpha3.KeptnMetricReference{
Name: metric.Name,
Namespace: namespace,
},
EvaluationTarget: "<11",
},
},
},
}

evaluation := &klcv1alpha3.KeptnEvaluation{
ObjectMeta: metav1.ObjectMeta{
Name: "my-evaluation",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationSpec{
EvaluationDefinition: evaluationDefinition.Name,
Retries: 1,
},
}

reconciler, fakeClient := setupReconcilerAndClient(t, metric, evaluationDefinition, evaluation)

request := controllerruntime.Request{
NamespacedName: types.NamespacedName{
Namespace: namespace,
Name: evaluation.Name,
},
}

reconcile, err := reconciler.Reconcile(context.TODO(), request)

require.Nil(t, err)
require.False(t, reconcile.Requeue)

updatedEvaluation := &klcv1alpha3.KeptnEvaluation{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{
Namespace: namespace,
Name: evaluation.Name,
}, updatedEvaluation)

require.Nil(t, err)

require.Equal(t, common.StateSucceeded, updatedEvaluation.Status.EvaluationStatus[metric.Name].Status)
require.Equal(t, "value '10' met objective '<11'", updatedEvaluation.Status.EvaluationStatus[metric.Name].Message)
}

func setupReconcilerAndClient(t *testing.T, objects ...client.Object) (*KeptnEvaluationReconciler, client.Client) {
scheme := runtime.NewScheme()

err := klcv1alpha3.AddToScheme(scheme)
require.Nil(t, err)

err = metricsapi.AddToScheme(scheme)
require.Nil(t, err)

// fake a tracer
tr := &fake.ITracerMock{StartFunc: func(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, trace.Span) {
return ctx, trace.SpanFromContext(ctx)
}}

tf := &fake.TracerFactoryMock{GetTracerFunc: func(name string) trace.Tracer {
return tr
}}

recorder := record.NewFakeRecorder(100)

fakeClient := k8sfake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build()

provider := metric.NewMeterProvider()
meter := provider.Meter("keptn/task")

r := &KeptnEvaluationReconciler{
Client: fakeClient,
Scheme: fakeClient.Scheme(),
Log: logr.Logger{},
Recorder: recorder,
Meters: controllercommon.SetUpKeptnTaskMeters(meter),
TracerFactory: tf,
}
return r, fakeClient
}
5 changes: 3 additions & 2 deletions operator/test/component/evaluation/evaluation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,9 @@ var _ = Describe("Evaluation", Ordered, func() {
g.Expect(evaluation2.Status.OverallStatus).To(Equal(apicommon.StateSucceeded))
g.Expect(evaluation2.Status.EvaluationStatus).To(Equal(map[string]klcv1alpha3.EvaluationStatusItem{
metricName: {
Value: "5",
Status: apicommon.StateSucceeded,
Value: "5",
Status: apicommon.StateSucceeded,
Message: "value '5' met objective '<10'",
},
}))
}, "30s").Should(Succeed())
Expand Down

0 comments on commit cc03a85

Please sign in to comment.