Alerting: Fix provisioning validation status codes and panics (#50464)
* Updates to all except alert rules * Return 400 when rules fail to validate, add testinfra * More sane package aliases * More package alias renames * One more bug in contact point validation * remove unused function Co-authored-by: Jean-Philippe Quémémer <jeanphilippe.quemener@grafana.com> Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
This commit is contained in:
co-authored by
Jean-Philippe Quémémer
Jean-Philippe Quéméner
parent
52deb821d6
commit
7dd78fee2c
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@@ -33,27 +33,27 @@ type ProvisioningSrv struct {
|
||||
}
|
||||
|
||||
type ContactPointService interface {
|
||||
GetContactPoints(ctx context.Context, orgID int64) ([]apimodels.EmbeddedContactPoint, error)
|
||||
CreateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, p alerting_models.Provenance) (apimodels.EmbeddedContactPoint, error)
|
||||
UpdateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, p alerting_models.Provenance) error
|
||||
GetContactPoints(ctx context.Context, orgID int64) ([]definitions.EmbeddedContactPoint, error)
|
||||
CreateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) (definitions.EmbeddedContactPoint, error)
|
||||
UpdateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) error
|
||||
DeleteContactPoint(ctx context.Context, orgID int64, uid string) error
|
||||
}
|
||||
|
||||
type TemplateService interface {
|
||||
GetTemplates(ctx context.Context, orgID int64) (map[string]string, error)
|
||||
SetTemplate(ctx context.Context, orgID int64, tmpl apimodels.MessageTemplate) (apimodels.MessageTemplate, error)
|
||||
SetTemplate(ctx context.Context, orgID int64, tmpl definitions.MessageTemplate) (definitions.MessageTemplate, error)
|
||||
DeleteTemplate(ctx context.Context, orgID int64, name string) error
|
||||
}
|
||||
|
||||
type NotificationPolicyService interface {
|
||||
GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error)
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p alerting_models.Provenance) error
|
||||
GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error)
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance) error
|
||||
}
|
||||
|
||||
type MuteTimingService interface {
|
||||
GetMuteTimings(ctx context.Context, orgID int64) ([]apimodels.MuteTimeInterval, error)
|
||||
CreateMuteTiming(ctx context.Context, mt apimodels.MuteTimeInterval, orgID int64) (*apimodels.MuteTimeInterval, error)
|
||||
UpdateMuteTiming(ctx context.Context, mt apimodels.MuteTimeInterval, orgID int64) (*apimodels.MuteTimeInterval, error)
|
||||
GetMuteTimings(ctx context.Context, orgID int64) ([]definitions.MuteTimeInterval, error)
|
||||
CreateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (*definitions.MuteTimeInterval, error)
|
||||
UpdateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (*definitions.MuteTimeInterval, error)
|
||||
DeleteMuteTiming(ctx context.Context, name string, orgID int64) error
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func (srv *ProvisioningSrv) RouteGetPolicyTree(c *models.ReqContext) response.Re
|
||||
return response.JSON(http.StatusOK, policies)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *models.ReqContext, tree apimodels.Route) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *models.ReqContext, tree definitions.Route) response.Response {
|
||||
err := srv.policies.UpdatePolicyTree(c.Req.Context(), c.OrgId, tree, alerting_models.ProvenanceAPI)
|
||||
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
@@ -100,18 +100,24 @@ func (srv *ProvisioningSrv) RouteGetContactPoints(c *models.ReqContext) response
|
||||
return response.JSON(http.StatusOK, cps)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePostContactPoint(c *models.ReqContext, cp apimodels.EmbeddedContactPoint) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePostContactPoint(c *models.ReqContext, cp definitions.EmbeddedContactPoint) response.Response {
|
||||
// TODO: provenance is hardcoded for now, change it later to make it more flexible
|
||||
contactPoint, err := srv.contactPointService.CreateContactPoint(c.Req.Context(), c.OrgId, cp, alerting_models.ProvenanceAPI)
|
||||
if errors.Is(err, provisioning.ErrValidation) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, contactPoint)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutContactPoint(c *models.ReqContext, cp apimodels.EmbeddedContactPoint) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutContactPoint(c *models.ReqContext, cp definitions.EmbeddedContactPoint) response.Response {
|
||||
cp.UID = pathParam(c, uidPathParam)
|
||||
err := srv.contactPointService.UpdateContactPoint(c.Req.Context(), c.OrgId, cp, alerting_models.ProvenanceAPI)
|
||||
if errors.Is(err, provisioning.ErrValidation) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
@@ -132,9 +138,9 @@ func (srv *ProvisioningSrv) RouteGetTemplates(c *models.ReqContext) response.Res
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
result := make([]apimodels.MessageTemplate, 0, len(templates))
|
||||
result := make([]definitions.MessageTemplate, 0, len(templates))
|
||||
for k, v := range templates {
|
||||
result = append(result, apimodels.MessageTemplate{Name: k, Template: v})
|
||||
result = append(result, definitions.MessageTemplate{Name: k, Template: v})
|
||||
}
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
@@ -146,14 +152,14 @@ func (srv *ProvisioningSrv) RouteGetTemplate(c *models.ReqContext) response.Resp
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
if tmpl, ok := templates[name]; ok {
|
||||
return response.JSON(http.StatusOK, apimodels.MessageTemplate{Name: name, Template: tmpl})
|
||||
return response.JSON(http.StatusOK, definitions.MessageTemplate{Name: name, Template: tmpl})
|
||||
}
|
||||
return response.Empty(http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutTemplate(c *models.ReqContext, body apimodels.MessageTemplateContent) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutTemplate(c *models.ReqContext, body definitions.MessageTemplateContent) response.Response {
|
||||
name := pathParam(c, namePathParam)
|
||||
tmpl := apimodels.MessageTemplate{
|
||||
tmpl := definitions.MessageTemplate{
|
||||
Name: name,
|
||||
Template: body.Template,
|
||||
Provenance: alerting_models.ProvenanceAPI,
|
||||
@@ -199,7 +205,7 @@ func (srv *ProvisioningSrv) RouteGetMuteTimings(c *models.ReqContext) response.R
|
||||
return response.JSON(http.StatusOK, timings)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt apimodels.MuteTimeInterval) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt definitions.MuteTimeInterval) response.Response {
|
||||
created, err := srv.muteTimings.CreateMuteTiming(c.Req.Context(), mt, c.OrgId)
|
||||
if err != nil {
|
||||
if errors.Is(err, provisioning.ErrValidation) {
|
||||
@@ -210,7 +216,7 @@ func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt apimode
|
||||
return response.JSON(http.StatusCreated, created)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutMuteTiming(c *models.ReqContext, mt apimodels.MuteTimeInterval) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutMuteTiming(c *models.ReqContext, mt definitions.MuteTimeInterval) response.Response {
|
||||
name := pathParam(c, namePathParam)
|
||||
mt.Name = name
|
||||
updated, err := srv.muteTimings.UpdateMuteTiming(c.Req.Context(), mt, c.OrgId)
|
||||
@@ -241,11 +247,14 @@ func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *models.ReqContext) respons
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, apimodels.NewAlertRule(rule, provenace))
|
||||
return response.JSON(http.StatusOK, definitions.NewAlertRule(rule, provenace))
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar definitions.AlertRule) response.Response {
|
||||
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
|
||||
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
@@ -255,8 +264,14 @@ func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar apimodel
|
||||
return response.JSON(http.StatusCreated, ar)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutAlertRule(c *models.ReqContext, ar definitions.AlertRule) response.Response {
|
||||
updatedAlertRule, err := srv.alertRules.UpdateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
|
||||
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
|
||||
return response.Empty(http.StatusNotFound)
|
||||
}
|
||||
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
@@ -273,7 +288,7 @@ func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *models.ReqContext) response.
|
||||
return response.JSON(http.StatusNoContent, "")
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag apimodels.AlertRuleGroup) response.Response {
|
||||
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroup) response.Response {
|
||||
rulegroup := pathParam(c, groupPathParam)
|
||||
folderUID := pathParam(c, folderUIDPathParam)
|
||||
err := srv.alertRules.UpdateRuleGroup(c.Req.Context(), c.OrgId, folderUID, rulegroup, ag.Interval)
|
||||
|
||||
@@ -2,147 +2,308 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
domain "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
gfcore "github.com/grafana/grafana/pkg/models"
|
||||
"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/store"
|
||||
secrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
prometheus "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/timeinterval"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProvisioningApi(t *testing.T) {
|
||||
t.Run("successful GET policies returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("successful PUT policies returns 202", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
rc := createTestRequestCtx()
|
||||
tree := apimodels.Route{}
|
||||
|
||||
response := sut.RoutePutPolicyTree(&rc, tree)
|
||||
|
||||
require.Equal(t, 202, response.Status())
|
||||
})
|
||||
|
||||
t.Run("when new policy tree is invalid", func(t *testing.T) {
|
||||
t.Run("PUT policies returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
sut.policies = &fakeRejectingNotificationPolicyService{}
|
||||
t.Run("policies", func(t *testing.T) {
|
||||
t.Run("successful GET returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
tree := apimodels.Route{}
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("successful PUT returns 202", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
tree := definitions.Route{}
|
||||
|
||||
response := sut.RoutePutPolicyTree(&rc, tree)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
expBody := `{"error":"invalid object specification: invalid policy tree","message":"invalid object specification: invalid policy tree"}`
|
||||
require.Equal(t, expBody, string(response.Body()))
|
||||
require.Equal(t, 202, response.Status())
|
||||
})
|
||||
|
||||
t.Run("when new policy tree is invalid", func(t *testing.T) {
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = &fakeRejectingNotificationPolicyService{}
|
||||
rc := createTestRequestCtx()
|
||||
tree := definitions.Route{}
|
||||
|
||||
response := sut.RoutePutPolicyTree(&rc, tree)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
expBody := `{"error":"invalid object specification: invalid policy tree","message":"invalid object specification: invalid policy tree"}`
|
||||
require.Equal(t, expBody, string(response.Body()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when org has no AM config", func(t *testing.T) {
|
||||
t.Run("GET returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rc.SignedInUser.OrgId = 2
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
|
||||
t.Run("POST returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rc.SignedInUser.OrgId = 2
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when an unspecified error occurrs", func(t *testing.T) {
|
||||
t.Run("GET returns 500", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = &fakeFailingNotificationPolicyService{}
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 500, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
})
|
||||
|
||||
t.Run("PUT returns 500", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = &fakeFailingNotificationPolicyService{}
|
||||
rc := createTestRequestCtx()
|
||||
tree := definitions.Route{}
|
||||
|
||||
response := sut.RoutePutPolicyTree(&rc, tree)
|
||||
|
||||
require.Equal(t, 500, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when org has no AM config", func(t *testing.T) {
|
||||
t.Run("GET policies returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
rc := createTestRequestCtx()
|
||||
rc.SignedInUser.OrgId = 2
|
||||
t.Run("contact points", func(t *testing.T) {
|
||||
t.Run("are invalid", func(t *testing.T) {
|
||||
t.Run("POST returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
cp := createInvalidContactPoint()
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
response := sut.RoutePostContactPoint(&rc, cp)
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "recipient must be specified")
|
||||
})
|
||||
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
cp := createInvalidContactPoint()
|
||||
|
||||
response := sut.RoutePutContactPoint(&rc, cp)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "recipient must be specified")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("templates", func(t *testing.T) {
|
||||
t.Run("are invalid", func(t *testing.T) {
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
withURLParams(rc, namePathParam, "test")
|
||||
tmpl := definitions.MessageTemplateContent{Template: ""}
|
||||
|
||||
response := sut.RoutePutTemplate(&rc, tmpl)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "template must have content")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("mute timings", func(t *testing.T) {
|
||||
t.Run("are invalid", func(t *testing.T) {
|
||||
t.Run("POST returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
mti := createInvalidMuteTiming()
|
||||
|
||||
response := sut.RoutePostMuteTiming(&rc, mti)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "invalid")
|
||||
})
|
||||
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
withURLParams(rc, namePathParam, "interval")
|
||||
mti := createInvalidMuteTiming()
|
||||
|
||||
response := sut.RoutePutMuteTiming(&rc, mti)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "invalid")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("POST policies returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
t.Run("are missing, PUT returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rc.SignedInUser.OrgId = 2
|
||||
withURLParams(rc, namePathParam, "does not exist")
|
||||
mti := definitions.MuteTimeInterval{}
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
response := sut.RoutePutMuteTiming(&rc, mti)
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when an unspecified error occurrs", func(t *testing.T) {
|
||||
t.Run("GET policies returns 500", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
sut.policies = &fakeFailingNotificationPolicyService{}
|
||||
rc := createTestRequestCtx()
|
||||
t.Run("alert rules", func(t *testing.T) {
|
||||
t.Run("are invalid", func(t *testing.T) {
|
||||
t.Run("POST returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
rule := createInvalidAlertRule()
|
||||
|
||||
response := sut.RouteGetPolicyTree(&rc)
|
||||
response := sut.RoutePostAlertRule(&rc, rule)
|
||||
|
||||
require.Equal(t, 500, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "invalid alert rule")
|
||||
})
|
||||
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
rule := createInvalidAlertRule()
|
||||
|
||||
response := sut.RoutePutAlertRule(&rc, rule)
|
||||
|
||||
require.Equal(t, 400, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "invalid alert rule")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("PUT policies returns 500", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut()
|
||||
sut.policies = &fakeFailingNotificationPolicyService{}
|
||||
t.Run("are missing, PUT returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
tree := apimodels.Route{}
|
||||
rule := createTestAlertRule("rule", 1)
|
||||
|
||||
response := sut.RoutePutPolicyTree(&rc, tree)
|
||||
response := sut.RoutePutAlertRule(&rc, rule)
|
||||
|
||||
require.Equal(t, 500, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createProvisioningSrvSut() ProvisioningSrv {
|
||||
func createProvisioningSrvSut(t *testing.T) ProvisioningSrv {
|
||||
t.Helper()
|
||||
secrets := secrets.NewFakeSecretsService()
|
||||
log := log.NewNopLogger()
|
||||
configs := &provisioning.MockAMConfigStore{}
|
||||
configs.EXPECT().
|
||||
GetsConfig(models.AlertConfiguration{
|
||||
AlertmanagerConfiguration: testConfig,
|
||||
})
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
store := store.DBstore{
|
||||
SQLStore: sqlStore,
|
||||
BaseInterval: time.Second * 10,
|
||||
}
|
||||
xact := &provisioning.NopTransactionManager{}
|
||||
prov := &provisioning.MockProvisioningStore{}
|
||||
prov.EXPECT().SaveSucceeds()
|
||||
prov.EXPECT().GetReturns(models.ProvenanceNone)
|
||||
|
||||
return ProvisioningSrv{
|
||||
log: log.NewNopLogger(),
|
||||
policies: newFakeNotificationPolicyService(),
|
||||
log: log,
|
||||
policies: newFakeNotificationPolicyService(),
|
||||
contactPointService: provisioning.NewContactPointService(configs, secrets, prov, xact, log),
|
||||
templates: provisioning.NewTemplateService(configs, prov, xact, log),
|
||||
muteTimings: provisioning.NewMuteTimingService(configs, prov, xact, log),
|
||||
alertRules: provisioning.NewAlertRuleService(store, prov, xact, 60, 10, log),
|
||||
}
|
||||
}
|
||||
|
||||
func createTestRequestCtx() models.ReqContext {
|
||||
return models.ReqContext{
|
||||
func createTestRequestCtx() gfcore.ReqContext {
|
||||
return gfcore.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &models.SignedInUser{
|
||||
SignedInUser: &gfcore.SignedInUser{
|
||||
OrgId: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func withURLParams(rc gfcore.ReqContext, key, value string) {
|
||||
params := web.Params(rc.Req)
|
||||
params[key] = value
|
||||
rc.Req = web.SetURLParams(rc.Req, params)
|
||||
}
|
||||
|
||||
type fakeNotificationPolicyService struct {
|
||||
tree apimodels.Route
|
||||
prov domain.Provenance
|
||||
tree definitions.Route
|
||||
prov models.Provenance
|
||||
}
|
||||
|
||||
func newFakeNotificationPolicyService() *fakeNotificationPolicyService {
|
||||
return &fakeNotificationPolicyService{
|
||||
tree: apimodels.Route{
|
||||
tree: definitions.Route{
|
||||
Receiver: "some-receiver",
|
||||
},
|
||||
prov: domain.ProvenanceNone,
|
||||
prov: models.ProvenanceNone,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
|
||||
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
if orgID != 1 {
|
||||
return apimodels.Route{}, store.ErrNoAlertmanagerConfiguration
|
||||
return definitions.Route{}, store.ErrNoAlertmanagerConfiguration
|
||||
}
|
||||
result := f.tree
|
||||
result.Provenance = f.prov
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
|
||||
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
||||
if orgID != 1 {
|
||||
return store.ErrNoAlertmanagerConfiguration
|
||||
}
|
||||
@@ -153,20 +314,112 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or
|
||||
|
||||
type fakeFailingNotificationPolicyService struct{}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
|
||||
return apimodels.Route{}, fmt.Errorf("something went wrong")
|
||||
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
return definitions.Route{}, fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
|
||||
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
||||
return fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
type fakeRejectingNotificationPolicyService struct{}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
|
||||
return apimodels.Route{}, nil
|
||||
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
return definitions.Route{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
|
||||
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
|
||||
return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
|
||||
}
|
||||
|
||||
func createInvalidContactPoint() definitions.EmbeddedContactPoint {
|
||||
settings, _ := simplejson.NewJson([]byte(`{}`))
|
||||
return definitions.EmbeddedContactPoint{
|
||||
Name: "test-contact-point",
|
||||
Type: "slack",
|
||||
Settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
func createInvalidMuteTiming() definitions.MuteTimeInterval {
|
||||
return definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: prometheus.MuteTimeInterval{
|
||||
Name: "interval",
|
||||
TimeIntervals: []timeinterval.TimeInterval{
|
||||
{
|
||||
Weekdays: []timeinterval.WeekdayRange{
|
||||
{
|
||||
InclusiveRange: timeinterval.InclusiveRange{
|
||||
Begin: -1,
|
||||
End: 7,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createInvalidAlertRule() definitions.AlertRule {
|
||||
return definitions.AlertRule{}
|
||||
}
|
||||
|
||||
func createTestAlertRule(title string, orgID int64) definitions.AlertRule {
|
||||
return definitions.AlertRule{
|
||||
OrgID: orgID,
|
||||
Title: title,
|
||||
Condition: "A",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
Model: json.RawMessage("{}"),
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(60),
|
||||
To: models.Duration(0),
|
||||
},
|
||||
},
|
||||
},
|
||||
RuleGroup: "my-cool-group",
|
||||
For: time.Second * 60,
|
||||
NoDataState: models.OK,
|
||||
ExecErrState: models.OkErrState,
|
||||
}
|
||||
}
|
||||
|
||||
func insertRule(t *testing.T, srv ProvisioningSrv, rule definitions.AlertRule) {
|
||||
t.Helper()
|
||||
|
||||
rc := createTestRequestCtx()
|
||||
resp := srv.RoutePostAlertRule(&rc, rule)
|
||||
require.Equal(t, 201, resp.Status())
|
||||
}
|
||||
|
||||
var testConfig = `
|
||||
{
|
||||
"template_files": {
|
||||
"a": "template"
|
||||
},
|
||||
"alertmanager_config": {
|
||||
"route": {
|
||||
"receiver": "grafana-default-email"
|
||||
},
|
||||
"receivers": [{
|
||||
"name": "grafana-default-email",
|
||||
"grafana_managed_receiver_configs": [{
|
||||
"uid": "",
|
||||
"name": "email receiver",
|
||||
"type": "email",
|
||||
"isDefault": true,
|
||||
"settings": {
|
||||
"addresses": "<example@email.com>"
|
||||
}
|
||||
}]
|
||||
}],
|
||||
"mute_time_intervals": [{
|
||||
"name": "interval",
|
||||
"time_intervals": []
|
||||
}]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
Reference in New Issue
Block a user