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:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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("").
|
||||
|
||||
Reference in New Issue
Block a user