The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/ngalert/provisioning/mute_timings_test.go

460 lines
14 KiB

package provisioning
import (
"context"
"fmt"
"testing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/prometheus/alertmanager/config"
mock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
func TestMuteTimingService(t *testing.T) {
t.Run("service returns timings from config file", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
result, err := sut.GetMuteTimings(context.Background(), 1)
require.NoError(t, err)
require.Len(t, result, 1)
require.Equal(t, "asdf", result[0].Name)
})
t.Run("service returns empty list when config file contains no mute timings", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
result, err := sut.GetMuteTimings(context.Background(), 1)
require.NoError(t, err)
require.Empty(t, result)
})
t.Run("service propagates errors", func(t *testing.T) {
t.Run("when unable to read config", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed"))
_, err := sut.GetMuteTimings(context.Background(), 1)
require.Error(t, err)
})
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
_, err := sut.GetMuteTimings(context.Background(), 1)
require.ErrorContains(t, err, "failed to deserialize")
})
t.Run("when no AM config in current org", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(nil)
_, err := sut.GetMuteTimings(context.Background(), 1)
require.ErrorContains(t, err, "no alertmanager configuration")
})
})
t.Run("creating mute timings", func(t *testing.T) {
t.Run("rejects mute timings that fail validation", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := definitions.MuteTimeInterval{
MuteTimeInterval: config.MuteTimeInterval{
Name: "",
},
}
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("propagates errors", func(t *testing.T) {
t.Run("when unable to read config", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed"))
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.Error(t, err)
})
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to deserialize")
})
t.Run("when no AM config in current org", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(nil)
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "no alertmanager configuration")
})
t.Run("when provenance fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to save provenance")
})
t.Run("when AM config fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to save config")
})
})
})
t.Run("updating mute timings", func(t *testing.T) {
t.Run("rejects mute timings that fail validation", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := definitions.MuteTimeInterval{
MuteTimeInterval: config.MuteTimeInterval{
Name: "",
},
}
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("returns nil if timing does not exist", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "does not exist"
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
updated, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.NoError(t, err)
require.Nil(t, updated)
})
t.Run("propagates errors", func(t *testing.T) {
t.Run("when unable to read config", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed"))
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.Error(t, err)
})
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to deserialize")
})
t.Run("when no AM config in current org", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(nil)
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "no alertmanager configuration")
})
t.Run("when provenance fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to save provenance")
})
t.Run("when AM config fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
require.ErrorContains(t, err, "failed to save config")
})
})
})
t.Run("deleting mute timings", func(t *testing.T) {
t.Run("returns nil if timing does not exist", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteMuteTiming(context.Background(), "does not exist", 1)
require.NoError(t, err)
})
t.Run("propagates errors", func(t *testing.T) {
t.Run("when unable to read config", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed"))
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.Error(t, err)
})
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.ErrorContains(t, err, "failed to deserialize")
})
t.Run("when no AM config in current org", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(nil)
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.ErrorContains(t, err, "no alertmanager configuration")
})
t.Run("when provenance fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.ErrorContains(t, err, "failed to save provenance")
})
t.Run("when AM config fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.ErrorContains(t, err, "failed to save config")
})
t.Run("when mute timing is used in route", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimingsInRoute,
})
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
require.Error(t, err)
})
})
})
}
func createMuteTimingSvcSut() *MuteTimingService {
return &MuteTimingService{
config: &MockAMConfigStore{},
prov: &MockProvisioningStore{},
xact: newNopTransactionManager(),
log: log.NewNopLogger(),
}
}
func createMuteTiming() definitions.MuteTimeInterval {
return definitions.MuteTimeInterval{
MuteTimeInterval: config.MuteTimeInterval{
Name: "interval",
},
}
}
var configWithMuteTimings = `
{
"template_files": {
"a": "template"
},
"alertmanager_config": {
"route": {
"receiver": "grafana-default-email"
},
"mute_time_intervals": [{
"name": "asdf",
"time_intervals": [{
"times": [],
"weekdays": ["monday"]
}]
}],
"receivers": [{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [{
"uid": "",
"name": "email receiver",
"type": "email",
"isDefault": true,
"settings": {
"addresses": "<example@email.com>"
}
}]
}]
}
}
`
var configWithMuteTimingsInRoute = `
{
"template_files": {
"a": "template"
},
"alertmanager_config": {
"route": {
"receiver": "grafana-default-email",
"routes": [
{
"receiver": "grafana-default-email",
"mute_time_intervals": ["asdf"]
}
]
},
"mute_time_intervals": [{
"name": "asdf",
"time_intervals": [{
"times": [],
"weekdays": ["monday"]
}]
}],
"receivers": [{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [{
"uid": "",
"name": "email receiver",
"type": "email",
"isDefault": true,
"settings": {
"addresses": "<example@email.com>"
}
}]
}]
}
}
`