Alerting: Resetting the notification policy tree to the default policy will also restore default contact points (#54608)

* Add test that resetting the route restores the default receiver

* Return error instead of panic

* Adjust error string to match styleguide
This commit is contained in:
Alexander Weaver 2022-09-07 09:28:10 -05:00 committed by GitHub
parent 8e8d0a08f9
commit b193eaed6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 38 deletions

View File

@ -132,6 +132,10 @@ func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID
return definitions.Route{}, err
}
revision.cfg.AlertmanagerConfig.Config.Route = route
err = nps.ensureDefaultReceiverExists(revision.cfg, defaultCfg)
if err != nil {
return definitions.Route{}, err
}
serialized, err := serializeAlertmanagerConfig(*revision.cfg)
if err != nil {
@ -169,3 +173,23 @@ func (nps *NotificationPolicyService) receiversToMap(records []*definitions.Post
}
return receivers, nil
}
func (nps *NotificationPolicyService) ensureDefaultReceiverExists(cfg *definitions.PostableUserConfig, defaultCfg *definitions.PostableUserConfig) error {
defaultRcv := cfg.AlertmanagerConfig.Route.Receiver
for _, rcv := range cfg.AlertmanagerConfig.Receivers {
if rcv.Name == defaultRcv {
return nil
}
}
for _, rcv := range defaultCfg.AlertmanagerConfig.Receivers {
if rcv.Name == defaultRcv {
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers, rcv)
return nil
}
}
nps.log.Error("Grafana Alerting has been configured with a default configuration that is internally inconsistent! The default configuration's notification policy must have a corresponding receiver.")
return fmt.Errorf("inconsistent default configuration")
}

View File

@ -31,19 +31,13 @@ func TestNotificationPolicyService(t *testing.T) {
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 := createTestAlertingConfig()
cfg.AlertmanagerConfig.MuteTimeIntervals = []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),
@ -69,19 +63,13 @@ func TestNotificationPolicyService(t *testing.T) {
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 := createTestAlertingConfig()
cfg.AlertmanagerConfig.MuteTimeIntervals = []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),
@ -132,20 +120,7 @@ func TestNotificationPolicyService(t *testing.T) {
sut.amStore.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
Return(
func(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery) error {
cfg, _ := deserializeAlertmanagerConfig([]byte(defaultConfig))
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
&definitions.PostableApiReceiver{
Receiver: config.Receiver{
// default one from createTestRoutingTree()
Name: "a new receiver",
},
})
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
&definitions.PostableApiReceiver{
Receiver: config.Receiver{
Name: "existing",
},
})
cfg := createTestAlertingConfig()
data, _ := serializeAlertmanagerConfig(*cfg)
query.Result = &models.AlertConfiguration{
AlertmanagerConfiguration: string(data),
@ -225,6 +200,44 @@ func TestNotificationPolicyService(t *testing.T) {
require.Nil(t, tree.Routes)
require.Equal(t, []model.LabelName{models.FolderTitleLabel, model.AlertNameLabel}, tree.GroupBy)
})
t.Run("deleting route with missing default receiver restores receiver", 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 := createTestAlertingConfig()
cfg.AlertmanagerConfig.Route = &definitions.Route{
Receiver: "a new receiver",
}
cfg.AlertmanagerConfig.Receivers = []*definitions.PostableApiReceiver{
{
Receiver: config.Receiver{
Name: "a new receiver",
},
},
// No default receiver! Only our custom one.
}
data, _ := serializeAlertmanagerConfig(*cfg)
query.Result = &models.AlertConfiguration{
AlertmanagerConfiguration: string(data),
}
return nil
})
var interceptedSave = models.SaveAlertmanagerConfigurationCmd{}
sut.amStore.(*MockAMConfigStore).EXPECT().SaveSucceedsIntercept(&interceptedSave)
tree, err := sut.ResetPolicyTree(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, "grafana-default-email", tree.Receiver)
require.NotEmpty(t, interceptedSave.AlertmanagerConfiguration)
// Deserializing with no error asserts that the saved config is semantically valid.
newCfg, err := deserializeAlertmanagerConfig([]byte(interceptedSave.AlertmanagerConfiguration))
require.NoError(t, err)
require.Len(t, newCfg.AlertmanagerConfig.Receivers, 2)
})
}
func createNotificationPolicyServiceSut() *NotificationPolicyService {
@ -244,3 +257,21 @@ func createTestRoutingTree() definitions.Route {
Receiver: "a new receiver",
}
}
func createTestAlertingConfig() *definitions.PostableUserConfig {
cfg, _ := deserializeAlertmanagerConfig([]byte(defaultConfig))
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
&definitions.PostableApiReceiver{
Receiver: config.Receiver{
// default one from createTestRoutingTree()
Name: "a new receiver",
},
})
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
&definitions.PostableApiReceiver{
Receiver: config.Receiver{
Name: "existing",
},
})
return cfg
}

View File

@ -160,6 +160,15 @@ func (m *MockAMConfigStore_Expecter) SaveSucceeds() *MockAMConfigStore_Expecter
return m
}
func (m *MockAMConfigStore_Expecter) SaveSucceedsIntercept(intercepted *models.SaveAlertmanagerConfigurationCmd) *MockAMConfigStore_Expecter {
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(nil).
Run(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) {
*intercepted = *cmd
})
return m
}
func (m *MockProvisioningStore_Expecter) GetReturns(p models.Provenance) *MockProvisioningStore_Expecter {
m.GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(p, nil)
return m