Alerting: Refactor State Transition (part 2 of n) (#99985)
* split create to create and patch and move to state patch will be refactored further * move setNextState to state transition * move tests * split tests for patch function
This commit is contained in:
@@ -3,15 +3,11 @@ package state
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@@ -118,313 +114,6 @@ func Test_expand(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_create(t *testing.T) {
|
||||
url := &url.URL{
|
||||
Scheme: "http",
|
||||
Host: "localhost:3000",
|
||||
Path: "/test",
|
||||
}
|
||||
l := log.New("test")
|
||||
c := newCache()
|
||||
|
||||
gen := models.RuleGen
|
||||
generateRule := gen.With(gen.WithNotEmptyLabels(5, "rule-")).GenerateRef
|
||||
|
||||
t.Run("should combine all labels", func(t *testing.T) {
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(5, "extra-")
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
for key, expected := range extraLabels {
|
||||
require.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
assert.Len(t, state.Labels, len(extraLabels)+len(rule.Labels)+len(result.Instance))
|
||||
for key, expected := range extraLabels {
|
||||
assert.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
for key, expected := range rule.Labels {
|
||||
assert.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
for key, expected := range result.Instance {
|
||||
assert.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
})
|
||||
t.Run("extra labels should take precedence over rule and result labels", func(t *testing.T) {
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
for key := range extraLabels {
|
||||
rule.Labels[key] = "rule-" + util.GenerateShortUID()
|
||||
result.Instance[key] = "result-" + util.GenerateShortUID()
|
||||
}
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
for key, expected := range extraLabels {
|
||||
require.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
})
|
||||
t.Run("rule labels should take precedence over result labels", func(t *testing.T) {
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
for key := range rule.Labels {
|
||||
result.Instance[key] = "result-" + util.GenerateShortUID()
|
||||
}
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
for key, expected := range rule.Labels {
|
||||
require.Equal(t, expected, state.Labels[key])
|
||||
}
|
||||
})
|
||||
t.Run("rule labels should be able to be expanded with result and extra labels", func(t *testing.T) {
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
labelTemplates := make(data.Labels)
|
||||
for key := range extraLabels {
|
||||
labelTemplates["rule-"+key] = fmt.Sprintf("{{ with (index .Labels \"%s\") }}{{.}}{{end}}", key)
|
||||
}
|
||||
for key := range result.Instance {
|
||||
labelTemplates["rule-"+key] = fmt.Sprintf("{{ with (index .Labels \"%s\") }}{{.}}{{end}}", key)
|
||||
}
|
||||
rule.Labels = labelTemplates
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
for key, expected := range extraLabels {
|
||||
assert.Equal(t, expected, state.Labels["rule-"+key])
|
||||
}
|
||||
for key, expected := range result.Instance {
|
||||
assert.Equal(t, expected, state.Labels["rule-"+key])
|
||||
}
|
||||
})
|
||||
t.Run("rule annotations should be able to be expanded with result and extra labels", func(t *testing.T) {
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
annotationTemplates := make(data.Labels)
|
||||
for key := range extraLabels {
|
||||
annotationTemplates["rule-"+key] = fmt.Sprintf("{{ with (index .Labels \"%s\") }}{{.}}{{end}}", key)
|
||||
}
|
||||
for key := range result.Instance {
|
||||
annotationTemplates["rule-"+key] = fmt.Sprintf("{{ with (index .Labels \"%s\") }}{{.}}{{end}}", key)
|
||||
}
|
||||
rule.Annotations = annotationTemplates
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
for key, expected := range extraLabels {
|
||||
assert.Equal(t, expected, state.Annotations["rule-"+key])
|
||||
}
|
||||
for key, expected := range result.Instance {
|
||||
assert.Equal(t, expected, state.Annotations["rule-"+key])
|
||||
}
|
||||
})
|
||||
t.Run("when result labels collide with system labels from LabelsUserCannotSpecify", func(t *testing.T) {
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
m := models.LabelsUserCannotSpecify
|
||||
t.Cleanup(func() {
|
||||
models.LabelsUserCannotSpecify = m
|
||||
})
|
||||
|
||||
models.LabelsUserCannotSpecify = map[string]struct{}{
|
||||
"__label1__": {},
|
||||
"label2__": {},
|
||||
"__label3": {},
|
||||
"label4": {},
|
||||
}
|
||||
result.Instance["__label1__"] = uuid.NewString()
|
||||
result.Instance["label2__"] = uuid.NewString()
|
||||
result.Instance["__label3"] = uuid.NewString()
|
||||
result.Instance["label4"] = uuid.NewString()
|
||||
|
||||
rule := generateRule()
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, nil, url)
|
||||
|
||||
for key := range models.LabelsUserCannotSpecify {
|
||||
assert.NotContains(t, state.Labels, key)
|
||||
}
|
||||
assert.Contains(t, state.Labels, "label1")
|
||||
assert.Equal(t, state.Labels["label1"], result.Instance["__label1__"])
|
||||
|
||||
assert.Contains(t, state.Labels, "label2")
|
||||
assert.Equal(t, state.Labels["label2"], result.Instance["label2__"])
|
||||
|
||||
assert.Contains(t, state.Labels, "label3")
|
||||
assert.Equal(t, state.Labels["label3"], result.Instance["__label3"])
|
||||
|
||||
assert.Contains(t, state.Labels, "label4_user")
|
||||
assert.Equal(t, state.Labels["label4_user"], result.Instance["label4"])
|
||||
|
||||
t.Run("should drop label if renamed collides with existing", func(t *testing.T) {
|
||||
result.Instance["label1"] = uuid.NewString()
|
||||
result.Instance["label1_user"] = uuid.NewString()
|
||||
result.Instance["label4_user"] = uuid.NewString()
|
||||
|
||||
state = c.create(context.Background(), l, rule, result, nil, url)
|
||||
assert.NotContains(t, state.Labels, "__label1__")
|
||||
assert.Contains(t, state.Labels, "label1")
|
||||
assert.Equal(t, state.Labels["label1"], result.Instance["label1"])
|
||||
assert.Equal(t, state.Labels["label1_user"], result.Instance["label1_user"])
|
||||
|
||||
assert.NotContains(t, state.Labels, "label4")
|
||||
assert.Equal(t, state.Labels["label4_user"], result.Instance["label4_user"])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("creates a state with preset fields if there is no current state", func(t *testing.T) {
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
|
||||
expectedLbl, expectedAnn := expandAnnotationsAndLabels(context.Background(), l, rule, result, extraLabels, url)
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
|
||||
assert.Equal(t, rule.OrgID, state.OrgID)
|
||||
assert.Equal(t, rule.UID, state.AlertRuleUID)
|
||||
assert.Equal(t, state.Labels.Fingerprint(), state.CacheID)
|
||||
assert.Equal(t, result.State, state.State)
|
||||
assert.Equal(t, "", state.StateReason)
|
||||
assert.Equal(t, result.Instance.Fingerprint(), state.ResultFingerprint)
|
||||
assert.Nil(t, state.LatestResult)
|
||||
assert.Nil(t, state.Error)
|
||||
assert.Nil(t, state.Image)
|
||||
assert.EqualValues(t, expectedAnn, state.Annotations)
|
||||
assert.EqualValues(t, expectedLbl, state.Labels)
|
||||
assert.Nil(t, state.Values)
|
||||
assert.Equal(t, result.EvaluatedAt, state.StartsAt)
|
||||
assert.Equal(t, result.EvaluatedAt, state.EndsAt)
|
||||
assert.Nil(t, state.ResolvedAt)
|
||||
assert.Nil(t, state.LastSentAt)
|
||||
assert.Equal(t, "", state.LastEvaluationString)
|
||||
assert.Equal(t, result.EvaluatedAt, state.LastEvaluationTime)
|
||||
assert.Equal(t, result.EvaluationDuration, state.EvaluationDuration)
|
||||
})
|
||||
|
||||
t.Run("it populates some fields from the current state if it exists", func(t *testing.T) {
|
||||
rule := generateRule()
|
||||
|
||||
extraLabels := models.GenerateAlertLabels(2, "extra-")
|
||||
|
||||
result := eval.Result{
|
||||
Instance: models.GenerateAlertLabels(5, "result-"),
|
||||
}
|
||||
|
||||
expectedLbl, expectedAnn := expandAnnotationsAndLabels(context.Background(), l, rule, result, extraLabels, url)
|
||||
|
||||
current := randomSate(rule.GetKey())
|
||||
current.CacheID = expectedLbl.Fingerprint()
|
||||
|
||||
c.set(¤t)
|
||||
|
||||
state := c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
|
||||
assert.Equal(t, rule.OrgID, state.OrgID)
|
||||
assert.Equal(t, rule.UID, state.AlertRuleUID)
|
||||
assert.Equal(t, state.Labels.Fingerprint(), state.CacheID)
|
||||
assert.Equal(t, result.Instance.Fingerprint(), state.ResultFingerprint)
|
||||
assert.EqualValues(t, expectedAnn, state.Annotations)
|
||||
assert.EqualValues(t, expectedLbl, state.Labels)
|
||||
assert.Equal(t, result.EvaluatedAt, state.LastEvaluationTime)
|
||||
assert.Equal(t, result.EvaluationDuration, state.EvaluationDuration)
|
||||
|
||||
assert.Equal(t, current.State, state.State)
|
||||
assert.Equal(t, current.StateReason, state.StateReason)
|
||||
assert.Equal(t, current.Image, state.Image)
|
||||
assert.Equal(t, current.LatestResult, state.LatestResult)
|
||||
assert.Equal(t, current.Error, state.Error)
|
||||
assert.Equal(t, current.Values, state.Values)
|
||||
assert.Equal(t, current.StartsAt, state.StartsAt)
|
||||
assert.Equal(t, current.EndsAt, state.EndsAt)
|
||||
assert.Equal(t, current.ResolvedAt, state.ResolvedAt)
|
||||
assert.Equal(t, current.LastSentAt, state.LastSentAt)
|
||||
assert.Equal(t, current.LastEvaluationString, state.LastEvaluationString)
|
||||
|
||||
t.Run("if result Error and current state is Error it should copy datasource_uid and ref_id labels", func(t *testing.T) {
|
||||
current = randomSate(rule.GetKey())
|
||||
current.CacheID = expectedLbl.Fingerprint()
|
||||
current.State = eval.Error
|
||||
current.Labels["datasource_uid"] = util.GenerateShortUID()
|
||||
current.Labels["ref_id"] = util.GenerateShortUID()
|
||||
|
||||
c.set(¤t)
|
||||
|
||||
result.State = eval.Error
|
||||
state = c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
|
||||
l := expectedLbl.Copy()
|
||||
l["datasource_uid"] = current.Labels["datasource_uid"]
|
||||
l["ref_id"] = current.Labels["ref_id"]
|
||||
|
||||
assert.Equal(t, current.CacheID, state.CacheID)
|
||||
assert.EqualValues(t, l, state.Labels)
|
||||
|
||||
assert.Equal(t, rule.OrgID, state.OrgID)
|
||||
assert.Equal(t, rule.UID, state.AlertRuleUID)
|
||||
|
||||
assert.Equal(t, result.Instance.Fingerprint(), state.ResultFingerprint)
|
||||
assert.EqualValues(t, expectedAnn, state.Annotations)
|
||||
assert.Equal(t, result.EvaluatedAt, state.LastEvaluationTime)
|
||||
assert.Equal(t, result.EvaluationDuration, state.EvaluationDuration)
|
||||
|
||||
assert.Equal(t, current.State, state.State)
|
||||
assert.Equal(t, current.StateReason, state.StateReason)
|
||||
assert.Equal(t, current.Image, state.Image)
|
||||
assert.Equal(t, current.LatestResult, state.LatestResult)
|
||||
assert.Equal(t, current.Error, state.Error)
|
||||
assert.Equal(t, current.Values, state.Values)
|
||||
assert.Equal(t, current.StartsAt, state.StartsAt)
|
||||
assert.Equal(t, current.EndsAt, state.EndsAt)
|
||||
assert.Equal(t, current.ResolvedAt, state.ResolvedAt)
|
||||
assert.Equal(t, current.LastSentAt, state.LastSentAt)
|
||||
assert.Equal(t, current.LastEvaluationString, state.LastEvaluationString)
|
||||
})
|
||||
t.Run("copies system-owned annotations from current state", func(t *testing.T) {
|
||||
current = randomSate(rule.GetKey())
|
||||
current.CacheID = expectedLbl.Fingerprint()
|
||||
current.State = eval.Error
|
||||
for key := range models.InternalAnnotationNameSet {
|
||||
current.Annotations[key] = util.GenerateShortUID()
|
||||
}
|
||||
c.set(¤t)
|
||||
|
||||
result.State = eval.Error
|
||||
state = c.create(context.Background(), l, rule, result, extraLabels, url)
|
||||
ann := expectedAnn.Copy()
|
||||
for key := range models.InternalAnnotationNameSet {
|
||||
ann[key] = current.Annotations[key]
|
||||
}
|
||||
assert.EqualValues(t, expectedLbl, state.Labels)
|
||||
assert.EqualValues(t, ann, state.Annotations)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_mergeLabels(t *testing.T) {
|
||||
t.Run("merges two maps", func(t *testing.T) {
|
||||
a := models.GenerateAlertLabels(5, "set1-")
|
||||
|
||||
Reference in New Issue
Block a user