mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
592 lines
21 KiB
Go
592 lines
21 KiB
Go
|
package migration
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/base64"
|
||
|
"encoding/json"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/google/go-cmp/cmp"
|
||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||
|
"github.com/prometheus/alertmanager/config"
|
||
|
"github.com/prometheus/alertmanager/pkg/labels"
|
||
|
"github.com/prometheus/common/model"
|
||
|
"github.com/stretchr/testify/require"
|
||
|
|
||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||
|
"github.com/grafana/grafana/pkg/infra/db"
|
||
|
legacymodels "github.com/grafana/grafana/pkg/services/alerting/models"
|
||
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||
|
migrationStore "github.com/grafana/grafana/pkg/services/ngalert/migration/store"
|
||
|
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
||
|
"github.com/grafana/grafana/pkg/setting"
|
||
|
"github.com/grafana/grafana/pkg/util"
|
||
|
)
|
||
|
|
||
|
func TestFilterReceiversForAlert(t *testing.T) {
|
||
|
tc := []struct {
|
||
|
name string
|
||
|
channelIds []migrationStore.UidOrID
|
||
|
receivers map[migrationStore.UidOrID]*apimodels.PostableApiReceiver
|
||
|
defaultReceivers map[string]struct{}
|
||
|
expected map[string]any
|
||
|
}{
|
||
|
{
|
||
|
name: "when an alert has multiple channels, each should filter for the correct receiver",
|
||
|
channelIds: []migrationStore.UidOrID{"uid1", "uid2"},
|
||
|
receivers: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("recv1", nil),
|
||
|
"uid2": createPostableApiReceiver("recv2", nil),
|
||
|
"uid3": createPostableApiReceiver("recv3", nil),
|
||
|
},
|
||
|
defaultReceivers: map[string]struct{}{},
|
||
|
expected: map[string]any{
|
||
|
"recv1": struct{}{},
|
||
|
"recv2": struct{}{},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when default receivers exist, they should be added to an alert's filtered receivers",
|
||
|
channelIds: []migrationStore.UidOrID{"uid1"},
|
||
|
receivers: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("recv1", nil),
|
||
|
"uid2": createPostableApiReceiver("recv2", nil),
|
||
|
"uid3": createPostableApiReceiver("recv3", nil),
|
||
|
},
|
||
|
defaultReceivers: map[string]struct{}{
|
||
|
"recv2": {},
|
||
|
},
|
||
|
expected: map[string]any{
|
||
|
"recv1": struct{}{}, // From alert
|
||
|
"recv2": struct{}{}, // From default
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when an alert has a channels associated by ID instead of UID, it should be included",
|
||
|
channelIds: []migrationStore.UidOrID{int64(42)},
|
||
|
receivers: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
int64(42): createPostableApiReceiver("recv1", nil),
|
||
|
},
|
||
|
defaultReceivers: map[string]struct{}{},
|
||
|
expected: map[string]any{
|
||
|
"recv1": struct{}{},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when an alert's receivers are covered by the defaults, return nil to use default receiver downstream",
|
||
|
channelIds: []migrationStore.UidOrID{"uid1"},
|
||
|
receivers: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("recv1", nil),
|
||
|
"uid2": createPostableApiReceiver("recv2", nil),
|
||
|
"uid3": createPostableApiReceiver("recv3", nil),
|
||
|
},
|
||
|
defaultReceivers: map[string]struct{}{
|
||
|
"recv1": {},
|
||
|
"recv2": {},
|
||
|
},
|
||
|
expected: nil, // recv1 is already a default
|
||
|
},
|
||
|
}
|
||
|
|
||
|
sqlStore := db.InitTestDB(t)
|
||
|
for _, tt := range tc {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
res := m.filterReceiversForAlert("", tt.channelIds, tt.receivers, tt.defaultReceivers)
|
||
|
|
||
|
require.Equal(t, tt.expected, res)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCreateRoute(t *testing.T) {
|
||
|
tc := []struct {
|
||
|
name string
|
||
|
channel *legacymodels.AlertNotification
|
||
|
recv *apimodels.PostableApiReceiver
|
||
|
expected *apimodels.Route
|
||
|
}{
|
||
|
{
|
||
|
name: "when a receiver is passed in, the route should regex match based on quoted name with continue=true",
|
||
|
channel: &legacymodels.AlertNotification{},
|
||
|
recv: createPostableApiReceiver("recv1", nil),
|
||
|
expected: &apimodels.Route{
|
||
|
Receiver: "recv1",
|
||
|
ObjectMatchers: apimodels.ObjectMatchers{{Type: 2, Name: ContactLabel, Value: `.*"recv1".*`}},
|
||
|
Routes: nil,
|
||
|
Continue: true,
|
||
|
GroupByStr: nil,
|
||
|
RepeatInterval: durationPointer(DisabledRepeatInterval),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "notification channel should be escaped for regex in the matcher",
|
||
|
channel: &legacymodels.AlertNotification{},
|
||
|
recv: createPostableApiReceiver(`. ^ $ * + - ? ( ) [ ] { } \ |`, nil),
|
||
|
expected: &apimodels.Route{
|
||
|
Receiver: `. ^ $ * + - ? ( ) [ ] { } \ |`,
|
||
|
ObjectMatchers: apimodels.ObjectMatchers{{Type: 2, Name: ContactLabel, Value: `.*"\. \^ \$ \* \+ - \? \( \) \[ \] \{ \} \\ \|".*`}},
|
||
|
Routes: nil,
|
||
|
Continue: true,
|
||
|
GroupByStr: nil,
|
||
|
RepeatInterval: durationPointer(DisabledRepeatInterval),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when a channel has sendReminder=true, the route should use the frequency in repeat interval",
|
||
|
channel: &legacymodels.AlertNotification{SendReminder: true, Frequency: time.Duration(42) * time.Hour},
|
||
|
recv: createPostableApiReceiver("recv1", nil),
|
||
|
expected: &apimodels.Route{
|
||
|
Receiver: "recv1",
|
||
|
ObjectMatchers: apimodels.ObjectMatchers{{Type: 2, Name: ContactLabel, Value: `.*"recv1".*`}},
|
||
|
Routes: nil,
|
||
|
Continue: true,
|
||
|
GroupByStr: nil,
|
||
|
RepeatInterval: durationPointer(model.Duration(time.Duration(42) * time.Hour)),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when a channel has sendReminder=false, the route should ignore the frequency in repeat interval and use DisabledRepeatInterval",
|
||
|
channel: &legacymodels.AlertNotification{SendReminder: false, Frequency: time.Duration(42) * time.Hour},
|
||
|
recv: createPostableApiReceiver("recv1", nil),
|
||
|
expected: &apimodels.Route{
|
||
|
Receiver: "recv1",
|
||
|
ObjectMatchers: apimodels.ObjectMatchers{{Type: 2, Name: ContactLabel, Value: `.*"recv1".*`}},
|
||
|
Routes: nil,
|
||
|
Continue: true,
|
||
|
GroupByStr: nil,
|
||
|
RepeatInterval: durationPointer(DisabledRepeatInterval),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tt := range tc {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
res, err := createRoute(channelReceiver{
|
||
|
channel: tt.channel,
|
||
|
receiver: tt.recv,
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// Order of nested routes is not guaranteed.
|
||
|
cOpt := []cmp.Option{
|
||
|
cmpopts.SortSlices(func(a, b *apimodels.Route) bool {
|
||
|
if a.Receiver != b.Receiver {
|
||
|
return a.Receiver < b.Receiver
|
||
|
}
|
||
|
return a.ObjectMatchers[0].Value < b.ObjectMatchers[0].Value
|
||
|
}),
|
||
|
cmpopts.IgnoreUnexported(apimodels.Route{}, labels.Matcher{}),
|
||
|
}
|
||
|
|
||
|
if !cmp.Equal(tt.expected, res, cOpt...) {
|
||
|
t.Errorf("Unexpected Route: %v", cmp.Diff(tt.expected, res, cOpt...))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func createNotChannel(t *testing.T, uid string, id int64, name string) *legacymodels.AlertNotification {
|
||
|
t.Helper()
|
||
|
return &legacymodels.AlertNotification{UID: uid, ID: id, Name: name, Settings: simplejson.New()}
|
||
|
}
|
||
|
|
||
|
func createNotChannelWithReminder(t *testing.T, uid string, id int64, name string, frequency time.Duration) *legacymodels.AlertNotification {
|
||
|
t.Helper()
|
||
|
return &legacymodels.AlertNotification{UID: uid, ID: id, Name: name, SendReminder: true, Frequency: frequency, Settings: simplejson.New()}
|
||
|
}
|
||
|
|
||
|
func TestCreateReceivers(t *testing.T) {
|
||
|
tc := []struct {
|
||
|
name string
|
||
|
allChannels []*legacymodels.AlertNotification
|
||
|
defaultChannels []*legacymodels.AlertNotification
|
||
|
expRecvMap map[migrationStore.UidOrID]*apimodels.PostableApiReceiver
|
||
|
expRecv []channelReceiver
|
||
|
expErr error
|
||
|
}{
|
||
|
{
|
||
|
name: "when given notification channels migrate them to receivers",
|
||
|
allChannels: []*legacymodels.AlertNotification{createNotChannel(t, "uid1", int64(1), "name1"), createNotChannel(t, "uid2", int64(2), "name2")},
|
||
|
expRecvMap: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("name1", []string{"name1"}),
|
||
|
"uid2": createPostableApiReceiver("name2", []string{"name2"}),
|
||
|
int64(1): createPostableApiReceiver("name1", []string{"name1"}),
|
||
|
int64(2): createPostableApiReceiver("name2", []string{"name2"}),
|
||
|
},
|
||
|
expRecv: []channelReceiver{
|
||
|
{
|
||
|
channel: createNotChannel(t, "uid1", int64(1), "name1"),
|
||
|
receiver: createPostableApiReceiver("name1", []string{"name1"}),
|
||
|
},
|
||
|
{
|
||
|
channel: createNotChannel(t, "uid2", int64(2), "name2"),
|
||
|
receiver: createPostableApiReceiver("name2", []string{"name2"}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given notification channel contains double quote sanitize with underscore",
|
||
|
allChannels: []*legacymodels.AlertNotification{createNotChannel(t, "uid1", int64(1), "name\"1")},
|
||
|
expRecvMap: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
int64(1): createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
},
|
||
|
expRecv: []channelReceiver{
|
||
|
{
|
||
|
channel: createNotChannel(t, "uid1", int64(1), "name\"1"),
|
||
|
receiver: createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given notification channels collide after sanitization add short hash to end",
|
||
|
allChannels: []*legacymodels.AlertNotification{createNotChannel(t, "uid1", int64(1), "name\"1"), createNotChannel(t, "uid2", int64(2), "name_1")},
|
||
|
expRecvMap: map[migrationStore.UidOrID]*apimodels.PostableApiReceiver{
|
||
|
"uid1": createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
"uid2": createPostableApiReceiver("name_1_dba13d", []string{"name_1_dba13d"}),
|
||
|
int64(1): createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
int64(2): createPostableApiReceiver("name_1_dba13d", []string{"name_1_dba13d"}),
|
||
|
},
|
||
|
expRecv: []channelReceiver{
|
||
|
{
|
||
|
channel: createNotChannel(t, "uid1", int64(1), "name\"1"),
|
||
|
receiver: createPostableApiReceiver("name_1", []string{"name_1"}),
|
||
|
},
|
||
|
{
|
||
|
channel: createNotChannel(t, "uid2", int64(2), "name_1"),
|
||
|
receiver: createPostableApiReceiver("name_1_dba13d", []string{"name_1_dba13d"}),
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
sqlStore := db.InitTestDB(t)
|
||
|
for _, tt := range tc {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
recvMap, recvs, err := m.createReceivers(tt.allChannels)
|
||
|
if tt.expErr != nil {
|
||
|
require.Error(t, err)
|
||
|
require.EqualError(t, err, tt.expErr.Error())
|
||
|
return
|
||
|
}
|
||
|
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// We ignore certain fields for the purposes of this test
|
||
|
for _, recv := range recvs {
|
||
|
for _, not := range recv.receiver.GrafanaManagedReceivers {
|
||
|
not.UID = ""
|
||
|
not.Settings = nil
|
||
|
not.SecureSettings = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
require.Equal(t, tt.expRecvMap, recvMap)
|
||
|
require.ElementsMatch(t, tt.expRecv, recvs)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMigrateNotificationChannelSecureSettings(t *testing.T) {
|
||
|
legacyEncryptFn := func(data string) string {
|
||
|
raw, err := util.Encrypt([]byte(data), setting.SecretKey)
|
||
|
require.NoError(t, err)
|
||
|
return string(raw)
|
||
|
}
|
||
|
decryptFn := func(data string, m *OrgMigration) string {
|
||
|
decoded, err := base64.StdEncoding.DecodeString(data)
|
||
|
require.NoError(t, err)
|
||
|
raw, err := m.encryptionService.Decrypt(context.Background(), decoded)
|
||
|
require.NoError(t, err)
|
||
|
return string(raw)
|
||
|
}
|
||
|
gen := func(nType string, fn func(channel *legacymodels.AlertNotification)) *legacymodels.AlertNotification {
|
||
|
not := &legacymodels.AlertNotification{
|
||
|
UID: "uid",
|
||
|
ID: 1,
|
||
|
Name: "channel name",
|
||
|
Type: nType,
|
||
|
Settings: simplejson.NewFromAny(map[string]any{
|
||
|
"something": "some value",
|
||
|
}),
|
||
|
SecureSettings: map[string][]byte{},
|
||
|
}
|
||
|
if fn != nil {
|
||
|
fn(not)
|
||
|
}
|
||
|
return not
|
||
|
}
|
||
|
genExpSlack := func(fn func(channel *apimodels.PostableGrafanaReceiver)) *apimodels.PostableGrafanaReceiver {
|
||
|
rawSettings, err := json.Marshal(map[string]string{
|
||
|
"something": "some value",
|
||
|
})
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
recv := &apimodels.PostableGrafanaReceiver{
|
||
|
UID: "uid",
|
||
|
Name: "channel name",
|
||
|
Type: "slack",
|
||
|
Settings: rawSettings,
|
||
|
SecureSettings: map[string]string{
|
||
|
"token": "secure token",
|
||
|
"url": "secure url",
|
||
|
},
|
||
|
}
|
||
|
|
||
|
if fn != nil {
|
||
|
fn(recv)
|
||
|
}
|
||
|
return recv
|
||
|
}
|
||
|
|
||
|
tc := []struct {
|
||
|
name string
|
||
|
channel *legacymodels.AlertNotification
|
||
|
expRecv *apimodels.PostableGrafanaReceiver
|
||
|
expErr error
|
||
|
}{
|
||
|
{
|
||
|
name: "when secure settings exist, migrate them to receiver secure settings",
|
||
|
channel: gen("slack", func(channel *legacymodels.AlertNotification) {
|
||
|
channel.SecureSettings = map[string][]byte{
|
||
|
"token": []byte(legacyEncryptFn("secure token")),
|
||
|
"url": []byte(legacyEncryptFn("secure url")),
|
||
|
}
|
||
|
}),
|
||
|
expRecv: genExpSlack(nil),
|
||
|
},
|
||
|
{
|
||
|
name: "when no secure settings are encrypted, do nothing",
|
||
|
channel: gen("slack", nil),
|
||
|
expRecv: genExpSlack(func(recv *apimodels.PostableGrafanaReceiver) {
|
||
|
delete(recv.SecureSettings, "token")
|
||
|
delete(recv.SecureSettings, "url")
|
||
|
}),
|
||
|
},
|
||
|
{
|
||
|
name: "when some secure settings are available unencrypted in settings, migrate them to secureSettings and encrypt",
|
||
|
channel: gen("slack", func(channel *legacymodels.AlertNotification) {
|
||
|
channel.SecureSettings = map[string][]byte{
|
||
|
"url": []byte(legacyEncryptFn("secure url")),
|
||
|
}
|
||
|
channel.Settings.Set("token", "secure token")
|
||
|
}),
|
||
|
expRecv: genExpSlack(nil),
|
||
|
},
|
||
|
}
|
||
|
sqlStore := db.InitTestDB(t)
|
||
|
for _, tt := range tc {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
recv, err := m.createNotifier(tt.channel)
|
||
|
if tt.expErr != nil {
|
||
|
require.Error(t, err)
|
||
|
require.EqualError(t, err, tt.expErr.Error())
|
||
|
return
|
||
|
}
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
if len(tt.expRecv.SecureSettings) > 0 {
|
||
|
require.NotEqual(t, tt.expRecv, recv) // Make sure they were actually encrypted at first.
|
||
|
}
|
||
|
for k, v := range recv.SecureSettings {
|
||
|
recv.SecureSettings[k] = decryptFn(v, m)
|
||
|
}
|
||
|
require.Equal(t, tt.expRecv, recv)
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// Generate tests for each notification channel type.
|
||
|
t.Run("secure settings migrations for each notifier type", func(t *testing.T) {
|
||
|
notifiers := channels_config.GetAvailableNotifiers()
|
||
|
t.Run("migrate notification channel secure settings to receiver secure settings", func(t *testing.T) {
|
||
|
for _, notifier := range notifiers {
|
||
|
nType := notifier.Type
|
||
|
secureSettings, err := channels_config.GetSecretKeysForContactPointType(nType)
|
||
|
require.NoError(t, err)
|
||
|
t.Run(nType, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
channel := gen(nType, func(channel *legacymodels.AlertNotification) {
|
||
|
for _, key := range secureSettings {
|
||
|
channel.SecureSettings[key] = []byte(legacyEncryptFn("secure " + key))
|
||
|
}
|
||
|
})
|
||
|
recv, err := m.createNotifier(channel)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.Equal(t, nType, recv.Type)
|
||
|
if len(secureSettings) > 0 {
|
||
|
for _, key := range secureSettings {
|
||
|
require.NotEqual(t, "secure "+key, recv.SecureSettings[key]) // Make sure they were actually encrypted at first.
|
||
|
}
|
||
|
}
|
||
|
require.Len(t, recv.SecureSettings, len(secureSettings))
|
||
|
for _, key := range secureSettings {
|
||
|
require.Equal(t, "secure "+key, decryptFn(recv.SecureSettings[key], m))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
|
||
|
t.Run("for certain legacy channel types, migrate secure fields stored in settings to secure settings", func(t *testing.T) {
|
||
|
for _, notifier := range notifiers {
|
||
|
nType := notifier.Type
|
||
|
secureSettings, ok := secureKeysToMigrate[nType]
|
||
|
if !ok {
|
||
|
continue
|
||
|
}
|
||
|
t.Run(nType, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
|
||
|
channel := gen(nType, func(channel *legacymodels.AlertNotification) {
|
||
|
for _, key := range secureSettings {
|
||
|
// Key difference to above. We store the secure settings in the settings field and expect
|
||
|
// them to be migrated to secureSettings.
|
||
|
channel.Settings.Set(key, "secure "+key)
|
||
|
}
|
||
|
})
|
||
|
recv, err := m.createNotifier(channel)
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
require.Equal(t, nType, recv.Type)
|
||
|
if len(secureSettings) > 0 {
|
||
|
for _, key := range secureSettings {
|
||
|
require.NotEqual(t, "secure "+key, recv.SecureSettings[key]) // Make sure they were actually encrypted at first.
|
||
|
}
|
||
|
}
|
||
|
require.Len(t, recv.SecureSettings, len(secureSettings))
|
||
|
for _, key := range secureSettings {
|
||
|
require.Equal(t, "secure "+key, decryptFn(recv.SecureSettings[key], m))
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func TestCreateDefaultRouteAndReceiver(t *testing.T) {
|
||
|
tc := []struct {
|
||
|
name string
|
||
|
amConfig *apimodels.PostableUserConfig
|
||
|
defaultChannels []*legacymodels.AlertNotification
|
||
|
expRecv *apimodels.PostableApiReceiver
|
||
|
expRoute *apimodels.Route
|
||
|
expErr error
|
||
|
}{
|
||
|
{
|
||
|
name: "when given multiple default notification channels migrate them to a single receiver",
|
||
|
defaultChannels: []*legacymodels.AlertNotification{createNotChannel(t, "uid1", int64(1), "name1"), createNotChannel(t, "uid2", int64(2), "name2")},
|
||
|
expRecv: createPostableApiReceiver("autogen-contact-point-default", []string{"name1", "name2"}),
|
||
|
expRoute: &apimodels.Route{
|
||
|
Receiver: "autogen-contact-point-default",
|
||
|
Routes: make([]*apimodels.Route, 0),
|
||
|
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
|
||
|
RepeatInterval: durationPointer(DisabledRepeatInterval),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given multiple default notification channels migrate them to a single receiver with RepeatInterval set to be the minimum of all channel frequencies",
|
||
|
defaultChannels: []*legacymodels.AlertNotification{
|
||
|
createNotChannelWithReminder(t, "uid1", int64(1), "name1", time.Duration(42)),
|
||
|
createNotChannelWithReminder(t, "uid2", int64(2), "name2", time.Duration(100000)),
|
||
|
},
|
||
|
expRecv: createPostableApiReceiver("autogen-contact-point-default", []string{"name1", "name2"}),
|
||
|
expRoute: &apimodels.Route{
|
||
|
Receiver: "autogen-contact-point-default",
|
||
|
Routes: make([]*apimodels.Route, 0),
|
||
|
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
|
||
|
RepeatInterval: durationPointer(model.Duration(42)),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given no default notification channels create a single empty receiver for default",
|
||
|
defaultChannels: []*legacymodels.AlertNotification{},
|
||
|
expRecv: createPostableApiReceiver("autogen-contact-point-default", nil),
|
||
|
expRoute: &apimodels.Route{
|
||
|
Receiver: "autogen-contact-point-default",
|
||
|
Routes: make([]*apimodels.Route, 0),
|
||
|
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
|
||
|
RepeatInterval: nil,
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given a single default notification channels don't create a new default receiver",
|
||
|
defaultChannels: []*legacymodels.AlertNotification{createNotChannel(t, "uid1", int64(1), "name1")},
|
||
|
expRecv: nil,
|
||
|
expRoute: &apimodels.Route{
|
||
|
Receiver: "name1",
|
||
|
Routes: make([]*apimodels.Route, 0),
|
||
|
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
|
||
|
RepeatInterval: durationPointer(DisabledRepeatInterval),
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "when given a single default notification channel with SendReminder=true, use the channels Frequency as the RepeatInterval",
|
||
|
defaultChannels: []*legacymodels.AlertNotification{createNotChannelWithReminder(t, "uid1", int64(1), "name1", time.Duration(42))},
|
||
|
expRecv: nil,
|
||
|
expRoute: &apimodels.Route{
|
||
|
Receiver: "name1",
|
||
|
Routes: make([]*apimodels.Route, 0),
|
||
|
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
|
||
|
RepeatInterval: durationPointer(model.Duration(42)),
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
sqlStore := db.InitTestDB(t)
|
||
|
for _, tt := range tc {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
service := NewTestMigrationService(t, sqlStore, nil)
|
||
|
m := service.newOrgMigration(1)
|
||
|
recv, route, err := m.createDefaultRouteAndReceiver(tt.defaultChannels)
|
||
|
if tt.expErr != nil {
|
||
|
require.Error(t, err)
|
||
|
require.EqualError(t, err, tt.expErr.Error())
|
||
|
return
|
||
|
}
|
||
|
|
||
|
require.NoError(t, err)
|
||
|
|
||
|
// We ignore certain fields for the purposes of this test
|
||
|
if recv != nil {
|
||
|
for _, not := range recv.GrafanaManagedReceivers {
|
||
|
not.UID = ""
|
||
|
not.Settings = nil
|
||
|
not.SecureSettings = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
require.Equal(t, tt.expRecv, recv)
|
||
|
require.Equal(t, tt.expRoute, route)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func createPostableApiReceiver(name string, integrationNames []string) *apimodels.PostableApiReceiver {
|
||
|
integrations := make([]*apimodels.PostableGrafanaReceiver, 0, len(integrationNames))
|
||
|
for _, integrationName := range integrationNames {
|
||
|
integrations = append(integrations, &apimodels.PostableGrafanaReceiver{Name: integrationName})
|
||
|
}
|
||
|
return &apimodels.PostableApiReceiver{
|
||
|
Receiver: config.Receiver{
|
||
|
Name: name,
|
||
|
},
|
||
|
PostableGrafanaReceivers: apimodels.PostableGrafanaReceivers{
|
||
|
GrafanaManagedReceivers: integrations,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func durationPointer(d model.Duration) *model.Duration {
|
||
|
return &d
|
||
|
}
|