mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Fix k8s api route fingerprint to include ObjectMatchers and Provenance (#96591)
Fix route fingerprint to include ObjectMatchers and Provenance
This commit is contained in:
parent
9223cd8f59
commit
65097d4b54
@ -259,6 +259,9 @@ func writeToHash(sum hash.Hash, r *definitions.Route) {
|
||||
for _, matcher := range r.Matchers {
|
||||
writeString(matcher.String())
|
||||
}
|
||||
for _, matcher := range r.ObjectMatchers {
|
||||
writeString(matcher.String())
|
||||
}
|
||||
for _, timeInterval := range r.MuteTimeIntervals {
|
||||
writeString(timeInterval)
|
||||
}
|
||||
@ -269,6 +272,7 @@ func writeToHash(sum hash.Hash, r *definitions.Route) {
|
||||
writeDuration(r.GroupWait)
|
||||
writeDuration(r.GroupInterval)
|
||||
writeDuration(r.RepeatInterval)
|
||||
writeString(string(r.Provenance))
|
||||
for _, route := range r.Routes {
|
||||
writeToHash(sum, route)
|
||||
}
|
||||
|
@ -2,11 +2,16 @@ package provisioning
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@ -22,13 +27,14 @@ import (
|
||||
func TestGetPolicyTree(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
rev := getDefaultConfigRevision()
|
||||
expectedVersion := calculateRouteFingerprint(*rev.Config.AlertmanagerConfig.Route)
|
||||
expectedRoute := *rev.Config.AlertmanagerConfig.Route
|
||||
expectedRoute.Provenance = definitions.Provenance(models.ProvenanceAPI)
|
||||
expectedVersion := calculateRouteFingerprint(expectedRoute)
|
||||
|
||||
sut, store, prov := createNotificationPolicyServiceSut()
|
||||
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||
return &rev, nil
|
||||
}
|
||||
expectedProvenance := models.ProvenanceAPI
|
||||
prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) {
|
||||
return models.ProvenanceAPI, nil
|
||||
}
|
||||
@ -36,11 +42,9 @@ func TestGetPolicyTree(t *testing.T) {
|
||||
tree, version, err := sut.GetPolicyTree(context.Background(), orgID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedRoute := *rev.Config.AlertmanagerConfig.Route
|
||||
expectedRoute.Provenance = definitions.Provenance(models.ProvenanceAPI)
|
||||
assert.Equal(t, expectedRoute, tree)
|
||||
assert.Equal(t, expectedVersion, version)
|
||||
assert.Equal(t, expectedProvenance, models.Provenance(tree.Provenance))
|
||||
assert.Equal(t, expectedRoute.Provenance, tree.Provenance)
|
||||
|
||||
assert.Len(t, store.Calls, 1)
|
||||
assert.Equal(t, "Get", store.Calls[0].Method)
|
||||
@ -334,6 +338,119 @@ func TestResetPolicyTree(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoute_Fingerprint(t *testing.T) {
|
||||
// Test that the fingerprint is stable.
|
||||
mustRegex := func(rg string) config.Regexp {
|
||||
var regex config.Regexp
|
||||
require.NoError(t, json.Unmarshal([]byte(rg), ®ex))
|
||||
return regex
|
||||
}
|
||||
mustMatcher := func(t *testing.T, mt labels.MatchType, lbl, val string) *labels.Matcher {
|
||||
m, err := labels.NewMatcher(mt, lbl, val)
|
||||
require.NoError(t, err)
|
||||
return m
|
||||
}
|
||||
baseRouteGen := func() definitions.Route {
|
||||
return definitions.Route{
|
||||
Receiver: "Receiver",
|
||||
GroupByStr: []string{"GroupByStr1", "GroupByStr2"},
|
||||
GroupBy: []model.LabelName{
|
||||
"...",
|
||||
},
|
||||
GroupByAll: true,
|
||||
Match: map[string]string{"Match1": "MatchValue1", "Match2": "MatchValue2"},
|
||||
MatchRE: map[string]config.Regexp{
|
||||
"MatchRE": mustRegex(`".*"`),
|
||||
},
|
||||
Matchers: config.Matchers{
|
||||
mustMatcher(t, labels.MatchNotEqual, "Matchers1", "Matchers1Value"),
|
||||
mustMatcher(t, labels.MatchEqual, "Matchers2", "Matchers2Value"),
|
||||
mustMatcher(t, labels.MatchRegexp, "Matchers3", "Matchers3Value"),
|
||||
},
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
mustMatcher(t, labels.MatchNotRegexp, "ObjectMatchers1", "ObjectMatchers1Value"),
|
||||
mustMatcher(t, labels.MatchRegexp, "ObjectMatchers2", "ObjectMatchers2Value"),
|
||||
},
|
||||
MuteTimeIntervals: []string{"MuteTimeIntervals1", "MuteTimeIntervals2"},
|
||||
ActiveTimeIntervals: []string{"ActiveTimeIntervals1", "ActiveTimeIntervals2"},
|
||||
Continue: true,
|
||||
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
|
||||
GroupInterval: util.Pointer(model.Duration(5 * time.Minute)),
|
||||
RepeatInterval: util.Pointer(model.Duration(30 * time.Hour)),
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
Routes: nil, // Nested routes are not included in the fingerprint test for simplicity.
|
||||
}
|
||||
}
|
||||
|
||||
completelyDifferentRoute := definitions.Route{
|
||||
Receiver: "Receiver_2",
|
||||
GroupByStr: []string{"GroupByStr1_2", "GroupByStr2_2"},
|
||||
GroupBy: []model.LabelName{
|
||||
"other",
|
||||
},
|
||||
GroupByAll: false,
|
||||
Match: map[string]string{"Match1_2": "MatchValue1", "Match2": "MatchValue2_2"},
|
||||
MatchRE: map[string]config.Regexp{
|
||||
"MatchRE": mustRegex(`".+"`),
|
||||
},
|
||||
Matchers: config.Matchers{
|
||||
mustMatcher(t, labels.MatchNotEqual, "Matchers1_2", "Matchers1Value"),
|
||||
mustMatcher(t, labels.MatchEqual, "Matchers2", "Matchers2Value_2"),
|
||||
mustMatcher(t, labels.MatchEqual, "Matchers3", "Matchers3Value"),
|
||||
},
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
mustMatcher(t, labels.MatchNotRegexp, "ObjectMatchers1_2", "ObjectMatchers1Value"),
|
||||
mustMatcher(t, labels.MatchRegexp, "ObjectMatchers2", "ObjectMatchers2Value_2"),
|
||||
},
|
||||
MuteTimeIntervals: []string{"MuteTimeIntervals1_2", "MuteTimeIntervals2_2"},
|
||||
ActiveTimeIntervals: []string{"ActiveTimeIntervals1_2", "ActiveTimeIntervals2_2"},
|
||||
Continue: false,
|
||||
GroupWait: util.Pointer(model.Duration(20 * time.Minute)),
|
||||
GroupInterval: util.Pointer(model.Duration(50 * time.Minute)),
|
||||
RepeatInterval: util.Pointer(model.Duration(300 * time.Hour)),
|
||||
Provenance: definitions.Provenance(models.ProvenanceFile),
|
||||
Routes: nil, // Nested routes are not included in the fingerprint test for simplicity, recursive fingerprinting is assumed.
|
||||
}
|
||||
|
||||
t.Run("stable across code changes", func(t *testing.T) {
|
||||
expectedFingerprint := "7faba12778df93b8" // If this is a valid fingerprint generation change, update the expected value.
|
||||
assert.Equal(t, expectedFingerprint, calculateRouteFingerprint(baseRouteGen()))
|
||||
})
|
||||
t.Run("unstable across field modification", func(t *testing.T) {
|
||||
fingerprint := calculateRouteFingerprint(baseRouteGen())
|
||||
excludedFields := map[string]struct{}{
|
||||
"Routes": {},
|
||||
}
|
||||
|
||||
reflectVal := reflect.ValueOf(&completelyDifferentRoute).Elem()
|
||||
|
||||
receiverType := reflect.TypeOf((*definitions.Route)(nil)).Elem()
|
||||
for i := 0; i < receiverType.NumField(); i++ {
|
||||
field := receiverType.Field(i).Name
|
||||
if _, ok := excludedFields[field]; ok {
|
||||
continue
|
||||
}
|
||||
cp := baseRouteGen()
|
||||
|
||||
// Get the current field being modified.
|
||||
v := reflect.ValueOf(&cp).Elem()
|
||||
vf := v.Field(i)
|
||||
|
||||
otherField := reflectVal.Field(i)
|
||||
if reflect.DeepEqual(otherField.Interface(), vf.Interface()) {
|
||||
assert.Failf(t, "fields are identical", "Route field %s is the same as the original, test does not ensure instability across the field", field)
|
||||
continue
|
||||
}
|
||||
|
||||
// Set the field to the value of the completelyDifferentRoute.
|
||||
vf.Set(otherField)
|
||||
|
||||
f2 := calculateRouteFingerprint(cp)
|
||||
assert.NotEqualf(t, fingerprint, f2, "Route field %s does not seem to be used in fingerprint", field)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func createNotificationPolicyServiceSut() (*NotificationPolicyService, *legacy_storage.AlertmanagerConfigStoreFake, *fakes.FakeProvisioningStore) {
|
||||
prov := fakes.NewFakeProvisioningStore()
|
||||
configStore := &legacy_storage.AlertmanagerConfigStoreFake{
|
||||
|
Loading…
Reference in New Issue
Block a user