Alerting: Persist rule position in the group (#50051)
Migrations: * add a new column alert_group_idx to alert_rule table * add a new column alert_group_idx to alert_rule_version table * re-index existing rules during migration API: * set group index on update. Use the natural order of items in the array as group index * sort rules in the group on GET * update the version of all rules of all affected groups. This will make optimistic lock work in the case of multiple concurrent request touching the same groups. UI: * update UI to keep the order of alerts in a group
This commit is contained in:
@@ -92,6 +92,8 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
|
||||
accesscontrol.AddManagedFolderAlertActionsMigration(mg)
|
||||
accesscontrol.AddActionNameMigrator(mg)
|
||||
addPlaylistUIDMigration(mg)
|
||||
|
||||
ualert.UpdateRuleGroupIndexMigration(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type alertRule struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
Title string
|
||||
Condition string
|
||||
@@ -22,6 +23,7 @@ type alertRule struct {
|
||||
UID string `xorm:"uid"`
|
||||
NamespaceUID string `xorm:"namespace_uid"`
|
||||
RuleGroup string
|
||||
RuleGroupIndex int `xorm:"rule_group_idx"`
|
||||
NoDataState string
|
||||
ExecErrState string
|
||||
For duration
|
||||
@@ -35,6 +37,7 @@ type alertRuleVersion struct {
|
||||
RuleUID string `xorm:"rule_uid"`
|
||||
RuleNamespaceUID string `xorm:"rule_namespace_uid"`
|
||||
RuleGroup string
|
||||
RuleGroupIndex int `xorm:"rule_group_idx"`
|
||||
ParentVersion int64
|
||||
RestoredFrom int64
|
||||
Version int64
|
||||
@@ -59,6 +62,7 @@ func (a *alertRule) makeVersion() *alertRuleVersion {
|
||||
RuleUID: a.UID,
|
||||
RuleNamespaceUID: a.NamespaceUID,
|
||||
RuleGroup: a.RuleGroup,
|
||||
RuleGroupIndex: a.RuleGroupIndex,
|
||||
ParentVersion: 0,
|
||||
RestoredFrom: 0,
|
||||
Version: 1,
|
||||
|
||||
@@ -241,6 +241,16 @@ func AddAlertRuleMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64)
|
||||
Cols: []string{"org_id", "dashboard_uid", "panel_id"},
|
||||
},
|
||||
))
|
||||
|
||||
mg.AddMigration("add rule_group_idx column to alert_rule", migrator.NewAddColumnMigration(
|
||||
migrator.Table{Name: "alert_rule"},
|
||||
&migrator.Column{
|
||||
Name: "rule_group_idx",
|
||||
Type: migrator.DB_Int,
|
||||
Nullable: false,
|
||||
Default: "1",
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
|
||||
@@ -284,6 +294,16 @@ func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
|
||||
|
||||
// add labels column
|
||||
mg.AddMigration("add column labels to alert_rule_version", migrator.NewAddColumnMigration(alertRuleVersion, &migrator.Column{Name: "labels", Type: migrator.DB_Text, Nullable: true}))
|
||||
|
||||
mg.AddMigration("add rule_group_idx column to alert_rule_version", migrator.NewAddColumnMigration(
|
||||
migrator.Table{Name: "alert_rule_version"},
|
||||
&migrator.Column{
|
||||
Name: "rule_group_idx",
|
||||
Type: migrator.DB_Int,
|
||||
Nullable: false,
|
||||
Default: "1",
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
func AddAlertmanagerConfigMigrations(mg *migrator.Migrator) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pb "github.com/prometheus/alertmanager/silence/silencepb"
|
||||
"xorm.io/xorm"
|
||||
@@ -37,6 +38,7 @@ var migTitle = "move dashboard alerts to unified alerting"
|
||||
var rmMigTitle = "remove unified alerting data"
|
||||
|
||||
const clearMigrationEntryTitle = "clear migration entry %q"
|
||||
const codeMigration = "code migration"
|
||||
|
||||
type MigrationError struct {
|
||||
AlertId int64
|
||||
@@ -227,7 +229,7 @@ type migration struct {
|
||||
}
|
||||
|
||||
func (m *migration) SQL(dialect migrator.Dialect) string {
|
||||
return "code migration"
|
||||
return codeMigration
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
@@ -511,7 +513,7 @@ type rmMigration struct {
|
||||
}
|
||||
|
||||
func (m *rmMigration) SQL(dialect migrator.Dialect) string {
|
||||
return "code migration"
|
||||
return codeMigration
|
||||
}
|
||||
|
||||
func (m *rmMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
@@ -710,7 +712,7 @@ func (u *upgradeNgAlerting) updateAlertmanagerFiles(orgId int64, migrator *migra
|
||||
}
|
||||
|
||||
func (u *upgradeNgAlerting) SQL(migrator.Dialect) string {
|
||||
return "code migration"
|
||||
return codeMigration
|
||||
}
|
||||
|
||||
// getAlertFolderNameFromDashboard generates a folder name for alerts that belong to a dashboard. Formats the string according to DASHBOARD_FOLDER format.
|
||||
@@ -776,5 +778,86 @@ func (c createDefaultFoldersForAlertingMigration) Exec(sess *xorm.Session, migra
|
||||
}
|
||||
|
||||
func (c createDefaultFoldersForAlertingMigration) SQL(migrator.Dialect) string {
|
||||
return "code migration"
|
||||
return codeMigration
|
||||
}
|
||||
|
||||
// UpdateRuleGroupIndexMigration updates a new field rule_group_index for alert rules that belong to a group with more than 1 alert.
|
||||
func UpdateRuleGroupIndexMigration(mg *migrator.Migrator) {
|
||||
if !mg.Cfg.UnifiedAlerting.IsEnabled() {
|
||||
return
|
||||
}
|
||||
mg.AddMigration("update group index for alert rules", &updateRulesOrderInGroup{})
|
||||
}
|
||||
|
||||
type updateRulesOrderInGroup struct {
|
||||
migrator.MigrationBase
|
||||
}
|
||||
|
||||
func (c updateRulesOrderInGroup) SQL(migrator.Dialect) string {
|
||||
return codeMigration
|
||||
}
|
||||
|
||||
func (c updateRulesOrderInGroup) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
|
||||
var rows []*alertRule
|
||||
if err := sess.Table(alertRule{}).Asc("id").Find(&rows); err != nil {
|
||||
return fmt.Errorf("failed to read the list of alert rules: %w", err)
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
migrator.Logger.Debug("No rules to migrate.")
|
||||
return nil
|
||||
}
|
||||
|
||||
groups := map[ngmodels.AlertRuleGroupKey][]*alertRule{}
|
||||
|
||||
for _, row := range rows {
|
||||
groupKey := ngmodels.AlertRuleGroupKey{
|
||||
OrgID: row.OrgID,
|
||||
NamespaceUID: row.NamespaceUID,
|
||||
RuleGroup: row.RuleGroup,
|
||||
}
|
||||
groups[groupKey] = append(groups[groupKey], row)
|
||||
}
|
||||
|
||||
toUpdate := make([]*alertRule, 0, len(rows))
|
||||
|
||||
for _, rules := range groups {
|
||||
for i, rule := range rules {
|
||||
if rule.RuleGroupIndex == i+1 {
|
||||
continue
|
||||
}
|
||||
rule.RuleGroupIndex = i + 1
|
||||
toUpdate = append(toUpdate, rule)
|
||||
}
|
||||
}
|
||||
|
||||
if len(toUpdate) == 0 {
|
||||
migrator.Logger.Debug("No rules to upgrade group index")
|
||||
return nil
|
||||
}
|
||||
|
||||
updated := time.Now()
|
||||
versions := make([]*alertRuleVersion, 0, len(toUpdate))
|
||||
|
||||
for _, rule := range toUpdate {
|
||||
rule.Updated = updated
|
||||
version := rule.makeVersion()
|
||||
version.Version = rule.Version + 1
|
||||
version.ParentVersion = rule.Version
|
||||
rule.Version++
|
||||
_, err := sess.ID(rule.ID).Cols("version", "updated", "rule_group_idx").Update(rule)
|
||||
if err != nil {
|
||||
migrator.Logger.Error("failed to update alert rule", "uid", rule.UID, "err", err)
|
||||
return fmt.Errorf("unable to update alert rules with group index: %w", err)
|
||||
}
|
||||
migrator.Logger.Debug("updated group index for alert rule", "rule_uid", rule.UID)
|
||||
versions = append(versions, version)
|
||||
}
|
||||
|
||||
_, err := sess.Insert(&versions)
|
||||
if err != nil {
|
||||
migrator.Logger.Error("failed to insert changes to alert_rule_version", "err", err)
|
||||
return fmt.Errorf("unable to update alert rules with group index: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user