Alerting: Add GUID to alert rule tables (#101321)

* add column guid to alert rule table and rule_guid to rule version table
+ populate the new field with UUID
* update storage and domain models
* patch GUID
* ignore GUID in fingerprint tests
This commit is contained in:
Yuri Tseretyan
2025-02-28 09:47:25 -05:00
committed by GitHub
parent 9eaaf95701
commit 879b121136
11 changed files with 138 additions and 6 deletions
+8 -1
View File
@@ -266,7 +266,9 @@ func NewUserUID(requester interface{ GetIdentifier() string }) *UserUID {
// AlertRule is the model for alert rules in unified alerting.
type AlertRule struct {
ID int64
ID int64
// Uniquely identifies alert rule across all organizations and time
GUID string
OrgID int64
Title string
Condition string
@@ -708,6 +710,7 @@ func (alertRule *AlertRule) Copy() *AlertRule {
}
result := AlertRule{
ID: alertRule.ID,
GUID: alertRule.GUID,
OrgID: alertRule.OrgID,
Title: alertRule.Title,
Condition: alertRule.Condition,
@@ -940,6 +943,10 @@ func PatchPartialAlertRule(existingRule *AlertRule, ruleToPatch *AlertRuleWithOp
if !ruleToPatch.HasEditorSettings {
ruleToPatch.Metadata.EditorSettings = existingRule.Metadata.EditorSettings
}
if ruleToPatch.GUID == "" {
ruleToPatch.GUID = existingRule.GUID
}
}
func ValidateRuleGroupInterval(intervalSeconds, baseIntervalSeconds int64) error {
@@ -418,6 +418,15 @@ func TestDiff(t *testing.T) {
assert.Equal(t, rule2.ID, diff[0].Right.Int())
difCnt++
}
if rule1.GUID != rule2.GUID {
diff := diffs.GetDiffsForField("GUID")
assert.Len(t, diff, 1)
assert.Equal(t, rule1.GUID, diff[0].Left.String())
assert.Equal(t, rule2.GUID, diff[0].Right.String())
difCnt++
}
if rule1.OrgID != rule2.OrgID {
diff := diffs.GetDiffsForField("OrgID")
assert.Len(t, diff, 1)
+8
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/go-openapi/strfmt"
"github.com/google/uuid"
alertingNotify "github.com/grafana/alerting/notify"
"github.com/grafana/grafana-plugin-sdk-go/data"
amv2 "github.com/prometheus/alertmanager/api/v2/models"
@@ -103,6 +104,7 @@ func (g *AlertRuleGenerator) Generate() AlertRule {
rule := AlertRule{
ID: 0,
GUID: uuid.NewString(),
OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation.
Title: fmt.Sprintf("title-%s", util.GenerateShortUID()),
Condition: "A",
@@ -584,6 +586,12 @@ func (a *AlertRuleMutators) WithMetadata(meta AlertRuleMetadata) AlertRuleMutato
}
}
func (a AlertRuleMutators) WithGUID(guid string) AlertRuleMutator {
return func(r *AlertRule) {
r.GUID = guid
}
}
func (g *AlertRuleGenerator) GenerateLabels(min, max int, prefix string) data.Labels {
count := max
if min > max {
@@ -151,13 +151,14 @@ func TestRuleWithFolderFingerprint(t *testing.T) {
f2 := ruleWithFolder{rule: rule, folderTitle: uuid.NewString()}.Fingerprint()
require.NotEqual(t, f, f2)
})
t.Run("Version, Updated, IntervalSeconds and Annotations should be excluded from fingerprint", func(t *testing.T) {
t.Run("Version, Updated, IntervalSeconds, GUID and Annotations should be excluded from fingerprint", func(t *testing.T) {
cp := models.CopyRule(rule)
cp.Version++
cp.Updated = cp.Updated.Add(1 * time.Second)
cp.IntervalSeconds++
cp.Annotations = make(map[string]string)
cp.Annotations["test"] = "test"
cp.GUID = uuid.NewString()
f2 := ruleWithFolder{rule: cp, folderTitle: title}.Fingerprint()
require.Equal(t, f, f2)
@@ -264,6 +265,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) {
"Annotations": {},
"ID": {},
"OrgID": {},
"GUID": {},
}
tp := reflect.TypeOf(rule).Elem()
+6
View File
@@ -261,6 +261,11 @@ func (st DBstore) InsertAlertRules(ctx context.Context, user *ngmodels.UserUID,
if err != nil {
return fmt.Errorf("failed to convert alert rule %q to storage model: %w", r.Title, err)
}
// assign unique identifier that will identify resource across space and time. The probability of collision is so low that we do not need to check for uniqueness.
// The unique keys will ensure uniqueness in rule and versions tables
converted.GUID = uuid.NewString()
newRules = append(newRules, converted)
ruleVersions = append(ruleVersions, alertRuleToAlertRuleVersion(converted))
}
@@ -313,6 +318,7 @@ func (st DBstore) UpdateAlertRules(ctx context.Context, user *ngmodels.UserUID,
r := rules[i]
r.New.ID = r.Existing.ID
r.New.Version = r.Existing.Version // xorm will take care of increasing it (see https://xorm.io/docs/chapter-06/1.lock/)
r.New.GUID = r.Existing.GUID
if err := st.validateAlertRule(r.New); err != nil {
return err
}
+4
View File
@@ -20,6 +20,7 @@ func alertRuleToModelsAlertRule(ar alertRule, l log.Logger) (models.AlertRule, e
result := models.AlertRule{
ID: ar.ID,
OrgID: ar.OrgID,
GUID: ar.GUID,
Title: ar.Title,
Condition: ar.Condition,
Data: data,
@@ -107,6 +108,7 @@ func parseNotificationSettings(s string) ([]models.NotificationSettings, error)
func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) {
result := alertRule{
ID: ar.ID,
GUID: ar.GUID,
OrgID: ar.OrgID,
Title: ar.Title,
Condition: ar.Condition,
@@ -180,6 +182,7 @@ func alertRuleFromModelsAlertRule(ar models.AlertRule) (alertRule, error) {
func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion {
return alertRuleVersion{
RuleOrgID: rule.OrgID,
RuleGUID: rule.GUID,
RuleUID: rule.UID,
RuleNamespaceUID: rule.NamespaceUID,
RuleGroup: rule.RuleGroup,
@@ -208,6 +211,7 @@ func alertRuleToAlertRuleVersion(rule alertRule) alertRuleVersion {
func alertRuleVersionToAlertRule(version alertRuleVersion) alertRule {
return alertRule{
ID: version.ID,
GUID: version.RuleGUID,
OrgID: version.RuleOrgID,
Title: version.Title,
Condition: version.Condition,
@@ -169,6 +169,12 @@ func TestCalculateChanges(t *testing.T) {
r.For = 0
},
},
{
name: "GUID is empty",
mutator: func(r *models.AlertRule) {
r.GUID = ""
},
},
}
dbRule := gen.With(gen.WithOrgID(orgId)).GenerateRef()
+5 -2
View File
@@ -4,8 +4,9 @@ import "time"
// alertRule represents a record in alert_rule table
type alertRule struct {
ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id"`
ID int64 `xorm:"pk autoincr 'id'"`
GUID string `xorm:"guid"`
OrgID int64 `xorm:"org_id"`
Title string
Condition string
Data string
@@ -38,6 +39,7 @@ func (a alertRule) TableName() string {
type alertRuleVersion struct {
ID int64 `xorm:"pk autoincr 'id'"`
RuleOrgID int64 `xorm:"rule_org_id"`
RuleGUID string `xorm:"rule_guid"`
RuleUID string `xorm:"rule_uid"`
RuleNamespaceUID string `xorm:"rule_namespace_uid"`
RuleGroup string
@@ -69,6 +71,7 @@ type alertRuleVersion struct {
// The comparison is very basic and can produce false-negative. Fields excluded: ID, ParentVersion, RestoredFrom, Version, Created and CreatedBy
func (a alertRuleVersion) EqualSpec(b alertRuleVersion) bool {
return a.RuleOrgID == b.RuleOrgID &&
a.RuleGUID == b.RuleGUID &&
a.RuleUID == b.RuleUID &&
a.RuleNamespaceUID == b.RuleNamespaceUID &&
a.RuleGroup == b.RuleGroup &&
@@ -145,4 +145,6 @@ func (oss *OSSMigrations) AddMigration(mg *Migrator) {
ualert.AddAlertRuleUpdatedByMigration(mg)
ualert.AddAlertRuleStateTable(mg)
ualert.AddAlertRuleGuidMigration(mg)
}
@@ -0,0 +1,83 @@
package ualert
import (
"github.com/google/uuid"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"xorm.io/xorm"
)
// AddAlertRuleGuidMigration sets up migrations for adding and managing GUID columns in alert_rule and alert_rule_version tables.
func AddAlertRuleGuidMigration(mg *migrator.Migrator) {
alertRuleVersion := migrator.Table{Name: "alert_rule_version"}
alertRule := migrator.Table{Name: "alert_rule"}
mg.AddMigration("add guid column to alert_rule table", migrator.NewAddColumnMigration(alertRule, &migrator.Column{
Name: "guid",
Type: migrator.DB_Varchar,
Length: 36,
Nullable: false,
Default: "''",
}))
mg.AddMigration("add rule_guid column to alert_rule_version table", migrator.NewAddColumnMigration(alertRuleVersion, &migrator.Column{
Name: "rule_guid",
Type: migrator.DB_Varchar,
Length: 36,
Nullable: false,
Default: "''",
}))
mg.AddMigration("drop index in alert_rule_version table on rule_org_id, rule_uid and version columns", migrator.NewDropIndexMigration(alertRuleVersion, alertRuleVersionUDX_OrgIdRuleUIDVersion))
mg.AddMigration("populate rule guid in alert rule table", &setRuleGuidMigration{})
mg.AddMigration("add index in alert_rule_version table on rule_org_id, rule_uid, rule_guid and version columns",
migrator.NewAddIndexMigration(alertRuleVersion,
&migrator.Index{Cols: []string{"rule_org_id", "rule_uid", "rule_guid", "version"}, Type: migrator.UniqueIndex},
),
)
mg.AddMigration("add index in alert_rule_version table on rule_guid and version columns",
migrator.NewAddIndexMigration(alertRuleVersion,
&migrator.Index{Cols: []string{"rule_guid", "version"}, Type: migrator.UniqueIndex},
),
)
mg.AddMigration("add index in alert_rule table on guid columns",
migrator.NewAddIndexMigration(alertRule,
&migrator.Index{Cols: []string{"guid"}, Type: migrator.UniqueIndex},
))
}
type setRuleGuidMigration struct {
migrator.MigrationBase
}
var _ migrator.CodeMigration = (*setRuleGuidMigration)(nil)
func (c setRuleGuidMigration) SQL(migrator.Dialect) string {
return codeMigration
}
func (c setRuleGuidMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
var results []string
if err := sess.SQL("SELECT uid FROM alert_rule").Find(&results); err != nil {
return err
}
if len(results) == 0 {
mg.Logger.Debug("no rules found")
return nil
}
for _, uid := range results {
u := uuid.NewString()
_, err := sess.Exec("UPDATE alert_rule_version SET rule_guid = ? WHERE rule_uid = ?", u, uid)
if err != nil {
mg.Logger.Error("Failed to update alert_rule_version table", "error", err)
return err
}
_, err = sess.Exec("UPDATE alert_rule SET guid = ? WHERE uid = ?", u, uid)
if err != nil {
mg.Logger.Error("Failed to update alert_rule table", "error", err)
return err
}
}
return nil
}
@@ -302,6 +302,8 @@ func addAlertRuleMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64)
UPDATE alert_rule SET is_paused = false;`))
}
var alertRuleVersionUDX_OrgIdRuleUIDVersion = &migrator.Index{Cols: []string{"rule_org_id", "rule_uid", "version"}, Type: migrator.UniqueIndex}
func addAlertRuleVersionMigrations(mg *migrator.Migrator) {
// DO NOT EDIT
alertRuleVersion := migrator.Table{
@@ -325,12 +327,12 @@ func addAlertRuleVersionMigrations(mg *migrator.Migrator) {
{Name: "exec_err_state", Type: migrator.DB_NVarchar, Length: 15, Nullable: false, Default: "'Alerting'"},
},
Indices: []*migrator.Index{
{Cols: []string{"rule_org_id", "rule_uid", "version"}, Type: migrator.UniqueIndex},
alertRuleVersionUDX_OrgIdRuleUIDVersion,
{Cols: []string{"rule_org_id", "rule_namespace_uid", "rule_group"}, Type: migrator.IndexType},
},
}
mg.AddMigration("create alert_rule_version table", migrator.NewAddTableMigration(alertRuleVersion))
mg.AddMigration("add index in alert_rule_version table on rule_org_id, rule_uid and version columns", migrator.NewAddIndexMigration(alertRuleVersion, alertRuleVersion.Indices[0]))
mg.AddMigration("add index in alert_rule_version table on rule_org_id, rule_uid and version columns", migrator.NewAddIndexMigration(alertRuleVersion, alertRuleVersionUDX_OrgIdRuleUIDVersion))
mg.AddMigration("add index in alert_rule_version table on rule_org_id, rule_namespace_uid and rule_group columns", migrator.NewAddIndexMigration(alertRuleVersion, alertRuleVersion.Indices[1]))
mg.AddMigration("alter alert_rule_version table data column to mediumtext in mysql", migrator.NewRawSQLMigration("").