Skip to content

Commit

Permalink
feat(metrics-operator): introduce scoring logic for Analysis evaluati…
Browse files Browse the repository at this point in the history
…ons (#1872)

Signed-off-by: odubajDT <ondrej.dubaj@dynatrace.com>
  • Loading branch information
odubajDT committed Aug 21, 2023
1 parent df874e2 commit b6f2172
Show file tree
Hide file tree
Showing 16 changed files with 1,411 additions and 0 deletions.
4 changes: 4 additions & 0 deletions metrics-operator/api/v1alpha3/analysisdefinition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,7 @@ type AnalysisDefinitionList struct {
func init() {
SchemeBuilder.Register(&AnalysisDefinition{}, &AnalysisDefinitionList{})
}

func (o *OperatorValue) GetFloatValue() float64 {
return o.FixedValue.AsApproximateFloat64()
}
16 changes: 16 additions & 0 deletions metrics-operator/api/v1alpha3/analysisdefinition_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package v1alpha3

import (
"testing"

"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/resource"
)

func TestOperatorValue_GetFloatValue(t *testing.T) {
o := OperatorValue{
FixedValue: *resource.NewQuantity(15, resource.DecimalSI),
}

require.Equal(t, 15.0, o.GetFloatValue())
}
53 changes: 53 additions & 0 deletions metrics-operator/controllers/common/analysis/analysis_evaluator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package analysis

import (
"github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/analysis/types"
)

type AnalysisEvaluator struct {
ObjectiveEvaluator IObjectiveEvaluator
}

func NewAnalysisEvaluator(o IObjectiveEvaluator) AnalysisEvaluator {
return AnalysisEvaluator{
ObjectiveEvaluator: o,
}
}

func (ae *AnalysisEvaluator) Evaluate(values map[string]string, ad *v1alpha3.AnalysisDefinition) types.AnalysisResult {
result := types.AnalysisResult{
ObjectiveResults: make([]types.ObjectiveResult, 0, len(ad.Spec.Objectives)),
}

keyObjectiveFailed := false
for _, objective := range ad.Spec.Objectives {
// evaluate a single objective and store it's result
objectiveResult := ae.ObjectiveEvaluator.Evaluate(values, &objective)
result.ObjectiveResults = append(result.ObjectiveResults, objectiveResult)

// count scores
result.MaximumScore += float64(objective.Weight)
result.TotalScore += objectiveResult.Score

//check if the objective was marked as 'key' and if it succeeded
if objectiveResult.IsFail() && objective.KeyObjective {
keyObjectiveFailed = true
}
}

achievedPercentage := result.GetAchievedPercentage()

if achievedPercentage >= float64(ad.Spec.TotalScore.PassPercentage) {
result.Pass = true
} else if achievedPercentage >= float64(ad.Spec.TotalScore.WarningPercentage) {
result.Warning = true
}

if keyObjectiveFailed {
result.Pass = false
result.Warning = false
}

return result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
package analysis

import (
"testing"

"github.com/keptn/lifecycle-toolkit/metrics-operator/api/v1alpha3"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/analysis/fake"
"github.com/keptn/lifecycle-toolkit/metrics-operator/controllers/common/analysis/types"
"github.com/stretchr/testify/require"
)

func TestAnalysisEvaluator_Evaluate(t *testing.T) {
tests := []struct {
name string
values map[string]string
a v1alpha3.AnalysisDefinition
want types.AnalysisResult
mockedEvaluator IObjectiveEvaluator
}{
{
name: "no objectives",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{},
},
},
want: types.AnalysisResult{
TotalScore: 0.0,
MaximumScore: 0.0,
Pass: true,
Warning: false,
ObjectiveResults: []types.ObjectiveResult{},
},
mockedEvaluator: nil,
},
{
name: "pass scenario",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{
{
Weight: 10,
},
},
TotalScore: v1alpha3.TotalScore{
PassPercentage: 80,
WarningPercentage: 50,
},
},
},
want: types.AnalysisResult{
TotalScore: 10.0,
MaximumScore: 10.0,
Pass: true,
Warning: false,
ObjectiveResults: []types.ObjectiveResult{
{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
},
},
},
mockedEvaluator: &fake.IObjectiveEvaluatorMock{
EvaluateFunc: func(values map[string]string, objective *v1alpha3.Objective) types.ObjectiveResult {
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
}
},
},
},
{
name: "pass scenario - multiple objectives",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{
{
Weight: 10,
},
{
Weight: 10,
},
},
TotalScore: v1alpha3.TotalScore{
PassPercentage: 80,
WarningPercentage: 50,
},
},
},
want: types.AnalysisResult{
TotalScore: 20.0,
MaximumScore: 20.0,
Pass: true,
Warning: false,
ObjectiveResults: []types.ObjectiveResult{
{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
},
{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
},
},
},
mockedEvaluator: &fake.IObjectiveEvaluatorMock{
EvaluateFunc: func(values map[string]string, objective *v1alpha3.Objective) types.ObjectiveResult {
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
}
},
},
},
{
name: "warning scenario",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{
{
Weight: 10,
},
},
TotalScore: v1alpha3.TotalScore{
PassPercentage: 80,
WarningPercentage: 50,
},
},
},
want: types.AnalysisResult{
TotalScore: 5.0,
MaximumScore: 10.0,
Pass: false,
Warning: true,
ObjectiveResults: []types.ObjectiveResult{
{
Result: types.TargetResult{},
Value: 5.0,
Score: 5.0,
Error: nil,
},
},
},
mockedEvaluator: &fake.IObjectiveEvaluatorMock{
EvaluateFunc: func(values map[string]string, objective *v1alpha3.Objective) types.ObjectiveResult {
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 5.0,
Error: nil,
}
},
},
},
{
name: "fail scenario",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{
{
Weight: 10,
},
},
TotalScore: v1alpha3.TotalScore{
PassPercentage: 80,
WarningPercentage: 50,
},
},
},
want: types.AnalysisResult{
TotalScore: 0.0,
MaximumScore: 10.0,
Pass: false,
Warning: false,
ObjectiveResults: []types.ObjectiveResult{
{
Result: types.TargetResult{},
Value: 5.0,
Score: 0.0,
Error: nil,
},
},
},
mockedEvaluator: &fake.IObjectiveEvaluatorMock{
EvaluateFunc: func(values map[string]string, objective *v1alpha3.Objective) types.ObjectiveResult {
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 0.0,
Error: nil,
}
},
},
},
{
name: "fail scenario - key objective failed",
values: map[string]string{},
a: v1alpha3.AnalysisDefinition{
Spec: v1alpha3.AnalysisDefinitionSpec{
Objectives: []v1alpha3.Objective{
{
Weight: 10,
KeyObjective: false,
},
{
Weight: 1,
KeyObjective: true,
},
},
TotalScore: v1alpha3.TotalScore{
PassPercentage: 80,
WarningPercentage: 50,
},
},
},
want: types.AnalysisResult{
TotalScore: 10.0,
MaximumScore: 11.0,
Pass: false,
Warning: false,
ObjectiveResults: []types.ObjectiveResult{
{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
},
{
Result: types.TargetResult{},
Value: 5.0,
Score: 0.0,
Error: nil,
},
},
},
mockedEvaluator: &fake.IObjectiveEvaluatorMock{
EvaluateFunc: func(values map[string]string, objective *v1alpha3.Objective) types.ObjectiveResult {
if objective.KeyObjective {
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 0.0,
Error: nil,
}
}
return types.ObjectiveResult{
Result: types.TargetResult{},
Value: 5.0,
Score: 10.0,
Error: nil,
}
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ae := NewAnalysisEvaluator(tt.mockedEvaluator)
require.Equal(t, tt.want, ae.Evaluate(tt.values, &tt.a))
})
}
}
Loading

0 comments on commit b6f2172

Please sign in to comment.