mirror of
https://github.com/grafana/grafana.git
synced 2025-02-16 10:24:54 -06:00
* Add folder store method for fetching all folder descendants * Modify GetDescendantCounts() to fetch folder descendants at once * Reduce DB calls when counting library panels under dashboard * Reduce DB calls when counting dashboards under folder * Reduce DB calls during folder delete * Modify folder registry to count/delete entities under multiple folders * Reduce DB calls when counting * Reduce DB calls when deleting
2133 lines
75 KiB
Go
2133 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", "folder5-1", 5, nil),
|
|
createDashboard(t, 2, 1, "dash2-1", "folder5-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.UID, 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
|
|
d1Copy.FolderUID = f2.UID
|
|
_, 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"),
|
|
}
|
|
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.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.UID, 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",
|
|
},
|
|
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,
|
|
}
|
|
}
|