mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: backend "ng" code cleanup (#33578)
This commit is contained in:
parent
599a9b9a6d
commit
b8f01fe034
@ -6,15 +6,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "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/setting"
|
||||
@ -86,41 +82,4 @@ func (api *API) RegisterAPIEndpoints(m *metrics.Metrics) {
|
||||
DatasourceCache: api.DatasourceCache,
|
||||
log: logger,
|
||||
}, m)
|
||||
|
||||
// Legacy routes; they will be removed in v8
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("", middleware.ReqSignedIn, routing.Wrap(api.listAlertDefinitions))
|
||||
alertDefinitions.Get("/eval/:alertDefinitionUID", middleware.ReqSignedIn, api.validateOrgAlertDefinition, routing.Wrap(api.alertDefinitionEvalEndpoint))
|
||||
alertDefinitions.Post("/eval", middleware.ReqSignedIn, binding.Bind(ngmodels.EvalAlertConditionCommand{}), routing.Wrap(api.conditionEvalEndpoint))
|
||||
alertDefinitions.Get("/:alertDefinitionUID", middleware.ReqSignedIn, api.validateOrgAlertDefinition, routing.Wrap(api.getAlertDefinitionEndpoint))
|
||||
alertDefinitions.Delete("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, routing.Wrap(api.deleteAlertDefinitionEndpoint))
|
||||
alertDefinitions.Post("/", middleware.ReqEditorRole, binding.Bind(ngmodels.SaveAlertDefinitionCommand{}), routing.Wrap(api.createAlertDefinitionEndpoint))
|
||||
alertDefinitions.Put("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, binding.Bind(ngmodels.UpdateAlertDefinitionCommand{}), routing.Wrap(api.updateAlertDefinitionEndpoint))
|
||||
alertDefinitions.Post("/pause", middleware.ReqEditorRole, binding.Bind(ngmodels.UpdateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionPauseEndpoint))
|
||||
alertDefinitions.Post("/unpause", middleware.ReqEditorRole, binding.Bind(ngmodels.UpdateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionUnpauseEndpoint))
|
||||
})
|
||||
|
||||
if api.Cfg.Env == setting.Dev {
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Post("/evalOld", middleware.ReqSignedIn, routing.Wrap(api.conditionEvalOldEndpoint))
|
||||
})
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("/evalOldByID/:id", middleware.ReqSignedIn, routing.Wrap(api.conditionEvalOldEndpointByID))
|
||||
})
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("/oldByID/:id", middleware.ReqSignedIn, routing.Wrap(api.conditionOldEndpointByID))
|
||||
})
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("/ruleGroupByOldID/:id", middleware.ReqSignedIn, routing.Wrap(api.ruleGroupByOldID))
|
||||
})
|
||||
}
|
||||
|
||||
api.RouteRegister.Group("/api/ngalert/", func(schedulerRouter routing.RouteRegister) {
|
||||
schedulerRouter.Post("/pause", routing.Wrap(api.pauseScheduler))
|
||||
schedulerRouter.Post("/unpause", routing.Wrap(api.unpauseScheduler))
|
||||
}, middleware.ReqOrgAdmin)
|
||||
|
||||
api.RouteRegister.Group("/api/alert-instances", func(alertInstances routing.RouteRegister) {
|
||||
alertInstances.Get("", middleware.ReqSignedIn, routing.Wrap(api.listAlertInstancesEndpoint))
|
||||
})
|
||||
}
|
||||
|
@ -258,25 +258,6 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimode
|
||||
return gettableExtendedRuleNode
|
||||
}
|
||||
|
||||
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
|
||||
postableExtendedRuleNode := apimodels.PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: r.Title,
|
||||
Condition: r.Condition,
|
||||
Data: r.Data,
|
||||
UID: r.UID,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
},
|
||||
}
|
||||
postableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||
For: model.Duration(r.For),
|
||||
Annotations: r.Annotations,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
return postableExtendedRuleNode
|
||||
}
|
||||
|
||||
func toNamespaceErrorResponse(err error) response.Response {
|
||||
if errors.Is(err, ngmodels.ErrCannotEditNamespace) {
|
||||
return response.Error(http.StatusForbidden, err.Error(), err)
|
||||
|
@ -1,215 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// listAlertInstancesEndpoint handles GET /api/alert-instances.
|
||||
func (api *API) listAlertInstancesEndpoint(c *models.ReqContext) response.Response {
|
||||
cmd := ngmodels.ListAlertInstancesQuery{DefinitionOrgID: c.SignedInUser.OrgId}
|
||||
|
||||
if err := api.Store.ListAlertInstances(&cmd); err != nil {
|
||||
return response.Error(500, "Failed to list alert instances", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/eval.
|
||||
func (api *API) conditionEvalEndpoint(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand) response.Response {
|
||||
return conditionEval(c, cmd, api.DatasourceCache, api.DataService, api.Cfg)
|
||||
}
|
||||
|
||||
// alertDefinitionEvalEndpoint handles GET /api/alert-definitions/eval/:alertDefinitionUID.
|
||||
func (api *API) alertDefinitionEvalEndpoint(c *models.ReqContext) response.Response {
|
||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||
|
||||
condition, err := api.LoadAlertCondition(alertDefinitionUID, c.SignedInUser.OrgId)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to load alert definition conditions", err)
|
||||
}
|
||||
|
||||
if err := validateCondition(*condition, c.SignedInUser, c.SkipCache, api.DatasourceCache); err != nil {
|
||||
return response.Error(400, "invalid condition", err)
|
||||
}
|
||||
|
||||
evaluator := eval.Evaluator{Cfg: api.Cfg}
|
||||
evalResults, err := evaluator.ConditionEval(condition, timeNow(), api.DataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate alert", err)
|
||||
}
|
||||
frame := evalResults.AsDataFrame()
|
||||
|
||||
return response.JSONStreaming(200, util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
})
|
||||
}
|
||||
|
||||
// getAlertDefinitionEndpoint handles GET /api/alert-definitions/:alertDefinitionUID.
|
||||
func (api *API) getAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||
|
||||
query := ngmodels.GetAlertDefinitionByUIDQuery{
|
||||
UID: alertDefinitionUID,
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
}
|
||||
|
||||
if err := api.Store.GetAlertDefinitionByUID(&query); err != nil {
|
||||
return response.Error(500, "Failed to get alert definition", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, &query.Result)
|
||||
}
|
||||
|
||||
// deleteAlertDefinitionEndpoint handles DELETE /api/alert-definitions/:alertDefinitionUID.
|
||||
func (api *API) deleteAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||
|
||||
cmd := ngmodels.DeleteAlertDefinitionByUIDCommand{
|
||||
UID: alertDefinitionUID,
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
}
|
||||
|
||||
if err := api.Store.DeleteAlertDefinitionByUID(&cmd); err != nil {
|
||||
return response.Error(500, "Failed to delete alert definition", err)
|
||||
}
|
||||
|
||||
return response.Success("Alert definition deleted")
|
||||
}
|
||||
|
||||
// updateAlertDefinitionEndpoint handles PUT /api/alert-definitions/:alertDefinitionUID.
|
||||
func (api *API) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionCommand) response.Response {
|
||||
cmd.UID = c.Params(":alertDefinitionUID")
|
||||
cmd.OrgID = c.SignedInUser.OrgId
|
||||
|
||||
evalCond := ngmodels.Condition{
|
||||
Condition: cmd.Condition,
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
Data: cmd.Data,
|
||||
}
|
||||
|
||||
if err := validateCondition(evalCond, c.SignedInUser, c.SkipCache, api.DatasourceCache); err != nil {
|
||||
return response.Error(400, "invalid condition", err)
|
||||
}
|
||||
|
||||
if err := api.Store.UpdateAlertDefinition(&cmd); err != nil {
|
||||
return response.Error(500, "Failed to update alert definition", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
// createAlertDefinitionEndpoint handles POST /api/alert-definitions.
|
||||
func (api *API) createAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.SaveAlertDefinitionCommand) response.Response {
|
||||
cmd.OrgID = c.SignedInUser.OrgId
|
||||
|
||||
evalCond := ngmodels.Condition{
|
||||
Condition: cmd.Condition,
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
Data: cmd.Data,
|
||||
}
|
||||
|
||||
if err := validateCondition(evalCond, c.SignedInUser, c.SkipCache, api.DatasourceCache); err != nil {
|
||||
return response.Error(400, "invalid condition", err)
|
||||
}
|
||||
|
||||
if err := api.Store.SaveAlertDefinition(&cmd); err != nil {
|
||||
return response.Error(500, "Failed to create alert definition", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, cmd.Result)
|
||||
}
|
||||
|
||||
// listAlertDefinitions handles GET /api/alert-definitions.
|
||||
func (api *API) listAlertDefinitions(c *models.ReqContext) response.Response {
|
||||
query := ngmodels.ListAlertDefinitionsQuery{OrgID: c.SignedInUser.OrgId}
|
||||
|
||||
if err := api.Store.GetOrgAlertDefinitions(&query); err != nil {
|
||||
return response.Error(500, "Failed to list alert definitions", err)
|
||||
}
|
||||
|
||||
return response.JSON(200, util.DynMap{"results": query.Result})
|
||||
}
|
||||
|
||||
func (api *API) pauseScheduler() response.Response {
|
||||
err := api.Schedule.Pause()
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to pause scheduler", err)
|
||||
}
|
||||
return response.JSON(200, util.DynMap{"message": "alert definition scheduler paused"})
|
||||
}
|
||||
|
||||
func (api *API) unpauseScheduler() response.Response {
|
||||
err := api.Schedule.Unpause()
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to unpause scheduler", err)
|
||||
}
|
||||
return response.JSON(200, util.DynMap{"message": "alert definition scheduler unpaused"})
|
||||
}
|
||||
|
||||
// alertDefinitionPauseEndpoint handles POST /api/alert-definitions/pause.
|
||||
func (api *API) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response {
|
||||
cmd.OrgID = c.SignedInUser.OrgId
|
||||
cmd.Paused = true
|
||||
|
||||
err := api.Store.UpdateAlertDefinitionPaused(&cmd)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to pause alert definition", err)
|
||||
}
|
||||
return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions paused", cmd.ResultCount)})
|
||||
}
|
||||
|
||||
// alertDefinitionUnpauseEndpoint handles POST /api/alert-definitions/unpause.
|
||||
func (api *API) alertDefinitionUnpauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response {
|
||||
cmd.OrgID = c.SignedInUser.OrgId
|
||||
cmd.Paused = false
|
||||
|
||||
err := api.Store.UpdateAlertDefinitionPaused(&cmd)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed to unpause alert definition", err)
|
||||
}
|
||||
return response.JSON(200, util.DynMap{"message": fmt.Sprintf("%d alert definitions unpaused", cmd.ResultCount)})
|
||||
}
|
||||
|
||||
// LoadAlertCondition returns a Condition object for the given alertDefinitionID.
|
||||
func (api *API) LoadAlertCondition(alertDefinitionUID string, orgID int64) (*ngmodels.Condition, error) {
|
||||
q := ngmodels.GetAlertDefinitionByUIDQuery{UID: alertDefinitionUID, OrgID: orgID}
|
||||
if err := api.Store.GetAlertDefinitionByUID(&q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alertDefinition := q.Result
|
||||
|
||||
err := api.Store.ValidateAlertDefinition(alertDefinition, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ngmodels.Condition{
|
||||
Condition: alertDefinition.Condition,
|
||||
OrgID: alertDefinition.OrgID,
|
||||
Data: alertDefinition.Data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) validateOrgAlertDefinition(c *models.ReqContext) {
|
||||
uid := c.ParamsEscape(":alertDefinitionUID")
|
||||
|
||||
if uid == "" {
|
||||
c.JsonApiErr(403, "Permission denied", nil)
|
||||
return
|
||||
}
|
||||
|
||||
query := ngmodels.GetAlertDefinitionByUIDQuery{UID: uid, OrgID: c.SignedInUser.OrgId}
|
||||
|
||||
if err := api.Store.GetAlertDefinitionByUID(&query); err != nil {
|
||||
c.JsonApiErr(404, "Alert definition not found", nil)
|
||||
return
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/expr/translate"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/evalOld.
|
||||
func (api *API) conditionEvalOldEndpoint(c *models.ReqContext) response.Response {
|
||||
b, err := c.Req.Body().Bytes()
|
||||
if err != nil {
|
||||
response.Error(400, "failed to read body", err)
|
||||
}
|
||||
evalCond, err := translate.DashboardAlertConditions(b, c.OrgId)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to translate alert conditions", err)
|
||||
}
|
||||
|
||||
if err := validateCondition(*evalCond, c.SignedInUser, c.SkipCache, api.DatasourceCache); err != nil {
|
||||
return response.Error(400, "invalid condition", err)
|
||||
}
|
||||
//now := cmd.Now
|
||||
//if now.IsZero() {
|
||||
//now := timeNow()
|
||||
//}
|
||||
evaluator := eval.Evaluator{Cfg: api.Cfg}
|
||||
evalResults, err := evaluator.ConditionEval(evalCond, timeNow(), api.DataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate conditions", err)
|
||||
}
|
||||
frame := evalResults.AsDataFrame()
|
||||
return response.JSONStreaming(200, util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
})
|
||||
}
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/evalOld.
|
||||
func (api *API) conditionEvalOldEndpointByID(c *models.ReqContext) response.Response {
|
||||
id := c.ParamsInt64("id")
|
||||
if id == 0 {
|
||||
return response.Error(400, "missing id", nil)
|
||||
}
|
||||
getAlert := &models.GetAlertByIdQuery{
|
||||
Id: id,
|
||||
}
|
||||
if err := bus.Dispatch(getAlert); err != nil {
|
||||
return response.Error(400, fmt.Sprintf("could find alert with id %v", id), err)
|
||||
}
|
||||
if getAlert.Result.OrgId != c.SignedInUser.OrgId {
|
||||
return response.Error(403, "alert does not match organization of user", nil)
|
||||
}
|
||||
settings := getAlert.Result.Settings
|
||||
sb, err := settings.ToDB()
|
||||
if err != nil {
|
||||
return response.Error(400, "failed to marshal alert settings", err)
|
||||
}
|
||||
evalCond, err := translate.DashboardAlertConditions(sb, c.OrgId)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to translate alert conditions", err)
|
||||
}
|
||||
|
||||
if err := validateCondition(*evalCond, c.SignedInUser, c.SkipCache, api.DatasourceCache); err != nil {
|
||||
return response.Error(400, "invalid condition", err)
|
||||
}
|
||||
//now := cmd.Now
|
||||
//if now.IsZero() {
|
||||
//now := timeNow()
|
||||
//}
|
||||
evaluator := eval.Evaluator{Cfg: api.Cfg}
|
||||
evalResults, err := evaluator.ConditionEval(evalCond, timeNow(), api.DataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate conditions", err)
|
||||
}
|
||||
frame := evalResults.AsDataFrame()
|
||||
return response.JSONStreaming(200, util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
})
|
||||
}
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/oldByID.
|
||||
func (api *API) conditionOldEndpointByID(c *models.ReqContext) response.Response {
|
||||
id := c.ParamsInt64("id")
|
||||
if id == 0 {
|
||||
return response.Error(400, "missing id", nil)
|
||||
}
|
||||
getAlert := &models.GetAlertByIdQuery{
|
||||
Id: id,
|
||||
}
|
||||
if err := bus.Dispatch(getAlert); err != nil {
|
||||
return response.Error(400, fmt.Sprintf("could find alert with id %v", id), err)
|
||||
}
|
||||
if getAlert.Result.OrgId != c.SignedInUser.OrgId {
|
||||
return response.Error(403, "alert does not match organization of user", nil)
|
||||
}
|
||||
settings := getAlert.Result.Settings
|
||||
sb, err := settings.ToDB()
|
||||
if err != nil {
|
||||
return response.Error(400, "failed to marshal alert settings", err)
|
||||
}
|
||||
evalCond, err := translate.DashboardAlertConditions(sb, c.OrgId)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to translate alert conditions", err)
|
||||
}
|
||||
return response.JSON(200, evalCond)
|
||||
}
|
||||
|
||||
// ruleGroupByOldID handles POST /api/alert-definitions/ruleGroupByOldID.
|
||||
func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response {
|
||||
id := c.ParamsInt64("id")
|
||||
if id == 0 {
|
||||
return response.Error(400, "missing id", nil)
|
||||
}
|
||||
save := c.Query("save") == "true"
|
||||
// Get dashboard alert definition from database.
|
||||
oldAlert, status, err := transGetAlertById(id, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(status, "failed to get alert", fmt.Errorf("failed to get alert for alert id %v: %w", id, err))
|
||||
}
|
||||
// Translate the dashboard's alerts conditions into SSE queries and conditions.
|
||||
sseCond, err := transToSSECondition(oldAlert, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(400, "failed to translate alert conditions",
|
||||
fmt.Errorf("failed to translate alert conditions for alert id %v: %w", id, err))
|
||||
}
|
||||
// Get the dashboard that contains the dashboard Alert.
|
||||
oldAlertsDash, status, err := transGetAlertsDashById(oldAlert.DashboardId, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(status, "failed to get alert's dashboard", fmt.Errorf("failed to get dashboard for alert id %v, %w", id, err))
|
||||
}
|
||||
isGeneralFolder := oldAlertsDash.FolderId == 0 && !oldAlertsDash.IsFolder
|
||||
var namespaceUID string
|
||||
if isGeneralFolder {
|
||||
namespaceUID = "General"
|
||||
} else {
|
||||
// Get the folder that contains the dashboard that contains the dashboard alert.
|
||||
getFolder := &models.GetDashboardQuery{
|
||||
Id: oldAlertsDash.FolderId,
|
||||
OrgId: oldAlertsDash.OrgId,
|
||||
}
|
||||
if err := bus.Dispatch(getFolder); err != nil {
|
||||
return response.Error(400, fmt.Sprintf("could find folder %v for alert with id %v", getFolder.Id, id), err)
|
||||
}
|
||||
namespaceUID = getFolder.Result.Uid
|
||||
}
|
||||
noDataSetting, execErrSetting, err := transNoDataExecSettings(oldAlert, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(400, "unable to translate nodata/exec error settings",
|
||||
fmt.Errorf("unable to translate nodata/exec error settings for alert id %v: %w", id, err))
|
||||
}
|
||||
|
||||
ruleTags := map[string]string{}
|
||||
|
||||
for k, v := range oldAlert.Settings.Get("alertRuleTags").MustMap() {
|
||||
sV, ok := v.(string)
|
||||
if !ok {
|
||||
return response.Error(400, "unable to unmarshal rule tags",
|
||||
fmt.Errorf("unexpected type %T for tag %v", v, k))
|
||||
}
|
||||
ruleTags[k] = sV
|
||||
}
|
||||
|
||||
rule := ngmodels.AlertRule{
|
||||
Title: oldAlert.Name,
|
||||
Data: sseCond.Data,
|
||||
Condition: sseCond.Condition,
|
||||
NoDataState: *noDataSetting,
|
||||
ExecErrState: *execErrSetting,
|
||||
For: oldAlert.For,
|
||||
Annotations: ruleTags,
|
||||
}
|
||||
rgc := apimodels.PostableRuleGroupConfig{
|
||||
// TODO? Generate new name on conflict?
|
||||
Name: oldAlert.Name,
|
||||
Interval: transAdjustInterval(oldAlert.Frequency),
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
toPostableExtendedRuleNode(rule),
|
||||
},
|
||||
}
|
||||
cmd := store.UpdateRuleGroupCmd{
|
||||
OrgID: oldAlert.OrgId,
|
||||
NamespaceUID: namespaceUID,
|
||||
RuleGroupConfig: rgc,
|
||||
}
|
||||
if !save {
|
||||
return response.JSON(200, cmd)
|
||||
}
|
||||
// note: Update rule group will set the Interval within the grafana_alert from
|
||||
// the interval of the group.
|
||||
err = api.RuleStore.UpdateRuleGroup(cmd)
|
||||
if err != nil {
|
||||
return response.JSON(400, util.DynMap{
|
||||
"message:": "failed to save alert rule",
|
||||
"error": err.Error(),
|
||||
"cmd": cmd,
|
||||
})
|
||||
}
|
||||
return response.JSON(200, cmd)
|
||||
}
|
||||
|
||||
func transAdjustInterval(freq int64) model.Duration {
|
||||
// 10 corresponds to the SchedulerCfg, but TODO not worrying about fetching for now.
|
||||
var baseFreq int64 = 10
|
||||
if freq <= baseFreq {
|
||||
return model.Duration(time.Second * 10)
|
||||
}
|
||||
return model.Duration(time.Duration((freq - (freq % baseFreq))) * time.Second)
|
||||
}
|
||||
|
||||
func transGetAlertById(id int64, user models.SignedInUser) (*models.Alert, int, error) {
|
||||
getAlert := &models.GetAlertByIdQuery{
|
||||
Id: id,
|
||||
}
|
||||
if err := bus.Dispatch(getAlert); err != nil {
|
||||
return nil, 400, fmt.Errorf("could find alert with id %v: %w", id, err)
|
||||
}
|
||||
if getAlert.Result.OrgId != user.OrgId {
|
||||
return nil, 403, fmt.Errorf("alert does not match organization of user")
|
||||
}
|
||||
return getAlert.Result, 0, nil
|
||||
}
|
||||
|
||||
func transGetAlertsDashById(dashboardId int64, user models.SignedInUser) (*models.Dashboard, int, error) {
|
||||
getDash := &models.GetDashboardQuery{
|
||||
Id: dashboardId,
|
||||
OrgId: user.OrgId,
|
||||
}
|
||||
if err := bus.Dispatch(getDash); err != nil {
|
||||
return nil, 400, fmt.Errorf("could find dashboard with id %v: %w", dashboardId, err)
|
||||
}
|
||||
return getDash.Result, 0, nil
|
||||
}
|
||||
|
||||
func transToSSECondition(m *models.Alert, user models.SignedInUser) (*ngmodels.Condition, error) {
|
||||
sb, err := m.Settings.ToDB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal alert settings: %w", err)
|
||||
}
|
||||
evalCond, err := translate.DashboardAlertConditions(sb, user.OrgId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to translate dashboard alert to SSE conditions: %w", err)
|
||||
}
|
||||
return evalCond, nil
|
||||
}
|
||||
|
||||
func transNoDataExecSettings(m *models.Alert, user models.SignedInUser) (*ngmodels.NoDataState, *ngmodels.ExecutionErrorState, error) {
|
||||
oldNoData := m.Settings.Get("noDataState").MustString()
|
||||
noDataSetting, err := transNoData(oldNoData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
oldExecErr := m.Settings.Get("executionErrorState").MustString()
|
||||
execErrSetting, err := transExecErr(oldExecErr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &noDataSetting, &execErrSetting, nil
|
||||
}
|
||||
|
||||
func transNoData(s string) (ngmodels.NoDataState, error) {
|
||||
switch s {
|
||||
case "ok":
|
||||
return ngmodels.OK, nil
|
||||
case "no_data":
|
||||
return ngmodels.NoData, nil
|
||||
case "alerting":
|
||||
return ngmodels.Alerting, nil
|
||||
case "keep_state":
|
||||
return ngmodels.KeepLastState, nil
|
||||
}
|
||||
return ngmodels.NoData, fmt.Errorf("unrecognized No Data setting %v", s)
|
||||
}
|
||||
|
||||
func transExecErr(s string) (ngmodels.ExecutionErrorState, error) {
|
||||
switch s {
|
||||
case "alerting":
|
||||
return ngmodels.AlertingErrState, nil
|
||||
case "KeepLastState":
|
||||
return ngmodels.KeepLastStateErrState, nil
|
||||
}
|
||||
return ngmodels.AlertingErrState, fmt.Errorf("unrecognized Execution Error setting %v", s)
|
||||
}
|
@ -80,7 +80,6 @@ type FetchUniqueOrgIdsQuery struct {
|
||||
type ListAlertInstancesQueryResult struct {
|
||||
DefinitionOrgID int64 `xorm:"def_org_id" json:"definitionOrgId"`
|
||||
DefinitionUID string `xorm:"def_uid" json:"definitionUid"`
|
||||
DefinitionTitle string `xorm:"def_title" json:"definitionTitle"`
|
||||
Labels InstanceLabels `json:"labels"`
|
||||
LabelsHash string `json:"labeHash"`
|
||||
CurrentState InstanceStateType `json:"currentState"`
|
||||
|
@ -1,133 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrAlertDefinitionNotFound is an error for an unknown alert definition.
|
||||
ErrAlertDefinitionNotFound = fmt.Errorf("could not find alert definition")
|
||||
// ErrAlertDefinitionFailedGenerateUniqueUID is an error for failure to generate alert definition UID
|
||||
ErrAlertDefinitionFailedGenerateUniqueUID = errors.New("failed to generate alert definition UID")
|
||||
)
|
||||
|
||||
// AlertDefinition is the model for alert definitions in Alerting NG.
|
||||
// Legacy model; It will be removed in v8
|
||||
type AlertDefinition struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'" json:"id"`
|
||||
OrgID int64 `xorm:"org_id" json:"orgId"`
|
||||
Title string `json:"title"`
|
||||
Condition string `json:"condition"`
|
||||
Data []AlertQuery `json:"data"`
|
||||
Updated time.Time `json:"updated"`
|
||||
IntervalSeconds int64 `json:"intervalSeconds"`
|
||||
Version int64 `json:"version"`
|
||||
UID string `xorm:"uid" json:"uid"`
|
||||
Paused bool `json:"paused"`
|
||||
}
|
||||
|
||||
// AlertDefinitionKey is the alert definition identifier
|
||||
type AlertDefinitionKey struct {
|
||||
OrgID int64
|
||||
DefinitionUID string
|
||||
}
|
||||
|
||||
func (k AlertDefinitionKey) String() string {
|
||||
return fmt.Sprintf("{orgID: %d, definitionUID: %s}", k.OrgID, k.DefinitionUID)
|
||||
}
|
||||
|
||||
// GetKey returns the alert definitions identifier
|
||||
func (alertDefinition *AlertDefinition) GetKey() AlertDefinitionKey {
|
||||
return AlertDefinitionKey{OrgID: alertDefinition.OrgID, DefinitionUID: alertDefinition.UID}
|
||||
}
|
||||
|
||||
// PreSave sets datasource and loads the updated model for each alert query.
|
||||
func (alertDefinition *AlertDefinition) PreSave(timeNow func() time.Time) error {
|
||||
for i, q := range alertDefinition.Data {
|
||||
err := q.PreSave()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid alert query %s: %w", q.RefID, err)
|
||||
}
|
||||
alertDefinition.Data[i] = q
|
||||
}
|
||||
alertDefinition.Updated = timeNow()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AlertDefinitionVersion is the model for alert definition versions in Alerting NG.
|
||||
// Legacy model; It will be removed in v8
|
||||
type AlertDefinitionVersion struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
AlertDefinitionID int64 `xorm:"alert_definition_id"`
|
||||
AlertDefinitionUID string `xorm:"alert_definition_uid"`
|
||||
ParentVersion int64
|
||||
RestoredFrom int64
|
||||
Version int64
|
||||
|
||||
Created time.Time
|
||||
Title string
|
||||
Condition string
|
||||
Data []AlertQuery
|
||||
IntervalSeconds int64
|
||||
}
|
||||
|
||||
// GetAlertDefinitionByUIDQuery is the query for retrieving/deleting an alert definition by UID and organisation ID.
|
||||
// Legacy model; It will be removed in v8
|
||||
type GetAlertDefinitionByUIDQuery struct {
|
||||
UID string
|
||||
OrgID int64
|
||||
|
||||
Result *AlertDefinition
|
||||
}
|
||||
|
||||
// DeleteAlertDefinitionByUIDCommand is the command for deleting an alert definition
|
||||
// Legacy model; It will be removed in v8
|
||||
type DeleteAlertDefinitionByUIDCommand struct {
|
||||
UID string
|
||||
OrgID int64
|
||||
}
|
||||
|
||||
// SaveAlertDefinitionCommand is the query for saving a new alert definition.
|
||||
// Legacy model; It will be removed in v8
|
||||
type SaveAlertDefinitionCommand struct {
|
||||
Title string `json:"title"`
|
||||
OrgID int64 `json:"-"`
|
||||
Condition string `json:"condition"`
|
||||
Data []AlertQuery `json:"data"`
|
||||
IntervalSeconds *int64 `json:"intervalSeconds"`
|
||||
|
||||
Result *AlertDefinition
|
||||
}
|
||||
|
||||
// UpdateAlertDefinitionCommand is the query for updating an existing alert definition.
|
||||
// Legacy model; It will be removed in v8
|
||||
type UpdateAlertDefinitionCommand struct {
|
||||
Title string `json:"title"`
|
||||
OrgID int64 `json:"-"`
|
||||
Condition string `json:"condition"`
|
||||
Data []AlertQuery `json:"data"`
|
||||
IntervalSeconds *int64 `json:"intervalSeconds"`
|
||||
UID string `json:"-"`
|
||||
|
||||
Result *AlertDefinition
|
||||
}
|
||||
|
||||
// ListAlertDefinitionsQuery is the query for listing alert definitions
|
||||
// Legacy model; It will be removed in v8
|
||||
type ListAlertDefinitionsQuery struct {
|
||||
OrgID int64 `json:"-"`
|
||||
|
||||
Result []*AlertDefinition
|
||||
}
|
||||
|
||||
// UpdateAlertDefinitionPausedCommand is the command for updating an alert definitions
|
||||
// Legacy model; It will be removed in v8
|
||||
type UpdateAlertDefinitionPausedCommand struct {
|
||||
OrgID int64 `json:"-"`
|
||||
UIDs []string `json:"uids"`
|
||||
Paused bool `json:"-"`
|
||||
|
||||
ResultCount int64
|
||||
}
|
@ -1,16 +1,11 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// TimeNow makes it possible to test usage of time
|
||||
@ -21,17 +16,9 @@ const AlertDefinitionMaxTitleLength = 190
|
||||
|
||||
// Store is the interface for persisting alert definitions and instances
|
||||
type Store interface {
|
||||
DeleteAlertDefinitionByUID(*models.DeleteAlertDefinitionByUIDCommand) error
|
||||
GetAlertDefinitionByUID(*models.GetAlertDefinitionByUIDQuery) error
|
||||
GetAlertDefinitions(*models.ListAlertDefinitionsQuery) error
|
||||
GetOrgAlertDefinitions(*models.ListAlertDefinitionsQuery) error
|
||||
SaveAlertDefinition(*models.SaveAlertDefinitionCommand) error
|
||||
UpdateAlertDefinition(*models.UpdateAlertDefinitionCommand) error
|
||||
GetAlertInstance(*models.GetAlertInstanceQuery) error
|
||||
ListAlertInstances(*models.ListAlertInstancesQuery) error
|
||||
SaveAlertInstance(*models.SaveAlertInstanceCommand) error
|
||||
ValidateAlertDefinition(*models.AlertDefinition, bool) error
|
||||
UpdateAlertDefinitionPaused(*models.UpdateAlertDefinitionPausedCommand) error
|
||||
FetchOrgIds(cmd *models.FetchUniqueOrgIdsQuery) error
|
||||
}
|
||||
|
||||
@ -50,297 +37,3 @@ type DBstore struct {
|
||||
DefaultIntervalSeconds int64
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
}
|
||||
|
||||
func getAlertDefinitionByUID(sess *sqlstore.DBSession, alertDefinitionUID string, orgID int64) (*models.AlertDefinition, error) {
|
||||
// we consider optionally enabling some caching
|
||||
alertDefinition := models.AlertDefinition{OrgID: orgID, UID: alertDefinitionUID}
|
||||
has, err := sess.Get(&alertDefinition)
|
||||
if !has {
|
||||
return nil, models.ErrAlertDefinitionNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &alertDefinition, nil
|
||||
}
|
||||
|
||||
// DeleteAlertDefinitionByUID is a handler for deleting an alert definition.
|
||||
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||
func (st DBstore) DeleteAlertDefinitionByUID(cmd *models.DeleteAlertDefinitionByUIDCommand) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.Exec("DELETE FROM alert_definition WHERE uid = ? AND org_id = ?", cmd.UID, cmd.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("DELETE FROM alert_definition_version WHERE alert_definition_uid = ?", cmd.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = sess.Exec("DELETE FROM alert_instance WHERE def_org_id = ? AND def_uid = ?", cmd.OrgID, cmd.UID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAlertDefinitionByUID is a handler for retrieving an alert definition from that database by its UID and organisation ID.
|
||||
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||
func (st DBstore) GetAlertDefinitionByUID(query *models.GetAlertDefinitionByUIDQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
alertDefinition, err := getAlertDefinitionByUID(sess, query.UID, query.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query.Result = alertDefinition
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// SaveAlertDefinition is a handler for saving a new alert definition.
|
||||
func (st DBstore) SaveAlertDefinition(cmd *models.SaveAlertDefinitionCommand) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
intervalSeconds := st.DefaultIntervalSeconds
|
||||
if cmd.IntervalSeconds != nil {
|
||||
intervalSeconds = *cmd.IntervalSeconds
|
||||
}
|
||||
|
||||
var initialVersion int64 = 1
|
||||
|
||||
uid, err := generateNewAlertDefinitionUID(sess, cmd.OrgID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate UID for alert definition %q: %w", cmd.Title, err)
|
||||
}
|
||||
|
||||
alertDefinition := &models.AlertDefinition{
|
||||
OrgID: cmd.OrgID,
|
||||
Title: cmd.Title,
|
||||
Condition: cmd.Condition,
|
||||
Data: cmd.Data,
|
||||
IntervalSeconds: intervalSeconds,
|
||||
Version: initialVersion,
|
||||
UID: uid,
|
||||
}
|
||||
|
||||
if err := st.ValidateAlertDefinition(alertDefinition, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := alertDefinition.PreSave(TimeNow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(alertDefinition); err != nil {
|
||||
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) && strings.Contains(err.Error(), "title") {
|
||||
return fmt.Errorf("an alert definition with the title '%s' already exists: %w", cmd.Title, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
alertDefVersion := models.AlertDefinitionVersion{
|
||||
AlertDefinitionID: alertDefinition.ID,
|
||||
AlertDefinitionUID: alertDefinition.UID,
|
||||
Version: alertDefinition.Version,
|
||||
Created: alertDefinition.Updated,
|
||||
Condition: alertDefinition.Condition,
|
||||
Title: alertDefinition.Title,
|
||||
Data: alertDefinition.Data,
|
||||
IntervalSeconds: alertDefinition.IntervalSeconds,
|
||||
}
|
||||
if _, err := sess.Insert(alertDefVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = alertDefinition
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAlertDefinition is a handler for updating an existing alert definition.
|
||||
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||
func (st DBstore) UpdateAlertDefinition(cmd *models.UpdateAlertDefinitionCommand) error {
|
||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
existingAlertDefinition, err := getAlertDefinitionByUID(sess, cmd.UID, cmd.OrgID)
|
||||
if err != nil {
|
||||
if errors.Is(err, models.ErrAlertDefinitionNotFound) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
title := cmd.Title
|
||||
if title == "" {
|
||||
title = existingAlertDefinition.Title
|
||||
}
|
||||
condition := cmd.Condition
|
||||
if condition == "" {
|
||||
condition = existingAlertDefinition.Condition
|
||||
}
|
||||
data := cmd.Data
|
||||
if data == nil {
|
||||
data = existingAlertDefinition.Data
|
||||
}
|
||||
intervalSeconds := cmd.IntervalSeconds
|
||||
if intervalSeconds == nil {
|
||||
intervalSeconds = &existingAlertDefinition.IntervalSeconds
|
||||
}
|
||||
|
||||
// explicitly set all fields regardless of being provided or not
|
||||
alertDefinition := &models.AlertDefinition{
|
||||
ID: existingAlertDefinition.ID,
|
||||
Title: title,
|
||||
Condition: condition,
|
||||
Data: data,
|
||||
OrgID: existingAlertDefinition.OrgID,
|
||||
IntervalSeconds: *intervalSeconds,
|
||||
UID: existingAlertDefinition.UID,
|
||||
}
|
||||
|
||||
if err := st.ValidateAlertDefinition(alertDefinition, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := alertDefinition.PreSave(TimeNow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
alertDefinition.Version = existingAlertDefinition.Version + 1
|
||||
|
||||
_, err = sess.ID(existingAlertDefinition.ID).Update(alertDefinition)
|
||||
if err != nil {
|
||||
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) && strings.Contains(err.Error(), "title") {
|
||||
return fmt.Errorf("an alert definition with the title '%s' already exists: %w", cmd.Title, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
alertDefVersion := models.AlertDefinitionVersion{
|
||||
AlertDefinitionID: alertDefinition.ID,
|
||||
AlertDefinitionUID: alertDefinition.UID,
|
||||
ParentVersion: alertDefinition.Version,
|
||||
Version: alertDefinition.Version,
|
||||
Condition: alertDefinition.Condition,
|
||||
Created: alertDefinition.Updated,
|
||||
Title: alertDefinition.Title,
|
||||
Data: alertDefinition.Data,
|
||||
IntervalSeconds: alertDefinition.IntervalSeconds,
|
||||
}
|
||||
if _, err := sess.Insert(alertDefVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = alertDefinition
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrgAlertDefinitions is a handler for retrieving alert definitions of specific organisation.
|
||||
func (st DBstore) GetOrgAlertDefinitions(query *models.ListAlertDefinitionsQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
alertDefinitions := make([]*models.AlertDefinition, 0)
|
||||
q := "SELECT * FROM alert_definition WHERE org_id = ?"
|
||||
if err := sess.SQL(q, query.OrgID).Find(&alertDefinitions); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = alertDefinitions
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAlertDefinitions returns alert definition identifier, interval, version and pause state
|
||||
// that are useful for it's scheduling.
|
||||
func (st DBstore) GetAlertDefinitions(query *models.ListAlertDefinitionsQuery) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
alerts := make([]*models.AlertDefinition, 0)
|
||||
q := "SELECT uid, org_id, interval_seconds, version, paused FROM alert_definition"
|
||||
if err := sess.SQL(q).Find(&alerts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = alerts
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateAlertDefinitionPaused update the pause state of an alert definition.
|
||||
func (st DBstore) UpdateAlertDefinitionPaused(cmd *models.UpdateAlertDefinitionPausedCommand) error {
|
||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
if len(cmd.UIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
placeHolders := strings.Builder{}
|
||||
const separator = ", "
|
||||
separatorVar := separator
|
||||
params := []interface{}{cmd.Paused, cmd.OrgID}
|
||||
for i, UID := range cmd.UIDs {
|
||||
if i == len(cmd.UIDs)-1 {
|
||||
separatorVar = ""
|
||||
}
|
||||
placeHolders.WriteString(fmt.Sprintf("?%s", separatorVar))
|
||||
params = append(params, UID)
|
||||
}
|
||||
sql := fmt.Sprintf("UPDATE alert_definition SET paused = ? WHERE org_id = ? AND uid IN (%s)", placeHolders.String())
|
||||
|
||||
// prepend sql statement to params
|
||||
var i interface{}
|
||||
params = append(params, i)
|
||||
copy(params[1:], params[0:])
|
||||
params[0] = sql
|
||||
|
||||
res, err := sess.Exec(params...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resultCount, err := res.RowsAffected(); err == nil {
|
||||
cmd.ResultCount = resultCount
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func generateNewAlertDefinitionUID(sess *sqlstore.DBSession, orgID int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&models.AlertDefinition{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", models.ErrAlertDefinitionFailedGenerateUniqueUID
|
||||
}
|
||||
|
||||
// ValidateAlertDefinition validates the alert definition interval and organisation.
|
||||
// If requireData is true checks that it contains at least one alert query
|
||||
func (st DBstore) ValidateAlertDefinition(alertDefinition *models.AlertDefinition, requireData bool) error {
|
||||
if !requireData && len(alertDefinition.Data) == 0 {
|
||||
return fmt.Errorf("no queries or expressions are found")
|
||||
}
|
||||
|
||||
if alertDefinition.Title == "" {
|
||||
return fmt.Errorf("title is empty")
|
||||
}
|
||||
|
||||
if alertDefinition.IntervalSeconds%int64(st.BaseInterval.Seconds()) != 0 {
|
||||
return fmt.Errorf("invalid interval: %v: interval should be divided exactly by scheduler interval: %v", time.Duration(alertDefinition.IntervalSeconds)*time.Second, st.BaseInterval)
|
||||
}
|
||||
|
||||
// enfore max name length in SQLite
|
||||
if len(alertDefinition.Title) > AlertDefinitionMaxTitleLength {
|
||||
return fmt.Errorf("name length should not be greater than %d", AlertDefinitionMaxTitleLength)
|
||||
}
|
||||
|
||||
if alertDefinition.OrgID == 0 {
|
||||
return fmt.Errorf("no organisation is found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,450 +0,0 @@
|
||||
// +build integration
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const baseIntervalSeconds = 10
|
||||
|
||||
func mockTimeNow() {
|
||||
var timeSeed int64
|
||||
store.TimeNow = func() time.Time {
|
||||
fakeNow := time.Unix(timeSeed, 0).UTC()
|
||||
timeSeed++
|
||||
return fakeNow
|
||||
}
|
||||
}
|
||||
|
||||
func resetTimeNow() {
|
||||
store.TimeNow = time.Now
|
||||
}
|
||||
|
||||
func TestCreatingAlertDefinition(t *testing.T) {
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
var customIntervalSeconds int64 = 120
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inputIntervalSeconds *int64
|
||||
inputTitle string
|
||||
expectedError error
|
||||
expectedInterval int64
|
||||
|
||||
expectedUpdated time.Time
|
||||
}{
|
||||
{
|
||||
desc: "should create successfully an alert definition with default interval",
|
||||
inputIntervalSeconds: nil,
|
||||
inputTitle: "a name",
|
||||
expectedInterval: dbstore.DefaultIntervalSeconds,
|
||||
expectedUpdated: time.Unix(0, 0).UTC(),
|
||||
},
|
||||
{
|
||||
desc: "should create successfully an alert definition with custom interval",
|
||||
inputIntervalSeconds: &customIntervalSeconds,
|
||||
inputTitle: "another name",
|
||||
expectedInterval: customIntervalSeconds,
|
||||
expectedUpdated: time.Unix(1, 0).UTC(),
|
||||
},
|
||||
{
|
||||
desc: "should fail to create an alert definition with too big name",
|
||||
inputIntervalSeconds: &customIntervalSeconds,
|
||||
inputTitle: getLongString(store.AlertDefinitionMaxTitleLength + 1),
|
||||
expectedError: errors.New(""),
|
||||
},
|
||||
{
|
||||
desc: "should fail to create an alert definition with empty title",
|
||||
inputIntervalSeconds: &customIntervalSeconds,
|
||||
inputTitle: "",
|
||||
expectedError: fmt.Errorf("title is empty"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
|
||||
q := models.SaveAlertDefinitionCommand{
|
||||
OrgID: 1,
|
||||
Title: tc.inputTitle,
|
||||
Condition: "B",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 3 > 1"
|
||||
}`),
|
||||
RefID: "B",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(time.Duration(5) * time.Hour),
|
||||
To: models.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if tc.inputIntervalSeconds != nil {
|
||||
q.IntervalSeconds = tc.inputIntervalSeconds
|
||||
}
|
||||
err := dbstore.SaveAlertDefinition(&q)
|
||||
switch {
|
||||
case tc.expectedError != nil:
|
||||
require.Error(t, err)
|
||||
default:
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedUpdated, q.Result.Updated)
|
||||
assert.Equal(t, tc.expectedInterval, q.Result.IntervalSeconds)
|
||||
assert.Equal(t, int64(1), q.Result.Version)
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestCreatingConflictionAlertDefinition(t *testing.T) {
|
||||
t.Run("Should fail to create alert definition with conflicting org_id, title", func(t *testing.T) {
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
q := models.SaveAlertDefinitionCommand{
|
||||
OrgID: 1,
|
||||
Title: "title",
|
||||
Condition: "B",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 3 > 1"
|
||||
}`),
|
||||
RefID: "B",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(time.Duration(5) * time.Hour),
|
||||
To: models.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := dbstore.SaveAlertDefinition(&q)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = dbstore.SaveAlertDefinition(&q)
|
||||
require.Error(t, err)
|
||||
assert.True(t, dbstore.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdatingAlertDefinition(t *testing.T) {
|
||||
t.Run("zero rows affected when updating unknown alert", func(t *testing.T) {
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
q := models.UpdateAlertDefinitionCommand{
|
||||
UID: "unknown",
|
||||
OrgID: 1,
|
||||
Title: "something completely different",
|
||||
Condition: "A",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 2 > 1"
|
||||
}`),
|
||||
RefID: "A",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(time.Duration(5) * time.Hour),
|
||||
To: models.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := dbstore.UpdateAlertDefinition(&q)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("updating existing alert", func(t *testing.T) {
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
var initialInterval int64 = 120
|
||||
alertDefinition := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||
created := alertDefinition.Updated
|
||||
|
||||
var customInterval int64 = 30
|
||||
testCases := []struct {
|
||||
desc string
|
||||
inputOrgID int64
|
||||
inputTitle string
|
||||
inputInterval *int64
|
||||
expectedError error
|
||||
expectedIntervalSeconds int64
|
||||
expectedUpdated time.Time
|
||||
expectedTitle string
|
||||
}{
|
||||
{
|
||||
desc: "should not update previous interval if it's not provided",
|
||||
inputInterval: nil,
|
||||
inputOrgID: alertDefinition.OrgID,
|
||||
inputTitle: "something completely different",
|
||||
expectedIntervalSeconds: initialInterval,
|
||||
expectedUpdated: time.Unix(1, 0).UTC(),
|
||||
expectedTitle: "something completely different",
|
||||
},
|
||||
{
|
||||
desc: "should update interval if it's provided",
|
||||
inputInterval: &customInterval,
|
||||
inputOrgID: alertDefinition.OrgID,
|
||||
inputTitle: "something completely different",
|
||||
expectedIntervalSeconds: customInterval,
|
||||
expectedUpdated: time.Unix(2, 0).UTC(),
|
||||
expectedTitle: "something completely different",
|
||||
},
|
||||
{
|
||||
desc: "should not update organisation if it's provided",
|
||||
inputInterval: &customInterval,
|
||||
inputOrgID: 0,
|
||||
inputTitle: "something completely different",
|
||||
expectedIntervalSeconds: customInterval,
|
||||
expectedUpdated: time.Unix(3, 0).UTC(),
|
||||
expectedTitle: "something completely different",
|
||||
},
|
||||
{
|
||||
desc: "should not update alert definition if the title it's too big",
|
||||
inputInterval: &customInterval,
|
||||
inputOrgID: 0,
|
||||
inputTitle: getLongString(store.AlertDefinitionMaxTitleLength + 1),
|
||||
expectedError: errors.New(""),
|
||||
},
|
||||
{
|
||||
desc: "should not update alert definition title if the title is empty",
|
||||
inputInterval: &customInterval,
|
||||
inputOrgID: 0,
|
||||
inputTitle: "",
|
||||
expectedIntervalSeconds: customInterval,
|
||||
expectedUpdated: time.Unix(4, 0).UTC(),
|
||||
expectedTitle: "something completely different",
|
||||
},
|
||||
}
|
||||
|
||||
q := models.UpdateAlertDefinitionCommand{
|
||||
UID: (*alertDefinition).UID,
|
||||
Condition: "B",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 3 > 1"
|
||||
}`),
|
||||
RefID: "B",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(5 * time.Hour),
|
||||
To: models.Duration(3 * time.Hour),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
lastUpdated := created
|
||||
previousAlertDefinition := alertDefinition
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
if tc.inputInterval != nil {
|
||||
q.IntervalSeconds = tc.inputInterval
|
||||
}
|
||||
if tc.inputOrgID != 0 {
|
||||
q.OrgID = tc.inputOrgID
|
||||
}
|
||||
q.Title = tc.inputTitle
|
||||
err := dbstore.UpdateAlertDefinition(&q)
|
||||
switch {
|
||||
case tc.expectedError != nil:
|
||||
require.Error(t, err)
|
||||
|
||||
assert.Equal(t, previousAlertDefinition.Title, q.Result.Title)
|
||||
assert.Equal(t, previousAlertDefinition.Condition, q.Result.Condition)
|
||||
assert.Equal(t, len(previousAlertDefinition.Data), len(q.Result.Data))
|
||||
assert.Equal(t, previousAlertDefinition.IntervalSeconds, q.Result.IntervalSeconds)
|
||||
assert.Equal(t, previousAlertDefinition.Updated, q.Result.Updated)
|
||||
assert.Equal(t, previousAlertDefinition.Version, q.Result.Version)
|
||||
assert.Equal(t, previousAlertDefinition.OrgID, q.Result.OrgID)
|
||||
assert.Equal(t, previousAlertDefinition.UID, q.Result.UID)
|
||||
default:
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, previousAlertDefinition.ID, q.Result.ID)
|
||||
assert.Equal(t, previousAlertDefinition.UID, q.Result.UID)
|
||||
assert.True(t, q.Result.Updated.After(lastUpdated))
|
||||
assert.Equal(t, tc.expectedUpdated, q.Result.Updated)
|
||||
assert.Equal(t, previousAlertDefinition.Version+1, q.Result.Version)
|
||||
|
||||
assert.Equal(t, alertDefinition.OrgID, q.Result.OrgID)
|
||||
|
||||
assert.Equal(t, "something completely different", q.Result.Title)
|
||||
assert.Equal(t, "B", q.Result.Condition)
|
||||
assert.Equal(t, 1, len(q.Result.Data))
|
||||
assert.Equal(t, tc.expectedUpdated, q.Result.Updated)
|
||||
assert.Equal(t, tc.expectedIntervalSeconds, q.Result.IntervalSeconds)
|
||||
assert.Equal(t, previousAlertDefinition.Version+1, q.Result.Version)
|
||||
assert.Equal(t, alertDefinition.OrgID, q.Result.OrgID)
|
||||
assert.Equal(t, alertDefinition.UID, q.Result.UID)
|
||||
|
||||
previousAlertDefinition = q.Result
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdatingConflictingAlertDefinition(t *testing.T) {
|
||||
t.Run("should fail to update alert definition with reserved title", func(t *testing.T) {
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
var initialInterval int64 = 120
|
||||
alertDef1 := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||
alertDef2 := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||
|
||||
q := models.UpdateAlertDefinitionCommand{
|
||||
UID: (*alertDef2).UID,
|
||||
Title: alertDef1.Title,
|
||||
Condition: "B",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 3 > 1"
|
||||
}`),
|
||||
RefID: "B",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(5 * time.Hour),
|
||||
To: models.Duration(3 * time.Hour),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := dbstore.UpdateAlertDefinition(&q)
|
||||
require.Error(t, err)
|
||||
assert.True(t, dbstore.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeletingAlertDefinition(t *testing.T) {
|
||||
t.Run("zero rows affected when deleting unknown alert", func(t *testing.T) {
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
q := models.DeleteAlertDefinitionByUIDCommand{
|
||||
UID: "unknown",
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
err := dbstore.DeleteAlertDefinitionByUID(&q)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("deleting successfully existing alert", func(t *testing.T) {
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
t.Cleanup(registry.ClearOverrides)
|
||||
|
||||
alertDefinition := createTestAlertDefinition(t, dbstore, 60)
|
||||
|
||||
q := models.DeleteAlertDefinitionByUIDCommand{
|
||||
UID: (*alertDefinition).UID,
|
||||
OrgID: 1,
|
||||
}
|
||||
|
||||
// save an instance for the definition
|
||||
saveCmd := &models.SaveAlertInstanceCommand{
|
||||
DefinitionOrgID: alertDefinition.OrgID,
|
||||
DefinitionUID: alertDefinition.UID,
|
||||
State: models.InstanceStateFiring,
|
||||
Labels: models.InstanceLabels{"test": "testValue"},
|
||||
}
|
||||
err := dbstore.SaveAlertInstance(saveCmd)
|
||||
require.NoError(t, err)
|
||||
listQuery := &models.ListAlertInstancesQuery{
|
||||
DefinitionOrgID: alertDefinition.OrgID,
|
||||
DefinitionUID: alertDefinition.UID,
|
||||
}
|
||||
err = dbstore.ListAlertInstances(listQuery)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, listQuery.Result, 1)
|
||||
|
||||
err = dbstore.DeleteAlertDefinitionByUID(&q)
|
||||
require.NoError(t, err)
|
||||
|
||||
// assert that alert instance is deleted
|
||||
err = dbstore.ListAlertInstances(listQuery)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, listQuery.Result, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func getLongString(n int) string {
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = 'a'
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// createTestAlertDefinition creates a dummy alert definition to be used by the tests.
|
||||
func createTestAlertDefinition(t *testing.T, dbstore *store.DBstore, intervalSeconds int64) *models.AlertDefinition {
|
||||
cmd := models.SaveAlertDefinitionCommand{
|
||||
OrgID: 1,
|
||||
Title: fmt.Sprintf("an alert definition %d", rand.Intn(1000)),
|
||||
Condition: "A",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
Model: json.RawMessage(`{
|
||||
"datasourceUid": "-100",
|
||||
"type":"math",
|
||||
"expression":"2 + 2 > 1"
|
||||
}`),
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(5 * time.Hour),
|
||||
To: models.Duration(3 * time.Hour),
|
||||
},
|
||||
RefID: "A",
|
||||
},
|
||||
},
|
||||
IntervalSeconds: &intervalSeconds,
|
||||
}
|
||||
err := dbstore.SaveAlertDefinition(&cmd)
|
||||
require.NoError(t, err)
|
||||
t.Logf("alert definition: %v with interval: %d created", cmd.Result.GetKey(), intervalSeconds)
|
||||
return cmd.Result
|
||||
}
|
@ -4,31 +4,44 @@ package tests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const baseIntervalSeconds = 10
|
||||
|
||||
func mockTimeNow() {
|
||||
var timeSeed int64
|
||||
store.TimeNow = func() time.Time {
|
||||
fakeNow := time.Unix(timeSeed, 0).UTC()
|
||||
timeSeed++
|
||||
return fakeNow
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertInstanceOperations(t *testing.T) {
|
||||
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||
|
||||
alertDefinition1 := createTestAlertDefinition(t, dbstore, 60)
|
||||
orgID := alertDefinition1.OrgID
|
||||
alertRule1 := createTestAlertRule(t, dbstore, 60)
|
||||
orgID := alertRule1.OrgID
|
||||
|
||||
alertDefinition2 := createTestAlertDefinition(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertDefinition2.OrgID)
|
||||
alertRule2 := createTestAlertRule(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertRule2.OrgID)
|
||||
|
||||
alertDefinition3 := createTestAlertDefinition(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertDefinition3.OrgID)
|
||||
alertRule3 := createTestAlertRule(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertRule3.OrgID)
|
||||
|
||||
alertDefinition4 := createTestAlertDefinition(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertDefinition4.OrgID)
|
||||
alertRule4 := createTestAlertRule(t, dbstore, 60)
|
||||
require.Equal(t, orgID, alertRule4.OrgID)
|
||||
|
||||
t.Run("can save and read new alert instance", func(t *testing.T) {
|
||||
saveCmd := &models.SaveAlertInstanceCommand{
|
||||
DefinitionOrgID: alertDefinition1.OrgID,
|
||||
DefinitionUID: alertDefinition1.UID,
|
||||
DefinitionOrgID: alertRule1.OrgID,
|
||||
DefinitionUID: alertRule1.UID,
|
||||
State: models.InstanceStateFiring,
|
||||
Labels: models.InstanceLabels{"test": "testValue"},
|
||||
}
|
||||
@ -45,14 +58,14 @@ func TestAlertInstanceOperations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, saveCmd.Labels, getCmd.Result.Labels)
|
||||
require.Equal(t, alertDefinition1.OrgID, getCmd.Result.DefinitionOrgID)
|
||||
require.Equal(t, alertDefinition1.UID, getCmd.Result.DefinitionUID)
|
||||
require.Equal(t, alertRule1.OrgID, getCmd.Result.DefinitionOrgID)
|
||||
require.Equal(t, alertRule1.UID, getCmd.Result.DefinitionUID)
|
||||
})
|
||||
|
||||
t.Run("can save and read new alert instance with no labels", func(t *testing.T) {
|
||||
saveCmd := &models.SaveAlertInstanceCommand{
|
||||
DefinitionOrgID: alertDefinition2.OrgID,
|
||||
DefinitionUID: alertDefinition2.UID,
|
||||
DefinitionOrgID: alertRule2.OrgID,
|
||||
DefinitionUID: alertRule2.UID,
|
||||
State: models.InstanceStateNormal,
|
||||
Labels: models.InstanceLabels{},
|
||||
}
|
||||
@ -67,15 +80,15 @@ func TestAlertInstanceOperations(t *testing.T) {
|
||||
err = dbstore.GetAlertInstance(getCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, alertDefinition2.OrgID, getCmd.Result.DefinitionOrgID)
|
||||
require.Equal(t, alertDefinition2.UID, getCmd.Result.DefinitionUID)
|
||||
require.Equal(t, alertRule2.OrgID, getCmd.Result.DefinitionOrgID)
|
||||
require.Equal(t, alertRule2.UID, getCmd.Result.DefinitionUID)
|
||||
require.Equal(t, saveCmd.Labels, getCmd.Result.Labels)
|
||||
})
|
||||
|
||||
t.Run("can save two instances with same org_id, uid and different labels", func(t *testing.T) {
|
||||
saveCmdOne := &models.SaveAlertInstanceCommand{
|
||||
DefinitionOrgID: alertDefinition3.OrgID,
|
||||
DefinitionUID: alertDefinition3.UID,
|
||||
DefinitionOrgID: alertRule3.OrgID,
|
||||
DefinitionUID: alertRule3.UID,
|
||||
State: models.InstanceStateFiring,
|
||||
Labels: models.InstanceLabels{"test": "testValue"},
|
||||
}
|
||||
@ -128,8 +141,8 @@ func TestAlertInstanceOperations(t *testing.T) {
|
||||
|
||||
t.Run("update instance with same org_id, uid and different labels", func(t *testing.T) {
|
||||
saveCmdOne := &models.SaveAlertInstanceCommand{
|
||||
DefinitionOrgID: alertDefinition4.OrgID,
|
||||
DefinitionUID: alertDefinition4.UID,
|
||||
DefinitionOrgID: alertRule4.OrgID,
|
||||
DefinitionUID: alertRule4.UID,
|
||||
State: models.InstanceStateFiring,
|
||||
Labels: models.InstanceLabels{"test": "testValue"},
|
||||
}
|
||||
@ -147,8 +160,8 @@ func TestAlertInstanceOperations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
listQuery := &models.ListAlertInstancesQuery{
|
||||
DefinitionOrgID: alertDefinition4.OrgID,
|
||||
DefinitionUID: alertDefinition4.UID,
|
||||
DefinitionOrgID: alertRule4.OrgID,
|
||||
DefinitionUID: alertRule4.UID,
|
||||
}
|
||||
|
||||
err = dbstore.ListAlertInstances(listQuery)
|
||||
@ -160,7 +173,5 @@ func TestAlertInstanceOperations(t *testing.T) {
|
||||
require.Equal(t, saveCmdTwo.DefinitionUID, listQuery.Result[0].DefinitionUID)
|
||||
require.Equal(t, saveCmdTwo.Labels, listQuery.Result[0].Labels)
|
||||
require.Equal(t, saveCmdTwo.State, listQuery.Result[0].CurrentState)
|
||||
require.NotEmpty(t, listQuery.Result[0].DefinitionTitle)
|
||||
require.Equal(t, alertDefinition4.Title, listQuery.Result[0].DefinitionTitle)
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user