Nested folders: Add alert rule counts and deletion to folder registry (#67259)

* Let alert rule service implement registry service
* Add count method to RuleStore interface
* Add implementation for deletion of alert rules
* Rename uid to folderUID in registry methods
* Check forceDeleteRule value for registry deletion
* Register alerting store with folder service
* Move folder test functions to separate package
* Add testing for alert rule counting, deletion
* Remove redundant count method
* Fix deleteChildrenInFolder signature
* Update pkg/services/ngalert/store/alert_rule.go
Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
* Add tests for nested folder deletion
* Refactor TestIntegrationNestedFolderService
* Add rules store as parameter for alertng provider

---------

Co-authored-by: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com>
This commit is contained in:
Arati R
2023-06-02 16:38:02 +02:00
committed by GitHub
co-authored by Sofia Papagiannaki
parent e6e88aa528
commit 6cb1a5e368
17 changed files with 377 additions and 184 deletions
+185 -53
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -27,6 +28,8 @@ import (
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/ngalert/models"
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/quota/quotatest"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
@@ -336,6 +339,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
b := bus.ProvideBus(tracing.InitializeTracerForTest())
ac := acimpl.ProvideAccessControl(cfg)
serviceWithFlagOn := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
@@ -343,9 +349,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOn,
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
bus: b,
db: db,
accessControl: acimpl.ProvideAccessControl(cfg),
accessControl: ac,
registry: make(map[string]folder.RegistryService),
}
@@ -358,16 +364,19 @@ func TestIntegrationNestedFolderService(t *testing.T) {
SignedInUser: &signedInUser,
}
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
t.Run("Should get descendant counts", func(t *testing.T) {
ac := acmock.New()
folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService()
depth := 5
t.Run("With nested folder feature flag on", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
_, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
require.NoError(t, err)
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
require.NoError(t, err)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd)
@@ -378,6 +387,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
countCmd := folder.GetDescendantCountsQuery{
UID: &ancestorUIDs[0],
@@ -386,8 +397,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
m, err := serviceWithFlagOn.GetDescendantCounts(context.Background(), &countCmd)
require.NoError(t, err)
require.Equal(t, m["folder"], int64(depth-1))
require.Equal(t, m["dashboard"], int64(2))
require.Equal(t, int64(depth-1), m["folder"])
require.Equal(t, int64(2), m["dashboard"])
require.Equal(t, int64(2), m["alertrule"])
t.Cleanup(func() {
guardian.New = origNewGuardian
@@ -410,7 +422,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOff,
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
bus: b,
db: db,
registry: make(map[string]folder.RegistryService),
}
@@ -418,7 +430,11 @@ func TestIntegrationNestedFolderService(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
_, err = service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff,
folderPermissions, dashboardPermissions, ac, serviceWithFlagOff)
require.NoError(t, err)
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, ac, dashSrv)
require.NoError(t, err)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd)
@@ -429,6 +445,8 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in parent", orgID, parent.ID, "prod")
_ = insertTestDashboard(t, serviceWithFlagOn.dashboardStore, "dashboard in subfolder", orgID, subfolder.ID, "prod")
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
countCmd := folder.GetDescendantCountsQuery{
UID: &ancestorUIDs[0],
@@ -437,8 +455,9 @@ func TestIntegrationNestedFolderService(t *testing.T) {
}
m, err := serviceWithFlagOff.GetDescendantCounts(context.Background(), &countCmd)
require.NoError(t, err)
require.Equal(t, m["folder"], int64(0))
require.Equal(t, m["dashboard"], int64(1))
require.Equal(t, int64(0), m["folder"])
require.Equal(t, int64(1), m["dashboard"])
require.Equal(t, int64(1), m["alertrule"])
t.Cleanup(func() {
guardian.New = origNewGuardian
@@ -452,29 +471,78 @@ func TestIntegrationNestedFolderService(t *testing.T) {
t.Run("Should delete folders", func(t *testing.T) {
t.Run("With nested folder feature flag on", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "", createCmd)
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
}
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOn, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
require.NoError(t, err)
for i, uid := range ancestorUIDs {
// dashboard table
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
// folder table
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
require.ErrorIs(t, err, folder.ErrFolderNotFound)
}
t.Cleanup(func() {
guardian.New = origNewGuardian
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, ac, dashSrv)
require.NoError(t, err)
t.Run("With force deletion of rules", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "with-force", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.NoError(t, err)
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
require.NoError(t, err)
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
ForceDeleteRules: true,
}
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
require.NoError(t, err)
for i, uid := range ancestorUIDs {
// dashboard table
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
// folder table
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
require.ErrorIs(t, err, folder.ErrFolderNotFound)
}
t.Cleanup(func() {
guardian.New = origNewGuardian
})
})
t.Run("Without force deletion of rules", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "without-force", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.NoError(t, err)
subfolder, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[1])
require.NoError(t, err)
_ = createRule(t, alertStore, parent.UID, "parent alert")
_ = createRule(t, alertStore, subfolder.UID, "sub alert")
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
ForceDeleteRules: false,
}
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
require.Error(t, dashboards.ErrFolderContainsAlertRules, err)
for i, uid := range ancestorUIDs {
// dashboard table
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
require.NoError(t, err)
// folder table
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
require.NoError(t, err)
}
t.Cleanup(func() {
guardian.New = origNewGuardian
})
})
})
t.Run("With nested folder feature flag off", func(t *testing.T) {
@@ -483,6 +551,11 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
dashSrv, err := service.ProvideDashboardServiceImpl(cfg, dashStore, folderStore, nil, featuresFlagOff, folderPermissions, dashboardPermissions, ac, serviceWithFlagOn)
require.NoError(t, err)
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOn, ac, dashSrv)
require.NoError(t, err)
serviceWithFlagOff := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
@@ -490,37 +563,74 @@ func TestIntegrationNestedFolderService(t *testing.T) {
dashboardFolderStore: folderStore,
store: nestedFolderStore,
features: featuresFlagOff,
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
bus: b,
db: db,
}
t.Run("With force deletion of rules", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-force", createCmd)
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.NoError(t, err)
_ = createRule(t, alertStore, parent.UID, "parent alert")
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
}
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
require.NoError(t, err)
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
ForceDeleteRules: true,
}
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
require.NoError(t, err)
for i, uid := range ancestorUIDs {
// dashboard table
_, err := serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
// folder table
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID})
require.NoError(t, err)
}
t.Cleanup(func() {
guardian.New = origNewGuardian
for _, uid := range ancestorUIDs {
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
require.NoError(t, err)
t.Cleanup(func() {
guardian.New = origNewGuardian
for _, uid := range ancestorUIDs {
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
require.NoError(t, err)
}
})
})
t.Run("Without force deletion of rules", func(t *testing.T) {
origNewGuardian := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "off-no-force", createCmd)
parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.NoError(t, err)
_ = createRule(t, alertStore, parent.UID, "parent alert")
deleteCmd := folder.DeleteFolderCommand{
UID: ancestorUIDs[0],
OrgID: orgID,
SignedInUser: &signedInUser,
ForceDeleteRules: false,
}
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
require.Error(t, dashboards.ErrFolderContainsAlertRules, err)
// dashboard table
_, err = serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorUIDs[0])
require.NoError(t, err)
// folder table
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[0], OrgID: orgID})
require.NoError(t, err)
t.Cleanup(func() {
guardian.New = origNewGuardian
for _, uid := range ancestorUIDs {
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
require.NoError(t, err)
}
})
})
})
})
@@ -1076,3 +1186,25 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
db: db,
}
}
func createRule(t *testing.T, store *ngstore.DBstore, folderUID, title string) *models.AlertRule {
t.Helper()
rule := models.AlertRule{
OrgID: orgID,
NamespaceUID: folderUID,
Title: title,
Updated: time.Now(),
UID: util.GenerateShortUID(),
}
err := store.SQLStore.WithDbSession(context.Background(), func(sess *db.Session) error {
_, err := sess.Table(models.AlertRule{}).InsertOne(rule)
if err != nil {
return err
}
return nil
})
require.NoError(t, err)
return &rule
}