Alerting: update rule versions on folder move (#88376)

* Alerting: update rule versions on folder move (#88361)
* Add tracing to folder.Move and folder.Update
This commit is contained in:
Alexander Akhmetov
2024-08-13 12:26:26 +02:00
committed by GitHub
parent 8044cb50f1
commit b2eeb0dd6e
21 changed files with 196 additions and 57 deletions
@@ -1819,7 +1819,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
require.NoError(t, err)
folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore)
folderService := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil)
folderService := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
store := store.DBstore{
Logger: log,
SQLStore: sqlStore,
+2 -2
View File
@@ -26,8 +26,8 @@ type RuleStore interface {
UpdateAlertRules(ctx context.Context, rule []ngmodels.UpdateRule) error
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
// IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace
IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error)
// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace uids
IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error)
accesscontrol.RuleUIDToNamespaceStore
}
+6 -5
View File
@@ -487,15 +487,16 @@ func (ng *AlertNG) init() error {
}
func subscribeToFolderChanges(logger log.Logger, bus bus.Bus, dbStore api.RuleStore) {
// if folder title is changed, we update all alert rules in that folder to make sure that all peers (in HA mode) will update folder title and
// if full path to the folder is changed, we update all alert rules in that folder to make sure that all peers (in HA mode) will update folder title and
// clean up the current state
bus.AddEventListener(func(ctx context.Context, evt *events.FolderTitleUpdated) error {
logger.Info("Got folder title updated event. updating rules in the folder", "folderUID", evt.UID)
_, err := dbStore.IncreaseVersionForAllRulesInNamespace(ctx, evt.OrgID, evt.UID)
bus.AddEventListener(func(ctx context.Context, evt *events.FolderFullPathUpdated) error {
logger.Info("Got folder full path updated event. updating rules in the folders", "folderUIDs", evt.UIDs)
updatedKeys, err := dbStore.IncreaseVersionForAllRulesInNamespaces(ctx, evt.OrgID, evt.UIDs)
if err != nil {
logger.Error("Failed to update alert rules in the folder after its title was changed", "error", err, "folderUID", evt.UID, "folder", evt.Title)
logger.Error("Failed to update alert rules in the folders after their full paths were changed", "error", err, "folderUIDs", evt.UIDs, "orgID", evt.OrgID)
return err
}
logger.Info("Updated version for alert rules", "keys", updatedKeys)
return nil
})
}
+30 -14
View File
@@ -9,6 +9,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus"
@@ -25,37 +26,52 @@ import (
)
func Test_subscribeToFolderChanges(t *testing.T) {
getRecordedCommand := func(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery {
results := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) {
c, ok := cmd.(fakes.GenericRecordedQuery)
if !ok || c.Name != "IncreaseVersionForAllRulesInNamespaces" {
return nil, false
}
return c, ok
})
var result []fakes.GenericRecordedQuery
for _, cmd := range results {
result = append(result, cmd.(fakes.GenericRecordedQuery))
}
return result
}
orgID := rand.Int63()
folder := &folder.Folder{
folder1 := &folder.Folder{
UID: util.GenerateShortUID(),
Title: "Folder" + util.GenerateShortUID(),
}
folder2 := &folder.Folder{
UID: util.GenerateShortUID(),
Title: "Folder" + util.GenerateShortUID(),
}
gen := models.RuleGen
rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(5)
rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder1)).GenerateManyRef(5)
bus := bus.ProvideBus(tracing.InitializeTracerForTest())
db := fakes.NewRuleStore(t)
db.Folders[orgID] = append(db.Folders[orgID], folder)
db.Folders[orgID] = append(db.Folders[orgID], folder1)
db.PutRule(context.Background(), rules...)
subscribeToFolderChanges(log.New("test"), bus, db)
err := bus.Publish(context.Background(), &events.FolderTitleUpdated{
err := bus.Publish(context.Background(), &events.FolderFullPathUpdated{
Timestamp: time.Now(),
Title: "Folder" + util.GenerateShortUID(),
UID: folder.UID,
UIDs: []string{folder1.UID, folder2.UID},
OrgID: orgID,
})
require.NoError(t, err)
require.Eventuallyf(t, func() bool {
return len(db.GetRecordedCommands(func(cmd any) (any, bool) {
c, ok := cmd.(fakes.GenericRecordedQuery)
if !ok || c.Name != "IncreaseVersionForAllRulesInNamespace" {
return nil, false
}
return c, true
})) > 0
require.EventuallyWithT(t, func(c *assert.CollectT) {
recordedCommands := getRecordedCommand(db)
require.Len(c, recordedCommands, 1)
require.Equal(c, recordedCommands[0].Params[0].(int64), orgID)
require.ElementsMatch(c, recordedCommands[0].Params[1].([]string), []string{folder1.UID, folder2.UID})
}, time.Second, 10*time.Millisecond, "expected to call db store method but nothing was called")
}
@@ -1479,7 +1479,7 @@ func TestProvisiongWithFullpath(t *testing.T) {
_, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg)
ac := acmock.New()
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
folderService := folderimpl.ProvideService(ac, inProcBus, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil)
folderService := folderimpl.ProvideService(ac, inProcBus, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
ruleService := createAlertRuleService(t, folderService)
var orgID int64 = 1
+14 -4
View File
@@ -75,16 +75,26 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI
})
}
// IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace
func (st DBstore) IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error) {
// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace. Returns all rules that belong to the namespaces
func (st DBstore) IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error) {
var keys []ngmodels.AlertRuleKeyWithVersion
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error {
now := TimeNow()
_, err := sess.Exec("UPDATE alert_rule SET version = version + 1, updated = ? WHERE namespace_uid = ? AND org_id = ?", now, namespaceUID, orgID)
namespaceUIDsArgs, in := getINSubQueryArgs(namespaceUIDs)
sql := fmt.Sprintf(
"UPDATE alert_rule SET version = version + 1, updated = ? WHERE org_id = ? AND namespace_uid IN (%s)",
strings.Join(in, ","),
)
args := make([]interface{}, 0, 3+len(namespaceUIDsArgs))
args = append(args, sql, now, orgID)
args = append(args, namespaceUIDsArgs...)
_, err := sess.Exec(args...)
if err != nil {
return err
}
return sess.Table(ngmodels.AlertRule{}).Where("namespace_uid = ? AND org_id = ?", namespaceUID, orgID).Find(&keys)
return sess.Table(ngmodels.AlertRule{}).Where("org_id = ?", orgID).In("namespace_uid", namespaceUIDs).Find(&keys)
})
return keys, err
}
@@ -1184,6 +1184,58 @@ func TestIntegrationRuleGroupsCaseSensitive(t *testing.T) {
})
}
func TestIncreaseVersionForAllRulesInNamespaces(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
cfg := setting.NewCfg()
cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: time.Duration(rand.Int63n(100)+1) * time.Second}
sqlStore := db.InitTestReplDB(t)
store := &DBstore{
SQLStore: sqlStore,
Cfg: cfg.UnifiedAlerting,
FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()),
Logger: &logtest.Fake{},
}
orgID := int64(1)
gen := models.RuleGen
gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval)).With(gen.WithOrgID(orgID))
alertRules := []*models.AlertRule{}
for i := 0; i < 5; i++ {
alertRules = append(alertRules, createRule(t, store, gen))
}
alertRuleNamespaceUIDs := make([]string, 0, len(alertRules))
for _, rule := range alertRules {
alertRuleNamespaceUIDs = append(alertRuleNamespaceUIDs, rule.NamespaceUID)
}
alertRuleInAnotherNamespace := createRule(t, store, gen)
requireAlertRuleVersion := func(t *testing.T, ruleID int64, orgID int64, expectedVersion int64) {
t.Helper()
dbrule := &models.AlertRule{}
err := sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error {
exist, err := sess.Table(models.AlertRule{}).ID(ruleID).Get(dbrule)
require.Truef(t, exist, fmt.Sprintf("rule with ID %d does not exist", ruleID))
return err
})
require.NoError(t, err)
require.Equal(t, expectedVersion, dbrule.Version)
}
t.Run("should increase version for all rules", func(t *testing.T) {
_, err := store.IncreaseVersionForAllRulesInNamespaces(context.Background(), orgID, alertRuleNamespaceUIDs)
require.NoError(t, err)
for _, rule := range alertRules {
requireAlertRuleVersion(t, rule.ID, orgID, rule.Version+1)
}
// this rule's version should not be changed
requireAlertRuleVersion(t, alertRuleInAnotherNamespace.ID, orgID, alertRuleInAnotherNamespace.Version)
})
}
// createAlertRule creates an alert rule in the database and returns it.
// If a generator is not specified, uniqueness of primary key is not guaranteed.
func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule {
+9 -4
View File
@@ -315,19 +315,24 @@ func (f *RuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceU
return nil
}
func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersion, error) {
func (f *RuleStore) IncreaseVersionForAllRulesInNamespaces(_ context.Context, orgID int64, namespaceUIDs []string) ([]models.AlertRuleKeyWithVersion, error) {
f.mtx.Lock()
defer f.mtx.Unlock()
f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{
Name: "IncreaseVersionForAllRulesInNamespace",
Params: []any{orgID, namespaceUID},
Name: "IncreaseVersionForAllRulesInNamespaces",
Params: []any{orgID, namespaceUIDs},
})
var result []models.AlertRuleKeyWithVersion
namespaceUIDsMap := make(map[string]struct{}, len(namespaceUIDs))
for _, namespaceUID := range namespaceUIDs {
namespaceUIDsMap[namespaceUID] = struct{}{}
}
for _, rule := range f.Rules[orgID] {
if rule.NamespaceUID == namespaceUID && rule.OrgID == orgID {
if _, ok := namespaceUIDsMap[rule.NamespaceUID]; ok && rule.OrgID == orgID {
rule.Version++
rule.Updated = time.Now()
result = append(result, models.AlertRuleKeyWithVersion{
+2 -1
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -26,7 +27,7 @@ import (
func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
tb.Helper()
return folderimpl.ProvideService(ac, bus, dashboardStore, folderStore, db, features, supportbundlestest.NewFakeBundleService(), nil)
return folderimpl.ProvideService(ac, bus, dashboardStore, folderStore, db, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest())
}
func SetupDashboardService(tb testing.TB, sqlStore db.ReplDB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) {