Alerting: Update scheduler to receive rule updates only from database (#64662)
Alerting: Update scheduler to get updates only from database (#64635)
* stop using the scheduler's Update and Delete methods all communication must be via the database
* update scheduler's registry to calculate diff before re-setting the cache
* update fetcher to return the diff generated by registry
* update processTick to update rule eval routine if the rule was updated and it is not going to be evaluated at this tick.
* remove references to the scheduler from api package
* remove unused methods in the scheduler
(cherry picked from commit 85a954cd81)
# Conflicts:
# pkg/services/ngalert/api/api_ruler.go
# pkg/services/ngalert/ngalert.go
# pkg/services/ngalert/ngalert_test.go
# pkg/services/ngalert/schedule/schedule.go
# pkg/services/ngalert/schedule/schedule_mock.go
# pkg/services/ngalert/schedule/schedule_unit_test.go
This commit is contained in:
@@ -22,7 +22,6 @@ import (
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -47,7 +46,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
return result
|
||||
}
|
||||
|
||||
assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *fakes.RuleStore, scheduler *schedule.FakeScheduleService) {
|
||||
assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *fakes.RuleStore) {
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
require.Len(t, deleteCommands, 1)
|
||||
cmd := deleteCommands[0]
|
||||
@@ -56,20 +55,6 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
for _, rule := range expectedRules {
|
||||
require.Containsf(t, actualUIDs, rule.UID, "Rule %s was expected to be deleted but it wasn't", rule.UID)
|
||||
}
|
||||
|
||||
notDeletedRules := make(map[models.AlertRuleKey]struct{}, len(expectedRules))
|
||||
for _, rule := range expectedRules {
|
||||
notDeletedRules[rule.GetKey()] = struct{}{}
|
||||
}
|
||||
for _, call := range scheduler.Calls {
|
||||
require.Equal(t, "DeleteAlertRule", call.Method)
|
||||
keys, ok := call.Arguments.Get(0).([]models.AlertRuleKey)
|
||||
require.Truef(t, ok, "Expected AlertRuleKey but got something else")
|
||||
for _, key := range keys {
|
||||
delete(notDeletedRules, key)
|
||||
}
|
||||
}
|
||||
require.Emptyf(t, notDeletedRules, "Not all rules were deleted")
|
||||
}
|
||||
|
||||
orgID := rand.Int63()
|
||||
@@ -89,14 +74,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
request := createRequestContext(orgID, org.RoleViewer, nil)
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule")
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
t.Run("editor should be able to delete all non-provisioned rules in folder", func(t *testing.T) {
|
||||
@@ -104,14 +85,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
rulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), rulesInFolder...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
request := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
assertRulesDeleted(t, rulesInFolder, ruleStore, scheduler)
|
||||
})
|
||||
t.Run("editor should be able to delete rules group if it is not provisioned", func(t *testing.T) {
|
||||
groupName := util.GenerateShortUID()
|
||||
@@ -124,26 +101,19 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// rules in the same group but different folder
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything).Return()
|
||||
|
||||
request := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request, folder.Title, groupName)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
assertRulesDeleted(t, rulesInFolderInGroup, ruleStore, scheduler)
|
||||
assertRulesDeleted(t, rulesInFolderInGroup, ruleStore)
|
||||
})
|
||||
t.Run("should return 202 if folder is empty", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
requestCtx := createRequestContext(orgID, org.RoleEditor, nil)
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule")
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
})
|
||||
@@ -155,25 +125,18 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything).Panic("should not be called")
|
||||
|
||||
ac := acMock.New()
|
||||
request := createRequestContext(orgID, "None", nil)
|
||||
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule")
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
t.Run("delete only non-provisioned groups that user is authorized", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
provisioningStore := provisioning.NewFakeProvisioningStore()
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
authorizedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("authz_"+util.GenerateShortUID())))
|
||||
|
||||
provisionedRulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("provisioned_"+util.GenerateShortUID())))
|
||||
@@ -187,10 +150,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...)))
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, scheduler, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||
assertRulesDeleted(t, authorizedRulesInFolder, ruleStore, scheduler)
|
||||
assertRulesDeleted(t, authorizedRulesInFolder, ruleStore)
|
||||
})
|
||||
t.Run("return 400 if all rules user can access are provisioned", func(t *testing.T) {
|
||||
ruleStore := initFakeRuleStore(t)
|
||||
@@ -204,15 +167,11 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// more rules in the same namespace but user does not have access to them
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, scheduler, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
|
||||
|
||||
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule")
|
||||
require.Empty(t, getRecordedCommand(ruleStore))
|
||||
})
|
||||
})
|
||||
@@ -226,15 +185,11 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
// more rules in the same group but user is not authorized to access them
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(authorizedRulesInGroup))
|
||||
|
||||
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule", mock.Anything)
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
require.Empty(t, deleteCommands)
|
||||
})
|
||||
@@ -248,15 +203,11 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
|
||||
ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)
|
||||
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
|
||||
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, scheduler, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
|
||||
|
||||
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
|
||||
scheduler.AssertNotCalled(t, "DeleteAlertRule", mock.Anything)
|
||||
deleteCommands := getRecordedCommand(ruleStore)
|
||||
require.Empty(t, deleteCommands)
|
||||
})
|
||||
@@ -277,7 +228,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
|
||||
|
||||
req := createRequestContext(orgID, "", nil)
|
||||
response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@@ -312,7 +263,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
req := createRequestContext(orgID, org.RoleViewer, nil)
|
||||
response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@@ -345,7 +296,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
svc := createService(ac, ruleStore, nil)
|
||||
svc := createService(ac, ruleStore)
|
||||
|
||||
// add provenance to the first generated rule
|
||||
rule := &models.AlertRule{
|
||||
@@ -389,7 +340,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title)
|
||||
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@@ -441,7 +392,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
request := createRequestContext(orgID, "", nil)
|
||||
t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(append(group1, group2[1:]...)))
|
||||
response := createService(ac, ruleStore, nil).RouteGetRulesConfig(request)
|
||||
response := createService(ac, ruleStore).RouteGetRulesConfig(request)
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@@ -471,7 +422,7 @@ func TestRouteGetRulesConfig(t *testing.T) {
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore, nil).RouteGetRulesConfig(createRequestContext(orgID, org.RoleViewer, nil))
|
||||
response := createService(ac, ruleStore).RouteGetRulesConfig(createRequestContext(orgID, org.RoleViewer, nil))
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
@@ -522,13 +473,13 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
|
||||
|
||||
t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules[1:]))
|
||||
response := createService(ac, ruleStore, nil).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
})
|
||||
|
||||
t.Run("and return rules if user has access to all of them", func(t *testing.T) {
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
|
||||
response := createService(ac, ruleStore, nil).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.RuleGroupConfigResponse{}
|
||||
@@ -551,7 +502,7 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore, nil).RouteGetRulesGroupConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title, groupKey.RuleGroup)
|
||||
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title, groupKey.RuleGroup)
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.RuleGroupConfigResponse{}
|
||||
@@ -638,19 +589,18 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func createServiceWithProvenanceStore(ac *acMock.Mock, store *fakes.RuleStore, scheduler schedule.ScheduleService, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
|
||||
svc := createService(ac, store, scheduler)
|
||||
func createServiceWithProvenanceStore(ac *acMock.Mock, store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
|
||||
svc := createService(ac, store)
|
||||
svc.provenanceStore = provenanceStore
|
||||
return svc
|
||||
}
|
||||
|
||||
func createService(ac *acMock.Mock, store *fakes.RuleStore, scheduler schedule.ScheduleService) *RulerSrv {
|
||||
func createService(ac *acMock.Mock, store *fakes.RuleStore) *RulerSrv {
|
||||
return &RulerSrv{
|
||||
xactManager: store,
|
||||
store: store,
|
||||
QuotaService: nil,
|
||||
provenanceStore: provisioning.NewFakeProvisioningStore(),
|
||||
scheduleService: scheduler,
|
||||
log: log.New("test"),
|
||||
cfg: nil,
|
||||
ac: ac,
|
||||
|
||||
Reference in New Issue
Block a user