Skip to content

Commit

Permalink
feat(operator): evaluation controller uses KeptnMetric as SLI provider (
Browse files Browse the repository at this point in the history
#661)

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>
Signed-off-by: realanna <anna.reale@dynatrace.com>
Co-authored-by: realanna <anna.reale@dynatrace.com>
  • Loading branch information
odubajDT and RealAnna committed Jan 20, 2023
1 parent 1ec07ba commit da8fcee
Show file tree
Hide file tree
Showing 13 changed files with 707 additions and 4 deletions.
52 changes: 52 additions & 0 deletions operator/apis/metrics/v1alpha1/keptnmetric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package v1alpha1

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestKeptnMetric_IsStatusSet(t *testing.T) {
tests := []struct {
name string
metric KeptnMetric
want bool
}{
{
name: "results set",
metric: KeptnMetric{
Status: KeptnMetricStatus{
Value: "val",
},
},
want: true,
},
{
name: "value empty",
metric: KeptnMetric{
Status: KeptnMetricStatus{
Value: "",
},
},
want: false,
},
{
name: "value not set",
metric: KeptnMetric{
Status: KeptnMetricStatus{},
},
want: false,
},
{
name: "status not set",
metric: KeptnMetric{},
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require.Equal(t, tt.want, tt.metric.IsStatusSet())
})
}
}
4 changes: 4 additions & 0 deletions operator/apis/metrics/v1alpha1/keptnmetric_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ type KeptnMetricList struct {
func init() {
SchemeBuilder.Register(&KeptnMetric{}, &KeptnMetricList{})
}

func (s KeptnMetric) IsStatusSet() bool {
return s.Status.Value != ""
}
18 changes: 18 additions & 0 deletions operator/controllers/common/providers/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package providers

import (
klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const DynatraceProviderName = "dynatrace"
const PrometheusProviderName = "prometheus"
const KeptnMetricProviderName = "keptn-metric"
const KLTNamespace = "keptn-lifecycle-toolkit-system"

var MetricDefaultProvider = &klcv1alpha2.KeptnEvaluationProvider{
ObjectMeta: metav1.ObjectMeta{
Name: KeptnMetricProviderName,
Namespace: KLTNamespace,
},
}
1 change: 1 addition & 0 deletions operator/controllers/common/providers/dynatrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type DynatraceData struct {
Values []*float64 `json:"values"`
}

// EvaluateQuery fetches the SLI values from dynatrace provider
func (d *KeptnDynatraceProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) {
qURL := provider.Spec.TargetServer + "/api/v2/metrics/query?metricSelector=" + objective.Query

Expand Down
34 changes: 34 additions & 0 deletions operator/controllers/common/providers/keptnmetric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package providers

import (
"context"
"fmt"

"github.com/go-logr/logr"
klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2"
metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/metrics/v1alpha1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type KeptnMetricProvider struct {
Log logr.Logger
k8sClient client.Client
}

// EvaluateQuery fetches the SLI values from KeptnMetric resource
func (p *KeptnMetricProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) {
metric := &metricsv1alpha1.KeptnMetric{}
if err := p.k8sClient.Get(ctx, types.NamespacedName{Name: objective.Name, Namespace: provider.Namespace}, metric); err != nil {
p.Log.Error(err, "Could not retrieve KeptnMetric")
return "", nil, err
}

if !metric.IsStatusSet() {
err := fmt.Errorf("empty value for: %s", metric.Name)
p.Log.Error(err, "KeptnMetric has no value")
return "", nil, err
}

return metric.Status.Value, metric.Status.RawValue, nil
}
93 changes: 93 additions & 0 deletions operator/controllers/common/providers/keptnmetric_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package providers

import (
"context"
"testing"

klcv1alpha2 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha2"
metricsv1alpha1 "github.com/keptn/lifecycle-toolkit/operator/apis/metrics/v1alpha1"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func Test_keptnmetric(t *testing.T) {
tests := []struct {
name string
metric *metricsv1alpha1.KeptnMetric
out string
outraw []byte
wantError bool
}{
{
name: "no KeptnMetric",
metric: &metricsv1alpha1.KeptnMetric{},
out: "",
outraw: []byte(nil),
wantError: true,
},
{
name: "KeptnMetric without results",
metric: &metricsv1alpha1.KeptnMetric{
ObjectMeta: metav1.ObjectMeta{
Name: "metric",
Namespace: "default",
},
},
out: "",
outraw: []byte(nil),
wantError: true,
},
{
name: "KeptnMetric with results",
metric: &metricsv1alpha1.KeptnMetric{
ObjectMeta: metav1.ObjectMeta{
Name: "metric",
Namespace: "default",
},
Status: metricsv1alpha1.KeptnMetricStatus{
Value: "1",
RawValue: []byte("1"),
},
},
out: "1",
outraw: []byte("1"),
wantError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := metricsv1alpha1.AddToScheme(scheme.Scheme)
require.Nil(t, err)
client := fake.NewClientBuilder().WithObjects(tt.metric).Build()

kmp := KeptnMetricProvider{
Log: ctrl.Log.WithName("testytest"),
k8sClient: client,
}

obj := klcv1alpha2.Objective{
Name: "metric",
}

p := klcv1alpha2.KeptnEvaluationProvider{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
Name: "provider",
},
}

r, raw, e := kmp.EvaluateQuery(context.TODO(), obj, p)
require.Equal(t, tt.out, r)
require.Equal(t, tt.outraw, raw)
if tt.wantError != (e != nil) {
t.Errorf("want error: %t, got: %v", tt.wantError, e)
}

})

}
}
1 change: 1 addition & 0 deletions operator/controllers/common/providers/prometheus.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type KeptnPrometheusProvider struct {
httpClient http.Client
}

// EvaluateQuery fetches the SLI values from prometheus provider
func (r *KeptnPrometheusProvider) EvaluateQuery(ctx context.Context, objective klcv1alpha2.Objective, provider klcv1alpha2.KeptnEvaluationProvider) (string, []byte, error) {
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
defer cancel()
Expand Down
9 changes: 7 additions & 2 deletions operator/controllers/common/providers/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,22 @@ type KeptnSLIProvider interface {
// NewProvider is a factory method that chooses the right implementation of KeptnSLIProvider
func NewProvider(provider string, log logr.Logger, k8sClient client.Client) (KeptnSLIProvider, error) {
switch strings.ToLower(provider) {
case "prometheus":
case PrometheusProviderName:
return &KeptnPrometheusProvider{
httpClient: http.Client{},
Log: log,
}, nil
case "dynatrace":
case DynatraceProviderName:
return &KeptnDynatraceProvider{
httpClient: http.Client{},
Log: log,
k8sClient: k8sClient,
}, nil
case KeptnMetricProviderName:
return &KeptnMetricProvider{
Log: log,
k8sClient: k8sClient,
}, nil
default:
return nil, fmt.Errorf("provider %s not supported", provider)
}
Expand Down
9 changes: 7 additions & 2 deletions operator/controllers/common/providers/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,20 @@ func TestFactory(t *testing.T) {
err bool
}{
{
name: "prometheus",
name: PrometheusProviderName,
provider: &KeptnPrometheusProvider{},
err: false,
},
{
name: "dynatrace",
name: DynatraceProviderName,
provider: &KeptnDynatraceProvider{},
err: false,
},
{
name: KeptnMetricProviderName,
provider: &KeptnMetricProvider{},
err: false,
},
{
name: "invalid",
provider: nil,
Expand Down
6 changes: 6 additions & 0 deletions operator/controllers/lifecycle/keptnevaluation/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,16 @@ func (r *KeptnEvaluationReconciler) fetchDefinitionAndProvider(ctx context.Conte
if err := r.Client.Get(ctx, namespacedDefinition, evaluationDefinition); err != nil {
return nil, nil, err
}

if evaluationDefinition.Spec.Source == providers.KeptnMetricProviderName {
return evaluationDefinition, providers.MetricDefaultProvider, nil
}

namespacedProvider := types.NamespacedName{
Namespace: namespacedDefinition.Namespace,
Name: evaluationDefinition.Spec.Source,
}

evaluationProvider := &klcv1alpha2.KeptnEvaluationProvider{}
if err := r.Client.Get(ctx, namespacedProvider, evaluationProvider); err != nil {
return nil, nil, err
Expand Down
Loading

0 comments on commit da8fcee

Please sign in to comment.