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:
Yuriy Tseretyan
2022-06-22 10:52:46 -04:00
committed by GitHub
parent 9fac806b6c
commit 4d02f73e5f
18 changed files with 703 additions and 36 deletions
@@ -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
}