@ -23,6 +23,8 @@ var migTitle = "move dashboard alerts to unified alerting"
var rmMigTitle = "remove unified alerting data"
const clearMigrationEntryTitle = "clear migration entry %q"
type MigrationError struct {
AlertId int64
Err error
@ -49,19 +51,23 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
case ngEnabled && ! migrationRun :
// Remove the migration entry that removes all unified alerting data. This is so when the feature
// flag is removed in future the "remove unified alerting data" migration will be run again.
err = mg . ClearMigrationEntry ( rmMigTitle )
mg . AddMigration ( fmt . Sprintf ( clearMigrationEntryTitle , rmMigTitle ) , & clearMigrationEntry {
migrationID : rmMigTitle ,
} )
if err != nil {
mg . Logger . Error ( "alert migration error: could not clear alert migration for removing data" , "error" , err )
}
mg . AddMigration ( migTitle , & migration {
seenChannelUIDs : make ( map [ string ] struct { } ) ,
migratedChannels : make ( map [ * notificationChannel ] struct { } ) ,
portedChannelGroups : make ( map [ string ] string ) ,
seenChannelUIDs : make ( map [ string ] struct { } ) ,
migratedChannelsPerOrg : make ( map [ int64 ] map [ * notificationChannel ] struct { } ) ,
portedChannelGroupsPerOrg : make ( map [ int64 ] map [ string ] string ) ,
} )
case ! ngEnabled && migrationRun :
// Remove the migration entry that creates unified alerting data. This is so when the feature
// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
err = mg . ClearMigrationEntry ( migTitle )
mg . AddMigration ( fmt . Sprintf ( clearMigrationEntryTitle , migTitle ) , & clearMigrationEntry {
migrationID : migTitle ,
} )
if err != nil {
mg . Logger . Error ( "alert migration error: could not clear dashboard alert migration" , "error" , err )
}
@ -69,17 +75,84 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
}
}
// RerunDashAlertMigration force the dashboard alert migration to run
// to make sure that the Alertmanager configurations will be created for each organisation
func RerunDashAlertMigration ( mg * migrator . Migrator ) {
logs , err := mg . GetMigrationLog ( )
if err != nil {
mg . Logger . Crit ( "alert migration failure: could not get migration log" , "error" , err )
os . Exit ( 1 )
}
cloneMigTitle := fmt . Sprintf ( "clone %s" , migTitle )
cloneRmMigTitle := fmt . Sprintf ( "clone %s" , rmMigTitle )
_ , migrationRun := logs [ cloneMigTitle ]
ngEnabled := mg . Cfg . IsNgAlertEnabled ( )
switch {
case ngEnabled && ! migrationRun :
// Removes all unified alerting data. It is not recorded so when the feature
// flag is removed in future the "clone remove unified alerting data" migration will be run again.
mg . AddMigration ( cloneRmMigTitle , & rmMigrationWithoutLogging { } )
mg . AddMigration ( cloneMigTitle , & migration {
seenChannelUIDs : make ( map [ string ] struct { } ) ,
migratedChannelsPerOrg : make ( map [ int64 ] map [ * notificationChannel ] struct { } ) ,
portedChannelGroupsPerOrg : make ( map [ int64 ] map [ string ] string ) ,
} )
case ! ngEnabled && migrationRun :
// Remove the migration entry that creates unified alerting data. This is so when the feature
// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
mg . AddMigration ( fmt . Sprintf ( clearMigrationEntryTitle , cloneMigTitle ) , & clearMigrationEntry {
migrationID : cloneMigTitle ,
} )
if err != nil {
mg . Logger . Error ( "alert migration error: could not clear clone dashboard alert migration" , "error" , err )
}
// Removes all unified alerting data. It is not recorded so when the feature
// flag is enabled in future the "clone remove unified alerting data" migration will be run again.
mg . AddMigration ( cloneRmMigTitle , & rmMigrationWithoutLogging { } )
}
}
// clearMigrationEntry removes an entry fromt the migration_log table.
// This migration is not recorded in the migration_log so that it can re-run several times.
type clearMigrationEntry struct {
migrator . MigrationBase
migrationID string
}
func ( m * clearMigrationEntry ) SQL ( dialect migrator . Dialect ) string {
return "clear migration entry code migration"
}
func ( m * clearMigrationEntry ) Exec ( sess * xorm . Session , mg * migrator . Migrator ) error {
_ , err := sess . SQL ( ` DELETE from migration_log where migration_id = ? ` , m . migrationID ) . Query ( )
if err != nil {
return fmt . Errorf ( "failed to clear migration entry %v: %w" , m . migrationID , err )
}
return nil
}
func ( m * clearMigrationEntry ) SkipMigrationLog ( ) bool {
return true
}
type migration struct {
migrator . MigrationBase
// session and mg are attached for convenience.
sess * xorm . Session
mg * migrator . Migrator
seenChannelUIDs map [ string ] struct { }
migratedChannels map [ * notificationChannel ] struct { }
silences [ ] * pb . MeshSilence
portedChannelGroups map [ string ] string // Channel group key -> receiver name.
lastReceiverID int // For the auto generated receivers.
seenChannelUIDs map [ string ] struct { }
migratedChannelsPerOrg map [ int64 ] map [ * notificationChannel ] struct { }
silences [ ] * pb . MeshSilence
portedChannelGroupsPerOrg map [ int64 ] map [ string ] string // Org -> Channel group key -> receiver name.
lastReceiverID int // For the auto generated receivers.
}
func ( m * migration ) SQL ( dialect migrator . Dialect ) string {
@ -108,13 +181,13 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
}
// allChannels: channelUID -> channelConfig
allChannels , defaultChannels , err := m . getNotificationChannelMap ( )
allChannelsPerOrg , defaultChannelsPerOrg , err := m . getNotificationChannelMap ( )
if err != nil {
return err
}
amConfig := PostableUserConfig { }
err = m . addDefaultChannels ( & amConfig , allChannels , defaultChannels )
amConfigPerOrg := make ( amConfigsPerOrg , len ( allChannelsPerOrg ) )
err = m . addDefaultChannels ( amConfigPerOr g , allChannelsPerOrg , defaultChannelsPerOrg )
if err != nil {
return err
}
@ -144,26 +217,11 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
}
// get folder if exists
folder := dashboard { }
if dash . FolderId > 0 {
exists , err := m . sess . Where ( "id=?" , dash . FolderId ) . Get ( & folder )
if err != nil {
return MigrationError {
Err : fmt . Errorf ( "failed to get folder %d: %w" , dash . FolderId , err ) ,
AlertId : da . Id ,
}
}
if ! exists {
return MigrationError {
Err : fmt . Errorf ( "folder with id %v not found" , dash . FolderId ) ,
AlertId : da . Id ,
}
}
if ! folder . IsFolder {
return MigrationError {
Err : fmt . Errorf ( "id %v is a dashboard not a folder" , dash . FolderId ) ,
AlertId : da . Id ,
}
folder , err := m . getFolder ( dash , da )
if err != nil {
return MigrationError {
Err : err ,
AlertId : da . Id ,
}
}
@ -220,8 +278,12 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
return err
}
if err := m . updateReceiverAndRoute ( allChannels , defaultChannels , da , rule , & amConfig ) ; err != nil {
return err
if _ , ok := amConfigPerOrg [ rule . OrgID ] ; ! ok {
m . mg . Logger . Info ( "no configuration found" , "org" , rule . OrgID )
} else {
if err := m . updateReceiverAndRoute ( allChannelsPerOrg , defaultChannelsPerOrg , da , rule , amConfigPerOrg [ rule . OrgID ] ) ; err != nil {
return err
}
}
if strings . HasPrefix ( mg . Dialect . DriverName ( ) , migrator . Postgres ) {
@ -234,8 +296,8 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
}
if err != nil {
// TODO better error handling, if constraint
rule . Title += fmt . Sprintf ( " %v" , rule . Uid )
rule . RuleGroup += fmt . Sprintf ( " %v" , rule . Uid )
rule . Title += fmt . Sprintf ( " %v" , rule . UID )
rule . RuleGroup += fmt . Sprintf ( " %v" , rule . UID )
_ , err = m . sess . Insert ( rule )
if err != nil {
@ -250,24 +312,26 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
}
}
// Create a separate receiver for all the unmigrated channels.
err = m . addUnmigratedChannels ( & amConfig , allChannels , defaultChannels )
if err != nil {
return err
}
for orgID , amConfig := range amConfigPerOrg {
// Create a separate receiver for all the unmigrated channels.
err = m . addUnmigratedChannels ( orgID , amConfig , allChannelsPerOrg [ orgID ] , defaultChannelsPerOrg [ orgID ] )
if err != nil {
return err
}
if err := m . writeAlertmanagerConfig ( & amConfig , allChannels ) ; err != nil {
return err
}
if err := m . writeAlertmanagerConfig ( orgID , amConfig , allChannelsPerOrg [ orgID ] ) ; err != nil {
return err
}
if err := m . writeSilencesFile ( ) ; err != nil {
m . mg . Logger . Error ( "alert migration error: failed to write silence file" , "err" , err )
if err := m . writeSilencesFile ( orgID ) ; err != nil {
m . mg . Logger . Error ( "alert migration error: failed to write silence file" , "err" , err )
}
}
return nil
}
func ( m * migration ) writeAlertmanagerConfig ( amConfig * PostableUserConfig , allChannels map [ interface { } ] * notificationChannel ) error {
func ( m * migration ) writeAlertmanagerConfig ( orgID int64 , amConfig * PostableUserConfig , allChannels map [ interface { } ] * notificationChannel ) error {
if len ( allChannels ) == 0 {
// No channels, hence don't require Alertmanager config.
m . mg . Logger . Info ( "alert migration: no notification channel found, skipping Alertmanager config" )
@ -288,6 +352,7 @@ func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allCha
// Since we are migration for a snapshot of the code, it is always going to migrate to
// the v1 config.
ConfigurationVersion : "v1" ,
OrgID : orgID ,
} )
if err != nil {
return err
@ -297,13 +362,15 @@ func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allCha
}
type AlertConfiguration struct {
ID int64 ` xorm:"pk autoincr 'id'" `
ID int64 ` xorm:"pk autoincr 'id'" `
OrgID int64 ` xorm:"org_id" `
AlertmanagerConfiguration string
ConfigurationVersion string
CreatedAt int64 ` xorm:"created" `
}
// rmMigration removes Grafana 8 alert data
type rmMigration struct {
migrator . MigrationBase
}
@ -343,9 +410,23 @@ func (m *rmMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
return err
}
if err := os . RemoveAll ( silencesFileName ( mg ) ) ; err != nil {
mg . Logger . Error ( "alert migration error: failed to remove silence file" , "err" , err )
files , err := getSilenceFileNamesForAllOrgs ( mg )
if err != nil {
return err
}
for _ , f := range files {
if err := os . Remove ( f ) ; err != nil {
mg . Logger . Error ( "alert migration error: failed to remove silence file" , "file" , f , "err" , err )
}
}
return nil
}
// rmMigrationWithoutLogging is similar migration to rmMigration
// but is not recorded in the migration_log table so that it can rerun in the future
type rmMigrationWithoutLogging = rmMigration
func ( m * rmMigrationWithoutLogging ) SkipMigrationLog ( ) bool {
return true
}