mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: rules delete API to check data source authorization (#46906)
* merge RuleSrv rule delete methods * remove unused store methods * implement delete by uid for fake store * add scheduler mock * implement tests for RouteDeleteAlertRules
This commit is contained in:
parent
a06329d988
commit
e20d157a9b
@ -42,52 +42,82 @@ var (
|
|||||||
errQuotaReached = errors.New("quota has been exceeded")
|
errQuotaReached = errors.New("quota has been exceeded")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
// RouteDeleteAlertRules deletes all alert rules user is authorized to access in the namespace (request parameter :Namespace)
|
||||||
|
// or, if specified, a group of rules (request parameter :Groupname) in the namespace
|
||||||
|
func (srv RulerSrv) RouteDeleteAlertRules(c *models.ReqContext) response.Response {
|
||||||
namespaceTitle := web.Params(c.Req)[":Namespace"]
|
namespaceTitle := web.Params(c.Req)[":Namespace"]
|
||||||
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toNamespaceErrorResponse(err)
|
return toNamespaceErrorResponse(err)
|
||||||
}
|
}
|
||||||
|
var loggerCtx = []interface{}{
|
||||||
|
"namespace",
|
||||||
|
namespace.Title,
|
||||||
|
}
|
||||||
|
var ruleGroup *string
|
||||||
|
if group, ok := web.Params(c.Req)[":Groupname"]; ok {
|
||||||
|
ruleGroup = &group
|
||||||
|
loggerCtx = append(loggerCtx, "group", group)
|
||||||
|
}
|
||||||
|
logger := srv.log.New(loggerCtx...)
|
||||||
|
|
||||||
uids, err := srv.store.DeleteNamespaceAlertRules(c.Req.Context(), c.SignedInUser.OrgId, namespace.Uid)
|
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||||
if err != nil {
|
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqOrgAdminOrEditor, evaluator)
|
||||||
return ErrResp(http.StatusInternalServerError, err, "failed to delete namespace alert rules")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, uid := range uids {
|
var canDelete, cannotDelete []string
|
||||||
srv.scheduleService.DeleteAlertRule(ngmodels.AlertRuleKey{
|
err = srv.xactManager.InTransaction(c.Req.Context(), func(ctx context.Context) error {
|
||||||
OrgID: c.SignedInUser.OrgId,
|
q := ngmodels.GetAlertRulesQuery{
|
||||||
UID: uid,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
})
|
NamespaceUID: namespace.Uid,
|
||||||
}
|
RuleGroup: ruleGroup,
|
||||||
|
}
|
||||||
|
if err = srv.store.GetAlertRules(ctx, &q); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "namespace rules deleted"})
|
if len(q.Result) == 0 {
|
||||||
}
|
logger.Debug("no alert rules to delete from namespace/group")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Response {
|
canDelete = make([]string, 0, len(q.Result))
|
||||||
namespaceTitle := web.Params(c.Req)[":Namespace"]
|
for _, rule := range q.Result {
|
||||||
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
if authorizeDatasourceAccessForRule(rule, hasAccess) {
|
||||||
if err != nil {
|
canDelete = append(canDelete, rule.UID)
|
||||||
return toNamespaceErrorResponse(err)
|
continue
|
||||||
}
|
}
|
||||||
ruleGroup := web.Params(c.Req)[":Groupname"]
|
cannotDelete = append(cannotDelete, rule.UID)
|
||||||
uids, err := srv.store.DeleteRuleGroupAlertRules(c.Req.Context(), c.SignedInUser.OrgId, namespace.Uid, ruleGroup)
|
}
|
||||||
|
|
||||||
|
if len(canDelete) == 0 {
|
||||||
|
return fmt.Errorf("%w to delete rules because user is not authorized to access data sources used by the rules", ErrAuthorization)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cannotDelete) > 0 {
|
||||||
|
logger.Info("user cannot delete one or many alert rules because it does not have access to data sources. Those rules will be skipped", "expected", len(q.Result), "authorized", len(canDelete), "unauthorized", cannotDelete)
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.OrgId, canDelete...)
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ngmodels.ErrRuleGroupNamespaceNotFound) {
|
if errors.Is(err, ErrAuthorization) {
|
||||||
return ErrResp(http.StatusNotFound, err, "failed to delete rule group")
|
return ErrResp(http.StatusUnauthorized, err, "")
|
||||||
}
|
}
|
||||||
return ErrResp(http.StatusInternalServerError, err, "failed to delete rule group")
|
return ErrResp(http.StatusInternalServerError, err, "failed to delete rule group")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, uid := range uids {
|
logger.Debug("rules have been deleted from the store. updating scheduler")
|
||||||
|
|
||||||
|
for _, uid := range canDelete {
|
||||||
srv.scheduleService.DeleteAlertRule(ngmodels.AlertRuleKey{
|
srv.scheduleService.DeleteAlertRule(ngmodels.AlertRuleKey{
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
UID: uid,
|
UID: uid,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group deleted"})
|
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rules deleted"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
||||||
|
@ -4,15 +4,24 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
models2 "github.com/grafana/grafana/pkg/models"
|
models2 "github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCalculateChanges(t *testing.T) {
|
func TestCalculateChanges(t *testing.T) {
|
||||||
@ -255,6 +264,266 @@ func TestCalculateChanges(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteDeleteAlertRules(t *testing.T) {
|
||||||
|
createService := func(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv {
|
||||||
|
return &RulerSrv{
|
||||||
|
xactManager: store,
|
||||||
|
store: store,
|
||||||
|
DatasourceCache: nil,
|
||||||
|
QuotaService: nil,
|
||||||
|
scheduleService: scheduler,
|
||||||
|
log: log.New("test"),
|
||||||
|
cfg: nil,
|
||||||
|
ac: ac,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecordedCommand := func(ruleStore *store.FakeRuleStore) []store.GenericRecordedQuery {
|
||||||
|
results := ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
|
||||||
|
c, ok := cmd.(store.GenericRecordedQuery)
|
||||||
|
if !ok || c.Name != "DeleteAlertRulesByUID" {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return c, ok
|
||||||
|
})
|
||||||
|
var result []store.GenericRecordedQuery
|
||||||
|
for _, cmd := range results {
|
||||||
|
result = append(result, cmd.(store.GenericRecordedQuery))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
assertRulesDeleted := func(t *testing.T, expectedRules []*models.AlertRule, ruleStore *store.FakeRuleStore, scheduler *schedule.FakeScheduleService) {
|
||||||
|
deleteCommands := getRecordedCommand(ruleStore)
|
||||||
|
require.Len(t, deleteCommands, 1)
|
||||||
|
cmd := deleteCommands[0]
|
||||||
|
actualUIDs := cmd.Params[1].([]string)
|
||||||
|
require.Len(t, actualUIDs, len(expectedRules))
|
||||||
|
for _, rule := range expectedRules {
|
||||||
|
require.Containsf(t, actualUIDs, rule.UID, "Rule %s was expected to be deleted but it wasn't", rule.UID)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Len(t, scheduler.Calls, len(expectedRules))
|
||||||
|
for _, call := range scheduler.Calls {
|
||||||
|
require.Equal(t, "DeleteAlertRule", call.Method)
|
||||||
|
key, ok := call.Arguments.Get(0).(models.AlertRuleKey)
|
||||||
|
require.Truef(t, ok, "Expected AlertRuleKey but got something else")
|
||||||
|
found := false
|
||||||
|
for _, rule := range expectedRules {
|
||||||
|
if rule.GetKey() == key {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Truef(t, found, "Key %v was not expected to be submitted to scheduler", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("when fine-grained access is disabled", func(t *testing.T) {
|
||||||
|
t.Run("viewer should not be authorized", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything).Panic("should not be called")
|
||||||
|
|
||||||
|
ac := acMock.New().WithDisabled()
|
||||||
|
request := createRequestContext(orgID, models2.ROLE_VIEWER, map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
})
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 401, response.Status(), "Expected 403 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 rules in folder", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
rulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||||
|
ruleStore.PutRule(context.Background(), rulesInFolder...)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||||
|
|
||||||
|
ac := acMock.New().WithDisabled()
|
||||||
|
request := createRequestContext(orgID, models2.ROLE_EDITOR, map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
})
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
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 in a group in a folder", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
groupName := util.GenerateShortUID()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
rulesInFolderInGroup := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
|
||||||
|
ruleStore.PutRule(context.Background(), rulesInFolderInGroup...)
|
||||||
|
// rules in different groups but in the same namespace
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||||
|
// rules in the same group but different folder
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||||
|
|
||||||
|
ac := acMock.New().WithDisabled()
|
||||||
|
request := createRequestContext(orgID, models2.ROLE_EDITOR, map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
":Groupname": groupName,
|
||||||
|
})
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||||
|
assertRulesDeleted(t, rulesInFolderInGroup, ruleStore, scheduler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("when fine-grained access is enabled", func(t *testing.T) {
|
||||||
|
t.Run("and user does not have access to any of data sources used by alert rules", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything).Panic("should not be called")
|
||||||
|
|
||||||
|
ac := acMock.New()
|
||||||
|
request := createRequestContext(orgID, "None", map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
})
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 401, response.Status(), "Expected 403 but got %d: %v", response.Status(), string(response.Body()))
|
||||||
|
|
||||||
|
scheduler.AssertNotCalled(t, "DeleteAlertRule")
|
||||||
|
require.Empty(t, getRecordedCommand(ruleStore))
|
||||||
|
})
|
||||||
|
t.Run("and user has access to all alert rules", func(t *testing.T) {
|
||||||
|
t.Run("should delete all rules", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
rulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||||
|
ruleStore.PutRule(context.Background(), rulesInFolder...)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||||
|
|
||||||
|
var permissions []*accesscontrol.Permission
|
||||||
|
for _, rule := range rulesInFolder {
|
||||||
|
for _, query := range rule.Data {
|
||||||
|
permissions = append(permissions, &accesscontrol.Permission{
|
||||||
|
Action: datasources.ActionQuery, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ac := acMock.New().WithPermissions(permissions)
|
||||||
|
request := createRequestContext(orgID, "None", map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
})
|
||||||
|
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||||
|
assertRulesDeleted(t, rulesInFolder, ruleStore, scheduler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("and user has access to data sources of some of alert rules", func(t *testing.T) {
|
||||||
|
t.Run("should delete only those that are accessible in folder", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
authorizedRulesInFolder := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||||
|
ruleStore.PutRule(context.Background(), authorizedRulesInFolder...)
|
||||||
|
// more rules in the same namespace but user does not have access to them
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||||
|
|
||||||
|
var permissions []*accesscontrol.Permission
|
||||||
|
for _, rule := range authorizedRulesInFolder {
|
||||||
|
for _, query := range rule.Data {
|
||||||
|
permissions = append(permissions, &accesscontrol.Permission{
|
||||||
|
Action: datasources.ActionQuery, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ac := acMock.New().WithPermissions(permissions)
|
||||||
|
request := createRequestContext(orgID, "None", map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
})
|
||||||
|
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||||
|
assertRulesDeleted(t, authorizedRulesInFolder, ruleStore, scheduler)
|
||||||
|
})
|
||||||
|
t.Run("should delete only rules in a group that are authorized", func(t *testing.T) {
|
||||||
|
ruleStore := store.NewFakeRuleStore(t)
|
||||||
|
orgID := rand.Int63()
|
||||||
|
groupName := util.GenerateShortUID()
|
||||||
|
folder := randFolder()
|
||||||
|
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||||
|
authorizedRulesInGroup := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
|
||||||
|
ruleStore.PutRule(context.Background(), authorizedRulesInGroup...)
|
||||||
|
// more rules in the same group but user is not authorized to access them
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
|
||||||
|
// rules in different groups but in the same namespace
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||||
|
// rules in the same group but different folder
|
||||||
|
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
|
||||||
|
|
||||||
|
scheduler := &schedule.FakeScheduleService{}
|
||||||
|
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||||
|
|
||||||
|
var permissions []*accesscontrol.Permission
|
||||||
|
for _, rule := range authorizedRulesInGroup {
|
||||||
|
for _, query := range rule.Data {
|
||||||
|
permissions = append(permissions, &accesscontrol.Permission{
|
||||||
|
Action: datasources.ActionQuery, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ac := acMock.New().WithPermissions(permissions)
|
||||||
|
request := createRequestContext(orgID, "None", map[string]string{
|
||||||
|
":Namespace": folder.Title,
|
||||||
|
":Groupname": groupName,
|
||||||
|
})
|
||||||
|
response := createService(ac, ruleStore, scheduler).RouteDeleteAlertRules(request)
|
||||||
|
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
|
||||||
|
assertRulesDeleted(t, authorizedRulesInGroup, ruleStore, scheduler)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRequestContext(orgID int64, role models2.RoleType, params map[string]string) *models2.ReqContext {
|
||||||
|
ctx := web.Context{Req: &http.Request{}}
|
||||||
|
ctx.Req = web.SetURLParams(ctx.Req, params)
|
||||||
|
|
||||||
|
return &models2.ReqContext{
|
||||||
|
SignedInUser: &models2.SignedInUser{
|
||||||
|
OrgRole: role,
|
||||||
|
OrgId: orgID,
|
||||||
|
},
|
||||||
|
Context: &ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func withOrgID(orgId int64) func(rule *models.AlertRule) {
|
func withOrgID(orgId int64) func(rule *models.AlertRule) {
|
||||||
return func(rule *models.AlertRule) {
|
return func(rule *models.AlertRule) {
|
||||||
rule.OrgID = orgId
|
rule.OrgID = orgId
|
||||||
|
@ -110,11 +110,11 @@ func (f *ForkedRulerApi) forkRoutePostNameRulesConfig(ctx *models.ReqContext, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForkedRulerApi) forkRouteDeleteNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
|
func (f *ForkedRulerApi) forkRouteDeleteNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
return f.GrafanaRuler.RouteDeleteNamespaceRulesConfig(ctx)
|
return f.GrafanaRuler.RouteDeleteAlertRules(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForkedRulerApi) forkRouteDeleteGrafanaRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
func (f *ForkedRulerApi) forkRouteDeleteGrafanaRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
||||||
return f.GrafanaRuler.RouteDeleteRuleGroupConfig(ctx)
|
return f.GrafanaRuler.RouteDeleteAlertRules(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForkedRulerApi) forkRouteGetNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
|
func (f *ForkedRulerApi) forkRouteGetNamespaceGrafanaRulesConfig(ctx *models.ReqContext) response.Response {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
// ScheduleService is an interface for a service that schedules the evaluation
|
// ScheduleService is an interface for a service that schedules the evaluation
|
||||||
// of alert rules.
|
// of alert rules.
|
||||||
|
//go:generate mockery --name ScheduleService --structname FakeScheduleService --inpackage --filename schedule_mock.go
|
||||||
type ScheduleService interface {
|
type ScheduleService interface {
|
||||||
// Run the scheduler until the context is canceled or the scheduler returns
|
// Run the scheduler until the context is canceled or the scheduler returns
|
||||||
// an error. The scheduler is terminated when this function returns.
|
// an error. The scheduler is terminated when this function returns.
|
||||||
|
118
pkg/services/ngalert/schedule/schedule_mock.go
Normal file
118
pkg/services/ngalert/schedule/schedule_mock.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Code generated by mockery v2.10.0. DO NOT EDIT.
|
||||||
|
|
||||||
|
package schedule
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
|
models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
time "time"
|
||||||
|
|
||||||
|
url "net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FakeScheduleService is an autogenerated mock type for the ScheduleService type
|
||||||
|
type FakeScheduleService struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlertmanagersFor provides a mock function with given fields: orgID
|
||||||
|
func (_m *FakeScheduleService) AlertmanagersFor(orgID int64) []*url.URL {
|
||||||
|
ret := _m.Called(orgID)
|
||||||
|
|
||||||
|
var r0 []*url.URL
|
||||||
|
if rf, ok := ret.Get(0).(func(int64) []*url.URL); ok {
|
||||||
|
r0 = rf(orgID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*url.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlertRule provides a mock function with given fields: key
|
||||||
|
func (_m *FakeScheduleService) DeleteAlertRule(key models.AlertRuleKey) {
|
||||||
|
_m.Called(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DroppedAlertmanagersFor provides a mock function with given fields: orgID
|
||||||
|
func (_m *FakeScheduleService) DroppedAlertmanagersFor(orgID int64) []*url.URL {
|
||||||
|
ret := _m.Called(orgID)
|
||||||
|
|
||||||
|
var r0 []*url.URL
|
||||||
|
if rf, ok := ret.Get(0).(func(int64) []*url.URL); ok {
|
||||||
|
r0 = rf(orgID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*url.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pause provides a mock function with given fields:
|
||||||
|
func (_m *FakeScheduleService) Pause() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run provides a mock function with given fields: _a0
|
||||||
|
func (_m *FakeScheduleService) Run(_a0 context.Context) error {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpause provides a mock function with given fields:
|
||||||
|
func (_m *FakeScheduleService) Unpause() error {
|
||||||
|
ret := _m.Called()
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func() error); ok {
|
||||||
|
r0 = rf()
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlertRule provides a mock function with given fields: key
|
||||||
|
func (_m *FakeScheduleService) UpdateAlertRule(key models.AlertRuleKey) {
|
||||||
|
_m.Called(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalApplied provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *FakeScheduleService) evalApplied(_a0 models.AlertRuleKey, _a1 time.Time) {
|
||||||
|
_m.Called(_a0, _a1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// overrideCfg provides a mock function with given fields: cfg
|
||||||
|
func (_m *FakeScheduleService) overrideCfg(cfg SchedulerCfg) {
|
||||||
|
_m.Called(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopApplied provides a mock function with given fields: _a0
|
||||||
|
func (_m *FakeScheduleService) stopApplied(_a0 models.AlertRuleKey) {
|
||||||
|
_m.Called(_a0)
|
||||||
|
}
|
@ -36,8 +36,6 @@ type UpsertRule struct {
|
|||||||
// Store is the interface for persisting alert rules and instances
|
// Store is the interface for persisting alert rules and instances
|
||||||
type RuleStore interface {
|
type RuleStore interface {
|
||||||
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
|
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
|
||||||
DeleteNamespaceAlertRules(ctx context.Context, orgID int64, namespaceUID string) ([]string, error)
|
|
||||||
DeleteRuleGroupAlertRules(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) ([]string, error)
|
|
||||||
DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error
|
DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error
|
||||||
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) error
|
GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) error
|
||||||
GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
|
GetAlertRulesForScheduling(ctx context.Context, query *ngmodels.ListAlertRulesQuery) error
|
||||||
@ -88,76 +86,6 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteNamespaceAlertRules is a handler for deleting namespace alert rules. A list of deleted rule UIDs are returned.
|
|
||||||
func (st DBstore) DeleteNamespaceAlertRules(ctx context.Context, orgID int64, namespaceUID string) ([]string, error) {
|
|
||||||
ruleUIDs := []string{}
|
|
||||||
|
|
||||||
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID).Find(&ruleUIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec("DELETE FROM alert_rule_version WHERE rule_org_id = ? and rule_namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec(`DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid NOT IN (
|
|
||||||
SELECT uid FROM alert_rule where org_id = ?
|
|
||||||
)`, orgID, orgID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return ruleUIDs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteRuleGroupAlertRules is a handler for deleting rule group alert rules. A list of deleted rule UIDs are returned.
|
|
||||||
func (st DBstore) DeleteRuleGroupAlertRules(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) ([]string, error) {
|
|
||||||
ruleUIDs := []string{}
|
|
||||||
|
|
||||||
err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
|
||||||
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?",
|
|
||||||
orgID, namespaceUID, ruleGroup).Find(&ruleUIDs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
exist, err := sess.Exist(&ngmodels.AlertRule{OrgID: orgID, NamespaceUID: namespaceUID, RuleGroup: ruleGroup})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exist {
|
|
||||||
return ngmodels.ErrRuleGroupNamespaceNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?", orgID, namespaceUID, ruleGroup); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec("DELETE FROM alert_rule_version WHERE rule_org_id = ? and rule_namespace_uid = ? and rule_group = ?", orgID, namespaceUID, ruleGroup); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := sess.Exec(`DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid NOT IN (
|
|
||||||
SELECT uid FROM alert_rule where org_id = ?
|
|
||||||
)`, orgID, orgID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return ruleUIDs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteAlertInstanceByRuleUID is a handler for deleting alert instances by alert rule UID when a rule has been updated
|
// DeleteAlertInstanceByRuleUID is a handler for deleting alert instances by alert rule UID when a rule has been updated
|
||||||
func (st DBstore) DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error {
|
func (st DBstore) DeleteAlertInstancesByRuleUID(ctx context.Context, orgID int64, ruleUID string) error {
|
||||||
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
|
@ -3,13 +3,16 @@ package store
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/annotations"
|
"github.com/grafana/grafana/pkg/services/annotations"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
|
|
||||||
models2 "github.com/grafana/grafana/pkg/models"
|
models2 "github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
@ -27,6 +30,7 @@ func NewFakeRuleStore(t *testing.T) *FakeRuleStore {
|
|||||||
Hook: func(interface{}) error {
|
Hook: func(interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
Folders: map[int64][]*models2.Folder{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +42,12 @@ type FakeRuleStore struct {
|
|||||||
Rules map[int64][]*models.AlertRule
|
Rules map[int64][]*models.AlertRule
|
||||||
Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error
|
Hook func(cmd interface{}) error // use Hook if you need to intercept some query and return an error
|
||||||
RecordedOps []interface{}
|
RecordedOps []interface{}
|
||||||
|
Folders map[int64][]*models2.Folder
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericRecordedQuery struct {
|
||||||
|
Name string
|
||||||
|
Params []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutRule puts the rule in the Rules map. If there are existing rule in the same namespace, they will be overwritten
|
// PutRule puts the rule in the Rules map. If there are existing rule in the same namespace, they will be overwritten
|
||||||
@ -55,6 +65,23 @@ mainloop:
|
|||||||
}
|
}
|
||||||
rgs = append(rgs, r)
|
rgs = append(rgs, r)
|
||||||
f.Rules[r.OrgID] = rgs
|
f.Rules[r.OrgID] = rgs
|
||||||
|
|
||||||
|
var existing *models2.Folder
|
||||||
|
folders := f.Folders[r.OrgID]
|
||||||
|
for _, folder := range folders {
|
||||||
|
if folder.Uid == r.NamespaceUID {
|
||||||
|
existing = folder
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if existing == nil {
|
||||||
|
folders = append(folders, &models2.Folder{
|
||||||
|
Id: rand.Int63(),
|
||||||
|
Uid: r.NamespaceUID,
|
||||||
|
Title: "TEST-FOLDER-" + util.GenerateShortUID(),
|
||||||
|
})
|
||||||
|
f.Folders[r.OrgID] = folders
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,15 +101,33 @@ func (f *FakeRuleStore) GetRecordedCommands(predicate func(cmd interface{}) (int
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FakeRuleStore) DeleteAlertRulesByUID(_ context.Context, _ int64, _ ...string) error {
|
func (f *FakeRuleStore) DeleteAlertRulesByUID(_ context.Context, orgID int64, UIDs ...string) error {
|
||||||
|
f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{
|
||||||
|
Name: "DeleteAlertRulesByUID",
|
||||||
|
Params: []interface{}{orgID, UIDs},
|
||||||
|
})
|
||||||
|
|
||||||
|
rules := f.Rules[orgID]
|
||||||
|
|
||||||
|
var result = make([]*models.AlertRule, 0, len(rules))
|
||||||
|
|
||||||
|
for _, rule := range rules {
|
||||||
|
add := true
|
||||||
|
for _, UID := range UIDs {
|
||||||
|
if rule.UID == UID {
|
||||||
|
add = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if add {
|
||||||
|
result = append(result, rule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Rules[orgID] = result
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (f *FakeRuleStore) DeleteNamespaceAlertRules(_ context.Context, _ int64, _ string) ([]string, error) {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
func (f *FakeRuleStore) DeleteRuleGroupAlertRules(_ context.Context, _ int64, _ string, _ string) ([]string, error) {
|
|
||||||
return []string{}, nil
|
|
||||||
}
|
|
||||||
func (f *FakeRuleStore) DeleteAlertInstancesByRuleUID(_ context.Context, _ int64, _ string) error {
|
func (f *FakeRuleStore) DeleteAlertInstancesByRuleUID(_ context.Context, _ int64, _ string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -179,8 +224,14 @@ func (f *FakeRuleStore) GetNamespaces(_ context.Context, orgID int64, _ *models2
|
|||||||
}
|
}
|
||||||
return namespacesMap, nil
|
return namespacesMap, nil
|
||||||
}
|
}
|
||||||
func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, _ string, _ int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) {
|
func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, title string, orgID int64, _ *models2.SignedInUser, _ bool) (*models2.Folder, error) {
|
||||||
return nil, nil
|
folders := f.Folders[orgID]
|
||||||
|
for _, folder := range folders {
|
||||||
|
if folder.Title == title {
|
||||||
|
return folder, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("not found")
|
||||||
}
|
}
|
||||||
func (f *FakeRuleStore) GetOrgRuleGroups(_ context.Context, q *models.ListOrgRuleGroupsQuery) error {
|
func (f *FakeRuleStore) GetOrgRuleGroups(_ context.Context, q *models.ListOrgRuleGroupsQuery) error {
|
||||||
f.mtx.Lock()
|
f.mtx.Lock()
|
||||||
|
@ -1890,7 +1890,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
// Finally, make sure we can delete it.
|
// Finally, make sure we can delete it.
|
||||||
{
|
{
|
||||||
t.Run("fail if he rule group name does not exists", func(t *testing.T) {
|
t.Run("succeed if the rule group name does not exists", func(t *testing.T) {
|
||||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/groupnotexist", grafanaListedAddr)
|
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/groupnotexist", grafanaListedAddr)
|
||||||
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -1903,8 +1903,8 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
b, err := ioutil.ReadAll(resp.Body)
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, http.StatusNotFound, resp.StatusCode)
|
require.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||||
require.JSONEq(t, `{"message": "failed to delete rule group: rule group not found under this namespace"}`, string(b))
|
require.JSONEq(t, `{"message":"rules deleted"}`, string(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("succeed if the rule group name does exist", func(t *testing.T) {
|
t.Run("succeed if the rule group name does exist", func(t *testing.T) {
|
||||||
@ -1921,7 +1921,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, http.StatusAccepted, resp.StatusCode)
|
require.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||||
require.JSONEq(t, `{"message":"rule group deleted"}`, string(b))
|
require.JSONEq(t, `{"message":"rules deleted"}`, string(b))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user