Skip to content

Commit

Permalink
feat(operator): introduce fallback search to KLT default namespace wh…
Browse files Browse the repository at this point in the history
…en KeptnEvaluationDefinition is not found (#1359)

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>
  • Loading branch information
odubajDT committed May 3, 2023
1 parent 737d478 commit d5ddf26
Show file tree
Hide file tree
Showing 7 changed files with 241 additions and 110 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,8 @@ In its state, it keeps track of the current status of the K8s Job created.

A `KeptnEvaluationDefinition` is a CRD used to define evaluation tasks that can be run by the Keptn Lifecycle Toolkit
as part of pre- and post-analysis phases of a workload or application.
`KeptnEvaluationDefinition` resource can be created in the namespace where the application is running, or
in the default KLT namespace, which will be the fallback option for the system to search.

A KeptnEvaluationDefinition looks like the following:

Expand Down
2 changes: 2 additions & 0 deletions docs/content/en/docs/concepts/evaluations/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ hidechildren: true # this flag hides all sub-pages in the sidebar-multicard.html

A `KeptnEvaluationDefinition` is a CRD used to define evaluation tasks that can be run by the Keptn Lifecycle Toolkit
as part of pre- and post-analysis phases of a workload or application.
`KeptnEvaluationDefinition` resource can be created in the namespace where the application is running, or
in the default KLT namespace, which will be the fallback option for the system to search.

A Keptn evaluation definition looks like the following:

Expand Down
21 changes: 19 additions & 2 deletions operator/controllers/common/helperfunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,27 @@ func GetTaskDefinition(k8sclient client.Client, log logr.Logger, ctx context.Con
definition := &klcv1alpha3.KeptnTaskDefinition{}
err := k8sclient.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: namespace}, definition)
if err != nil {
log.Error(err, "Failed to get KeptnTaskDefinition from application namespace")
log.Info("Failed to get KeptnTaskDefinition from application namespace", "KeptnTaskDefinition", definitionName, "namespace", namespace)
if k8serrors.IsNotFound(err) {
if err := k8sclient.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: KLTNamespace}, definition); err != nil {
log.Error(err, "Failed to get KeptnTaskDefinition from default KLT namespace")
log.Info("Failed to get KeptnTaskDefinition from default KLT namespace", "KeptnTaskDefinition", definitionName)
return nil, err
}
return definition, nil
}
return nil, err
}
return definition, nil
}

func GetEvaluationDefinition(k8sclient client.Client, log logr.Logger, ctx context.Context, definitionName string, namespace string) (*klcv1alpha3.KeptnEvaluationDefinition, error) {
definition := &klcv1alpha3.KeptnEvaluationDefinition{}
err := k8sclient.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: namespace}, definition)
if err != nil {
log.Info("Failed to get KeptnEvaluationDefinition from application namespace", "KeptnEvaluationDefinition", definitionName, "namespace", namespace)
if k8serrors.IsNotFound(err) {
if err := k8sclient.Get(ctx, types.NamespacedName{Name: definitionName, Namespace: KLTNamespace}, definition); err != nil {
log.Info("Failed to get KeptnEvaluationDefinition from default KLT namespace", "KeptnEvaluationDefinition", definitionName)
return nil, err
}
return definition, nil
Expand Down
83 changes: 83 additions & 0 deletions operator/controllers/common/helperfunctions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ func Test_setAnnotations(t *testing.T) {
}
}

//nolint:dupl
func Test_GetTaskDefinition(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -487,3 +488,85 @@ func Test_GetTaskDefinition(t *testing.T) {
})
}
}

//nolint:dupl
func Test_GetEvaluationDefinition(t *testing.T) {
tests := []struct {
name string
evalDef *klcv1alpha3.KeptnEvaluationDefinition
evalDefName string
evalDefNamespace string
out *klcv1alpha3.KeptnEvaluationDefinition
wantError bool
}{
{
name: "evalDef not found",
evalDef: &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "evalDef",
Namespace: "some-other-namespace",
},
},
evalDefName: "evalDef",
evalDefNamespace: "some-namespace",
out: nil,
wantError: true,
},
{
name: "evalDef found",
evalDef: &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "evalDef",
Namespace: "some-namespace",
},
},
evalDefName: "evalDef",
evalDefNamespace: "some-namespace",
out: &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "evalDef",
Namespace: "some-namespace",
},
},
wantError: false,
},
{
name: "evalDef found in default KLT namespace",
evalDef: &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "evalDef",
Namespace: KLTNamespace,
},
},
evalDefName: "evalDef",
evalDefNamespace: "some-namespace",
out: &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: v1.ObjectMeta{
Name: "evalDef",
Namespace: KLTNamespace,
},
},
wantError: false,
},
}

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

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := fake.NewClientBuilder().WithObjects(tt.evalDef).Build()
d, err := GetEvaluationDefinition(client, ctrl.Log.WithName("testytest"), context.TODO(), tt.evalDefName, tt.evalDefNamespace)
if tt.out != nil && d != nil {
require.Equal(t, tt.out.Name, d.Name)
require.Equal(t, tt.out.Namespace, d.Namespace)
} else if tt.out != d {
t.Errorf("want: %v, got: %v", tt.out, d)
}
if tt.wantError != (err != nil) {
t.Errorf("want error: %t, got: %v", tt.wantError, err)
}

})
}
}
16 changes: 1 addition & 15 deletions operator/controllers/lifecycle/keptnevaluation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (
"go.opentelemetry.io/otel/trace"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand Down Expand Up @@ -94,11 +93,7 @@ func (r *KeptnEvaluationReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}

if !evaluation.Status.OverallStatus.IsSucceeded() {
namespacedDefinition := types.NamespacedName{
Namespace: req.NamespacedName.Namespace,
Name: evaluation.Spec.EvaluationDefinition,
}
evaluationDefinition, err := r.fetchDefinition(ctx, namespacedDefinition)
evaluationDefinition, err := controllercommon.GetEvaluationDefinition(r.Client, r.Log, ctx, evaluation.Spec.EvaluationDefinition, req.NamespacedName.Namespace)
if err != nil {
if errors.IsNotFound(err) {
r.Log.Info(err.Error() + ", ignoring error since object must be deleted")
Expand Down Expand Up @@ -261,15 +256,6 @@ func (r *KeptnEvaluationReconciler) SetupWithManager(mgr ctrl.Manager) error {
Complete(r)
}

func (r *KeptnEvaluationReconciler) fetchDefinition(ctx context.Context, namespacedDefinition types.NamespacedName) (*klcv1alpha3.KeptnEvaluationDefinition, error) {
evaluationDefinition := &klcv1alpha3.KeptnEvaluationDefinition{}
if err := r.Client.Get(ctx, namespacedDefinition, evaluationDefinition); err != nil {
return nil, err
}

return evaluationDefinition, nil
}

func (r *KeptnEvaluationReconciler) getTracer() controllercommon.ITracer {
return r.TracerFactory.GetTracer(traceComponentName)
}
145 changes: 52 additions & 93 deletions operator/controllers/lifecycle/keptnevaluation/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import (
"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"
Expand All @@ -27,114 +25,75 @@ import (

const KltNamespace = "klt-namespace"

func TestKeptnEvaluationReconciler_fetchDefinition(t *testing.T) {

metricEvalDef, EvalDef := setupEvalDefinitions()
DTProv, PromProv := setupProviders()
client := fake.NewClient(metricEvalDef, EvalDef, DTProv, PromProv)

r := &KeptnEvaluationReconciler{
Client: client,
Scheme: client.Scheme(),
Log: testr.New(t),
}

tests := []struct {
name string
namespacedDefinition types.NamespacedName
wantDef *klcv1alpha3.KeptnEvaluationDefinition
wantErr bool
}{
{
name: "keptn metrics",
namespacedDefinition: types.NamespacedName{
Namespace: KltNamespace,
Name: "myKeptn",
},
wantDef: metricEvalDef,
},
func TestKeptnEvaluationReconciler_Reconcile_FailEvaluation(t *testing.T) {

{
name: "Unexisting Evaluation Def",
namespacedDefinition: types.NamespacedName{
Namespace: KltNamespace,
Name: "whatever",
},
wantDef: nil,
wantErr: true,
const namespace = "my-namespace"
metric := &metricsapi.KeptnMetric{
ObjectMeta: metav1.ObjectMeta{
Name: "my-metric",
Namespace: namespace,
},
{
name: "Unexisting Provider",
namespacedDefinition: types.NamespacedName{
Namespace: KltNamespace,
Name: "mydef",
},
wantDef: nil,
wantErr: true,
Status: metricsapi.KeptnMetricStatus{
Value: "10",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

got, err := r.fetchDefinition(context.TODO(), tt.namespacedDefinition)
if (err != nil) != tt.wantErr {
t.Errorf("fetchDefinitionAndProvider() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.wantDef != nil {
require.Equal(t, got.Name, tt.wantDef.Name)
} else {
require.Nil(t, got)
}
})
}
}

func setupEvalDefinitions() (*klcv1alpha3.KeptnEvaluationDefinition, *klcv1alpha3.KeptnEvaluationDefinition) {
metricEvalDef := &klcv1alpha3.KeptnEvaluationDefinition{
evaluationDefinition := &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: metav1.ObjectMeta{
Namespace: KltNamespace,
Name: "myKeptn",
Name: "my-definition",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationDefinitionSpec{
Objectives: nil,
Objectives: []klcv1alpha3.Objective{
{
KeptnMetricRef: klcv1alpha3.KeptnMetricReference{
Name: metric.Name,
Namespace: namespace,
},
EvaluationTarget: "<5",
},
},
},
Status: klcv1alpha3.KeptnEvaluationDefinitionStatus{},
}

EvalDef := &klcv1alpha3.KeptnEvaluationDefinition{
evaluation := &klcv1alpha3.KeptnEvaluation{
ObjectMeta: metav1.ObjectMeta{
Namespace: KltNamespace,
Name: "mdef",
Name: "my-evaluation",
Namespace: namespace,
},
Spec: klcv1alpha3.KeptnEvaluationDefinitionSpec{
Objectives: nil,
Spec: klcv1alpha3.KeptnEvaluationSpec{
EvaluationDefinition: evaluationDefinition.Name,
Retries: 1,
},
Status: klcv1alpha3.KeptnEvaluationDefinitionStatus{},
}

return metricEvalDef, EvalDef
}
reconciler, fakeClient := setupReconcilerAndClient(t, metric, evaluationDefinition, evaluation)

func setupProviders() (*metricsapi.KeptnMetricsProvider, *metricsapi.KeptnMetricsProvider) {
DTProv := &metricsapi.KeptnMetricsProvider{
ObjectMeta: metav1.ObjectMeta{
Name: providers.DynatraceProviderName,
Namespace: KltNamespace,
request := controllerruntime.Request{
NamespacedName: types.NamespacedName{
Namespace: namespace,
Name: evaluation.Name,
},
}

PromProv := &metricsapi.KeptnMetricsProvider{
ObjectMeta: metav1.ObjectMeta{
Name: providers.PrometheusProviderName,
Namespace: KltNamespace,
},
}
reconcile, err := reconciler.Reconcile(context.TODO(), request)

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

return DTProv, PromProv
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_FailEvaluation(t *testing.T) {
func TestKeptnEvaluationReconciler_Reconcile_SucceedEvaluation(t *testing.T) {

const namespace = "my-namespace"
metric := &metricsapi.KeptnMetric{
Expand All @@ -159,7 +118,7 @@ func TestKeptnEvaluationReconciler_Reconcile_FailEvaluation(t *testing.T) {
Name: metric.Name,
Namespace: namespace,
},
EvaluationTarget: "<5",
EvaluationTarget: "<11",
},
},
},
Expand Down Expand Up @@ -188,7 +147,7 @@ func TestKeptnEvaluationReconciler_Reconcile_FailEvaluation(t *testing.T) {
reconcile, err := reconciler.Reconcile(context.TODO(), request)

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

updatedEvaluation := &klcv1alpha3.KeptnEvaluation{}
err = fakeClient.Get(context.TODO(), types.NamespacedName{
Expand All @@ -198,11 +157,11 @@ func TestKeptnEvaluationReconciler_Reconcile_FailEvaluation(t *testing.T) {

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)
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 TestKeptnEvaluationReconciler_Reconcile_SucceedEvaluation(t *testing.T) {
func TestKeptnEvaluationReconciler_Reconcile_SucceedEvaluation_withDefinitionInDefaultKLTNamespace(t *testing.T) {

const namespace = "my-namespace"
metric := &metricsapi.KeptnMetric{
Expand All @@ -218,7 +177,7 @@ func TestKeptnEvaluationReconciler_Reconcile_SucceedEvaluation(t *testing.T) {
evaluationDefinition := &klcv1alpha3.KeptnEvaluationDefinition{
ObjectMeta: metav1.ObjectMeta{
Name: "my-definition",
Namespace: namespace,
Namespace: controllercommon.KLTNamespace,
},
Spec: klcv1alpha3.KeptnEvaluationDefinitionSpec{
Objectives: []klcv1alpha3.Objective{
Expand Down
Loading

0 comments on commit d5ddf26

Please sign in to comment.