aa03b8f8a7
This PR has two steps that together create a functional dry-run capability for the migration.
By enabling the feature flag alertingPreviewUpgrade when on legacy alerting it will:
a. Allow all Grafana Alerting background services except for the scheduler to start (multiorg alertmanager, state manager, routes, …).
b. Allow the UI to show Grafana Alerting pages alongside legacy ones (with appropriate in-app warnings that UA is not actually running).
c. Show a new “Alerting Upgrade” page and register associated /api/v1/upgrade endpoints that will allow the user to upgrade their organization live without restart and present a summary of the upgrade in a table.
2135 lines
75 KiB
Go
2135 lines
75 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"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/log/logtest"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
migmodels "github.com/grafana/grafana/pkg/services/ngalert/migration/models"
|
|
migrationStore "github.com/grafana/grafana/pkg/services/ngalert/migration/store"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
|
|
"xorm.io/xorm"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
legacymodels "github.com/grafana/grafana/pkg/services/alerting/models"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
// TestServiceRevert tests migration revert.
|
|
func TestServiceRevert(t *testing.T) {
|
|
alerts := []*legacymodels.Alert{
|
|
createAlert(t, 1, 1, 1, "alert1", []string{"notifier1"}),
|
|
}
|
|
channels := []*legacymodels.AlertNotification{
|
|
createAlertNotification(t, int64(1), "notifier1", "email", emailSettings, false),
|
|
}
|
|
dashes := []*dashboards.Dashboard{
|
|
createDashboard(t, 1, 1, "dash1-1", 5, nil),
|
|
createDashboard(t, 2, 1, "dash2-1", 5, nil),
|
|
createDashboard(t, 8, 1, "dash-in-general-1", 0, nil),
|
|
}
|
|
folders := []*dashboards.Dashboard{
|
|
createFolder(t, 5, 1, "folder5-1"),
|
|
}
|
|
|
|
t.Run("revert deletes UA resources", func(t *testing.T) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
|
|
setupLegacyAlertsTables(t, x, channels, alerts, folders, dashes)
|
|
|
|
dashCount, err := x.Table("dashboard").Count(&dashboards.Dashboard{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(4), dashCount)
|
|
|
|
// Run migration.
|
|
ctx := context.Background()
|
|
cfg := &setting.Cfg{
|
|
UnifiedAlerting: setting.UnifiedAlertingSettings{
|
|
Enabled: pointer(true),
|
|
},
|
|
}
|
|
service := NewTestMigrationService(t, sqlStore, cfg)
|
|
|
|
err = service.migrationStore.SetCurrentAlertingType(ctx, migrationStore.Legacy)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, service.Run(ctx))
|
|
|
|
// Verify migration was run.
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
|
|
// Currently, we fill in some random data for tables that aren't populated during migration.
|
|
_, err = x.Table("ngalert_configuration").Insert(models.AdminConfiguration{OrgID: 1})
|
|
require.NoError(t, err)
|
|
_, err = x.Table("alert_instance").Insert(models.AlertInstance{
|
|
AlertInstanceKey: models.AlertInstanceKey{
|
|
RuleOrgID: 1,
|
|
RuleUID: "alert1",
|
|
LabelsHash: "",
|
|
},
|
|
CurrentState: models.InstanceStateNormal,
|
|
CurrentStateSince: time.Now(),
|
|
CurrentStateEnd: time.Now(),
|
|
LastEvalTime: time.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Verify various UA resources exist
|
|
tables := [][2]string{
|
|
{"alert_rule", "org_id"},
|
|
{"alert_rule_version", "rule_org_id"},
|
|
{"alert_configuration", "org_id"},
|
|
{"ngalert_configuration", "org_id"},
|
|
{"alert_instance", "rule_org_id"},
|
|
}
|
|
for _, table := range tables {
|
|
count, err := x.Table(table[0]).Where(fmt.Sprintf("%s=?", table[1]), 1).Count()
|
|
require.NoErrorf(t, err, "table %s error", table[0])
|
|
require.True(t, count > 0, "table %s should have at least one row", table[0])
|
|
}
|
|
|
|
// Revert migration.
|
|
err = service.migrationStore.RevertAllOrgs(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify revert was run.
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, false)
|
|
|
|
// Verify various UA resources are gone
|
|
for _, table := range tables {
|
|
count, err := x.Table(table[0]).Where(fmt.Sprintf("%s=?", table[1]), 1).Count()
|
|
require.NoErrorf(t, err, "table %s error", table[0])
|
|
require.Equal(t, int64(0), count, "table %s should have no rows", table[0])
|
|
}
|
|
})
|
|
|
|
t.Run("revert deletes folders created during migration", func(t *testing.T) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
alerts = []*legacymodels.Alert{
|
|
createAlert(t, 1, 8, 1, "alert1", []string{"notifier1"}),
|
|
}
|
|
setupLegacyAlertsTables(t, x, channels, alerts, folders, dashes)
|
|
|
|
dashCount, err := x.Table("dashboard").Count(&dashboards.Dashboard{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(4), dashCount)
|
|
|
|
// Run migration.
|
|
ctx := context.Background()
|
|
cfg := &setting.Cfg{
|
|
UnifiedAlerting: setting.UnifiedAlertingSettings{
|
|
Enabled: pointer(true),
|
|
},
|
|
}
|
|
service := NewTestMigrationService(t, sqlStore, cfg)
|
|
|
|
err = service.migrationStore.SetCurrentAlertingType(ctx, migrationStore.Legacy)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, service.Run(ctx))
|
|
|
|
// Verify migration was run.
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
|
|
// Verify we created some folders.
|
|
newDashCount, err := x.Table("dashboard").Count(&dashboards.Dashboard{})
|
|
require.NoError(t, err)
|
|
require.Truef(t, newDashCount > dashCount, "newDashCount: %d should be greater than dashCount: %d", newDashCount, dashCount)
|
|
|
|
// Check that dashboards and folders from before migration still exist.
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash1-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash2-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash-in-general-1"))
|
|
|
|
state, err := service.migrationStore.GetOrgMigrationState(ctx, 1)
|
|
require.NoError(t, err)
|
|
|
|
// Verify list of created folders.
|
|
require.NotEmpty(t, state.CreatedFolders)
|
|
for _, uid := range state.CreatedFolders {
|
|
require.NotNil(t, getDashboard(t, x, 1, uid))
|
|
}
|
|
|
|
// Revert migration.
|
|
err = service.migrationStore.RevertAllOrgs(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify revert was run. Should only set migration status for org.
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, false)
|
|
|
|
// Verify we are back to the original count.
|
|
newDashCount, err = x.Table("dashboard").Count(&dashboards.Dashboard{})
|
|
require.NoError(t, err)
|
|
require.Equalf(t, dashCount, newDashCount, "newDashCount: %d should be equal to dashCount: %d after revert", newDashCount, dashCount)
|
|
|
|
// Check that dashboards and folders from before migration still exist.
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash1-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash2-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash-in-general-1"))
|
|
|
|
// Check that folders created during migration are gone.
|
|
for _, uid := range state.CreatedFolders {
|
|
require.Nil(t, getDashboard(t, x, 1, uid))
|
|
}
|
|
})
|
|
|
|
t.Run("revert skips migrated folders that are not empty", func(t *testing.T) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
alerts = []*legacymodels.Alert{
|
|
createAlert(t, 1, 8, 1, "alert1", []string{"notifier1"}),
|
|
}
|
|
setupLegacyAlertsTables(t, x, channels, alerts, folders, dashes)
|
|
|
|
dashCount, err := x.Table("dashboard").Count(&dashboards.Dashboard{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(4), dashCount)
|
|
|
|
// Run migration.
|
|
ctx := context.Background()
|
|
cfg := &setting.Cfg{
|
|
UnifiedAlerting: setting.UnifiedAlertingSettings{
|
|
Enabled: pointer(true),
|
|
Upgrade: setting.UnifiedAlertingUpgradeSettings{},
|
|
},
|
|
}
|
|
service := NewTestMigrationService(t, sqlStore, cfg)
|
|
|
|
err = service.migrationStore.SetCurrentAlertingType(ctx, migrationStore.Legacy)
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, service.Run(ctx))
|
|
|
|
// Verify migration was run.
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
|
|
// Verify we created some folders.
|
|
newDashCount, err := x.Table("dashboard").Count(&dashboards.Dashboard{OrgID: 1})
|
|
require.NoError(t, err)
|
|
require.Truef(t, newDashCount > dashCount, "newDashCount: %d should be greater than dashCount: %d", newDashCount, dashCount)
|
|
|
|
// Check that dashboards and folders from before migration still exist.
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash1-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash2-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash-in-general-1"))
|
|
|
|
state, err := service.migrationStore.GetOrgMigrationState(ctx, 1)
|
|
require.NoError(t, err)
|
|
|
|
// Verify list of created folders.
|
|
require.NotEmpty(t, state.CreatedFolders)
|
|
var generalAlertingFolder *dashboards.Dashboard
|
|
for _, uid := range state.CreatedFolders {
|
|
f := getDashboard(t, x, 1, uid)
|
|
require.NotNil(t, f)
|
|
if f.Slug == "general-alerting" {
|
|
generalAlertingFolder = f
|
|
}
|
|
}
|
|
require.NotNil(t, generalAlertingFolder)
|
|
|
|
// Create dashboard in general alerting.
|
|
newDashes := []*dashboards.Dashboard{
|
|
createDashboard(t, 99, 1, "dash-in-general-alerting-1", generalAlertingFolder.ID, nil),
|
|
}
|
|
_, err = x.Insert(newDashes)
|
|
require.NoError(t, err)
|
|
|
|
newF := getDashboard(t, x, 1, "dash-in-general-alerting-1")
|
|
require.NotNil(t, newF)
|
|
|
|
// Revert migration.
|
|
err = service.migrationStore.RevertAllOrgs(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
// Verify revert was run. Should only set migration status for org.
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, false)
|
|
|
|
// Verify we are back to the original count + 2.
|
|
newDashCount, err = x.Table("dashboard").Count(&dashboards.Dashboard{OrgID: 1})
|
|
require.NoError(t, err)
|
|
require.Equalf(t, dashCount+2, newDashCount, "newDashCount: %d should be equal to dashCount + 2: %d after revert", newDashCount, dashCount)
|
|
|
|
// Check that dashboards and folders from before migration still exist.
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash1-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash2-1"))
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash-in-general-1"))
|
|
|
|
// Check that the general alerting folder still exists.
|
|
require.NotNil(t, getDashboard(t, x, 1, generalAlertingFolder.UID))
|
|
// Check that the new dashboard in general alerting folder still exists.
|
|
require.NotNil(t, getDashboard(t, x, 1, "dash-in-general-alerting-1"))
|
|
|
|
// Check that other folders created during migration are gone.
|
|
for _, uid := range state.CreatedFolders {
|
|
if uid == generalAlertingFolder.UID {
|
|
continue
|
|
}
|
|
require.Nil(t, getDashboard(t, x, 1, uid))
|
|
}
|
|
})
|
|
|
|
t.Run("CleanUpgrade story", func(t *testing.T) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
|
|
setupLegacyAlertsTables(t, x, channels, alerts, folders, dashes)
|
|
|
|
ctx := context.Background()
|
|
cfg := &setting.Cfg{
|
|
UnifiedAlerting: setting.UnifiedAlertingSettings{
|
|
Enabled: pointer(true),
|
|
},
|
|
}
|
|
service := NewTestMigrationService(t, sqlStore, cfg)
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, false)
|
|
checkAlertRulesCount(t, x, 1, 0)
|
|
|
|
// Enable UA.
|
|
// First run should migrate org.
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
checkAlertRulesCount(t, x, 1, 1)
|
|
|
|
// Disable UA.
|
|
// This run should just set migration status to false.
|
|
service.cfg.UnifiedAlerting.Enabled = pointer(false)
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
checkAlertRulesCount(t, x, 1, 1)
|
|
|
|
// Add another alert.
|
|
// Enable UA without clean flag.
|
|
// This run should not remigrate org, new alert is not migrated.
|
|
_, alertErr := x.Insert(createAlert(t, 1, 1, 2, "alert2", []string{"notifier1"}))
|
|
require.NoError(t, alertErr)
|
|
service.cfg.UnifiedAlerting.Enabled = pointer(true)
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
checkAlertRulesCount(t, x, 1, 1) // Still 1
|
|
|
|
// Disable UA with clean flag.
|
|
// This run should not revert UA data.
|
|
service.cfg.UnifiedAlerting.Enabled = pointer(false)
|
|
service.cfg.UnifiedAlerting.Upgrade.CleanUpgrade = true
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
checkAlertRulesCount(t, x, 1, 1) // Still 1
|
|
|
|
// Enable UA with clean flag.
|
|
// This run should revert and remigrate org, new alert is migrated.
|
|
service.cfg.UnifiedAlerting.Enabled = pointer(true)
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.UnifiedAlerting)
|
|
checkMigrationStatus(t, ctx, service, 1, true)
|
|
checkAlertRulesCount(t, x, 1, 2) // Now we have 2
|
|
|
|
// The following tests ForceMigration which is deprecated and will be removed in v11.
|
|
service.cfg.UnifiedAlerting.Upgrade.CleanUpgrade = false
|
|
|
|
// Disable UA with force flag.
|
|
// This run should not revert UA data.
|
|
service.cfg.UnifiedAlerting.Enabled = pointer(false)
|
|
service.cfg.ForceMigration = true
|
|
require.NoError(t, service.Run(ctx))
|
|
checkAlertingType(t, ctx, service, migrationStore.Legacy)
|
|
checkMigrationStatus(t, ctx, service, 1, false)
|
|
checkAlertRulesCount(t, x, 1, 0)
|
|
})
|
|
}
|
|
|
|
func checkMigrationStatus(t *testing.T, ctx context.Context, service *migrationService, orgID int64, expected bool) {
|
|
migrated, err := service.migrationStore.IsMigrated(ctx, orgID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, migrated)
|
|
}
|
|
|
|
func checkAlertingType(t *testing.T, ctx context.Context, service *migrationService, expected migrationStore.AlertingType) {
|
|
aType, err := service.migrationStore.GetCurrentAlertingType(ctx)
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected, aType)
|
|
}
|
|
|
|
func checkAlertRulesCount(t *testing.T, x *xorm.Engine, orgID int64, count int) {
|
|
cnt, err := x.Table("alert_rule").Where("org_id=?", orgID).Count()
|
|
require.NoError(t, err, "table alert_rule error")
|
|
require.Equal(t, int(cnt), count, "table alert_rule should have no rows")
|
|
}
|
|
|
|
type testcase struct {
|
|
name string
|
|
orgToMigrate int64
|
|
skipExisting bool
|
|
|
|
// Common Inputs
|
|
folders []*dashboards.Dashboard
|
|
dashboards []*dashboards.Dashboard
|
|
dashboardPerms map[string][]accesscontrol.SetResourcePermissionCommand
|
|
|
|
initialLegacyState legacyState
|
|
initialUAState *uaState
|
|
|
|
operations []testOp
|
|
}
|
|
type legacyState struct {
|
|
alerts []*legacymodels.Alert
|
|
channels []*legacymodels.AlertNotification
|
|
}
|
|
type uaState struct {
|
|
alerts []*models.AlertRule
|
|
amConfig *definitions.PostableUserConfig
|
|
migState *migrationStore.OrgMigrationState
|
|
serviceState *definitions.OrgMigrationState // output only.
|
|
}
|
|
|
|
type testOp struct {
|
|
description string
|
|
newLegacyState *legacyState
|
|
updateLegacyState *legacyState
|
|
operation func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error
|
|
expectedUAState *uaState
|
|
expectedErrors []string
|
|
}
|
|
|
|
//nolint:gocyclo
|
|
func TestCommonServicePatterns(t *testing.T) {
|
|
sh := newServiceHelper(t)
|
|
f1 := sh.genFolder()
|
|
f2 := sh.genFolder()
|
|
generalAlertingFolder := sh.genFolder()
|
|
generalAlertingFolder.UID = "general-alerting"
|
|
generalAlertingFolder.Title = "General Alerting"
|
|
sh.folders[generalAlertingFolder.ID] = generalAlertingFolder
|
|
sh.foldersByUID[generalAlertingFolder.UID] = generalAlertingFolder
|
|
|
|
generalFolder := &dashboards.Dashboard{
|
|
ID: 0,
|
|
Title: "General",
|
|
}
|
|
sh.folders[generalFolder.ID] = generalFolder
|
|
sh.foldersByUID[generalFolder.UID] = generalFolder
|
|
|
|
d1 := sh.genDash(f1)
|
|
alerts1 := sh.genAlerts(d1, 10)
|
|
rules1, pairs1 := sh.genAlertPairs(f1, d1, alerts1)
|
|
|
|
d2 := sh.genDash(f1)
|
|
alerts2 := sh.genAlerts(d2, 10)
|
|
rules2, pairs2 := sh.genAlertPairs(f1, d2, alerts2)
|
|
|
|
d3 := sh.genDash(f2)
|
|
alerts3 := sh.genAlerts(d3, 10)
|
|
_, pairs3 := sh.genAlertPairs(f2, d3, alerts3)
|
|
|
|
channels1 := sh.genChannels(10)
|
|
channels2 := sh.genChannels(10)
|
|
|
|
modifiedAlerts := func(alerts []*legacymodels.Alert, muts ...func(alert *legacymodels.Alert)) []*legacymodels.Alert {
|
|
newAlerts := copyAlerts(alerts...)
|
|
for _, alert := range newAlerts {
|
|
for _, mut := range muts {
|
|
mut(alert)
|
|
}
|
|
}
|
|
return newAlerts
|
|
}
|
|
|
|
modifiedSuffix := "-modified"
|
|
|
|
withModifiedName := func(alert *legacymodels.Alert) {
|
|
alert.Name = alert.Name + modifiedSuffix
|
|
}
|
|
|
|
withName := func(name string) func(alert *legacymodels.Alert) {
|
|
return func(alert *legacymodels.Alert) {
|
|
alert.Name = name
|
|
}
|
|
}
|
|
|
|
withNotifiers := func(alert *legacymodels.Alert) {
|
|
alert.Settings.Set("notifications", []notificationKey{{ID: alert.ID}})
|
|
}
|
|
|
|
modifiedPairs := func(pairs []*migmodels.AlertPair, muts ...func(alert *migmodels.AlertPair)) []*migmodels.AlertPair {
|
|
newPairs := copyPairs(pairs...)
|
|
for _, pair := range newPairs {
|
|
for _, mut := range muts {
|
|
mut(pair)
|
|
}
|
|
}
|
|
return newPairs
|
|
}
|
|
|
|
withModifiedTitle := func(pair *migmodels.AlertPair) {
|
|
pair.Rule.Title = pair.Rule.Title + modifiedSuffix
|
|
}
|
|
|
|
withTitle := func(name string) func(pair *migmodels.AlertPair) {
|
|
return func(pair *migmodels.AlertPair) {
|
|
pair.Rule.Title = name
|
|
pair.LegacyRule.Name = name
|
|
}
|
|
}
|
|
|
|
withNotifierLabels := func(pair *migmodels.AlertPair) {
|
|
withNotifiers(pair.LegacyRule)
|
|
pair.Rule.Labels[contactLabel(fmt.Sprintf("notifiername%d", pair.LegacyRule.ID))] = "true"
|
|
}
|
|
|
|
modifiedRules := func(alerts []*models.AlertRule, muts ...func(alert *models.AlertRule)) []*models.AlertRule {
|
|
newAlerts := copyRules(alerts...)
|
|
for _, alert := range newAlerts {
|
|
for _, mut := range muts {
|
|
mut(alert)
|
|
}
|
|
}
|
|
return newAlerts
|
|
}
|
|
|
|
withFolder := func(f *dashboards.Dashboard) func(alert *models.AlertRule) {
|
|
return func(alert *models.AlertRule) {
|
|
alert.NamespaceUID = f.UID
|
|
}
|
|
}
|
|
|
|
modifiedChannels := func(channels []*legacymodels.AlertNotification, muts ...func(c *legacymodels.AlertNotification)) []*legacymodels.AlertNotification {
|
|
newChannels := copyChannels(channels...)
|
|
for _, c := range newChannels {
|
|
for _, mut := range muts {
|
|
mut(c)
|
|
}
|
|
}
|
|
return newChannels
|
|
}
|
|
|
|
withModifiedChannelName := func(c *legacymodels.AlertNotification) {
|
|
c.Name = c.Name + modifiedSuffix
|
|
}
|
|
|
|
withType := func(t string) func(c *legacymodels.AlertNotification) {
|
|
return func(c *legacymodels.AlertNotification) {
|
|
c.Type = t
|
|
}
|
|
}
|
|
|
|
modifiedState := func(state *uaState, muts ...func(state *uaState)) *uaState {
|
|
for _, mut := range muts {
|
|
mut(state)
|
|
}
|
|
return state
|
|
}
|
|
|
|
for _, tt := range []testcase{
|
|
{
|
|
name: "Standard org migration",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: alerts1,
|
|
channels: channels1,
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, channels1, pairs1),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Standard org migration with multiple dashboards",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1, d2, d3},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: append(append(alerts1, alerts2...), alerts3...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, nil, pairs1, pairs2, pairs3),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "with existing alerts & channels not from migration, doesn't delete existing",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: alerts1,
|
|
channels: channels1,
|
|
},
|
|
initialUAState: &uaState{ // Not connected to the migration.
|
|
alerts: rules2,
|
|
amConfig: createPostableUserConfig(t, channels2...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
{
|
|
description: "all dashboards migration, no change",
|
|
operation: migrateAllDashboardAlertsOp(false),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
{
|
|
description: "all channels migration, no change",
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
{
|
|
description: "dashboard d1 migration, no change",
|
|
operation: migrateDashboardAlertsOp(false, d1.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
{
|
|
description: "channels[0] migration, no change",
|
|
operation: migrateChannelOp(d1.ID, channels1[0].ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
{
|
|
description: "alerts d1[0] migration, no change",
|
|
operation: migrateAlertOp(d1.ID, alerts1[0].PanelID),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, pairs1), func(state *uaState) {
|
|
state.alerts = append(state.alerts, rules2...)
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "migrateAlert with existing alerts in different folder",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
initialLegacyState: legacyState{
|
|
alerts: alerts1[:5:5],
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, nil, pairs1[:5:5]),
|
|
},
|
|
{
|
|
description: "move dashboard d1 to folder f2",
|
|
operation: func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
d1Copy := *d1
|
|
//nolint:staticcheck
|
|
d1Copy.FolderID = f2.ID
|
|
_, err := x.ID(d1.ID).Update(d1Copy)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
description: "add more alerts to d1 and migrate them",
|
|
newLegacyState: &legacyState{
|
|
alerts: alerts1[5:6:6],
|
|
},
|
|
operation: migrateAlertOp(d1.ID, alerts1[5].PanelID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1[:6:6]), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards[0].FolderUID = f2.UID
|
|
state.serviceState.MigratedDashboards[0].FolderName = f2.Title
|
|
}),
|
|
},
|
|
{
|
|
description: "add more alerts to d1 and migrate them using migrate dashboard skipExisting=true",
|
|
newLegacyState: &legacyState{
|
|
alerts: alerts1[6:10:10],
|
|
},
|
|
operation: migrateDashboardAlertsOp(true, d1.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards[0].FolderUID = f2.UID
|
|
state.serviceState.MigratedDashboards[0].FolderName = f2.Title
|
|
}),
|
|
},
|
|
{
|
|
description: "migrate with skipExisting=false should move all the alerts to f2",
|
|
operation: migrateDashboardAlertsOp(false, d1.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, modifiedPairs(pairs1, func(p *migmodels.AlertPair) { p.Rule.NamespaceUID = f2.UID })), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards[0].FolderUID = f2.UID
|
|
state.serviceState.MigratedDashboards[0].FolderName = f2.Title
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "migrate alerts using various skipExisting",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1, d2, d3},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: alerts1,
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, nil, pairs1),
|
|
},
|
|
{
|
|
description: "add some alerts for d2 & d3 and migrate dashboard d2",
|
|
newLegacyState: &legacyState{
|
|
alerts: append(alerts2[:5:5], alerts3[:5:5]...),
|
|
},
|
|
operation: migrateDashboardAlertsOp(false, d2.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, pairs2[:5:5]), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards = append(state.serviceState.MigratedDashboards, &definitions.DashboardUpgrade{
|
|
DashboardID: d3.ID,
|
|
DashboardUID: d3.UID,
|
|
DashboardName: d3.Title,
|
|
FolderUID: f2.UID,
|
|
FolderName: f2.Title,
|
|
MigratedAlerts: make([]*definitions.AlertPair, 5),
|
|
Error: "dashboard not upgraded",
|
|
})
|
|
for i, a := range alerts3[:5:5] {
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts[i] = &definitions.AlertPair{
|
|
LegacyAlert: fromLegacyAlert(a),
|
|
Error: "alert not upgraded",
|
|
}
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "modify the alerts already migrated for d2 and add the rest, migrate dashboard d2 with skipExisting",
|
|
updateLegacyState: &legacyState{
|
|
alerts: modifiedAlerts(alerts2[:5:5], withModifiedName),
|
|
},
|
|
newLegacyState: &legacyState{
|
|
alerts: alerts2[5:],
|
|
},
|
|
operation: migrateDashboardAlertsOp(true, d2.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, pairs2), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
state.serviceState.MigratedDashboards = append(state.serviceState.MigratedDashboards, &definitions.DashboardUpgrade{
|
|
DashboardID: d3.ID,
|
|
DashboardUID: d3.UID,
|
|
DashboardName: d3.Title,
|
|
FolderUID: f2.UID,
|
|
FolderName: f2.Title,
|
|
MigratedAlerts: make([]*definitions.AlertPair, 5),
|
|
Error: "dashboard not upgraded",
|
|
})
|
|
for i, a := range alerts3[:5:5] {
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts[i] = &definitions.AlertPair{
|
|
LegacyAlert: fromLegacyAlert(a),
|
|
Error: "alert not upgraded",
|
|
}
|
|
}
|
|
}), // Because of skipExisting, expected doesn't contain the modifications.
|
|
},
|
|
{
|
|
description: "migrate dashboard d3 using migrate all dashboards",
|
|
operation: migrateAllDashboardAlertsOp(true),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, pairs2, pairs3[:5:5]), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "migrate dashboard d2 with skipExisting=false should update using the modifications",
|
|
operation: migrateDashboardAlertsOp(false, d2.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, append(modifiedPairs(pairs2[:5:5], withModifiedTitle), pairs2[5:]...), pairs3[:5:5]), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "modify existing d3 alerts and add the rest, migrate one new alert on dashboard d3, should not update modifications",
|
|
updateLegacyState: &legacyState{
|
|
alerts: modifiedAlerts(alerts3[:5:5], withModifiedName),
|
|
},
|
|
newLegacyState: &legacyState{
|
|
alerts: alerts3[5:],
|
|
},
|
|
operation: migrateAlertOp(d3.ID, alerts3[5].PanelID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, append(modifiedPairs(pairs2[:5:5], withModifiedTitle), pairs2[5:]...), pairs3[:6:6]), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
for _, a := range alerts3[6:] {
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts = append(state.serviceState.MigratedDashboards[2].MigratedAlerts, &definitions.AlertPair{
|
|
LegacyAlert: fromLegacyAlert(a),
|
|
Error: "alert not upgraded",
|
|
})
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "migrate one existing alert on dashboard d3, should update modifications",
|
|
operation: migrateAlertOp(d3.ID, alerts3[0].PanelID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, pairs1, append(modifiedPairs(pairs2[:5:5], withModifiedTitle), pairs2[5:]...), append(modifiedPairs(pairs3[0:1:1], withModifiedTitle), pairs3[1:6:6]...)), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
for _, a := range alerts3[6:] {
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts = append(state.serviceState.MigratedDashboards[2].MigratedAlerts, &definitions.AlertPair{
|
|
LegacyAlert: fromLegacyAlert(a),
|
|
Error: "alert not upgraded",
|
|
})
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "update d1 alerts, and re-migrate all dashboards",
|
|
updateLegacyState: &legacyState{
|
|
alerts: modifiedAlerts(alerts1, withModifiedName),
|
|
},
|
|
operation: migrateAllDashboardAlertsOp(false),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, modifiedPairs(pairs1, withModifiedTitle), append(modifiedPairs(pairs2[:5:5], withModifiedTitle), pairs2[5:]...), append(modifiedPairs(pairs3[0:5:5], withModifiedTitle), pairs3[5:]...)), func(state *uaState) {
|
|
for i := 0; i < 5; i++ {
|
|
state.serviceState.MigratedDashboards[1].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
state.serviceState.MigratedDashboards[2].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
state.serviceState.MigratedDashboards[0].MigratedAlerts[i].LegacyAlert.Name += modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateAllChannels skip=true doesn't update existing channels",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
updateLegacyState: &legacyState{channels: modifiedChannels(channels1, withModifiedChannelName)},
|
|
operation: migrateAllChannelsOp(true),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1), func(state *uaState) {
|
|
for _, c := range state.serviceState.MigratedChannels {
|
|
c.LegacyChannel.Name += modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateAllChannels skip=false updates existing channels",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
updateLegacyState: &legacyState{channels: modifiedChannels(channels1, withModifiedChannelName)},
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: sh.uaState(t, modifiedChannels(channels1, withModifiedChannelName)),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateAllChannels skip=false doesn't delete existing channels unrelated to migration",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: &uaState{
|
|
amConfig: createPostableUserConfig(t, channels2...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1), func(state *uaState) {
|
|
state.amConfig = createPostableUserConfig(t, append(channels1, channels2...)...)
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateAllChannels skip=true adds new channels",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
newLegacyState: &legacyState{channels: channels2},
|
|
operation: migrateAllChannelsOp(true),
|
|
expectedUAState: sh.uaState(t, append(channels1, channels2...)),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateAllChannels skip=false adds new channels",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
newLegacyState: &legacyState{channels: channels2},
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: sh.uaState(t, append(channels1, channels2...)),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateSingleChannel adds new channel and doesn't affect others",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
newLegacyState: &legacyState{channels: channels2[0:1:1]},
|
|
operation: migrateChannelOp(channels2[0].ID),
|
|
expectedUAState: sh.uaState(t, append(channels1, channels2[0])),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateSingleChannel updates existing channel and doesn't affect others",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
initialUAState: sh.uaState(t, channels1),
|
|
operations: []testOp{
|
|
{
|
|
updateLegacyState: &legacyState{channels: modifiedChannels(channels1, withModifiedChannelName)},
|
|
operation: migrateChannelOp(channels1[9].ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, append(channels1[0:9:9], modifiedChannels(channels1[9:], withModifiedChannelName)...)), func(state *uaState) {
|
|
for i := 0; i < 10; i++ {
|
|
state.serviceState.MigratedChannels[i].LegacyChannel.Name = channels1[i].Name + modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateSingleChannel removes deleted channel and doesn't affect others",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1[0:9:9]},
|
|
initialUAState: sh.uaState(t, channels1), // Existing state has channels1[9].
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateChannelOp(channels1[9].ID),
|
|
expectedUAState: sh.uaState(t, channels1[0:9:9]),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "MigrateSingleChannel doesn't delete existing channels unrelated to migration",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: []*legacymodels.AlertNotification{channels1[0]}},
|
|
initialUAState: &uaState{
|
|
amConfig: createPostableUserConfig(t, channels2...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateChannelOp(channels1[0].ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1[0:1:1]), func(state *uaState) {
|
|
state.amConfig = createPostableUserConfig(t, append(channels2, channels1[0])...)
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert titles should be deduplicated",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1, withName("duplicate name")),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, modifiedPairs(pairs1, withTitle("duplicate name"))), func(state *uaState) {
|
|
state.alerts[0].Title = "duplicate name"
|
|
for i := 1; i < len(state.alerts); i++ { // First pair doesn't need to be deduplicated.
|
|
state.alerts[i].Title = fmt.Sprintf("duplicate name #%d", i+1)
|
|
}
|
|
for i := 0; i < len(state.alerts); i++ {
|
|
state.serviceState.MigratedDashboards[0].MigratedAlerts[i].AlertRule.Title = state.alerts[i].Title
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert titles should be truncated",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1[0:1:1], withName(strings.Repeat("a", store.AlertDefinitionMaxTitleLength+1))),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, modifiedPairs(pairs1[0:1:1], withTitle(strings.Repeat("a", store.AlertDefinitionMaxTitleLength)))), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards[0].MigratedAlerts[0].LegacyAlert.Name = strings.Repeat("a", store.AlertDefinitionMaxTitleLength+1)
|
|
})},
|
|
},
|
|
},
|
|
{
|
|
name: "alert titles should be truncated and deduplicated",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1, withName(strings.Repeat("a", store.AlertDefinitionMaxTitleLength+1))),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: func() *uaState {
|
|
pairs := modifiedPairs(pairs1, withTitle(strings.Repeat("a", store.AlertDefinitionMaxTitleLength)))
|
|
for i := 1; i < len(pairs); i++ { // First pair doesn't need to be deduplicated.
|
|
suffix := fmt.Sprintf(" #%d", i+1)
|
|
pairs[i].Rule.Title = fmt.Sprintf("%s%s", pairs[i].Rule.Title[:store.AlertDefinitionMaxTitleLength-len(suffix)], suffix)
|
|
}
|
|
state := sh.uaState(t, nil, pairs)
|
|
for i := 0; i < len(pairs); i++ {
|
|
state.serviceState.MigratedDashboards[0].MigratedAlerts[i].LegacyAlert.Name = strings.Repeat("a", store.AlertDefinitionMaxTitleLength+1)
|
|
}
|
|
return state
|
|
}(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert has invalid settings, should return pair with error",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
// Break the last half of the alerts.
|
|
alerts: append(alerts1[:5:5], modifiedAlerts(alerts1[5:10:10], func(alert *legacymodels.Alert) { alert.Settings.Set("noDataState", 1.5) })...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: &uaState{
|
|
alerts: rules1[:5:5],
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: map[int64]*migrationStore.DashboardUpgrade{
|
|
d1.ID: sh.dashUpgrade(d1.ID, f1.UID, append(pairs1[:5:5], modifiedPairs(pairs1[5:10:10], func(pair *migmodels.AlertPair) {
|
|
pair.Error = errors.New("parse settings: json: cannot unmarshal number into Go struct field dashAlertSettings.noDataState of type string")
|
|
pair.Rule.UID = ""
|
|
})...), ""),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert has missing dashboard, should return pair with error",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
// Set dashboard to nonexisting id for the last half of the alerts.
|
|
alerts: append(alerts1[:5:5], modifiedAlerts(alerts1[5:10:10], func(alert *legacymodels.Alert) { alert.DashboardID = -42 })...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: &uaState{
|
|
alerts: rules1[:5:5],
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: map[int64]*migrationStore.DashboardUpgrade{
|
|
d1.ID: sh.dashUpgrade(d1.ID, f1.UID, pairs1[:5:5], ""),
|
|
-42: sh.dashUpgrade(-42, "", modifiedPairs(pairs1[5:10:10], func(pair *migmodels.AlertPair) {
|
|
pair.Error = errors.New("orphaned: missing dashboard")
|
|
pair.Rule.UID = ""
|
|
}), ""),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert dashboard has missing folder, should migrate to new general alerting folder",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, generalAlertingFolder},
|
|
//nolint:staticcheck
|
|
dashboards: []*dashboards.Dashboard{func(d dashboards.Dashboard) *dashboards.Dashboard { d.FolderID = 99999; return &d }(*d1), d2},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: append(alerts1, alerts2...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: &uaState{
|
|
alerts: append(modifiedRules(rules1, withFolder(generalAlertingFolder)), rules2...),
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: map[int64]*migrationStore.DashboardUpgrade{
|
|
d1.ID: sh.dashUpgrade(d1.ID, generalAlertingFolder.UID, pairs1, "dashboard alerts moved to general alerting folder during upgrade: original folder not found"),
|
|
d2.ID: sh.dashUpgrade(d2.ID, f1.UID, pairs2, ""),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert dashboard in general folder, should migrate to new general alerting folder",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, generalAlertingFolder},
|
|
//nolint:staticcheck
|
|
dashboards: []*dashboards.Dashboard{func(d dashboards.Dashboard) *dashboards.Dashboard { d.FolderID = 0; return &d }(*d1), d2},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: append(alerts1[0:1:1], alerts2[0]),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: &uaState{
|
|
alerts: append(modifiedRules(rules1[0:1:1], withFolder(generalAlertingFolder)), rules2[0]),
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: map[int64]*migrationStore.DashboardUpgrade{
|
|
d1.ID: sh.dashUpgrade(d1.ID, generalAlertingFolder.UID, pairs1[0:1:1], "dashboard alerts moved to general alerting folder during upgrade: general folder not supported"),
|
|
d2.ID: sh.dashUpgrade(d2.ID, f1.UID, pairs2[0:1:1], ""),
|
|
},
|
|
},
|
|
serviceState: &definitions.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: []*definitions.DashboardUpgrade{
|
|
{
|
|
DashboardID: d1.ID,
|
|
DashboardUID: d1.UID,
|
|
DashboardName: d1.Title,
|
|
FolderUID: generalFolder.UID,
|
|
FolderName: generalFolder.Title,
|
|
NewFolderUID: generalAlertingFolder.UID,
|
|
NewFolderName: generalAlertingFolder.Title,
|
|
MigratedAlerts: []*definitions.AlertPair{
|
|
{LegacyAlert: fromLegacyAlert(alerts1[0]), AlertRule: fromAlertRuleUpgrade(rules1[0], []string{"autogen-contact-point-default"})},
|
|
},
|
|
Warning: "dashboard alerts moved to general alerting folder during upgrade: general folder not supported",
|
|
},
|
|
{
|
|
DashboardID: d2.ID,
|
|
DashboardUID: d2.UID,
|
|
DashboardName: d2.Title,
|
|
FolderUID: f1.UID,
|
|
FolderName: f1.Title,
|
|
NewFolderUID: f1.UID,
|
|
NewFolderName: f1.Title,
|
|
MigratedAlerts: []*definitions.AlertPair{
|
|
{LegacyAlert: fromLegacyAlert(alerts2[0]), AlertRule: fromAlertRuleUpgrade(rules2[0], []string{"autogen-contact-point-default"})},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert dashboard has custom permissions, should migrate to new folder",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{
|
|
f1,
|
|
func() *dashboards.Dashboard {
|
|
// The folder name is deterministic, so we can create the expected folder beforehand. This is so we know the uid for expected states.
|
|
f := createFolder(t, 100, 1, "created-folder-id")
|
|
f.Title = "folder1 Alerts - 787427ef800a01a544d6bae21970b4d2"
|
|
return f
|
|
}(),
|
|
},
|
|
dashboards: []*dashboards.Dashboard{d1, d2},
|
|
dashboardPerms: map[string][]accesscontrol.SetResourcePermissionCommand{
|
|
d2.UID: {{BuiltinRole: string(org.RoleViewer), Permission: dashboardaccess.PERMISSION_ADMIN.String()}}, // This permission maps to the 787427ef800a01a544d6bae21970b4d2 hash above.
|
|
},
|
|
initialLegacyState: legacyState{
|
|
alerts: append(alerts1, alerts2...),
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateOrgOp,
|
|
expectedUAState: &uaState{
|
|
alerts: append(rules1, modifiedRules(rules2, withFolder(&dashboards.Dashboard{UID: "created-folder-id"}))...),
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: map[int64]*migrationStore.DashboardUpgrade{
|
|
d1.ID: sh.dashUpgrade(d1.ID, f1.UID, pairs1, ""),
|
|
d2.ID: sh.dashUpgrade(d2.ID, "created-folder-id", pairs2, "dashboard alerts moved to new folder during upgrade: folder permission changes were needed"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "channel is discontinued, should return pair with error",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
operations: []testOp{
|
|
{
|
|
updateLegacyState: &legacyState{channels: modifiedChannels([]*legacymodels.AlertNotification{channels1[8], channels1[9]}, withType("hipchat"))},
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: &uaState{
|
|
amConfig: createPostableUserConfig(t, channels1[:8:8]...),
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedChannels: func() map[int64]*migrationStore.ContactPair {
|
|
pairs := sh.contactPairs(channels1...)
|
|
pairs[channels1[8].ID].Error = "'hipchat': discontinued"
|
|
pairs[channels1[8].ID].NewReceiverUID = ""
|
|
pairs[channels1[9].ID].Error = "'hipchat': discontinued"
|
|
pairs[channels1[9].ID].NewReceiverUID = ""
|
|
return pairs
|
|
}(),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "channel name updates are reflected in alert labels",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1, withNotifiers),
|
|
channels: channels1,
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, channels1, modifiedPairs(pairs1, withNotifierLabels)),
|
|
},
|
|
{
|
|
description: "update channel names and migrate single channel",
|
|
updateLegacyState: &legacyState{
|
|
channels: modifiedChannels(channels1, withModifiedChannelName),
|
|
},
|
|
operation: migrateChannelOp(channels1[9].ID),
|
|
expectedUAState: modifiedState(sh.uaState(t,
|
|
append(channels1[:9:9], modifiedChannels(channels1[9:10:10], withModifiedChannelName)...),
|
|
append(modifiedPairs(pairs1[:9:9], withNotifierLabels), modifiedPairs(pairs1[9:10:10], func(a *migmodels.AlertPair) {
|
|
withNotifiers(a.LegacyRule)
|
|
a.Rule.Labels[contactLabel(fmt.Sprintf("notifiername%d-modified", a.LegacyRule.ID))] = "true"
|
|
})...),
|
|
), func(state *uaState) {
|
|
for i := range pairs1 {
|
|
// Service state knows the updated legacy channel names.
|
|
state.serviceState.MigratedChannels[i].LegacyChannel.Name = channels1[i].Name + modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
{
|
|
description: "migrate the rest of the channels",
|
|
operation: migrateAllChannelsOp(false),
|
|
expectedUAState: sh.uaState(t,
|
|
modifiedChannels(channels1, withModifiedChannelName),
|
|
modifiedPairs(pairs1, func(a *migmodels.AlertPair) {
|
|
withNotifiers(a.LegacyRule)
|
|
a.Rule.Labels[contactLabel(fmt.Sprintf("notifiername%d-modified", a.LegacyRule.ID))] = "true"
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "contact point name updates are reflected in alert labels",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1, withNotifiers),
|
|
channels: channels1,
|
|
},
|
|
initialUAState: modifiedState(sh.uaState(t, channels1, modifiedPairs(pairs1, withNotifierLabels)), func(state *uaState) {
|
|
// Update all the contact points names. Done here so we simulate it being done post-migration.
|
|
for i := 0; i < 10; i++ {
|
|
state.amConfig.AlertmanagerConfig.Receivers[i+1].Name += modifiedSuffix
|
|
state.amConfig.AlertmanagerConfig.Receivers[i+1].GrafanaManagedReceivers[0].Name += modifiedSuffix
|
|
state.amConfig.AlertmanagerConfig.Route.Routes[0].Routes[i].Receiver += modifiedSuffix
|
|
}
|
|
}),
|
|
operations: []testOp{
|
|
{
|
|
description: "re-migrated alerts should get the label to route to the correct contact point",
|
|
operation: migrateAllDashboardAlertsOp(false),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1, modifiedPairs(pairs1, withNotifierLabels)), func(state *uaState) {
|
|
for i := range pairs1 {
|
|
state.serviceState.MigratedDashboards[0].MigratedAlerts[i].AlertRule.SendsTo = []string{channels1[i].Name + modifiedSuffix}
|
|
state.serviceState.MigratedChannels[i].ContactPointUpgrade.Name += modifiedSuffix
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
state.amConfig.AlertmanagerConfig.Receivers[i+1].Name += modifiedSuffix
|
|
state.amConfig.AlertmanagerConfig.Receivers[i+1].GrafanaManagedReceivers[0].Name += modifiedSuffix
|
|
state.amConfig.AlertmanagerConfig.Route.Routes[0].Routes[i].Receiver += modifiedSuffix
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "alert labels are correct when when alert is migrated before channel",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
|
|
initialLegacyState: legacyState{
|
|
alerts: modifiedAlerts(alerts1[:5:5], withNotifiers),
|
|
channels: channels1[:5:5],
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration of first 5 alerts and channels",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: sh.uaState(t, channels1[:5:5], modifiedPairs(pairs1[:5:5], withNotifierLabels)),
|
|
},
|
|
{
|
|
description: "add the last 5 alerts and channels, migrate just the last 5 alerts",
|
|
newLegacyState: &legacyState{
|
|
alerts: modifiedAlerts(alerts1[5:10:10], withNotifiers),
|
|
channels: channels1[5:10:10],
|
|
},
|
|
operation: migrateAllDashboardAlertsOp(true),
|
|
},
|
|
{
|
|
description: "migrate the last 5 channels",
|
|
operation: migrateAllChannelsOp(true),
|
|
expectedUAState: sh.uaState(t, channels1, modifiedPairs(pairs1, withNotifierLabels)),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "empty folders previously created by migration should be deleted",
|
|
orgToMigrate: 1,
|
|
folders: []*dashboards.Dashboard{f1, f2},
|
|
dashboards: []*dashboards.Dashboard{d1},
|
|
initialLegacyState: legacyState{
|
|
alerts: alerts1,
|
|
},
|
|
initialUAState: &uaState{
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
CreatedFolders: []string{f1.UID},
|
|
},
|
|
},
|
|
operations: []testOp{
|
|
{
|
|
description: "initial migration",
|
|
operation: migrateOrgOp,
|
|
expectedUAState: func() *uaState {
|
|
state := sh.uaState(t, nil, pairs1)
|
|
state.migState.CreatedFolders = []string{f1.UID}
|
|
return state
|
|
}(),
|
|
},
|
|
{
|
|
description: "move dashboard d1 to folder f2",
|
|
operation: func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
d1Copy := *d1
|
|
//nolint:staticcheck
|
|
d1Copy.FolderID = f2.ID
|
|
_, err := x.ID(d1.ID).Update(d1Copy)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
description: "migrate with skipExisting=false should move all the alerts to f2 and cleanup f1",
|
|
operation: migrateDashboardAlertsOp(false, d1.ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, nil, modifiedPairs(pairs1, func(p *migmodels.AlertPair) { p.Rule.NamespaceUID = f2.UID })), func(state *uaState) {
|
|
state.serviceState.MigratedDashboards[0].FolderUID = f2.UID
|
|
state.serviceState.MigratedDashboards[0].FolderName = f2.Title
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unmigrated channels should show up in GetOrgMigration state",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1},
|
|
operations: []testOp{
|
|
{
|
|
operation: migrateChannelOp(channels1[0].ID),
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1[0:1:1]), func(state *uaState) {
|
|
for _, c := range channels1[1:] {
|
|
state.serviceState.MigratedChannels = append(state.serviceState.MigratedChannels, &definitions.ContactPair{
|
|
LegacyChannel: fromLegacyChannel(c),
|
|
Error: "channel not upgraded",
|
|
})
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "channels deleted after migration should show up in GetOrgMigration state",
|
|
orgToMigrate: 1,
|
|
initialLegacyState: legacyState{channels: channels1[0:9:9]},
|
|
initialUAState: sh.uaState(t, channels1), // Existing state has channels1[9].
|
|
operations: []testOp{
|
|
{
|
|
expectedUAState: modifiedState(sh.uaState(t, channels1), func(state *uaState) {
|
|
for _, c := range state.serviceState.MigratedChannels {
|
|
if c.LegacyChannel.ID == channels1[9].ID {
|
|
c.LegacyChannel = &definitions.LegacyChannel{ID: c.LegacyChannel.ID}
|
|
c.Error = "channel no longer exists"
|
|
}
|
|
}
|
|
}),
|
|
},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tcRun(t, tt)
|
|
})
|
|
}
|
|
}
|
|
|
|
var migrateOrgOp = func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
_, err := service.MigrateOrg(ctx, tt.orgToMigrate, tt.skipExisting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var migrateAllDashboardAlertsOp = func(skipExisting bool) func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
return func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
_, err := service.MigrateAllDashboardAlerts(ctx, tt.orgToMigrate, skipExisting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var migrateAllChannelsOp = func(skipExisting bool) func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
return func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
_, err := service.MigrateAllChannels(ctx, tt.orgToMigrate, skipExisting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var migrateDashboardAlertsOp = func(skipExisting bool, ids ...int64) func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
return func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
for _, id := range ids {
|
|
_, err := service.MigrateDashboardAlerts(ctx, tt.orgToMigrate, id, skipExisting)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var migrateChannelOp = func(ids ...int64) func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
return func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
for _, id := range ids {
|
|
_, err := service.MigrateChannel(ctx, tt.orgToMigrate, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var migrateAlertOp = func(dashboardId int64, panelIds ...int64) func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
return func(ctx context.Context, tt testcase, service *migrationService, x *xorm.Engine) error {
|
|
for _, id := range panelIds {
|
|
_, err := service.MigrateAlert(ctx, tt.orgToMigrate, dashboardId, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func tcRun(t *testing.T, tt testcase) {
|
|
sqlStore := db.InitTestDB(t)
|
|
x := sqlStore.GetEngine()
|
|
store := &store.DBstore{
|
|
SQLStore: sqlStore,
|
|
Logger: &logtest.Fake{},
|
|
Cfg: setting.UnifiedAlertingSettings{
|
|
BaseInterval: 10 * time.Second,
|
|
DefaultRuleEvaluationInterval: time.Minute,
|
|
},
|
|
}
|
|
service := NewTestMigrationService(t, sqlStore, &setting.Cfg{})
|
|
defer teardown(t, x, service)
|
|
setupLegacyAlertsTables(t, x, tt.initialLegacyState.channels, tt.initialLegacyState.alerts, tt.folders, tt.dashboards)
|
|
if tt.initialUAState == nil {
|
|
tt.initialUAState = &uaState{}
|
|
}
|
|
setupUATables(t, store, tt.orgToMigrate, tt.initialUAState.alerts, tt.initialUAState.amConfig)
|
|
if tt.initialUAState.migState != nil {
|
|
require.NoError(t, service.migrationStore.SetOrgMigrationState(context.Background(), tt.orgToMigrate, tt.initialUAState.migState))
|
|
}
|
|
|
|
if tt.dashboardPerms != nil {
|
|
for uid, perms := range tt.dashboardPerms {
|
|
_, err := service.migrationStore.SetDashboardPermissions(context.Background(), 1, uid, perms...)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
ctx := context.Background()
|
|
require.NoError(t, service.migrationStore.SetMigrated(context.Background(), tt.orgToMigrate, true)) // To bypass verification.
|
|
|
|
for _, op := range tt.operations {
|
|
if op.description != "" {
|
|
t.Logf("Running operation: %s", op.description)
|
|
}
|
|
if op.newLegacyState != nil {
|
|
err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
if len(op.newLegacyState.channels) > 0 {
|
|
_, err := sess.Insert(op.newLegacyState.channels)
|
|
require.NoError(t, err)
|
|
}
|
|
if len(op.newLegacyState.alerts) > 0 {
|
|
_, err := sess.Insert(op.newLegacyState.alerts)
|
|
require.NoError(t, err)
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
if op.updateLegacyState != nil {
|
|
err := sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
for _, c := range op.updateLegacyState.channels {
|
|
_, err := sess.ID(c.ID).Update(c)
|
|
require.NoError(t, err)
|
|
}
|
|
for _, a := range op.updateLegacyState.alerts {
|
|
_, err := sess.ID(a.ID).Update(a)
|
|
require.NoError(t, err)
|
|
}
|
|
return nil
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if op.operation != nil {
|
|
err := op.operation(ctx, tt, service, x)
|
|
if len(op.expectedErrors) > 0 {
|
|
for _, expErr := range op.expectedErrors {
|
|
require.ErrorContains(t, err, expErr)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if op.expectedUAState != nil {
|
|
compareRules(t, x, tt.orgToMigrate, op.expectedUAState.alerts)
|
|
compareAmConfig(t, x, tt.orgToMigrate, op.expectedUAState.amConfig)
|
|
compareState(t, x, service, tt.orgToMigrate, op.expectedUAState.migState, op.expectedUAState.serviceState)
|
|
}
|
|
}
|
|
}
|
|
|
|
func compareRules(t *testing.T, x *xorm.Engine, orgId int64, expectedRules []*models.AlertRule) {
|
|
if expectedRules == nil {
|
|
return
|
|
}
|
|
rules := make([]*models.AlertRule, 0)
|
|
err := x.Table("alert_rule").Where("org_id = ?", orgId).Find(&rules)
|
|
require.NoError(t, err)
|
|
|
|
cOpt := []cmp.Option{
|
|
cmpopts.SortSlices(func(a, b models.AlertRule) bool {
|
|
return a.Title < b.Title
|
|
}),
|
|
cmpopts.IgnoreUnexported(models.AlertRule{}, models.AlertQuery{}),
|
|
cmpopts.IgnoreFields(models.AlertRule{}, "Updated", "UID", "ID", "Version"),
|
|
cmpopts.IgnoreMapEntries(func(k string, v string) bool { return k == "rule_uid" }),
|
|
}
|
|
if !cmp.Equal(expectedRules, rules, cOpt...) {
|
|
t.Errorf("Unexpected Rule: %v", cmp.Diff(expectedRules, rules, cOpt...))
|
|
}
|
|
}
|
|
|
|
func compareAmConfig(t *testing.T, x *xorm.Engine, orgId int64, expectedConfig *definitions.PostableUserConfig) {
|
|
if expectedConfig == nil {
|
|
return
|
|
}
|
|
amConfig := getAlertmanagerConfig(t, x, orgId)
|
|
|
|
// Order of nested GrafanaManagedReceivers is not guaranteed.
|
|
cOpt := []cmp.Option{
|
|
cmpopts.IgnoreUnexported(definitions.PostableApiReceiver{}),
|
|
cmpopts.IgnoreFields(definitions.PostableGrafanaReceiver{}, "UID", "SecureSettings"),
|
|
cmpopts.SortSlices(func(a, b *definitions.PostableGrafanaReceiver) bool { return a.Name < b.Name }),
|
|
cmpopts.SortSlices(func(a, b *definitions.PostableApiReceiver) bool { return a.Name < b.Name }),
|
|
}
|
|
if !cmp.Equal(expectedConfig.AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...) {
|
|
t.Errorf("Unexpected Receivers: %v", cmp.Diff(expectedConfig.AlertmanagerConfig.Receivers, amConfig.AlertmanagerConfig.Receivers, cOpt...))
|
|
}
|
|
|
|
// Order of routes is not guaranteed.
|
|
cOpt = []cmp.Option{
|
|
cmpopts.SortSlices(func(a, b *definitions.Route) bool {
|
|
if a.Receiver != b.Receiver {
|
|
return a.Receiver < b.Receiver
|
|
}
|
|
return a.ObjectMatchers[0].Value < b.ObjectMatchers[0].Value
|
|
}),
|
|
cmpopts.IgnoreUnexported(definitions.Route{}, labels.Matcher{}),
|
|
cmpopts.IgnoreFields(definitions.Route{}, "GroupBy", "GroupByAll"),
|
|
}
|
|
if !cmp.Equal(expectedConfig.AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...) {
|
|
t.Errorf("Unexpected Route: %v", cmp.Diff(expectedConfig.AlertmanagerConfig.Route, amConfig.AlertmanagerConfig.Route, cOpt...))
|
|
}
|
|
}
|
|
|
|
func compareState(t *testing.T, x *xorm.Engine, service *migrationService, orgId int64, expectedState *migrationStore.OrgMigrationState, expectedServiceState *definitions.OrgMigrationState) {
|
|
if expectedState == nil && expectedServiceState == nil {
|
|
return
|
|
}
|
|
|
|
// Assign real UIDS to expected state for comparison.
|
|
type ruleUid struct {
|
|
DashboardID int64 `xorm:"dashboard_id"`
|
|
PanelID int64 `xorm:"panel_id"`
|
|
UID string `xorm:"uid"`
|
|
}
|
|
ruleUids := make([]ruleUid, 0)
|
|
err := x.SQL("SELECT d.id as dashboard_id, ar.panel_id, ar.uid FROM alert_rule ar INNER JOIN dashboard d ON d.uid = ar.dashboard_uid WHERE ar.org_id = ?", orgId).Find(&ruleUids)
|
|
require.NoError(t, err)
|
|
uidMap := make(map[string]string)
|
|
for _, r := range ruleUids {
|
|
if du, ok := expectedState.MigratedDashboards[r.DashboardID]; ok {
|
|
if _, ok := du.MigratedAlerts[r.PanelID]; ok {
|
|
uidMap[du.MigratedAlerts[r.PanelID].NewRuleUID] = r.UID
|
|
du.MigratedAlerts[r.PanelID].NewRuleUID = r.UID
|
|
}
|
|
}
|
|
}
|
|
|
|
state, err := service.migrationStore.GetOrgMigrationState(context.Background(), orgId)
|
|
require.NoError(t, err)
|
|
|
|
cOpt := []cmp.Option{
|
|
cmpopts.SortSlices(func(a, b string) bool { return a < b }),
|
|
cmpopts.EquateEmpty(),
|
|
}
|
|
if !cmp.Equal(expectedState, state, cOpt...) {
|
|
t.Errorf("Unexpected OrgMigrationState: %v", cmp.Diff(expectedState, state, cOpt...))
|
|
}
|
|
|
|
if expectedServiceState != nil {
|
|
for _, du := range expectedServiceState.MigratedDashboards {
|
|
for _, a := range du.MigratedAlerts {
|
|
if a.AlertRule != nil {
|
|
a.AlertRule.UID = uidMap[a.AlertRule.UID]
|
|
}
|
|
}
|
|
}
|
|
|
|
serviceState, err := service.GetOrgMigrationState(context.Background(), orgId)
|
|
require.NoError(t, err)
|
|
|
|
cOpt := []cmp.Option{
|
|
cmpopts.SortSlices(func(a, b *definitions.DashboardUpgrade) bool { return a.DashboardID < b.DashboardID }),
|
|
cmpopts.SortSlices(func(a, b *definitions.AlertPair) bool { return a.LegacyAlert.ID < b.LegacyAlert.ID }),
|
|
cmpopts.SortSlices(func(a, b *definitions.ContactPair) bool { return a.LegacyChannel.ID < b.LegacyChannel.ID }),
|
|
cmpopts.IgnoreMapEntries(func(k string, v string) bool { return k == "rule_uid" }),
|
|
cmpopts.IgnoreUnexported(labels.Matcher{}),
|
|
cmpopts.EquateEmpty(),
|
|
}
|
|
if !cmp.Equal(expectedServiceState, serviceState, cOpt...) {
|
|
t.Errorf("Unexpected OrgMigrationState: %v", cmp.Diff(expectedServiceState, serviceState, cOpt...))
|
|
}
|
|
}
|
|
}
|
|
|
|
// setupUATables inserts data into the UA tables.
|
|
func setupUATables(t *testing.T, store *store.DBstore, orgID int64, rules []*models.AlertRule, amConfig *definitions.PostableUserConfig) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
rs := make([]models.AlertRule, 0, len(rules))
|
|
for _, r := range rules {
|
|
rs = append(rs, *r)
|
|
}
|
|
|
|
if len(rs) > 0 {
|
|
_, err := store.InsertAlertRules(ctx, rs)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if amConfig != nil {
|
|
rawAmConfig, err := json.Marshal(amConfig)
|
|
require.NoError(t, err)
|
|
cmd := models.SaveAlertmanagerConfigurationCmd{
|
|
AlertmanagerConfiguration: string(rawAmConfig),
|
|
ConfigurationVersion: fmt.Sprintf("v%d", models.AlertConfigurationVersion),
|
|
Default: false,
|
|
OrgID: orgID,
|
|
LastApplied: 0,
|
|
}
|
|
err = store.SaveAlertmanagerConfiguration(ctx, &cmd)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
func createPostableUserConfig(t *testing.T, channels ...*legacymodels.AlertNotification) *definitions.PostableUserConfig {
|
|
t.Helper()
|
|
am := &definitions.PostableUserConfig{
|
|
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
|
Config: definitions.Config{Route: &definitions.Route{
|
|
Receiver: "autogen-contact-point-default",
|
|
GroupByStr: []string{models.FolderTitleLabel, model.AlertNameLabel},
|
|
Routes: []*definitions.Route{
|
|
{
|
|
ObjectMatchers: definitions.ObjectMatchers{{Type: labels.MatchEqual, Name: models.MigratedUseLegacyChannelsLabel, Value: "true"}},
|
|
Continue: true,
|
|
Routes: []*definitions.Route{},
|
|
},
|
|
},
|
|
}},
|
|
Receivers: []*definitions.PostableApiReceiver{
|
|
{Receiver: config.Receiver{Name: "autogen-contact-point-default"}, PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{}},
|
|
},
|
|
},
|
|
}
|
|
for _, c := range channels {
|
|
settings, err := c.Settings.MarshalJSON()
|
|
require.NoError(t, err)
|
|
am.AlertmanagerConfig.Receivers = append(am.AlertmanagerConfig.Receivers, &definitions.PostableApiReceiver{Receiver: config.Receiver{Name: c.Name}, PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{{UID: c.UID, Name: c.Name, Type: c.Type, Settings: settings}}}})
|
|
am.AlertmanagerConfig.Route.Routes[0].Routes = append(am.AlertmanagerConfig.Route.Routes[0].Routes, &definitions.Route{Receiver: c.Name, ObjectMatchers: definitions.ObjectMatchers{{Type: labels.MatchEqual, Name: contactLabel(c.Name), Value: "true"}}, Routes: nil, Continue: true, RepeatInterval: durationPointer(DisabledRepeatInterval)})
|
|
}
|
|
return am
|
|
}
|
|
|
|
type serviceHelper struct {
|
|
t *testing.T
|
|
dashIncr int64
|
|
alertIncr int64
|
|
ruleIncr int64
|
|
channelIncr int64
|
|
|
|
dashes map[int64]*dashboards.Dashboard
|
|
folders map[int64]*dashboards.Dashboard
|
|
foldersByUID map[string]*dashboards.Dashboard
|
|
}
|
|
|
|
func newServiceHelper(t *testing.T) serviceHelper {
|
|
return serviceHelper{
|
|
t: t,
|
|
dashIncr: int64(1),
|
|
alertIncr: int64(1),
|
|
ruleIncr: int64(1),
|
|
channelIncr: int64(1),
|
|
|
|
dashes: make(map[int64]*dashboards.Dashboard),
|
|
folders: make(map[int64]*dashboards.Dashboard),
|
|
foldersByUID: make(map[string]*dashboards.Dashboard),
|
|
}
|
|
}
|
|
|
|
func (h *serviceHelper) genAlerts(d *dashboards.Dashboard, cnt int) []*legacymodels.Alert {
|
|
d.Title = fmt.Sprintf("dash title%d", h.dashIncr)
|
|
alerts := make([]*legacymodels.Alert, 0, cnt)
|
|
|
|
for i := 0; i < cnt; i++ {
|
|
a := createAlertWithCond(h.t, 1, int(d.ID), int(h.alertIncr), fmt.Sprintf("alert%d", h.alertIncr), nil,
|
|
[]dashAlertCondition{createCondition("A", "max", "gt", 42, 1, "5m", "now")})
|
|
a.ID = h.alertIncr
|
|
|
|
alerts = append(alerts, a)
|
|
h.alertIncr++
|
|
}
|
|
h.dashIncr++
|
|
return alerts
|
|
}
|
|
|
|
func (h *serviceHelper) genFolder() *dashboards.Dashboard {
|
|
f := createFolder(h.t, h.dashIncr, 1, fmt.Sprintf("folder%d", h.dashIncr))
|
|
h.dashIncr++
|
|
h.folders[f.ID] = f
|
|
h.foldersByUID[f.UID] = f
|
|
return f
|
|
}
|
|
|
|
func (h *serviceHelper) genDash(folder *dashboards.Dashboard) *dashboards.Dashboard {
|
|
d := createDashboard(h.t, h.dashIncr, 1, fmt.Sprintf("dash%d", h.dashIncr), folder.ID, nil)
|
|
d.Title = fmt.Sprintf("dash title%d", h.dashIncr)
|
|
|
|
h.dashIncr++
|
|
h.dashes[d.ID] = d
|
|
return d
|
|
}
|
|
|
|
func (h *serviceHelper) genChannels(cnt int) []*legacymodels.AlertNotification {
|
|
channels := make([]*legacymodels.AlertNotification, 0, cnt)
|
|
for i := 0; i < cnt; i++ {
|
|
c := createAlertNotification(h.t, int64(1), fmt.Sprintf("notifier%d", h.channelIncr), "email", emailSettings, false)
|
|
c.Name = fmt.Sprintf("notifiername%d", h.channelIncr)
|
|
c.ID = h.channelIncr
|
|
channels = append(channels, c)
|
|
h.channelIncr++
|
|
}
|
|
return channels
|
|
}
|
|
|
|
func (h *serviceHelper) genAlertPairs(f *dashboards.Dashboard, d *dashboards.Dashboard, alerts []*legacymodels.Alert) ([]*models.AlertRule, []*migmodels.AlertPair) {
|
|
pairs := make([]*migmodels.AlertPair, 0, len(alerts))
|
|
rules := make([]*models.AlertRule, 0, len(alerts))
|
|
for _, a := range alerts {
|
|
uid := util.GenerateShortUID()
|
|
r := &models.AlertRule{
|
|
UID: uid,
|
|
ID: h.ruleIncr,
|
|
OrgID: 1,
|
|
Title: a.Name,
|
|
Condition: "B",
|
|
Data: []models.AlertQuery{createAlertQuery("A", "ds1-1", "5m", "now"), createClassicConditionQuery("B", []classicCondition{
|
|
cond("A", "max", "gt", 42),
|
|
})},
|
|
IntervalSeconds: 60,
|
|
Version: 1,
|
|
NamespaceUID: f.UID,
|
|
DashboardUID: pointer(d.UID),
|
|
PanelID: pointer(a.PanelID),
|
|
RuleGroup: fmt.Sprintf("%s - 1m", d.Title),
|
|
RuleGroupIndex: 1,
|
|
NoDataState: models.NoData,
|
|
ExecErrState: models.AlertingErrState,
|
|
For: 60 * time.Second,
|
|
Annotations: map[string]string{
|
|
models.MigratedAlertIdAnnotation: fmt.Sprintf("%d", a.ID),
|
|
models.MigratedMessageAnnotation: "message",
|
|
models.DashboardUIDAnnotation: d.UID,
|
|
models.PanelIDAnnotation: fmt.Sprintf("%d", a.PanelID),
|
|
},
|
|
Labels: map[string]string{
|
|
models.MigratedUseLegacyChannelsLabel: "true",
|
|
"rule_uid": uid,
|
|
},
|
|
IsPaused: false,
|
|
}
|
|
for _, v := range extractChannelIds(h.t, a) {
|
|
id := v.ID
|
|
if id != 0 {
|
|
// Relies on the naming pattern.
|
|
r.Labels[contactLabel(fmt.Sprintf("notifiername%d", id))] = "true"
|
|
}
|
|
}
|
|
rules = append(rules, r)
|
|
pairs = append(pairs, &migmodels.AlertPair{
|
|
LegacyRule: a,
|
|
Rule: r,
|
|
})
|
|
h.ruleIncr++
|
|
}
|
|
return rules, pairs
|
|
}
|
|
|
|
func (h *serviceHelper) dashUpgrade(dashboardID int64, alertFolderUID string, migPairs []*migmodels.AlertPair, warning string) *migrationStore.DashboardUpgrade {
|
|
return &migrationStore.DashboardUpgrade{
|
|
DashboardID: dashboardID,
|
|
AlertFolderUID: alertFolderUID,
|
|
MigratedAlerts: func() map[int64]*migrationStore.AlertPair {
|
|
pairs := make(map[int64]*migrationStore.AlertPair, len(migPairs))
|
|
for _, p := range migPairs {
|
|
channelsIds := make([]int64, 0)
|
|
for _, v := range extractChannelIds(h.t, p.LegacyRule) {
|
|
channelsIds = append(channelsIds, v.ID)
|
|
}
|
|
pair := migrationStore.AlertPair{
|
|
LegacyID: p.LegacyRule.ID,
|
|
PanelID: p.LegacyRule.PanelID,
|
|
NewRuleUID: p.Rule.UID,
|
|
ChannelIDs: channelsIds,
|
|
}
|
|
if p.Error != nil {
|
|
pair.Error = p.Error.Error()
|
|
}
|
|
pairs[p.LegacyRule.PanelID] = &pair
|
|
}
|
|
return pairs
|
|
}(),
|
|
Warning: warning,
|
|
}
|
|
}
|
|
|
|
func (h *serviceHelper) contactPairs(c ...*legacymodels.AlertNotification) map[int64]*migrationStore.ContactPair {
|
|
pairs := make(map[int64]*migrationStore.ContactPair, len(c))
|
|
for _, ch := range c {
|
|
pairs[ch.ID] = &migrationStore.ContactPair{
|
|
LegacyID: ch.ID,
|
|
NewReceiverUID: ch.UID,
|
|
Error: "",
|
|
}
|
|
}
|
|
return pairs
|
|
}
|
|
|
|
func (h *serviceHelper) uaState(t *testing.T, channels []*legacymodels.AlertNotification, dashPairs ...[]*migmodels.AlertPair) *uaState {
|
|
s := &uaState{
|
|
migState: &migrationStore.OrgMigrationState{
|
|
OrgID: 1,
|
|
},
|
|
serviceState: h.serviceState(channels, dashPairs...),
|
|
}
|
|
|
|
if len(channels) > 0 {
|
|
s.amConfig = createPostableUserConfig(t, channels...)
|
|
s.migState.MigratedChannels = h.contactPairs(channels...)
|
|
}
|
|
|
|
if len(dashPairs) > 0 {
|
|
s.migState.MigratedDashboards = map[int64]*migrationStore.DashboardUpgrade{}
|
|
for _, pairs := range dashPairs {
|
|
for _, p := range pairs {
|
|
s.alerts = append(s.alerts, p.Rule)
|
|
}
|
|
s.migState.MigratedDashboards[pairs[0].LegacyRule.DashboardID] = h.dashUpgrade(pairs[0].LegacyRule.DashboardID, pairs[0].Rule.NamespaceUID, pairs, "")
|
|
}
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (h *serviceHelper) serviceState(channels []*legacymodels.AlertNotification, dashPairs ...[]*migmodels.AlertPair) *definitions.OrgMigrationState {
|
|
state := &definitions.OrgMigrationState{
|
|
OrgID: 1,
|
|
MigratedDashboards: []*definitions.DashboardUpgrade{},
|
|
MigratedChannels: []*definitions.ContactPair{},
|
|
}
|
|
channelName := make(map[int64]string)
|
|
for _, c := range channels {
|
|
channelName[c.ID] = c.Name
|
|
}
|
|
|
|
for _, pairs := range dashPairs {
|
|
d := h.dashes[pairs[0].LegacyRule.DashboardID]
|
|
//nolint:staticcheck
|
|
f := h.folders[d.FolderID]
|
|
f2 := h.foldersByUID[pairs[0].Rule.NamespaceUID]
|
|
du := &definitions.DashboardUpgrade{
|
|
DashboardID: d.ID,
|
|
DashboardUID: d.UID,
|
|
DashboardName: d.Title,
|
|
FolderUID: f.UID,
|
|
FolderName: f.Title,
|
|
NewFolderUID: f2.UID,
|
|
NewFolderName: f2.Title,
|
|
Provisioned: false,
|
|
Warning: "",
|
|
}
|
|
for _, pair := range pairs {
|
|
var sendsTo []string
|
|
for _, v := range extractChannelIds(h.t, pair.LegacyRule) {
|
|
sendsTo = append(sendsTo, channelName[v.ID])
|
|
}
|
|
if len(sendsTo) == 0 {
|
|
sendsTo = []string{"autogen-contact-point-default"}
|
|
}
|
|
p := &definitions.AlertPair{
|
|
LegacyAlert: fromLegacyAlert(pair.LegacyRule),
|
|
AlertRule: fromAlertRuleUpgrade(pair.Rule, sendsTo),
|
|
}
|
|
if pair.Error != nil {
|
|
p.Error = pair.Error.Error()
|
|
}
|
|
du.MigratedAlerts = append(du.MigratedAlerts, p)
|
|
}
|
|
state.MigratedDashboards = append(state.MigratedDashboards, du)
|
|
}
|
|
if len(channels) > 0 {
|
|
for _, c := range channels {
|
|
route, _ := createRoute(c, c.Name)
|
|
state.MigratedChannels = append(state.MigratedChannels, &definitions.ContactPair{
|
|
LegacyChannel: fromLegacyChannel(c),
|
|
ContactPointUpgrade: &definitions.ContactPointUpgrade{
|
|
Name: c.Name,
|
|
Type: c.Type,
|
|
RouteMatchers: route.ObjectMatchers,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
return state
|
|
}
|
|
|
|
func copyMap(m map[string]string) map[string]string {
|
|
c := make(map[string]string, len(m))
|
|
for k, v := range m {
|
|
c[k] = v
|
|
}
|
|
return c
|
|
}
|
|
|
|
func copyAlerts(alerts ...*legacymodels.Alert) []*legacymodels.Alert {
|
|
copies := make([]*legacymodels.Alert, len(alerts))
|
|
for i, a := range alerts {
|
|
c := *a
|
|
settingsMap := c.Settings.MustMap()
|
|
c.Settings = simplejson.New()
|
|
for k, v := range settingsMap {
|
|
c.Settings.Set(k, v)
|
|
}
|
|
|
|
copies[i] = &c
|
|
}
|
|
return copies
|
|
}
|
|
|
|
func copyRules(rules ...*models.AlertRule) []*models.AlertRule {
|
|
copies := make([]*models.AlertRule, len(rules))
|
|
for i, a := range rules {
|
|
c := *a
|
|
c.Labels = copyMap(c.Labels)
|
|
c.Annotations = copyMap(c.Annotations)
|
|
copies[i] = &c
|
|
}
|
|
return copies
|
|
}
|
|
|
|
func copyChannels(channels ...*legacymodels.AlertNotification) []*legacymodels.AlertNotification {
|
|
copies := make([]*legacymodels.AlertNotification, len(channels))
|
|
for i, a := range channels {
|
|
c := *a
|
|
copies[i] = &c
|
|
}
|
|
return copies
|
|
}
|
|
|
|
func copyPairs(pairs ...*migmodels.AlertPair) []*migmodels.AlertPair {
|
|
newPairs := make([]*migmodels.AlertPair, len(pairs))
|
|
for i, pair := range pairs {
|
|
clr := copyAlerts(pair.LegacyRule)[0]
|
|
cr := copyRules(pair.Rule)[0]
|
|
newPairs[i] = &migmodels.AlertPair{
|
|
LegacyRule: clr,
|
|
Rule: cr,
|
|
Error: pair.Error,
|
|
}
|
|
}
|
|
return newPairs
|
|
}
|
|
|
|
func extractChannelIds(t *testing.T, alert *legacymodels.Alert) []notificationKey {
|
|
b, err := alert.Settings.Get("notifications").ToDB()
|
|
if err == nil && b != nil {
|
|
require.NoError(t, err)
|
|
var nots []notificationKey
|
|
err = json.Unmarshal(b, ¬s)
|
|
require.NoError(t, err)
|
|
|
|
return nots
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func fromLegacyAlert(alert *legacymodels.Alert) *definitions.LegacyAlert {
|
|
if alert == nil {
|
|
return nil
|
|
}
|
|
return &definitions.LegacyAlert{
|
|
ID: alert.ID,
|
|
DashboardID: alert.DashboardID,
|
|
PanelID: alert.PanelID,
|
|
Name: alert.Name,
|
|
}
|
|
}
|
|
|
|
func fromAlertRuleUpgrade(rule *models.AlertRule, sendsTo []string) *definitions.AlertRuleUpgrade {
|
|
if rule == nil {
|
|
return nil
|
|
}
|
|
return &definitions.AlertRuleUpgrade{
|
|
UID: rule.UID,
|
|
Title: rule.Title,
|
|
SendsTo: sendsTo,
|
|
}
|
|
}
|
|
|
|
func fromLegacyChannel(channel *legacymodels.AlertNotification) *definitions.LegacyChannel {
|
|
if channel == nil {
|
|
return nil
|
|
}
|
|
return &definitions.LegacyChannel{
|
|
ID: channel.ID,
|
|
Name: channel.Name,
|
|
Type: channel.Type,
|
|
}
|
|
}
|