From 4a76436be2e8a579842deb1e36d1af9bfcc8cf5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Tue, 5 Jul 2022 19:09:17 +0200 Subject: [PATCH] Altering: validate that the mute time intervals exist when updating routing tree (#51573) * validate that the mute time intervals exist when updating routing tree * run lint * add tests --- .../definitions/alertmanager_validation.go | 15 ++++ .../provisioning/notification_policies.go | 9 +++ .../notification_policies_test.go | 79 ++++++++++++++++++- 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go index c6759f245f5..20338b4fc5a 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_validation.go @@ -114,6 +114,21 @@ func (r *Route) ValidateReceivers(receivers map[string]struct{}) error { return nil } +func (r *Route) ValidateMuteTimes(muteTimes map[string]struct{}) error { + for _, name := range r.MuteTimeIntervals { + if _, exists := muteTimes[name]; !exists { + return fmt.Errorf("mute time interval '%s' does not exist", name) + } + } + for _, child := range r.Routes { + err := child.ValidateMuteTimes(muteTimes) + if err != nil { + return err + } + } + return nil +} + func (mt *MuteTimeInterval) Validate() error { s, err := yaml.Marshal(mt.MuteTimeInterval) if err != nil { diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index b16a6377dd4..8597360fa03 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -76,6 +76,15 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI return fmt.Errorf("%w: %s", ErrValidation, err.Error()) } + muteTimes := map[string]struct{}{} + for _, mt := range revision.cfg.AlertmanagerConfig.MuteTimeIntervals { + muteTimes[mt.Name] = struct{}{} + } + err = tree.ValidateMuteTimes(muteTimes) + if err != nil { + return fmt.Errorf("%w: %s", ErrValidation, err.Error()) + } + revision.cfg.AlertmanagerConfig.Config.Route = &tree serialized, err := serializeAlertmanagerConfig(*revision.cfg) diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index 6b0c2bb5a00..cff09e8899a 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -8,8 +8,9 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/timeinterval" "github.com/prometheus/common/model" - mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -23,6 +24,82 @@ func TestNotificationPolicyService(t *testing.T) { require.Equal(t, "grafana-default-email", tree.Receiver) }) + t.Run("error if referenced mute time interval is not existing", func(t *testing.T) { + sut := createNotificationPolicyServiceSut() + sut.amStore = &MockAMConfigStore{} + sut.amStore.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything). + Return( + func(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery) error { + cfg, _ := deserializeAlertmanagerConfig([]byte(defaultConfig)) + mti := config.MuteTimeInterval{ + Name: "not-the-one-we-need", + TimeIntervals: []timeinterval.TimeInterval{}, + } + cfg.AlertmanagerConfig.MuteTimeIntervals = append(cfg.AlertmanagerConfig.MuteTimeIntervals, mti) + cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers, + &definitions.PostableApiReceiver{ + Receiver: config.Receiver{ + // default one from createTestRoutingTree() + Name: "a new receiver", + }, + }) + data, _ := serializeAlertmanagerConfig(*cfg) + query.Result = &models.AlertConfiguration{ + AlertmanagerConfiguration: string(data), + } + return nil + }) + sut.amStore.(*MockAMConfigStore).EXPECT(). + UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything). + Return(nil) + newRoute := createTestRoutingTree() + newRoute.Routes = append(newRoute.Routes, &definitions.Route{ + Receiver: "a new receiver", + MuteTimeIntervals: []string{"not-existing"}, + }) + + err := sut.UpdatePolicyTree(context.Background(), 1, newRoute, models.ProvenanceNone) + require.Error(t, err) + }) + + t.Run("pass if referenced mute time interval is existing", func(t *testing.T) { + sut := createNotificationPolicyServiceSut() + sut.amStore = &MockAMConfigStore{} + sut.amStore.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything). + Return( + func(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery) error { + cfg, _ := deserializeAlertmanagerConfig([]byte(defaultConfig)) + mti := config.MuteTimeInterval{ + Name: "existing", + TimeIntervals: []timeinterval.TimeInterval{}, + } + cfg.AlertmanagerConfig.MuteTimeIntervals = append(cfg.AlertmanagerConfig.MuteTimeIntervals, mti) + cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers, + &definitions.PostableApiReceiver{ + Receiver: config.Receiver{ + // default one from createTestRoutingTree() + Name: "a new receiver", + }, + }) + data, _ := serializeAlertmanagerConfig(*cfg) + query.Result = &models.AlertConfiguration{ + AlertmanagerConfiguration: string(data), + } + return nil + }) + sut.amStore.(*MockAMConfigStore).EXPECT(). + UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything). + Return(nil) + newRoute := createTestRoutingTree() + newRoute.Routes = append(newRoute.Routes, &definitions.Route{ + Receiver: "a new receiver", + MuteTimeIntervals: []string{"existing"}, + }) + + err := sut.UpdatePolicyTree(context.Background(), 1, newRoute, models.ProvenanceNone) + require.NoError(t, err) + }) + t.Run("service stitches policy tree into org's AM config", func(t *testing.T) { sut := createNotificationPolicyServiceSut()