[v8.5.x] Alerting: Update migration to put alerts to the default folder if dashboard folder is missing (#66616)

Alerting: Update migration to put alerts to the default folder if dashboard folder is missing (#65577)

* extract function

* use context logger

* put alert to general folder if folder is missing

* move folderHelper init

* add test

* Update pkg/services/sqlstore/migrations/ualert/ualert.go

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>

---------

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
(cherry picked from commit 7b2f44762e)

# Conflicts:
#	pkg/services/sqlstore/migrations/ualert/migration_test.go
#	pkg/services/sqlstore/migrations/ualert/ualert.go
This commit is contained in:
Yuri Tseretyan
2023-04-17 15:58:30 +02:00
committed by GitHub
parent d1031e7a28
commit f23efb1bbd
2 changed files with 96 additions and 21 deletions
@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations/ualert"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@@ -459,6 +460,49 @@ func TestDashAlertMigration(t *testing.T) {
}
}
// TestDashAlertMigration tests the execution of the main DashAlertMigration specifically for migrations of models.
func TestDashAlertMigrationFolders(t *testing.T) {
// Run initial migration to have a working DB.
x := setupTestDB(t)
t.Run("when folder is missing put alert in General folder", func(t *testing.T) {
o := createOrg(t, 1)
folder1 := createDashboard(t, 1, o.Id, "folder-1")
folder1.IsFolder = true
dash1 := createDashboard(t, 3, o.Id, "dash1")
dash1.FolderId = folder1.Id
dash2 := createDashboard(t, 4, o.Id, "dash2")
dash2.FolderId = 22 // missing folder
a1 := createAlert(t, o.Id, dash1.Id, int64(1), "alert-1", []string{})
a2 := createAlert(t, o.Id, dash2.Id, int64(1), "alert-2", []string{})
_, err := x.Insert(o, folder1, dash1, dash2, a1, a2)
require.NoError(t, err)
runDashAlertMigrationTestRun(t, x)
rules := getAlertRules(t, x, o.Id)
require.Len(t, rules, 2)
var generalFolder models.Dashboard
_, err = x.Table(&models.Dashboard{}).Where("title = ? AND org_id = ?", ualert.GENERAL_FOLDER, o.Id).Get(&generalFolder)
require.NoError(t, err)
require.NotNil(t, generalFolder)
for _, rule := range rules {
var expectedFolder models.Dashboard
if rule.Title == a1.Name {
expectedFolder = *folder1
} else {
expectedFolder = generalFolder
}
require.Equal(t, expectedFolder.Uid, rule.NamespaceUID)
}
})
}
// setupTestDB prepares the sqlite database and runs OSS migrations to initialize the schemas.
func setupTestDB(t *testing.T) *xorm.Engine {
t.Helper()
@@ -593,6 +637,18 @@ func teardown(t *testing.T, x *xorm.Engine) {
require.NoError(t, err)
}
func runDashAlertMigrationTestRun(t *testing.T, x *xorm.Engine) {
_, errDeleteMig := x.Exec("DELETE FROM migration_log WHERE migration_id = ?", ualert.MigTitle)
require.NoError(t, errDeleteMig)
alertMigrator := migrator.NewMigrator(x, &setting.Cfg{})
alertMigrator.AddMigration(ualert.RmMigTitle, &ualert.RmMigration{})
ualert.AddDashAlertMigration(alertMigrator)
errRunningMig := alertMigrator.Start(false, 0)
require.NoError(t, errRunningMig)
}
// setupLegacyAlertsTables inserts data into the legacy alerting tables that is needed for testing the migration.
func setupLegacyAlertsTables(t *testing.T, x *xorm.Engine, legacyChannels []*models.AlertNotification, alerts []*models.Alert) {
t.Helper()
@@ -637,6 +693,14 @@ func setupLegacyAlertsTables(t *testing.T, x *xorm.Engine, legacyChannels []*mod
}
}
func getAlertRules(t *testing.T, x *xorm.Engine, orgId int64) []*ngModels.AlertRule {
rules := make([]*ngModels.AlertRule, 0)
err := x.Table("alert_rule").Where("org_id = ?", orgId).Find(&rules)
require.NoError(t, err)
return rules
}
// getAlertmanagerConfig retreives the Alertmanager Config from the database for a given orgId.
func getAlertmanagerConfig(t *testing.T, x *xorm.Engine, orgId int64) *ualert.PostableUserConfig {
amConfig := ""
@@ -248,11 +248,31 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
// cache for folders created for dashboards that have custom permissions
folderCache := make(map[string]*dashboard)
gf := func(dash dashboard, da dashAlert) (*dashboard, error) {
f, ok := folderCache[GENERAL_FOLDER]
if !ok {
// get or create general folder
f, err = m.getOrCreateGeneralFolder(dash.OrgId)
if err != nil {
return nil, MigrationError{
Err: fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
AlertId: da.Id,
}
}
folderCache[GENERAL_FOLDER] = f
}
// No need to assign default permissions to general folder
// because they are included to the query result if it's a folder with no permissions
// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
return f, nil
}
// Store of newly created rules to later create routes
rulesPerOrg := make(map[int64]map[string]dashAlert)
for _, da := range dashAlerts {
l := mg.Logger.New("ruleID", da.Id, "ruleName", da.Name, "dashboardUID", da.DashboardUID, "orgID", da.OrgId)
newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
if err != nil {
return err
@@ -282,7 +302,7 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
folderName := getAlertFolderNameFromDashboard(&dash)
f, ok := folderCache[folderName]
if !ok {
mg.Logger.Info("create a new folder for alerts that belongs to dashboard because it has custom permissions", "org", dash.OrgId, "dashboard_uid", dash.Uid, "folder", folderName)
l.Info("create a new folder for alerts that belongs to dashboard because it has custom permissions", "folder", folderName)
// create folder and assign the permissions of the dashboard (included default and inherited)
f, err = m.createFolder(dash.OrgId, folderName)
if err != nil {
@@ -312,29 +332,20 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
// get folder if exists
f, err := m.getFolder(dash, da)
if err != nil {
return MigrationError{
Err: err,
AlertId: da.Id,
}
}
folder = &f
default:
f, ok := folderCache[GENERAL_FOLDER]
if !ok {
// get or create general folder
f, err = m.getOrCreateGeneralFolder(dash.OrgId)
// If folder does not exist then the dashboard is an orphan and we migrate the alert to the general folder.
l.Warn("Failed to find folder for dashboard. Migrate rule to the default folder", "rule_name", da.Name, "dashboard_uid", da.DashboardUID, "missing_folder_id", dash.FolderId)
folder, err = gf(dash, da)
if err != nil {
return MigrationError{
Err: fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
AlertId: da.Id,
}
return err
}
folderCache[GENERAL_FOLDER] = f
} else {
folder = &f
}
default:
folder, err = gf(dash, da)
if err != nil {
return err
}
// No need to assign default permissions to general folder
// because they are included to the query result if it's a folder with no permissions
// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
folder = f
}
if folder.Uid == "" {