5a80962de9
* Alerting: Add clean_upgrade config and deprecate force_migration Upgrading to UA and rolling back will no longer delete any data by default. Instead, each set of tables will remain unchanged when switching between legacy and UA. As such, the force_migration config has been deprecated and no extra configuration is required to roll back to legacy anymore. If clean_upgrade is set to true when upgrading from legacy alerting to Unified Alerting, grafana will first delete all existing Unified Alerting resources, thus re-upgrading all organizations from scratch. If false or unset, organizations that have previously upgraded will not lose their existing Unified Alerting data when switching between legacy and Unified Alerting. Similar to force_migration, it should be kept false when not needed as it may cause unintended data-loss if left enabled. --------- Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
374 lines
13 KiB
Go
374 lines
13 KiB
Go
package migration
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
migrationStore "github.com/grafana/grafana/pkg/services/ngalert/migration/store"
|
|
"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")
|
|
}
|