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/sqlstore/migrations/ualert/migration_test.go

710 lines
27 KiB

package ualert_test
import (
"encoding/json"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations/ualert"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting"
)
// TestAddDashAlertMigration tests the AddDashAlertMigration wrapper method that decides when to run the migration based on migration status and settings.
func TestAddDashAlertMigration(t *testing.T) {
x := setupTestDB(t)
tc := []struct {
name string
config *setting.Cfg
isMigrationRun bool
shouldPanic bool
expected []string // set of migration titles
}{
{
name: "when unified alerting enabled and migration not already run, then add main migration and clear rmMigration log entry",
config: &setting.Cfg{
UnifiedAlerting: setting.UnifiedAlertingSettings{
Enabled: boolPointer(true),
},
},
isMigrationRun: false,
expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.RmMigTitle), ualert.MigTitle},
},
{
name: "when unified alerting disabled and migration is already run, then add rmMigration and clear main migration log entry",
config: &setting.Cfg{
UnifiedAlerting: setting.UnifiedAlertingSettings{
Enabled: boolPointer(false),
},
ForceMigration: true,
},
isMigrationRun: true,
expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.MigTitle), ualert.RmMigTitle},
},
{
name: "when unified alerting disabled, migration is already run and force migration is disabled, then the migration should panic",
config: &setting.Cfg{
UnifiedAlerting: setting.UnifiedAlertingSettings{
Enabled: boolPointer(false),
},
ForceMigration: false,
},
isMigrationRun: true,
expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.MigTitle), ualert.RmMigTitle},
},
{
name: "when unified alerting enabled and migration is already run, then do nothing",
config: &setting.Cfg{
UnifiedAlerting: setting.UnifiedAlertingSettings{
Enabled: boolPointer(true),
},
},
isMigrationRun: true,
expected: []string{},
},
{
name: "when unified alerting disabled and migration is not already run, then do nothing",
config: &setting.Cfg{
UnifiedAlerting: setting.UnifiedAlertingSettings{
Enabled: boolPointer(false),
},
},
isMigrationRun: false,
expected: []string{},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
defer func() {
// if the code should panic, make sure it has
if r := recover(); r == nil && tt.shouldPanic {
t.Errorf("The code did not panic")
}
}()
if tt.isMigrationRun {
log := migrator.MigrationLog{
MigrationID: ualert.MigTitle,
SQL: "",
Timestamp: time.Now(),
Success: true,
}
_, err := x.Insert(log)
require.NoError(t, err)
} else {
_, err := x.Exec("DELETE FROM migration_log WHERE migration_id = ?", ualert.MigTitle)
require.NoError(t, err)
}
mg := migrator.NewMigrator(x, tt.config)
ualert.AddDashAlertMigration(mg)
require.Equal(t, tt.expected, mg.GetMigrationIDs(false))
})
}
}
// TestDashAlertMigration tests the execution of the main DashAlertMigration.
func TestDashAlertMigration(t *testing.T) {
// Run initial migration to have a working DB.
x := setupTestDB(t)
emailSettings := `{"addresses": "test"}`
slackSettings := `{"recipient": "test", "token": "test"}`
opsgenieSettings := `{"apiKey": "test"}`
tc := []struct {
name string
legacyChannels []*models.AlertNotification
alerts []*models.Alert
expected map[int64]*ualert.PostableUserConfig
expErr error
}{
{
name: "general multi-org, multi-alert, multi-channel migration",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false),
createAlertNotification(t, int64(1), "notifier3", "opsgenie", opsgenieSettings, false),
createAlertNotification(t, int64(2), "notifier4", "email", emailSettings, false),
createAlertNotification(t, int64(2), "notifier5", "slack", slackSettings, false),
createAlertNotification(t, int64(2), "notifier6", "opsgenie", opsgenieSettings, true), // default
},
alerts: []*models.Alert{
createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}),
createAlert(t, int64(1), int64(1), int64(2), "alert2", []string{"notifier2", "notifier3"}),
createAlert(t, int64(1), int64(2), int64(3), "alert3", []string{"notifier3"}),
createAlert(t, int64(2), int64(3), int64(1), "alert4", []string{"notifier4"}),
createAlert(t, int64(2), int64(3), int64(2), "alert5", []string{"notifier4", "notifier5", "notifier6"}),
createAlert(t, int64(2), int64(4), int64(3), "alert6", []string{}),
},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
Routes: []*ualert.Route{
{Receiver: "notifier1", Matchers: createAlertNameMatchers("alert1")}, // These Matchers are temporary and will be replaced below with generated rule_uid.
{Matchers: createAlertNameMatchers("alert2"), Routes: []*ualert.Route{
{Receiver: "notifier2", Matchers: createAlertNameMatchers("alert2"), Continue: true},
{Receiver: "notifier3", Matchers: createAlertNameMatchers("alert2"), Continue: true},
}},
{Receiver: "notifier3", Matchers: createAlertNameMatchers("alert3")},
},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "notifier2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}}},
{Name: "notifier3", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier3", Type: "opsgenie"}}},
{Name: "autogen-contact-point-default"}, // empty default
},
},
},
int64(2): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "notifier6",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
Routes: []*ualert.Route{
{Matchers: createAlertNameMatchers("alert4"), Routes: []*ualert.Route{
{Receiver: "notifier4", Matchers: createAlertNameMatchers("alert4"), Continue: true},
{Receiver: "notifier6", Matchers: createAlertNameMatchers("alert4"), Continue: true},
}},
{Matchers: createAlertNameMatchers("alert5"), Routes: []*ualert.Route{
{Receiver: "notifier4", Matchers: createAlertNameMatchers("alert5"), Continue: true},
{Receiver: "notifier5", Matchers: createAlertNameMatchers("alert5"), Continue: true},
{Receiver: "notifier6", Matchers: createAlertNameMatchers("alert5"), Continue: true},
}},
},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier4", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier4", Type: "email"}}},
{Name: "notifier5", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier5", Type: "slack"}}},
{Name: "notifier6", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier6", Type: "opsgenie"}}}, // empty default
},
},
},
},
},
{
name: "when no default channel, create empty autogen-contact-point-default",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "autogen-contact-point-default"},
},
},
},
},
},
{
name: "when single default channel, don't create autogen-contact-point-default",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true),
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "notifier1",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
},
},
},
},
},
{
name: "when multiple default channels, add them to autogen-contact-point-default as well",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true),
createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, true),
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "notifier2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}}},
{Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}}},
},
},
},
},
},
{
name: "when default channels exist alongside non-default, add only defaults to autogen-contact-point-default",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default
createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false),
createAlertNotification(t, int64(1), "notifier3", "opsgenie", opsgenieSettings, true), // default
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "notifier2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}}},
{Name: "notifier3", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier3", Type: "opsgenie"}}},
{Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier3", Type: "opsgenie"}}}},
},
},
},
},
{
name: "when alert has only defaults, don't create route for it",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, true), // default
createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, true), // default
},
alerts: []*models.Alert{
createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}),
createAlert(t, int64(1), int64(2), int64(3), "alert2", []string{}),
},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "notifier2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}}},
{Name: "autogen-contact-point-default", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}, {Name: "notifier2", Type: "slack"}}},
},
},
},
},
},
{
name: "when alerts share channels, only create one receiver per legacy channel",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
createAlertNotification(t, int64(1), "notifier2", "slack", slackSettings, false),
},
alerts: []*models.Alert{
createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1"}),
createAlert(t, int64(1), int64(1), int64(1), "alert2", []string{"notifier1", "notifier2"}),
},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
Routes: []*ualert.Route{
{Receiver: "notifier1", Matchers: createAlertNameMatchers("alert1")},
{Matchers: createAlertNameMatchers("alert2"), Routes: []*ualert.Route{
{Receiver: "notifier1", Matchers: createAlertNameMatchers("alert2"), Continue: true},
{Receiver: "notifier2", Matchers: createAlertNameMatchers("alert2"), Continue: true},
}},
},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "notifier2", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier2", Type: "slack"}}},
{Name: "autogen-contact-point-default"},
},
},
},
},
},
{
name: "when channel not linked to any alerts, still create a receiver for it",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "autogen-contact-point-default"},
},
},
},
},
},
{
name: "when unsupported channels, do not migrate them",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
createAlertNotification(t, int64(1), "notifier2", "hipchat", "", false),
createAlertNotification(t, int64(1), "notifier3", "sensu", "", false),
},
alerts: []*models.Alert{},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "autogen-contact-point-default"},
},
},
},
},
},
{
name: "when unsupported channel linked to alert, do not migrate only that channel",
legacyChannels: []*models.AlertNotification{
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
createAlertNotification(t, int64(1), "notifier2", "sensu", "", false),
},
alerts: []*models.Alert{
createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notifier1", "notifier2"}),
},
expected: map[int64]*ualert.PostableUserConfig{
int64(1): {
AlertmanagerConfig: ualert.PostableApiAlertingConfig{
Route: &ualert.Route{
Receiver: "autogen-contact-point-default",
GroupByStr: []string{ngModels.FolderTitleLabel, model.AlertNameLabel},
Routes: []*ualert.Route{
{Receiver: "notifier1", Matchers: createAlertNameMatchers("alert1")},
},
},
Receivers: []*ualert.PostableApiReceiver{
{Name: "notifier1", GrafanaManagedReceivers: []*ualert.PostableGrafanaReceiver{{Name: "notifier1", Type: "email"}}},
{Name: "autogen-contact-point-default"},
},
},
},
},
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
defer teardown(t, x)
setupLegacyAlertsTables(t, x, tt.legacyChannels, tt.alerts)
_, errDeleteMig := x.Exec("DELETE FROM migration_log WHERE migration_id = ?", ualert.MigTitle)
require.NoError(t, errDeleteMig)
alertMigrator := migrator.NewMigrator(x, &setting.Cfg{})
alertMigrator.AddMigration(ualert.RmMigTitle, &ualert.RmMigration{})
ualert.AddDashAlertMigration(alertMigrator)
errRunningMig := alertMigrator.Start(false, 0)
require.NoError(t, errRunningMig)
for orgId := range tt.expected {
amConfig := getAlertmanagerConfig(t, x, orgId)
// Order of nested GrafanaManagedReceivers is not guaranteed.
cOpt := []cmp.Option{
cmpopts.IgnoreFields(ualert.PostableGrafanaReceiver{}, "UID", "Settings", "SecureSettings"),
cmpopts.SortSlices(func(a, b *ualert.PostableGrafanaReceiver) bool { return a.Name < b.Name }),
cmpopts.SortSlices(func(a, b *ualert.PostableApiReceiver) bool { return a.Name < b.Name }),
}
if !cmp.Equal(tt.expected[orgId].AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...) {
t.Errorf("Unexpected Receivers: %v", cmp.Diff(tt.expected[orgId].AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...))
}
// Since routes and alerts are connecting solely by the Matchers on rule_uid, which is created at runtime we need to do some prep-work to populate the expected Matchers.
alertUids := getAlertNameToUidMap(t, x, orgId)
replaceAlertNameMatcherWithRuleUid(t, tt.expected[orgId].AlertmanagerConfig.Route.Routes, alertUids)
// Order of nested routes is not guaranteed.
cOpt = []cmp.Option{
cmpopts.SortSlices(func(a, b *ualert.Route) bool {
if a.Receiver != b.Receiver {
return a.Receiver < b.Receiver
}
return a.Matchers[0].Value < b.Matchers[0].Value
}),
cmpopts.IgnoreUnexported(ualert.Route{}, labels.Matcher{}),
}
if !cmp.Equal(tt.expected[orgId].AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...) {
t.Errorf("Unexpected Route: %v", cmp.Diff(tt.expected[orgId].AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...))
}
}
})
}
}
// setupTestDB prepares the sqlite database and runs OSS migrations to initialize the schemas.
func setupTestDB(t *testing.T) *xorm.Engine {
t.Helper()
testDB := sqlutil.SQLite3TestDB()
x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr)
require.NoError(t, err)
err = migrator.NewDialect(x).CleanDB()
require.NoError(t, err)
mg := migrator.NewMigrator(x, &setting.Cfg{})
migrations := &migrations.OSSMigrations{}
migrations.AddMigration(mg)
err = mg.Start(false, 0)
require.NoError(t, err)
return x
}
var (
now = time.Now()
)
// createAlertNotification creates a legacy alert notification channel for inserting into the test database.
func createAlertNotification(t *testing.T, orgId int64, uid string, channelType string, settings string, defaultChannel bool) *models.AlertNotification {
t.Helper()
settingsJson := simplejson.New()
if settings != "" {
s, err := simplejson.NewJson([]byte(settings))
if err != nil {
t.Fatalf("Failed to unmarshal alert notification json: %v", err)
}
settingsJson = s
}
return &models.AlertNotification{
OrgId: orgId,
Uid: uid,
Name: uid, // Same as uid to make testing easier.
Type: channelType,
DisableResolveMessage: false,
IsDefault: defaultChannel,
Settings: settingsJson,
SecureSettings: make(map[string][]byte),
Created: now,
Updated: now,
}
}
// createAlert creates a legacy alert rule for inserting into the test database.
func createAlert(t *testing.T, orgId int64, dashboardId int64, panelsId int64, name string, notifierUids []string) *models.Alert {
t.Helper()
var settings = simplejson.New()
if len(notifierUids) != 0 {
notifiers := make([]interface{}, 0)
for _, n := range notifierUids {
notifiers = append(notifiers, struct {
Uid string
}{Uid: n})
}
settings.Set("notifications", notifiers)
}
return &models.Alert{
OrgId: orgId,
DashboardId: dashboardId,
PanelId: panelsId,
Name: name,
Message: "message",
Frequency: int64(60),
For: time.Duration(time.Duration(60).Seconds()),
State: models.AlertStateOK,
Settings: settings,
NewStateDate: now,
Created: now,
Updated: now,
}
}
// createDashboard creates a dashboard for inserting into the test database.
func createDashboard(t *testing.T, id int64, orgId int64, uid string) *models.Dashboard {
t.Helper()
return &models.Dashboard{
Id: id,
OrgId: orgId,
Uid: uid,
Created: now,
Updated: now,
Title: uid, // Not tested, needed to satisfy contraint.
}
}
// createDatasource creates a ddatasource for inserting into the test database.
func createDatasource(t *testing.T, id int64, orgId int64, uid string) *datasources.DataSource {
t.Helper()
return &datasources.DataSource{
Id: id,
OrgId: orgId,
Uid: uid,
Created: now,
Updated: now,
Name: uid, // Not tested, needed to satisfy contraint.
}
}
func createOrg(t *testing.T, id int64) *models.Org {
t.Helper()
return &models.Org{
Id: id,
Version: 1,
Name: fmt.Sprintf("org_%d", id),
Created: time.Now(),
Updated: time.Now(),
}
}
// teardown cleans the input tables between test cases.
func teardown(t *testing.T, x *xorm.Engine) {
_, err := x.Exec("DELETE from org")
require.NoError(t, err)
_, err = x.Exec("DELETE from alert")
require.NoError(t, err)
_, err = x.Exec("DELETE from alert_notification")
require.NoError(t, err)
_, err = x.Exec("DELETE from dashboard")
require.NoError(t, err)
_, err = x.Exec("DELETE from data_source")
require.NoError(t, err)
}
// setupLegacyAlertsTables inserts data into the legacy alerting tables that is needed for testing the migration.
func setupLegacyAlertsTables(t *testing.T, x *xorm.Engine, legacyChannels []*models.AlertNotification, alerts []*models.Alert) {
t.Helper()
orgs := []models.Org{
*createOrg(t, 1),
*createOrg(t, 2),
}
// Setup dashboards.
dashboards := []models.Dashboard{
*createDashboard(t, 1, 1, "dash1-1"),
*createDashboard(t, 2, 1, "dash2-1"),
*createDashboard(t, 3, 2, "dash3-2"),
*createDashboard(t, 4, 2, "dash4-2"),
}
_, errDashboards := x.Insert(dashboards)
require.NoError(t, errDashboards)
// Setup data_sources.
dataSources := []datasources.DataSource{
*createDatasource(t, 1, 1, "ds1-1"),
*createDatasource(t, 2, 1, "ds2-1"),
*createDatasource(t, 3, 2, "ds3-2"),
*createDatasource(t, 4, 2, "ds4-2"),
}
_, errOrgs := x.Insert(orgs)
require.NoError(t, errOrgs)
_, errDataSourcess := x.Insert(dataSources)
require.NoError(t, errDataSourcess)
if len(legacyChannels) > 0 {
_, channelErr := x.Insert(legacyChannels)
require.NoError(t, channelErr)
}
if len(alerts) > 0 {
_, alertErr := x.Insert(alerts)
require.NoError(t, alertErr)
}
}
// getAlertmanagerConfig retreives the Alertmanager Config from the database for a given orgId.
func getAlertmanagerConfig(t *testing.T, x *xorm.Engine, orgId int64) *ualert.PostableUserConfig {
amConfig := ""
_, err := x.Table("alert_configuration").Where("org_id = ?", orgId).Cols("alertmanager_configuration").Get(&amConfig)
require.NoError(t, err)
config := ualert.PostableUserConfig{}
err = json.Unmarshal([]byte(amConfig), &config)
require.NoError(t, err)
return &config
}
// getAlertNameToUidMap fetches alert_rules from database to create map of alert.Name -> alert.Uid. This is needed as alert Uid is created during migration and is used to match routes to alerts.
func getAlertNameToUidMap(t *testing.T, x *xorm.Engine, orgId int64) map[string]string {
t.Helper()
alerts := []struct {
Title string
Uid string
}{}
err := x.Table("alert_rule").Where("org_id = ?", orgId).Find(&alerts)
require.NoError(t, err)
res := make(map[string]string)
for _, alert := range alerts {
res[alert.Title] = alert.Uid
}
return res
}
// replaceAlertNameMatcherWithRuleUid replaces the stub matchers based on alert_name with the rule_uid's generated during migration.
func replaceAlertNameMatcherWithRuleUid(t *testing.T, rts []*ualert.Route, alertUids map[string]string) {
for _, rt := range rts {
if len(rt.Matchers) > 0 {
// Replace alert name matcher with generated rule_uid matcher
for _, m := range rt.Matchers {
if m.Name == "alert_name" {
m.Name = "rule_uid"
m.Value = alertUids[m.Value]
}
}
}
// Recurse for nested routes.
replaceAlertNameMatcherWithRuleUid(t, rt.Routes, alertUids)
}
}
func boolPointer(b bool) *bool {
return &b
}
// createAlertNameMatchers creates a temporary alert_name Matchers that will be replaced during runtime with the generated rule_uid.
func createAlertNameMatchers(alertName string) ualert.Matchers {
matcher, _ := labels.NewMatcher(labels.MatchEqual, "alert_name", alertName)
return ualert.Matchers(labels.Matchers{matcher})
}