mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
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.
768 lines
31 KiB
768 lines
31 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/org"
|
|
"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))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAMConfigMigration tests the execution of the main DashAlertMigration specifically for migrations of channels and routes.
|
|
func TestAMConfigMigration(t *testing.T) {
|
|
// Run initial migration to have a working DB.
|
|
x := setupTestDB(t)
|
|
|
|
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", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier2", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier2".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier3", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier3".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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{
|
|
{Receiver: "notifier4", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier4".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier5", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier5".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier6", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier6".*`}}, Routes: nil, 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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier2", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier2".*`}}, Routes: nil, 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", 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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier2", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier2".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier3", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier3".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier2", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier2".*`}}, Routes: nil, 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", 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", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
{Receiver: "notifier2", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier2".*`}}, Routes: nil, 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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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},
|
|
Routes: []*ualert.Route{
|
|
{Receiver: "notifier1", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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", ObjectMatchers: ualert.ObjectMatchers{{Type: 2, Name: ualert.ContactLabel, Value: `.*"notifier1".*`}}, Routes: nil, Continue: true},
|
|
},
|
|
},
|
|
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)
|
|
runDashAlertMigrationTestRun(t, x)
|
|
|
|
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...))
|
|
}
|
|
|
|
// Order of 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.ObjectMatchers[0].Value < b.ObjectMatchers[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...))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDashAlertMigration tests the execution of the main DashAlertMigration specifically for migrations of alerts.
|
|
func TestDashAlertMigration(t *testing.T) {
|
|
// Run initial migration to have a working DB.
|
|
x := setupTestDB(t)
|
|
|
|
t.Run("when DashAlertMigration create ContactLabel on migrated AlertRules", func(t *testing.T) {
|
|
defer teardown(t, x)
|
|
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]map[string]*ngModels.AlertRule{
|
|
int64(1): {
|
|
"alert1": {Labels: map[string]string{ualert.ContactLabel: `"notifier1"`}},
|
|
"alert2": {Labels: map[string]string{ualert.ContactLabel: `"notifier2","notifier3"`}},
|
|
"alert3": {Labels: map[string]string{ualert.ContactLabel: `"notifier3"`}},
|
|
},
|
|
int64(2): {
|
|
"alert4": {Labels: map[string]string{ualert.ContactLabel: `"notifier4","notifier6"`}},
|
|
"alert5": {Labels: map[string]string{ualert.ContactLabel: `"notifier4","notifier5","notifier6"`}},
|
|
"alert6": {Labels: map[string]string{}},
|
|
},
|
|
}
|
|
setupLegacyAlertsTables(t, x, legacyChannels, alerts)
|
|
runDashAlertMigrationTestRun(t, x)
|
|
|
|
for orgId := range expected {
|
|
rules := getAlertRules(t, x, orgId)
|
|
expectedRulesMap := expected[orgId]
|
|
require.Len(t, rules, len(expectedRulesMap))
|
|
for _, r := range rules {
|
|
require.Equal(t, expectedRulesMap[r.Title].Labels[ualert.ContactLabel], r.Labels[ualert.ContactLabel])
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("when DashAlertMigration create ContactLabel with sanitized name if name contains double quote", func(t *testing.T) {
|
|
defer teardown(t, x)
|
|
legacyChannels := []*models.AlertNotification{
|
|
createAlertNotification(t, int64(1), "notif\"ier1", "email", emailSettings, false),
|
|
}
|
|
alerts := []*models.Alert{
|
|
createAlert(t, int64(1), int64(1), int64(1), "alert1", []string{"notif\"ier1"}),
|
|
}
|
|
expected := map[int64]map[string]*ngModels.AlertRule{
|
|
int64(1): {
|
|
"alert1": {Labels: map[string]string{ualert.ContactLabel: `"notif_ier1"`}},
|
|
},
|
|
}
|
|
setupLegacyAlertsTables(t, x, legacyChannels, alerts)
|
|
runDashAlertMigrationTestRun(t, x)
|
|
|
|
for orgId := range expected {
|
|
rules := getAlertRules(t, x, orgId)
|
|
expectedRulesMap := expected[orgId]
|
|
require.Len(t, rules, len(expectedRulesMap))
|
|
for _, r := range rules {
|
|
require.Equal(t, expectedRulesMap[r.Title].Labels[ualert.ContactLabel], r.Labels[ualert.ContactLabel])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const (
|
|
emailSettings = `{"addresses": "test"}`
|
|
slackSettings = `{"recipient": "test", "token": "test"}`
|
|
opsgenieSettings = `{"apiKey": "test"}`
|
|
)
|
|
|
|
// 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) *org.Org {
|
|
t.Helper()
|
|
return &org.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)
|
|
}
|
|
|
|
// setupDashAlertMigrationTestRun runs DashAlertMigration for a new test run.
|
|
func runDashAlertMigrationTestRun(t *testing.T, x *xorm.Engine) {
|
|
_, 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)
|
|
}
|
|
|
|
// 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 := []org.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
|
|
}
|
|
|
|
// getAlertmanagerConfig retreives the Alertmanager Config from the database for a given orgId.
|
|
func getAlertRules(t *testing.T, x *xorm.Engine, orgId int64) []*ngModels.AlertRule {
|
|
rules := make([]*ngModels.AlertRule, 0)
|
|
err := x.Table("alert_rule").Where("org_id = ?", orgId).Find(&rules)
|
|
require.NoError(t, err)
|
|
|
|
return rules
|
|
}
|
|
|
|
func boolPointer(b bool) *bool {
|
|
return &b
|
|
}
|
|
|