Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(operator): evaluation controller uses KeptnMetric as SLI provider #661

Merged
merged 9 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
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
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
}

odubajDT marked this conversation as resolved.
Show resolved Hide resolved
if evaluationDefinition.Spec.Source == providers.KeptnMetricProviderName {
odubajDT marked this conversation as resolved.
Show resolved Hide resolved
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