[v10.0.x] Alerting: Fix unique violation when updating rule group with title chains/cycles (#70467)

* Alerting: Fix TestIntegration_GetAlertRulesForScheduling to make sure rules are created in different org (#69088)

make sure rules are created in different org

(cherry picked from commit b57ef1f2c7)

* Alerting: Fix flaky TestIntegrationUpdateAlertRules (#69106)

Prevents duplicate alert rule ids and 0 value for BaseInterval and IntervalSeconds

(cherry picked from commit 97ae6ae6ef)

* Alerting: Fix unique violation when updating rule group with title chains/cycles (#67868)

* Alerting: Fix unique violation when updating rule group with title chains/cycles

The uniqueness constraint for titles within an org+folder is enforced on every update within a transaction instead of on commit (deferred constraint). This means that there could be a set of updates that will throw a unique constraint violation in an intermediate step even though the final state is valid. For example, a chain of updates RuleA -> RuleB -> RuleC could fail if not executed in the correct order, or a swap of titles RuleA <-> RuleB cannot be executed in any order without violating the constraint.

The exact solution to this is complex and requires determining directed paths and cycles in the update graph, adding in temporary updates to break cycles, and then executing the updates in reverse topological order (see first commit in PR if curious).

This is not implemented here.

Instead, we choose a simpler solution that works in all cases but might perform more updates than necessary. This simpler solution makes a determination of whether an intermediate collision could occur and if so, adds a temporary title on all updated rules to break any cycles and remove the need for specific ordering.

In addition, we make sure diffs are executed in the following order: DELETES, UPDATES, INSERTS.

(cherry picked from commit 0c688190f7)

* Linting

---------

Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
Matthew Jacobson
2023-06-21 14:38:38 -04:00
committed by GitHub
parent ddd6b2803c
commit c364c8dc0b
7 changed files with 644 additions and 78 deletions
+28 -7
View File
@@ -358,9 +358,10 @@ func TestIntegrationAlertRuleConflictingTitle(t *testing.T) {
require.Len(t, createdRuleGroup.Rules, 2)
t.Run("trying to create alert with same title under same folder should fail", func(t *testing.T) {
rules := newTestingRuleConfig(t)
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
rulesWithUID.Rules = append(rulesWithUID.Rules, rules.Rules[0]) // Create new copy of first rule.
status, body := apiClient.PostRulesGroup(t, "folder1", &rules)
status, body := apiClient.PostRulesGroup(t, "folder1", &rulesWithUID)
assert.Equal(t, http.StatusInternalServerError, status)
var res map[string]interface{}
@@ -369,12 +370,10 @@ func TestIntegrationAlertRuleConflictingTitle(t *testing.T) {
})
t.Run("trying to update an alert to the title of an existing alert in the same folder should fail", func(t *testing.T) {
rules := newTestingRuleConfig(t)
rules.Rules[0].GrafanaManagedAlert.UID = createdRuleGroup.Rules[0].GrafanaManagedAlert.UID
rules.Rules[1].GrafanaManagedAlert.UID = createdRuleGroup.Rules[1].GrafanaManagedAlert.UID
rules.Rules[1].GrafanaManagedAlert.Title = "AlwaysFiring"
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "AlwaysFiring"
status, body := apiClient.PostRulesGroup(t, "folder1", &rules)
status, body := apiClient.PostRulesGroup(t, "folder1", &rulesWithUID)
assert.Equal(t, http.StatusInternalServerError, status)
var res map[string]interface{}
@@ -388,6 +387,28 @@ func TestIntegrationAlertRuleConflictingTitle(t *testing.T) {
assert.Equal(t, http.StatusAccepted, status)
require.JSONEq(t, `{"message":"rule group updated successfully"}`, body)
})
t.Run("trying to swap titles of existing alerts in the same folder should work", func(t *testing.T) {
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
title0 := rulesWithUID.Rules[0].GrafanaManagedAlert.Title
title1 := rulesWithUID.Rules[1].GrafanaManagedAlert.Title
rulesWithUID.Rules[0].GrafanaManagedAlert.Title = title1
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = title0
status, body := apiClient.PostRulesGroup(t, "folder1", &rulesWithUID)
assert.Equal(t, http.StatusAccepted, status)
require.JSONEq(t, `{"message":"rule group updated successfully"}`, body)
})
t.Run("trying to update titles of existing alerts in a chain in the same folder should work", func(t *testing.T) {
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
rulesWithUID.Rules[0].GrafanaManagedAlert.Title = rulesWithUID.Rules[1].GrafanaManagedAlert.Title
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "something new"
status, body := apiClient.PostRulesGroup(t, "folder1", &rulesWithUID)
assert.Equal(t, http.StatusAccepted, status)
require.JSONEq(t, `{"message":"rule group updated successfully"}`, body)
})
}
func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) {