mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AlertingNG: Split into several packages (#31719)
* AlertingNG: Split into several packages * Move AlertQuery to models
This commit is contained in:
parent
124ef813ab
commit
4ce0a49eac
1
go.mod
1
go.mod
@ -68,6 +68,7 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.9.0
|
github.com/prometheus/client_golang v1.9.0
|
||||||
github.com/prometheus/client_model v0.2.0
|
github.com/prometheus/client_model v0.2.0
|
||||||
github.com/prometheus/common v0.18.0
|
github.com/prometheus/common v0.18.0
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3 // indirect
|
||||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/goxmldsig v1.1.0
|
github.com/russellhaering/goxmldsig v1.1.0
|
||||||
|
4
go.sum
4
go.sum
@ -1303,6 +1303,10 @@ github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4 h1:54z99l8
|
|||||||
github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY=
|
github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY=
|
||||||
github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw=
|
github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw=
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/quasilyte/go-ruleguard v0.3.1 h1:2KTXnHBCR4BUl8UAL2bCUorOBGC8RsmYncuDA9NEFW4=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl v0.3.1 h1:CHGOKP2LDz35P49TjW4Bx4BCfFI6ZZU/8zcneECD0q4=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3 h1:eL7x4/zMnlquMxYe7V078BD7MGskZ0daGln+SJCVzuY=
|
||||||
|
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3/go.mod h1:P7JlQWFT7jDcFZMtUPQbtGzzzxva3rBn6oIF+LPwFcM=
|
||||||
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
|
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// timeNow makes it possible to test usage of time
|
|
||||||
var timeNow = time.Now
|
|
||||||
|
|
||||||
// preSave sets datasource and loads the updated model for each alert query.
|
|
||||||
func (alertDefinition *AlertDefinition) preSave() 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
|
|
||||||
}
|
|
@ -1,7 +1,13 @@
|
|||||||
package ngalert
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
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/go-macaron/binding"
|
"github.com/go-macaron/binding"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
@ -17,26 +23,31 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiImpl struct {
|
// timeNow makes it possible to test usage of time
|
||||||
Cfg *setting.Cfg `inject:""`
|
var timeNow = time.Now
|
||||||
DatasourceCache datasources.CacheService `inject:""`
|
|
||||||
RouteRegister routing.RouteRegister `inject:""`
|
// API handlers.
|
||||||
|
type API struct {
|
||||||
|
Cfg *setting.Cfg
|
||||||
|
DatasourceCache datasources.CacheService
|
||||||
|
RouteRegister routing.RouteRegister
|
||||||
DataService *tsdb.Service
|
DataService *tsdb.Service
|
||||||
schedule scheduleService
|
Schedule schedule.ScheduleService
|
||||||
store store
|
Store store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiImpl) registerAPIEndpoints() {
|
// RegisterAPIEndpoints registers API handlers
|
||||||
|
func (api *API) RegisterAPIEndpoints() {
|
||||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||||
alertDefinitions.Get("", middleware.ReqSignedIn, routing.Wrap(api.listAlertDefinitions))
|
alertDefinitions.Get("", middleware.ReqSignedIn, routing.Wrap(api.listAlertDefinitions))
|
||||||
alertDefinitions.Get("/eval/:alertDefinitionUID", middleware.ReqSignedIn, api.validateOrgAlertDefinition, routing.Wrap(api.alertDefinitionEvalEndpoint))
|
alertDefinitions.Get("/eval/:alertDefinitionUID", middleware.ReqSignedIn, api.validateOrgAlertDefinition, routing.Wrap(api.alertDefinitionEvalEndpoint))
|
||||||
alertDefinitions.Post("/eval", middleware.ReqSignedIn, binding.Bind(evalAlertConditionCommand{}), routing.Wrap(api.conditionEvalEndpoint))
|
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.Get("/:alertDefinitionUID", middleware.ReqSignedIn, api.validateOrgAlertDefinition, routing.Wrap(api.getAlertDefinitionEndpoint))
|
||||||
alertDefinitions.Delete("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, routing.Wrap(api.deleteAlertDefinitionEndpoint))
|
alertDefinitions.Delete("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, routing.Wrap(api.deleteAlertDefinitionEndpoint))
|
||||||
alertDefinitions.Post("/", middleware.ReqEditorRole, binding.Bind(saveAlertDefinitionCommand{}), routing.Wrap(api.createAlertDefinitionEndpoint))
|
alertDefinitions.Post("/", middleware.ReqEditorRole, binding.Bind(ngmodels.SaveAlertDefinitionCommand{}), routing.Wrap(api.createAlertDefinitionEndpoint))
|
||||||
alertDefinitions.Put("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, binding.Bind(updateAlertDefinitionCommand{}), routing.Wrap(api.updateAlertDefinitionEndpoint))
|
alertDefinitions.Put("/:alertDefinitionUID", middleware.ReqEditorRole, api.validateOrgAlertDefinition, binding.Bind(ngmodels.UpdateAlertDefinitionCommand{}), routing.Wrap(api.updateAlertDefinitionEndpoint))
|
||||||
alertDefinitions.Post("/pause", middleware.ReqEditorRole, binding.Bind(updateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionPauseEndpoint))
|
alertDefinitions.Post("/pause", middleware.ReqEditorRole, binding.Bind(ngmodels.UpdateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionPauseEndpoint))
|
||||||
alertDefinitions.Post("/unpause", middleware.ReqEditorRole, binding.Bind(updateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionUnpauseEndpoint))
|
alertDefinitions.Post("/unpause", middleware.ReqEditorRole, binding.Bind(ngmodels.UpdateAlertDefinitionPausedCommand{}), routing.Wrap(api.alertDefinitionUnpauseEndpoint))
|
||||||
})
|
})
|
||||||
|
|
||||||
api.RouteRegister.Group("/api/ngalert/", func(schedulerRouter routing.RouteRegister) {
|
api.RouteRegister.Group("/api/ngalert/", func(schedulerRouter routing.RouteRegister) {
|
||||||
@ -50,7 +61,7 @@ func (api *apiImpl) registerAPIEndpoints() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// conditionEvalEndpoint handles POST /api/alert-definitions/eval.
|
// conditionEvalEndpoint handles POST /api/alert-definitions/eval.
|
||||||
func (api *apiImpl) conditionEvalEndpoint(c *models.ReqContext, cmd evalAlertConditionCommand) response.Response {
|
func (api *API) conditionEvalEndpoint(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand) response.Response {
|
||||||
evalCond := eval.Condition{
|
evalCond := eval.Condition{
|
||||||
RefID: cmd.Condition,
|
RefID: cmd.Condition,
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
@ -84,7 +95,7 @@ func (api *apiImpl) conditionEvalEndpoint(c *models.ReqContext, cmd evalAlertCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// alertDefinitionEvalEndpoint handles GET /api/alert-definitions/eval/:alertDefinitionUID.
|
// alertDefinitionEvalEndpoint handles GET /api/alert-definitions/eval/:alertDefinitionUID.
|
||||||
func (api *apiImpl) alertDefinitionEvalEndpoint(c *models.ReqContext) response.Response {
|
func (api *API) alertDefinitionEvalEndpoint(c *models.ReqContext) response.Response {
|
||||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||||
|
|
||||||
condition, err := api.LoadAlertCondition(alertDefinitionUID, c.SignedInUser.OrgId)
|
condition, err := api.LoadAlertCondition(alertDefinitionUID, c.SignedInUser.OrgId)
|
||||||
@ -118,15 +129,15 @@ func (api *apiImpl) alertDefinitionEvalEndpoint(c *models.ReqContext) response.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getAlertDefinitionEndpoint handles GET /api/alert-definitions/:alertDefinitionUID.
|
// getAlertDefinitionEndpoint handles GET /api/alert-definitions/:alertDefinitionUID.
|
||||||
func (api *apiImpl) getAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
func (api *API) getAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
||||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||||
|
|
||||||
query := getAlertDefinitionByUIDQuery{
|
query := ngmodels.GetAlertDefinitionByUIDQuery{
|
||||||
UID: alertDefinitionUID,
|
UID: alertDefinitionUID,
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.store.getAlertDefinitionByUID(&query); err != nil {
|
if err := api.Store.GetAlertDefinitionByUID(&query); err != nil {
|
||||||
return response.Error(500, "Failed to get alert definition", err)
|
return response.Error(500, "Failed to get alert definition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,15 +145,15 @@ func (api *apiImpl) getAlertDefinitionEndpoint(c *models.ReqContext) response.Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deleteAlertDefinitionEndpoint handles DELETE /api/alert-definitions/:alertDefinitionUID.
|
// deleteAlertDefinitionEndpoint handles DELETE /api/alert-definitions/:alertDefinitionUID.
|
||||||
func (api *apiImpl) deleteAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
func (api *API) deleteAlertDefinitionEndpoint(c *models.ReqContext) response.Response {
|
||||||
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
alertDefinitionUID := c.Params(":alertDefinitionUID")
|
||||||
|
|
||||||
cmd := deleteAlertDefinitionByUIDCommand{
|
cmd := ngmodels.DeleteAlertDefinitionByUIDCommand{
|
||||||
UID: alertDefinitionUID,
|
UID: alertDefinitionUID,
|
||||||
OrgID: c.SignedInUser.OrgId,
|
OrgID: c.SignedInUser.OrgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.store.deleteAlertDefinitionByUID(&cmd); err != nil {
|
if err := api.Store.DeleteAlertDefinitionByUID(&cmd); err != nil {
|
||||||
return response.Error(500, "Failed to delete alert definition", err)
|
return response.Error(500, "Failed to delete alert definition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,7 +161,7 @@ func (api *apiImpl) deleteAlertDefinitionEndpoint(c *models.ReqContext) response
|
|||||||
}
|
}
|
||||||
|
|
||||||
// updateAlertDefinitionEndpoint handles PUT /api/alert-definitions/:alertDefinitionUID.
|
// updateAlertDefinitionEndpoint handles PUT /api/alert-definitions/:alertDefinitionUID.
|
||||||
func (api *apiImpl) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd updateAlertDefinitionCommand) response.Response {
|
func (api *API) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionCommand) response.Response {
|
||||||
cmd.UID = c.Params(":alertDefinitionUID")
|
cmd.UID = c.Params(":alertDefinitionUID")
|
||||||
cmd.OrgID = c.SignedInUser.OrgId
|
cmd.OrgID = c.SignedInUser.OrgId
|
||||||
|
|
||||||
@ -163,7 +174,7 @@ func (api *apiImpl) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd upda
|
|||||||
return response.Error(400, "invalid condition", err)
|
return response.Error(400, "invalid condition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.store.updateAlertDefinition(&cmd); err != nil {
|
if err := api.Store.UpdateAlertDefinition(&cmd); err != nil {
|
||||||
return response.Error(500, "Failed to update alert definition", err)
|
return response.Error(500, "Failed to update alert definition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +182,7 @@ func (api *apiImpl) updateAlertDefinitionEndpoint(c *models.ReqContext, cmd upda
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createAlertDefinitionEndpoint handles POST /api/alert-definitions.
|
// createAlertDefinitionEndpoint handles POST /api/alert-definitions.
|
||||||
func (api *apiImpl) createAlertDefinitionEndpoint(c *models.ReqContext, cmd saveAlertDefinitionCommand) response.Response {
|
func (api *API) createAlertDefinitionEndpoint(c *models.ReqContext, cmd ngmodels.SaveAlertDefinitionCommand) response.Response {
|
||||||
cmd.OrgID = c.SignedInUser.OrgId
|
cmd.OrgID = c.SignedInUser.OrgId
|
||||||
|
|
||||||
evalCond := eval.Condition{
|
evalCond := eval.Condition{
|
||||||
@ -183,7 +194,7 @@ func (api *apiImpl) createAlertDefinitionEndpoint(c *models.ReqContext, cmd save
|
|||||||
return response.Error(400, "invalid condition", err)
|
return response.Error(400, "invalid condition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := api.store.saveAlertDefinition(&cmd); err != nil {
|
if err := api.Store.SaveAlertDefinition(&cmd); err != nil {
|
||||||
return response.Error(500, "Failed to create alert definition", err)
|
return response.Error(500, "Failed to create alert definition", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,26 +202,26 @@ func (api *apiImpl) createAlertDefinitionEndpoint(c *models.ReqContext, cmd save
|
|||||||
}
|
}
|
||||||
|
|
||||||
// listAlertDefinitions handles GET /api/alert-definitions.
|
// listAlertDefinitions handles GET /api/alert-definitions.
|
||||||
func (api *apiImpl) listAlertDefinitions(c *models.ReqContext) response.Response {
|
func (api *API) listAlertDefinitions(c *models.ReqContext) response.Response {
|
||||||
query := listAlertDefinitionsQuery{OrgID: c.SignedInUser.OrgId}
|
query := ngmodels.ListAlertDefinitionsQuery{OrgID: c.SignedInUser.OrgId}
|
||||||
|
|
||||||
if err := api.store.getOrgAlertDefinitions(&query); err != nil {
|
if err := api.Store.GetOrgAlertDefinitions(&query); err != nil {
|
||||||
return response.Error(500, "Failed to list alert definitions", err)
|
return response.Error(500, "Failed to list alert definitions", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(200, util.DynMap{"results": query.Result})
|
return response.JSON(200, util.DynMap{"results": query.Result})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiImpl) pauseScheduler() response.Response {
|
func (api *API) pauseScheduler() response.Response {
|
||||||
err := api.schedule.Pause()
|
err := api.Schedule.Pause()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(500, "Failed to pause scheduler", err)
|
return response.Error(500, "Failed to pause scheduler", err)
|
||||||
}
|
}
|
||||||
return response.JSON(200, util.DynMap{"message": "alert definition scheduler paused"})
|
return response.JSON(200, util.DynMap{"message": "alert definition scheduler paused"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiImpl) unpauseScheduler() response.Response {
|
func (api *API) unpauseScheduler() response.Response {
|
||||||
err := api.schedule.Unpause()
|
err := api.Schedule.Unpause()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(500, "Failed to unpause scheduler", err)
|
return response.Error(500, "Failed to unpause scheduler", err)
|
||||||
}
|
}
|
||||||
@ -218,11 +229,11 @@ func (api *apiImpl) unpauseScheduler() response.Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// alertDefinitionPauseEndpoint handles POST /api/alert-definitions/pause.
|
// alertDefinitionPauseEndpoint handles POST /api/alert-definitions/pause.
|
||||||
func (api *apiImpl) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd updateAlertDefinitionPausedCommand) response.Response {
|
func (api *API) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response {
|
||||||
cmd.OrgID = c.SignedInUser.OrgId
|
cmd.OrgID = c.SignedInUser.OrgId
|
||||||
cmd.Paused = true
|
cmd.Paused = true
|
||||||
|
|
||||||
err := api.store.updateAlertDefinitionPaused(&cmd)
|
err := api.Store.UpdateAlertDefinitionPaused(&cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(500, "Failed to pause alert definition", err)
|
return response.Error(500, "Failed to pause alert definition", err)
|
||||||
}
|
}
|
||||||
@ -230,13 +241,70 @@ func (api *apiImpl) alertDefinitionPauseEndpoint(c *models.ReqContext, cmd updat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// alertDefinitionUnpauseEndpoint handles POST /api/alert-definitions/unpause.
|
// alertDefinitionUnpauseEndpoint handles POST /api/alert-definitions/unpause.
|
||||||
func (api *apiImpl) alertDefinitionUnpauseEndpoint(c *models.ReqContext, cmd updateAlertDefinitionPausedCommand) response.Response {
|
func (api *API) alertDefinitionUnpauseEndpoint(c *models.ReqContext, cmd ngmodels.UpdateAlertDefinitionPausedCommand) response.Response {
|
||||||
cmd.OrgID = c.SignedInUser.OrgId
|
cmd.OrgID = c.SignedInUser.OrgId
|
||||||
cmd.Paused = false
|
cmd.Paused = false
|
||||||
|
|
||||||
err := api.store.updateAlertDefinitionPaused(&cmd)
|
err := api.Store.UpdateAlertDefinitionPaused(&cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(500, "Failed to unpause alert definition", err)
|
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)})
|
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) (*eval.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 &eval.Condition{
|
||||||
|
RefID: alertDefinition.Condition,
|
||||||
|
OrgID: alertDefinition.OrgID,
|
||||||
|
QueriesAndExpressions: alertDefinition.Data,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) validateCondition(c eval.Condition, user *models.SignedInUser, skipCache bool) error {
|
||||||
|
var refID string
|
||||||
|
|
||||||
|
if len(c.QueriesAndExpressions) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, query := range c.QueriesAndExpressions {
|
||||||
|
if c.RefID == query.RefID {
|
||||||
|
refID = c.RefID
|
||||||
|
}
|
||||||
|
|
||||||
|
datasourceUID, err := query.GetDatasource()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isExpression, err := query.IsExpression()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if isExpression {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.DatasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get datasource: %s: %w", datasourceUID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if refID == "" {
|
||||||
|
return fmt.Errorf("condition %s not found in any query or expression", c.RefID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
19
pkg/services/ngalert/api/instance_api.go
Normal file
19
pkg/services/ngalert/api/instance_api.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
23
pkg/services/ngalert/api/middleware.go
Normal file
23
pkg/services/ngalert/api/middleware.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ type Condition struct {
|
|||||||
RefID string `json:"refId"`
|
RefID string `json:"refId"`
|
||||||
OrgID int64 `json:"-"`
|
OrgID int64 `json:"-"`
|
||||||
|
|
||||||
QueriesAndExpressions []AlertQuery `json:"queriesAndExpressions"`
|
QueriesAndExpressions []models.AlertQuery `json:"queriesAndExpressions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecutionResults contains the unevaluated results from executing
|
// ExecutionResults contains the unevaluated results from executing
|
||||||
@ -117,16 +119,16 @@ func (c *Condition) execute(ctx AlertExecCtx, now time.Time, dataService *tsdb.S
|
|||||||
|
|
||||||
for i := range c.QueriesAndExpressions {
|
for i := range c.QueriesAndExpressions {
|
||||||
q := c.QueriesAndExpressions[i]
|
q := c.QueriesAndExpressions[i]
|
||||||
model, err := q.getModel()
|
model, err := q.GetModel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get query model: %w", err)
|
return nil, fmt.Errorf("failed to get query model: %w", err)
|
||||||
}
|
}
|
||||||
interval, err := q.getIntervalDuration()
|
interval, err := q.GetIntervalDuration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve intervalMs from the model: %w", err)
|
return nil, fmt.Errorf("failed to retrieve intervalMs from the model: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
maxDatapoints, err := q.getMaxDatapoints()
|
maxDatapoints, err := q.GetMaxDatapoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve maxDatapoints from the model: %w", err)
|
return nil, fmt.Errorf("failed to retrieve maxDatapoints from the model: %w", err)
|
||||||
}
|
}
|
||||||
@ -137,7 +139,7 @@ func (c *Condition) execute(ctx AlertExecCtx, now time.Time, dataService *tsdb.S
|
|||||||
RefID: q.RefID,
|
RefID: q.RefID,
|
||||||
MaxDataPoints: maxDatapoints,
|
MaxDataPoints: maxDatapoints,
|
||||||
QueryType: q.QueryType,
|
QueryType: q.QueryType,
|
||||||
TimeRange: q.RelativeTimeRange.toTimeRange(now),
|
TimeRange: q.RelativeTimeRange.ToTimeRange(now),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (sch *schedule) fetchAllDetails(now time.Time) []*AlertDefinition {
|
|
||||||
q := listAlertDefinitionsQuery{}
|
|
||||||
err := sch.store.getAlertDefinitions(&q)
|
|
||||||
if err != nil {
|
|
||||||
sch.log.Error("failed to fetch alert definitions", "now", now, "err", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return q.Result
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// listAlertInstancesEndpoint handles GET /api/alert-instances.
|
|
||||||
func (api *apiImpl) listAlertInstancesEndpoint(c *models.ReqContext) response.Response {
|
|
||||||
cmd := 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)
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
// +build integration
|
|
||||||
|
|
||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAlertInstanceOperations(t *testing.T) {
|
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
|
||||||
|
|
||||||
alertDefinition1 := createTestAlertDefinition(t, store, 60)
|
|
||||||
orgID := alertDefinition1.OrgID
|
|
||||||
|
|
||||||
alertDefinition2 := createTestAlertDefinition(t, store, 60)
|
|
||||||
require.Equal(t, orgID, alertDefinition2.OrgID)
|
|
||||||
|
|
||||||
alertDefinition3 := createTestAlertDefinition(t, store, 60)
|
|
||||||
require.Equal(t, orgID, alertDefinition3.OrgID)
|
|
||||||
|
|
||||||
alertDefinition4 := createTestAlertDefinition(t, store, 60)
|
|
||||||
require.Equal(t, orgID, alertDefinition4.OrgID)
|
|
||||||
|
|
||||||
t.Run("can save and read new alert instance", func(t *testing.T) {
|
|
||||||
saveCmd := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: alertDefinition1.OrgID,
|
|
||||||
DefinitionUID: alertDefinition1.UID,
|
|
||||||
State: InstanceStateFiring,
|
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
|
||||||
}
|
|
||||||
err := store.saveAlertInstance(saveCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
getCmd := &getAlertInstanceQuery{
|
|
||||||
DefinitionOrgID: saveCmd.DefinitionOrgID,
|
|
||||||
DefinitionUID: saveCmd.DefinitionUID,
|
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.getAlertInstance(getCmd)
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("can save and read new alert instance with no labels", func(t *testing.T) {
|
|
||||||
saveCmd := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: alertDefinition2.OrgID,
|
|
||||||
DefinitionUID: alertDefinition2.UID,
|
|
||||||
State: InstanceStateNormal,
|
|
||||||
}
|
|
||||||
err := store.saveAlertInstance(saveCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
getCmd := &getAlertInstanceQuery{
|
|
||||||
DefinitionOrgID: saveCmd.DefinitionOrgID,
|
|
||||||
DefinitionUID: saveCmd.DefinitionUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.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, saveCmd.Labels, getCmd.Result.Labels)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("can save two instances with same org_id, uid and different labels", func(t *testing.T) {
|
|
||||||
saveCmdOne := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: alertDefinition3.OrgID,
|
|
||||||
DefinitionUID: alertDefinition3.UID,
|
|
||||||
State: InstanceStateFiring,
|
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.saveAlertInstance(saveCmdOne)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
saveCmdTwo := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
|
||||||
DefinitionUID: saveCmdOne.DefinitionUID,
|
|
||||||
State: InstanceStateFiring,
|
|
||||||
Labels: InstanceLabels{"test": "meow"},
|
|
||||||
}
|
|
||||||
err = store.saveAlertInstance(saveCmdTwo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
listCommand := &listAlertInstancesQuery{
|
|
||||||
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
|
||||||
DefinitionUID: saveCmdOne.DefinitionUID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.listAlertInstances(listCommand)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, listCommand.Result, 2)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("can list all added instances in org", func(t *testing.T) {
|
|
||||||
listCommand := &listAlertInstancesQuery{
|
|
||||||
DefinitionOrgID: orgID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.listAlertInstances(listCommand)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, listCommand.Result, 4)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("can list all added instances in org filtered by current state", func(t *testing.T) {
|
|
||||||
listCommand := &listAlertInstancesQuery{
|
|
||||||
DefinitionOrgID: orgID,
|
|
||||||
State: InstanceStateNormal,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.listAlertInstances(listCommand)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, listCommand.Result, 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("update instance with same org_id, uid and different labels", func(t *testing.T) {
|
|
||||||
saveCmdOne := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: alertDefinition4.OrgID,
|
|
||||||
DefinitionUID: alertDefinition4.UID,
|
|
||||||
State: InstanceStateFiring,
|
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := store.saveAlertInstance(saveCmdOne)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
saveCmdTwo := &saveAlertInstanceCommand{
|
|
||||||
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
|
||||||
DefinitionUID: saveCmdOne.DefinitionUID,
|
|
||||||
State: InstanceStateNormal,
|
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
|
||||||
}
|
|
||||||
err = store.saveAlertInstance(saveCmdTwo)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
listCommand := &listAlertInstancesQuery{
|
|
||||||
DefinitionOrgID: alertDefinition4.OrgID,
|
|
||||||
DefinitionUID: alertDefinition4.UID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.listAlertInstances(listCommand)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, listCommand.Result, 1)
|
|
||||||
|
|
||||||
require.Equal(t, saveCmdTwo.DefinitionOrgID, listCommand.Result[0].DefinitionOrgID)
|
|
||||||
require.Equal(t, saveCmdTwo.DefinitionUID, listCommand.Result[0].DefinitionUID)
|
|
||||||
require.Equal(t, saveCmdTwo.Labels, listCommand.Result[0].Labels)
|
|
||||||
require.Equal(t, saveCmdTwo.State, listCommand.Result[0].CurrentState)
|
|
||||||
require.NotEmpty(t, listCommand.Result[0].DefinitionTitle)
|
|
||||||
require.Equal(t, alertDefinition4.Title, listCommand.Result[0].DefinitionTitle)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (api *apiImpl) validateOrgAlertDefinition(c *models.ReqContext) {
|
|
||||||
uid := c.ParamsEscape(":alertDefinitionUID")
|
|
||||||
|
|
||||||
if uid == "" {
|
|
||||||
c.JsonApiErr(403, "Permission denied", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query := 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,115 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errAlertDefinitionFailedGenerateUniqueUID = errors.New("failed to generate alert definition UID")
|
|
||||||
|
|
||||||
// AlertDefinition is the model for alert definitions in Alerting NG.
|
|
||||||
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 []eval.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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type alertDefinitionKey struct {
|
|
||||||
orgID int64
|
|
||||||
definitionUID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k alertDefinitionKey) String() string {
|
|
||||||
return fmt.Sprintf("{orgID: %d, definitionUID: %s}", k.orgID, k.definitionUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (alertDefinition *AlertDefinition) getKey() alertDefinitionKey {
|
|
||||||
return alertDefinitionKey{orgID: alertDefinition.OrgID, definitionUID: alertDefinition.UID}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlertDefinitionVersion is the model for alert definition versions in Alerting NG.
|
|
||||||
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 []eval.AlertQuery
|
|
||||||
IntervalSeconds int64
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// errAlertDefinitionNotFound is an error for an unknown alert definition.
|
|
||||||
errAlertDefinitionNotFound = fmt.Errorf("could not find alert definition")
|
|
||||||
)
|
|
||||||
|
|
||||||
// getAlertDefinitionByUIDQuery is the query for retrieving/deleting an alert definition by UID and organisation ID.
|
|
||||||
type getAlertDefinitionByUIDQuery struct {
|
|
||||||
UID string
|
|
||||||
OrgID int64
|
|
||||||
|
|
||||||
Result *AlertDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
type deleteAlertDefinitionByUIDCommand struct {
|
|
||||||
UID string
|
|
||||||
OrgID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// saveAlertDefinitionCommand is the query for saving a new alert definition.
|
|
||||||
type saveAlertDefinitionCommand struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
Condition string `json:"condition"`
|
|
||||||
Data []eval.AlertQuery `json:"data"`
|
|
||||||
IntervalSeconds *int64 `json:"intervalSeconds"`
|
|
||||||
|
|
||||||
Result *AlertDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateAlertDefinitionCommand is the query for updating an existing alert definition.
|
|
||||||
type updateAlertDefinitionCommand struct {
|
|
||||||
Title string `json:"title"`
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
Condition string `json:"condition"`
|
|
||||||
Data []eval.AlertQuery `json:"data"`
|
|
||||||
IntervalSeconds *int64 `json:"intervalSeconds"`
|
|
||||||
UID string `json:"-"`
|
|
||||||
|
|
||||||
Result *AlertDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
type evalAlertConditionCommand struct {
|
|
||||||
Condition string `json:"condition"`
|
|
||||||
Data []eval.AlertQuery `json:"data"`
|
|
||||||
Now time.Time `json:"now"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type listAlertDefinitionsQuery struct {
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
|
|
||||||
Result []*AlertDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
type updateAlertDefinitionPausedCommand struct {
|
|
||||||
OrgID int64 `json:"-"`
|
|
||||||
UIDs []string `json:"uids"`
|
|
||||||
Paused bool `json:"-"`
|
|
||||||
|
|
||||||
ResultCount int64
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package eval
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -49,7 +49,7 @@ func (rtr *RelativeTimeRange) isValid() bool {
|
|||||||
return rtr.From > rtr.To
|
return rtr.From > rtr.To
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rtr *RelativeTimeRange) toTimeRange(now time.Time) backend.TimeRange {
|
func (rtr *RelativeTimeRange) ToTimeRange(now time.Time) backend.TimeRange {
|
||||||
return backend.TimeRange{
|
return backend.TimeRange{
|
||||||
From: now.Add(-time.Duration(rtr.From)),
|
From: now.Add(-time.Duration(rtr.From)),
|
||||||
To: now.Add(-time.Duration(rtr.To)),
|
To: now.Add(-time.Duration(rtr.To)),
|
||||||
@ -147,7 +147,7 @@ func (aq *AlertQuery) setMaxDatapoints() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aq *AlertQuery) getMaxDatapoints() (int64, error) {
|
func (aq *AlertQuery) GetMaxDatapoints() (int64, error) {
|
||||||
err := aq.setMaxDatapoints()
|
err := aq.setMaxDatapoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -192,7 +192,7 @@ func (aq *AlertQuery) getIntervalMS() (int64, error) {
|
|||||||
return int64(intervalMs), nil
|
return int64(intervalMs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aq *AlertQuery) getIntervalDuration() (time.Duration, error) {
|
func (aq *AlertQuery) GetIntervalDuration() (time.Duration, error) {
|
||||||
err := aq.setIntervalMS()
|
err := aq.setIntervalMS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@ -214,7 +214,7 @@ func (aq *AlertQuery) GetDatasource() (string, error) {
|
|||||||
return aq.DatasourceUID, nil
|
return aq.DatasourceUID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aq *AlertQuery) getModel() ([]byte, error) {
|
func (aq *AlertQuery) GetModel() ([]byte, error) {
|
||||||
err := aq.setDatasource()
|
err := aq.setDatasource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -270,7 +270,7 @@ func (aq *AlertQuery) PreSave() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// override model
|
// override model
|
||||||
model, err := aq.getModel()
|
model, err := aq.GetModel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package eval
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -186,7 +186,7 @@ func TestAlertQuery(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can update model maxDataPoints (if missing)", func(t *testing.T) {
|
t.Run("can update model maxDataPoints (if missing)", func(t *testing.T) {
|
||||||
maxDataPoints, err := tc.alertQuery.getMaxDatapoints()
|
maxDataPoints, err := tc.alertQuery.GetMaxDatapoints()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expectedMaxPoints, maxDataPoints)
|
require.Equal(t, tc.expectedMaxPoints, maxDataPoints)
|
||||||
})
|
})
|
||||||
@ -198,7 +198,7 @@ func TestAlertQuery(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("can get the updated model with the default properties (if missing)", func(t *testing.T) {
|
t.Run("can get the updated model with the default properties (if missing)", func(t *testing.T) {
|
||||||
blob, err := tc.alertQuery.getModel()
|
blob, err := tc.alertQuery.GetModel()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
model := make(map[string]interface{})
|
model := make(map[string]interface{})
|
||||||
err = json.Unmarshal(blob, &model)
|
err = json.Unmarshal(blob, &model)
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -33,8 +33,8 @@ func (i InstanceStateType) IsValid() bool {
|
|||||||
i == InstanceStateNormal
|
i == InstanceStateNormal
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveAlertInstanceCommand is the query for saving a new alert instance.
|
// SaveAlertInstanceCommand is the query for saving a new alert instance.
|
||||||
type saveAlertInstanceCommand struct {
|
type SaveAlertInstanceCommand struct {
|
||||||
DefinitionOrgID int64
|
DefinitionOrgID int64
|
||||||
DefinitionUID string
|
DefinitionUID string
|
||||||
Labels InstanceLabels
|
Labels InstanceLabels
|
||||||
@ -42,9 +42,9 @@ type saveAlertInstanceCommand struct {
|
|||||||
LastEvalTime time.Time
|
LastEvalTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAlertDefinitionByIDQuery is the query for retrieving/deleting an alert definition by ID.
|
// GetAlertInstanceQuery is the query for retrieving/deleting an alert definition by ID.
|
||||||
// nolint:unused
|
// nolint:unused
|
||||||
type getAlertInstanceQuery struct {
|
type GetAlertInstanceQuery struct {
|
||||||
DefinitionOrgID int64
|
DefinitionOrgID int64
|
||||||
DefinitionUID string
|
DefinitionUID string
|
||||||
Labels InstanceLabels
|
Labels InstanceLabels
|
||||||
@ -52,17 +52,17 @@ type getAlertInstanceQuery struct {
|
|||||||
Result *AlertInstance
|
Result *AlertInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// listAlertInstancesCommand is the query list alert Instances.
|
// ListAlertInstancesQuery is the query list alert Instances.
|
||||||
type listAlertInstancesQuery struct {
|
type ListAlertInstancesQuery struct {
|
||||||
DefinitionOrgID int64 `json:"-"`
|
DefinitionOrgID int64 `json:"-"`
|
||||||
DefinitionUID string
|
DefinitionUID string
|
||||||
State InstanceStateType
|
State InstanceStateType
|
||||||
|
|
||||||
Result []*listAlertInstancesQueryResult
|
Result []*ListAlertInstancesQueryResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// listAlertInstancesQueryResult represents the result of listAlertInstancesQuery.
|
// ListAlertInstancesQueryResult represents the result of listAlertInstancesQuery.
|
||||||
type listAlertInstancesQueryResult struct {
|
type ListAlertInstancesQueryResult struct {
|
||||||
DefinitionOrgID int64 `xorm:"def_org_id" json:"definitionOrgId"`
|
DefinitionOrgID int64 `xorm:"def_org_id" json:"definitionOrgId"`
|
||||||
DefinitionUID string `xorm:"def_uid" json:"definitionUid"`
|
DefinitionUID string `xorm:"def_uid" json:"definitionUid"`
|
||||||
DefinitionTitle string `xorm:"def_title" json:"definitionTitle"`
|
DefinitionTitle string `xorm:"def_title" json:"definitionTitle"`
|
||||||
@ -73,9 +73,9 @@ type listAlertInstancesQueryResult struct {
|
|||||||
LastEvalTime time.Time `json:"lastEvalTime"`
|
LastEvalTime time.Time `json:"lastEvalTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateAlertInstance validates that the alert instance contains an alert definition id,
|
// ValidateAlertInstance validates that the alert instance contains an alert definition id,
|
||||||
// and state.
|
// and state.
|
||||||
func validateAlertInstance(alertInstance *AlertInstance) error {
|
func ValidateAlertInstance(alertInstance *AlertInstance) error {
|
||||||
if alertInstance == nil {
|
if alertInstance == nil {
|
||||||
return fmt.Errorf("alert instance is invalid because it is nil")
|
return fmt.Errorf("alert instance is invalid because it is nil")
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// nolint:gosec
|
// nolint:gosec
|
147
pkg/services/ngalert/models/models.go
Normal file
147
pkg/services/ngalert/models/models.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
type GetAlertDefinitionByUIDQuery struct {
|
||||||
|
UID string
|
||||||
|
OrgID int64
|
||||||
|
|
||||||
|
Result *AlertDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAlertDefinitionByUIDCommand is the command for deleting an alert definition
|
||||||
|
type DeleteAlertDefinitionByUIDCommand struct {
|
||||||
|
UID string
|
||||||
|
OrgID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAlertDefinitionCommand is the query for saving a new alert definition.
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// EvalAlertConditionCommand is the command for evaluating a condition
|
||||||
|
type EvalAlertConditionCommand struct {
|
||||||
|
Condition string `json:"condition"`
|
||||||
|
Data []AlertQuery `json:"data"`
|
||||||
|
Now time.Time `json:"now"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAlertDefinitionsQuery is the query for listing alert definitions
|
||||||
|
type ListAlertDefinitionsQuery struct {
|
||||||
|
OrgID int64 `json:"-"`
|
||||||
|
|
||||||
|
Result []*AlertDefinition
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAlertDefinitionPausedCommand is the command for updating an alert definitions
|
||||||
|
type UpdateAlertDefinitionPausedCommand struct {
|
||||||
|
OrgID int64 `json:"-"`
|
||||||
|
UIDs []string `json:"uids"`
|
||||||
|
Paused bool `json:"-"`
|
||||||
|
|
||||||
|
ResultCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Condition contains backend expressions and queries and the RefID
|
||||||
|
// of the query or expression that will be evaluated.
|
||||||
|
type Condition struct {
|
||||||
|
RefID string `json:"refId"`
|
||||||
|
OrgID int64 `json:"-"`
|
||||||
|
|
||||||
|
QueriesAndExpressions []AlertQuery `json:"queriesAndExpressions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid checks the condition's validity.
|
||||||
|
func (c Condition) IsValid() bool {
|
||||||
|
// TODO search for refIDs in QueriesAndExpressions
|
||||||
|
return len(c.QueriesAndExpressions) != 0
|
||||||
|
}
|
@ -4,6 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
"github.com/benbjohnson/clock"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
@ -36,8 +41,8 @@ type AlertNG struct {
|
|||||||
RouteRegister routing.RouteRegister `inject:""`
|
RouteRegister routing.RouteRegister `inject:""`
|
||||||
SQLStore *sqlstore.SQLStore `inject:""`
|
SQLStore *sqlstore.SQLStore `inject:""`
|
||||||
DataService *tsdb.Service `inject:""`
|
DataService *tsdb.Service `inject:""`
|
||||||
log log.Logger
|
Log log.Logger
|
||||||
schedule scheduleService
|
schedule schedule.ScheduleService
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -46,37 +51,37 @@ func init() {
|
|||||||
|
|
||||||
// Init initializes the AlertingService.
|
// Init initializes the AlertingService.
|
||||||
func (ng *AlertNG) Init() error {
|
func (ng *AlertNG) Init() error {
|
||||||
ng.log = log.New("ngalert")
|
ng.Log = log.New("ngalert")
|
||||||
|
|
||||||
baseInterval := baseIntervalSeconds * time.Second
|
baseInterval := baseIntervalSeconds * time.Second
|
||||||
|
|
||||||
store := storeImpl{baseInterval: baseInterval, SQLStore: ng.SQLStore}
|
store := store.DBstore{BaseInterval: baseInterval, DefaultIntervalSeconds: defaultIntervalSeconds, SQLStore: ng.SQLStore}
|
||||||
|
|
||||||
schedCfg := schedulerCfg{
|
schedCfg := schedule.SchedulerCfg{
|
||||||
c: clock.New(),
|
C: clock.New(),
|
||||||
baseInterval: baseInterval,
|
BaseInterval: baseInterval,
|
||||||
logger: ng.log,
|
Logger: ng.Log,
|
||||||
evaluator: eval.Evaluator{Cfg: ng.Cfg},
|
MaxAttempts: maxAttempts,
|
||||||
store: store,
|
Evaluator: eval.Evaluator{Cfg: ng.Cfg},
|
||||||
|
Store: store,
|
||||||
}
|
}
|
||||||
ng.schedule = newScheduler(schedCfg, ng.DataService)
|
ng.schedule = schedule.NewScheduler(schedCfg, ng.DataService)
|
||||||
|
|
||||||
api := apiImpl{
|
api := api.API{
|
||||||
Cfg: ng.Cfg,
|
Cfg: ng.Cfg,
|
||||||
DatasourceCache: ng.DatasourceCache,
|
DatasourceCache: ng.DatasourceCache,
|
||||||
RouteRegister: ng.RouteRegister,
|
RouteRegister: ng.RouteRegister,
|
||||||
DataService: ng.DataService,
|
DataService: ng.DataService,
|
||||||
schedule: ng.schedule,
|
Schedule: ng.schedule,
|
||||||
store: store,
|
Store: store}
|
||||||
}
|
api.RegisterAPIEndpoints()
|
||||||
api.registerAPIEndpoints()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the scheduler
|
// Run starts the scheduler
|
||||||
func (ng *AlertNG) Run(ctx context.Context) error {
|
func (ng *AlertNG) Run(ctx context.Context) error {
|
||||||
ng.log.Debug("ngalert starting")
|
ng.Log.Debug("ngalert starting")
|
||||||
return ng.schedule.Ticker(ctx)
|
return ng.schedule.Ticker(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,23 +105,3 @@ func (ng *AlertNG) AddMigration(mg *migrator.Migrator) {
|
|||||||
// Create alert_instance table
|
// Create alert_instance table
|
||||||
alertInstanceMigration(mg)
|
alertInstanceMigration(mg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAlertCondition returns a Condition object for the given alertDefinitionID.
|
|
||||||
func (api *apiImpl) LoadAlertCondition(alertDefinitionUID string, orgID int64) (*eval.Condition, error) {
|
|
||||||
q := 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 &eval.Condition{
|
|
||||||
RefID: alertDefinition.Condition,
|
|
||||||
OrgID: alertDefinition.OrgID,
|
|
||||||
QueriesAndExpressions: alertDefinition.Data,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
17
pkg/services/ngalert/schedule/fetcher.go
Normal file
17
pkg/services/ngalert/schedule/fetcher.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package schedule
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (sch *schedule) fetchAllDetails(now time.Time) []*models.AlertDefinition {
|
||||||
|
q := models.ListAlertDefinitionsQuery{}
|
||||||
|
err := sch.store.GetAlertDefinitions(&q)
|
||||||
|
if err != nil {
|
||||||
|
sch.log.Error("failed to fetch alert definitions", "now", now, "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return q.Result
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package schedule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -6,6 +6,10 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
"github.com/benbjohnson/clock"
|
"github.com/benbjohnson/clock"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/alerting"
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
@ -14,25 +18,29 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type scheduleService interface {
|
// timeNow makes it possible to test usage of time
|
||||||
|
var timeNow = time.Now
|
||||||
|
|
||||||
|
// ScheduleService handles scheduling
|
||||||
|
type ScheduleService interface {
|
||||||
Ticker(context.Context) error
|
Ticker(context.Context) error
|
||||||
Pause() error
|
Pause() error
|
||||||
Unpause() error
|
Unpause() error
|
||||||
|
|
||||||
// the following are used by tests only used for tests
|
// the following are used by tests only used for tests
|
||||||
evalApplied(alertDefinitionKey, time.Time)
|
evalApplied(models.AlertDefinitionKey, time.Time)
|
||||||
stopApplied(alertDefinitionKey)
|
stopApplied(models.AlertDefinitionKey)
|
||||||
overrideCfg(cfg schedulerCfg)
|
overrideCfg(cfg SchedulerCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefinitionKey,
|
func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key models.AlertDefinitionKey,
|
||||||
evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
|
evalCh <-chan *evalContext, stopCh <-chan struct{}) error {
|
||||||
sch.log.Debug("alert definition routine started", "key", key)
|
sch.log.Debug("alert definition routine started", "key", key)
|
||||||
|
|
||||||
evalRunning := false
|
evalRunning := false
|
||||||
var start, end time.Time
|
var start, end time.Time
|
||||||
var attempt int64
|
var attempt int64
|
||||||
var alertDefinition *AlertDefinition
|
var alertDefinition *models.AlertDefinition
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case ctx := <-evalCh:
|
case ctx := <-evalCh:
|
||||||
@ -45,8 +53,8 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefi
|
|||||||
|
|
||||||
// fetch latest alert definition version
|
// fetch latest alert definition version
|
||||||
if alertDefinition == nil || alertDefinition.Version < ctx.version {
|
if alertDefinition == nil || alertDefinition.Version < ctx.version {
|
||||||
q := getAlertDefinitionByUIDQuery{OrgID: key.orgID, UID: key.definitionUID}
|
q := models.GetAlertDefinitionByUIDQuery{OrgID: key.OrgID, UID: key.DefinitionUID}
|
||||||
err := sch.store.getAlertDefinitionByUID(&q)
|
err := sch.store.GetAlertDefinitionByUID(&q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sch.log.Error("failed to fetch alert definition", "key", key)
|
sch.log.Error("failed to fetch alert definition", "key", key)
|
||||||
return err
|
return err
|
||||||
@ -70,8 +78,8 @@ func (sch *schedule) definitionRoutine(grafanaCtx context.Context, key alertDefi
|
|||||||
}
|
}
|
||||||
for _, r := range results {
|
for _, r := range results {
|
||||||
sch.log.Debug("alert definition result", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "instance", r.Instance, "state", r.State.String())
|
sch.log.Debug("alert definition result", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "duration", end.Sub(start), "instance", r.Instance, "state", r.State.String())
|
||||||
cmd := saveAlertInstanceCommand{DefinitionOrgID: key.orgID, DefinitionUID: key.definitionUID, State: InstanceStateType(r.State.String()), Labels: InstanceLabels(r.Instance), LastEvalTime: ctx.now}
|
cmd := models.SaveAlertInstanceCommand{DefinitionOrgID: key.OrgID, DefinitionUID: key.DefinitionUID, State: models.InstanceStateType(r.State.String()), Labels: models.InstanceLabels(r.Instance), LastEvalTime: ctx.now}
|
||||||
err := sch.store.saveAlertInstance(&cmd)
|
err := sch.store.SaveAlertInstance(&cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sch.log.Error("failed saving alert instance", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "instance", r.Instance, "state", r.State.String(), "error", err)
|
sch.log.Error("failed saving alert instance", "title", alertDefinition.Title, "key", key, "attempt", attempt, "now", ctx.now, "instance", r.Instance, "state", r.State.String(), "error", err)
|
||||||
}
|
}
|
||||||
@ -120,60 +128,62 @@ type schedule struct {
|
|||||||
// evalApplied is only used for tests: test code can set it to non-nil
|
// evalApplied is only used for tests: test code can set it to non-nil
|
||||||
// function, and then it'll be called from the event loop whenever the
|
// function, and then it'll be called from the event loop whenever the
|
||||||
// message from evalApplied is handled.
|
// message from evalApplied is handled.
|
||||||
evalAppliedFunc func(alertDefinitionKey, time.Time)
|
evalAppliedFunc func(models.AlertDefinitionKey, time.Time)
|
||||||
|
|
||||||
// stopApplied is only used for tests: test code can set it to non-nil
|
// stopApplied is only used for tests: test code can set it to non-nil
|
||||||
// function, and then it'll be called from the event loop whenever the
|
// function, and then it'll be called from the event loop whenever the
|
||||||
// message from stopApplied is handled.
|
// message from stopApplied is handled.
|
||||||
stopAppliedFunc func(alertDefinitionKey)
|
stopAppliedFunc func(models.AlertDefinitionKey)
|
||||||
|
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
||||||
evaluator eval.Evaluator
|
evaluator eval.Evaluator
|
||||||
|
|
||||||
store store
|
store store.Store
|
||||||
|
|
||||||
dataService *tsdb.Service
|
dataService *tsdb.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
type schedulerCfg struct {
|
// SchedulerCfg is the scheduler configuration.
|
||||||
c clock.Clock
|
type SchedulerCfg struct {
|
||||||
baseInterval time.Duration
|
C clock.Clock
|
||||||
logger log.Logger
|
BaseInterval time.Duration
|
||||||
evalAppliedFunc func(alertDefinitionKey, time.Time)
|
Logger log.Logger
|
||||||
stopAppliedFunc func(alertDefinitionKey)
|
EvalAppliedFunc func(models.AlertDefinitionKey, time.Time)
|
||||||
evaluator eval.Evaluator
|
MaxAttempts int64
|
||||||
store store
|
StopAppliedFunc func(models.AlertDefinitionKey)
|
||||||
|
Evaluator eval.Evaluator
|
||||||
|
Store store.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
// newScheduler returns a new schedule.
|
// NewScheduler returns a new schedule.
|
||||||
func newScheduler(cfg schedulerCfg, dataService *tsdb.Service) *schedule {
|
func NewScheduler(cfg SchedulerCfg, dataService *tsdb.Service) *schedule {
|
||||||
ticker := alerting.NewTicker(cfg.c.Now(), time.Second*0, cfg.c, int64(cfg.baseInterval.Seconds()))
|
ticker := alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds()))
|
||||||
sch := schedule{
|
sch := schedule{
|
||||||
registry: alertDefinitionRegistry{alertDefinitionInfo: make(map[alertDefinitionKey]alertDefinitionInfo)},
|
registry: alertDefinitionRegistry{alertDefinitionInfo: make(map[models.AlertDefinitionKey]alertDefinitionInfo)},
|
||||||
maxAttempts: maxAttempts,
|
maxAttempts: cfg.MaxAttempts,
|
||||||
clock: cfg.c,
|
clock: cfg.C,
|
||||||
baseInterval: cfg.baseInterval,
|
baseInterval: cfg.BaseInterval,
|
||||||
log: cfg.logger,
|
log: cfg.Logger,
|
||||||
heartbeat: ticker,
|
heartbeat: ticker,
|
||||||
evalAppliedFunc: cfg.evalAppliedFunc,
|
evalAppliedFunc: cfg.EvalAppliedFunc,
|
||||||
stopAppliedFunc: cfg.stopAppliedFunc,
|
stopAppliedFunc: cfg.StopAppliedFunc,
|
||||||
evaluator: cfg.evaluator,
|
evaluator: cfg.Evaluator,
|
||||||
store: cfg.store,
|
store: cfg.Store,
|
||||||
dataService: dataService,
|
dataService: dataService,
|
||||||
}
|
}
|
||||||
return &sch
|
return &sch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sch *schedule) overrideCfg(cfg schedulerCfg) {
|
func (sch *schedule) overrideCfg(cfg SchedulerCfg) {
|
||||||
sch.clock = cfg.c
|
sch.clock = cfg.C
|
||||||
sch.baseInterval = cfg.baseInterval
|
sch.baseInterval = cfg.BaseInterval
|
||||||
sch.heartbeat = alerting.NewTicker(cfg.c.Now(), time.Second*0, cfg.c, int64(cfg.baseInterval.Seconds()))
|
sch.heartbeat = alerting.NewTicker(cfg.C.Now(), time.Second*0, cfg.C, int64(cfg.BaseInterval.Seconds()))
|
||||||
sch.evalAppliedFunc = cfg.evalAppliedFunc
|
sch.evalAppliedFunc = cfg.EvalAppliedFunc
|
||||||
sch.stopAppliedFunc = cfg.stopAppliedFunc
|
sch.stopAppliedFunc = cfg.StopAppliedFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sch *schedule) evalApplied(alertDefKey alertDefinitionKey, now time.Time) {
|
func (sch *schedule) evalApplied(alertDefKey models.AlertDefinitionKey, now time.Time) {
|
||||||
if sch.evalAppliedFunc == nil {
|
if sch.evalAppliedFunc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -181,7 +191,7 @@ func (sch *schedule) evalApplied(alertDefKey alertDefinitionKey, now time.Time)
|
|||||||
sch.evalAppliedFunc(alertDefKey, now)
|
sch.evalAppliedFunc(alertDefKey, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sch *schedule) stopApplied(alertDefKey alertDefinitionKey) {
|
func (sch *schedule) stopApplied(alertDefKey models.AlertDefinitionKey) {
|
||||||
if sch.stopAppliedFunc == nil {
|
if sch.stopAppliedFunc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -223,7 +233,7 @@ func (sch *schedule) Ticker(grafanaCtx context.Context) error {
|
|||||||
registeredDefinitions := sch.registry.keyMap()
|
registeredDefinitions := sch.registry.keyMap()
|
||||||
|
|
||||||
type readyToRunItem struct {
|
type readyToRunItem struct {
|
||||||
key alertDefinitionKey
|
key models.AlertDefinitionKey
|
||||||
definitionInfo alertDefinitionInfo
|
definitionInfo alertDefinitionInfo
|
||||||
}
|
}
|
||||||
readyToRun := make([]readyToRunItem, 0)
|
readyToRun := make([]readyToRunItem, 0)
|
||||||
@ -232,7 +242,7 @@ func (sch *schedule) Ticker(grafanaCtx context.Context) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
key := item.getKey()
|
key := item.GetKey()
|
||||||
itemVersion := item.Version
|
itemVersion := item.Version
|
||||||
newRoutine := !sch.registry.exists(key)
|
newRoutine := !sch.registry.exists(key)
|
||||||
definitionInfo := sch.registry.getOrCreateInfo(key, itemVersion)
|
definitionInfo := sch.registry.getOrCreateInfo(key, itemVersion)
|
||||||
@ -292,12 +302,12 @@ func (sch *schedule) Ticker(grafanaCtx context.Context) error {
|
|||||||
|
|
||||||
type alertDefinitionRegistry struct {
|
type alertDefinitionRegistry struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
alertDefinitionInfo map[alertDefinitionKey]alertDefinitionInfo
|
alertDefinitionInfo map[models.AlertDefinitionKey]alertDefinitionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrCreateInfo returns the channel for the specific alert definition
|
// getOrCreateInfo returns the channel for the specific alert definition
|
||||||
// if it does not exists creates one and returns it
|
// if it does not exists creates one and returns it
|
||||||
func (r *alertDefinitionRegistry) getOrCreateInfo(key alertDefinitionKey, definitionVersion int64) alertDefinitionInfo {
|
func (r *alertDefinitionRegistry) getOrCreateInfo(key models.AlertDefinitionKey, definitionVersion int64) alertDefinitionInfo {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
@ -313,7 +323,7 @@ func (r *alertDefinitionRegistry) getOrCreateInfo(key alertDefinitionKey, defini
|
|||||||
|
|
||||||
// get returns the channel for the specific alert definition
|
// get returns the channel for the specific alert definition
|
||||||
// if the key does not exist returns an error
|
// if the key does not exist returns an error
|
||||||
func (r *alertDefinitionRegistry) get(key alertDefinitionKey) (*alertDefinitionInfo, error) {
|
func (r *alertDefinitionRegistry) get(key models.AlertDefinitionKey) (*alertDefinitionInfo, error) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
@ -324,7 +334,7 @@ func (r *alertDefinitionRegistry) get(key alertDefinitionKey) (*alertDefinitionI
|
|||||||
return &info, nil
|
return &info, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *alertDefinitionRegistry) exists(key alertDefinitionKey) bool {
|
func (r *alertDefinitionRegistry) exists(key models.AlertDefinitionKey) bool {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
@ -332,15 +342,15 @@ func (r *alertDefinitionRegistry) exists(key alertDefinitionKey) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *alertDefinitionRegistry) del(key alertDefinitionKey) {
|
func (r *alertDefinitionRegistry) del(key models.AlertDefinitionKey) {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
defer r.mu.Unlock()
|
defer r.mu.Unlock()
|
||||||
|
|
||||||
delete(r.alertDefinitionInfo, key)
|
delete(r.alertDefinitionInfo, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *alertDefinitionRegistry) iter() <-chan alertDefinitionKey {
|
func (r *alertDefinitionRegistry) iter() <-chan models.AlertDefinitionKey {
|
||||||
c := make(chan alertDefinitionKey)
|
c := make(chan models.AlertDefinitionKey)
|
||||||
|
|
||||||
f := func() {
|
f := func() {
|
||||||
r.mu.Lock()
|
r.mu.Lock()
|
||||||
@ -356,8 +366,8 @@ func (r *alertDefinitionRegistry) iter() <-chan alertDefinitionKey {
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *alertDefinitionRegistry) keyMap() map[alertDefinitionKey]struct{} {
|
func (r *alertDefinitionRegistry) keyMap() map[models.AlertDefinitionKey]struct{} {
|
||||||
definitionsIDs := make(map[alertDefinitionKey]struct{})
|
definitionsIDs := make(map[models.AlertDefinitionKey]struct{})
|
||||||
for k := range r.iter() {
|
for k := range r.iter() {
|
||||||
definitionsIDs[k] = struct{}{}
|
definitionsIDs[k] = struct{}{}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -7,36 +7,51 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type store interface {
|
// TimeNow makes it possible to test usage of time
|
||||||
deleteAlertDefinitionByUID(*deleteAlertDefinitionByUIDCommand) error
|
var TimeNow = time.Now
|
||||||
getAlertDefinitionByUID(*getAlertDefinitionByUIDQuery) error
|
|
||||||
getAlertDefinitions(query *listAlertDefinitionsQuery) error
|
// AlertDefinitionMaxTitleLength is the maximum length of the alert definition titles
|
||||||
getOrgAlertDefinitions(query *listAlertDefinitionsQuery) error
|
const AlertDefinitionMaxTitleLength = 190
|
||||||
saveAlertDefinition(*saveAlertDefinitionCommand) error
|
|
||||||
updateAlertDefinition(*updateAlertDefinitionCommand) error
|
// ErrEmptyTitleError is an error returned if the alert definition title is empty
|
||||||
getAlertInstance(*getAlertInstanceQuery) error
|
var ErrEmptyTitleError = errors.New("title is empty")
|
||||||
listAlertInstances(cmd *listAlertInstancesQuery) error
|
|
||||||
saveAlertInstance(cmd *saveAlertInstanceCommand) error
|
// Store is the interface for persisting alert definitions and instances
|
||||||
validateAlertDefinition(*AlertDefinition, bool) error
|
type Store interface {
|
||||||
updateAlertDefinitionPaused(*updateAlertDefinitionPausedCommand) error
|
DeleteAlertDefinitionByUID(*models.DeleteAlertDefinitionByUIDCommand) error
|
||||||
|
GetAlertDefinitionByUID(*models.GetAlertDefinitionByUIDQuery) error
|
||||||
|
GetAlertDefinitions(query *models.ListAlertDefinitionsQuery) error
|
||||||
|
GetOrgAlertDefinitions(query *models.ListAlertDefinitionsQuery) error
|
||||||
|
SaveAlertDefinition(*models.SaveAlertDefinitionCommand) error
|
||||||
|
UpdateAlertDefinition(*models.UpdateAlertDefinitionCommand) error
|
||||||
|
GetAlertInstance(*models.GetAlertInstanceQuery) error
|
||||||
|
ListAlertInstances(cmd *models.ListAlertInstancesQuery) error
|
||||||
|
SaveAlertInstance(cmd *models.SaveAlertInstanceCommand) error
|
||||||
|
ValidateAlertDefinition(*models.AlertDefinition, bool) error
|
||||||
|
UpdateAlertDefinitionPaused(*models.UpdateAlertDefinitionPausedCommand) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeImpl struct {
|
// DBstore stores the alert definitions and instances in the database.
|
||||||
|
type DBstore struct {
|
||||||
// the base scheduler tick rate; it's used for validating definition interval
|
// the base scheduler tick rate; it's used for validating definition interval
|
||||||
baseInterval time.Duration
|
BaseInterval time.Duration
|
||||||
SQLStore *sqlstore.SQLStore `inject:""`
|
// default alert definiiton interval
|
||||||
|
DefaultIntervalSeconds int64
|
||||||
|
SQLStore *sqlstore.SQLStore `inject:""`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAlertDefinitionByUID(sess *sqlstore.DBSession, alertDefinitionUID string, orgID int64) (*AlertDefinition, error) {
|
func getAlertDefinitionByUID(sess *sqlstore.DBSession, alertDefinitionUID string, orgID int64) (*models.AlertDefinition, error) {
|
||||||
// we consider optionally enabling some caching
|
// we consider optionally enabling some caching
|
||||||
alertDefinition := AlertDefinition{OrgID: orgID, UID: alertDefinitionUID}
|
alertDefinition := models.AlertDefinition{OrgID: orgID, UID: alertDefinitionUID}
|
||||||
has, err := sess.Get(&alertDefinition)
|
has, err := sess.Get(&alertDefinition)
|
||||||
if !has {
|
if !has {
|
||||||
return nil, errAlertDefinitionNotFound
|
return nil, models.ErrAlertDefinitionNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -44,9 +59,9 @@ func getAlertDefinitionByUID(sess *sqlstore.DBSession, alertDefinitionUID string
|
|||||||
return &alertDefinition, nil
|
return &alertDefinition, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteAlertDefinitionByID is a handler for deleting an alert definition.
|
// DeleteAlertDefinitionByUID is a handler for deleting an alert definition.
|
||||||
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||||
func (st storeImpl) deleteAlertDefinitionByUID(cmd *deleteAlertDefinitionByUIDCommand) error {
|
func (st DBstore) DeleteAlertDefinitionByUID(cmd *models.DeleteAlertDefinitionByUIDCommand) error {
|
||||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) 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)
|
_, err := sess.Exec("DELETE FROM alert_definition WHERE uid = ? AND org_id = ?", cmd.UID, cmd.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -66,9 +81,9 @@ func (st storeImpl) deleteAlertDefinitionByUID(cmd *deleteAlertDefinitionByUIDCo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAlertDefinitionByUID is a handler for retrieving an alert definition from that database by its UID and organisation ID.
|
// 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.
|
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||||
func (st storeImpl) getAlertDefinitionByUID(query *getAlertDefinitionByUIDQuery) error {
|
func (st DBstore) GetAlertDefinitionByUID(query *models.GetAlertDefinitionByUIDQuery) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
alertDefinition, err := getAlertDefinitionByUID(sess, query.UID, query.OrgID)
|
alertDefinition, err := getAlertDefinitionByUID(sess, query.UID, query.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -79,10 +94,10 @@ func (st storeImpl) getAlertDefinitionByUID(query *getAlertDefinitionByUIDQuery)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveAlertDefinition is a handler for saving a new alert definition.
|
// SaveAlertDefinition is a handler for saving a new alert definition.
|
||||||
func (st storeImpl) saveAlertDefinition(cmd *saveAlertDefinitionCommand) error {
|
func (st DBstore) SaveAlertDefinition(cmd *models.SaveAlertDefinitionCommand) error {
|
||||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
intervalSeconds := defaultIntervalSeconds
|
intervalSeconds := st.DefaultIntervalSeconds
|
||||||
if cmd.IntervalSeconds != nil {
|
if cmd.IntervalSeconds != nil {
|
||||||
intervalSeconds = *cmd.IntervalSeconds
|
intervalSeconds = *cmd.IntervalSeconds
|
||||||
}
|
}
|
||||||
@ -94,7 +109,7 @@ func (st storeImpl) saveAlertDefinition(cmd *saveAlertDefinitionCommand) error {
|
|||||||
return fmt.Errorf("failed to generate UID for alert definition %q: %w", cmd.Title, err)
|
return fmt.Errorf("failed to generate UID for alert definition %q: %w", cmd.Title, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
alertDefinition := &AlertDefinition{
|
alertDefinition := &models.AlertDefinition{
|
||||||
OrgID: cmd.OrgID,
|
OrgID: cmd.OrgID,
|
||||||
Title: cmd.Title,
|
Title: cmd.Title,
|
||||||
Condition: cmd.Condition,
|
Condition: cmd.Condition,
|
||||||
@ -104,11 +119,11 @@ func (st storeImpl) saveAlertDefinition(cmd *saveAlertDefinitionCommand) error {
|
|||||||
UID: uid,
|
UID: uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := st.validateAlertDefinition(alertDefinition, false); err != nil {
|
if err := st.ValidateAlertDefinition(alertDefinition, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := alertDefinition.preSave(); err != nil {
|
if err := alertDefinition.PreSave(TimeNow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +134,7 @@ func (st storeImpl) saveAlertDefinition(cmd *saveAlertDefinitionCommand) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
alertDefVersion := AlertDefinitionVersion{
|
alertDefVersion := models.AlertDefinitionVersion{
|
||||||
AlertDefinitionID: alertDefinition.ID,
|
AlertDefinitionID: alertDefinition.ID,
|
||||||
AlertDefinitionUID: alertDefinition.UID,
|
AlertDefinitionUID: alertDefinition.UID,
|
||||||
Version: alertDefinition.Version,
|
Version: alertDefinition.Version,
|
||||||
@ -138,13 +153,13 @@ func (st storeImpl) saveAlertDefinition(cmd *saveAlertDefinitionCommand) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateAlertDefinition is a handler for updating an existing alert definition.
|
// UpdateAlertDefinition is a handler for updating an existing alert definition.
|
||||||
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
// It returns models.ErrAlertDefinitionNotFound if no alert definition is found for the provided ID.
|
||||||
func (st storeImpl) updateAlertDefinition(cmd *updateAlertDefinitionCommand) error {
|
func (st DBstore) UpdateAlertDefinition(cmd *models.UpdateAlertDefinitionCommand) error {
|
||||||
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
existingAlertDefinition, err := getAlertDefinitionByUID(sess, cmd.UID, cmd.OrgID)
|
existingAlertDefinition, err := getAlertDefinitionByUID(sess, cmd.UID, cmd.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errAlertDefinitionNotFound) {
|
if errors.Is(err, models.ErrAlertDefinitionNotFound) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -168,7 +183,7 @@ func (st storeImpl) updateAlertDefinition(cmd *updateAlertDefinitionCommand) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// explicitly set all fields regardless of being provided or not
|
// explicitly set all fields regardless of being provided or not
|
||||||
alertDefinition := &AlertDefinition{
|
alertDefinition := &models.AlertDefinition{
|
||||||
ID: existingAlertDefinition.ID,
|
ID: existingAlertDefinition.ID,
|
||||||
Title: title,
|
Title: title,
|
||||||
Condition: condition,
|
Condition: condition,
|
||||||
@ -178,11 +193,11 @@ func (st storeImpl) updateAlertDefinition(cmd *updateAlertDefinitionCommand) err
|
|||||||
UID: existingAlertDefinition.UID,
|
UID: existingAlertDefinition.UID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := st.validateAlertDefinition(alertDefinition, true); err != nil {
|
if err := st.ValidateAlertDefinition(alertDefinition, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := alertDefinition.preSave(); err != nil {
|
if err := alertDefinition.PreSave(TimeNow); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +211,7 @@ func (st storeImpl) updateAlertDefinition(cmd *updateAlertDefinitionCommand) err
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
alertDefVersion := AlertDefinitionVersion{
|
alertDefVersion := models.AlertDefinitionVersion{
|
||||||
AlertDefinitionID: alertDefinition.ID,
|
AlertDefinitionID: alertDefinition.ID,
|
||||||
AlertDefinitionUID: alertDefinition.UID,
|
AlertDefinitionUID: alertDefinition.UID,
|
||||||
ParentVersion: alertDefinition.Version,
|
ParentVersion: alertDefinition.Version,
|
||||||
@ -216,10 +231,10 @@ func (st storeImpl) updateAlertDefinition(cmd *updateAlertDefinitionCommand) err
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrgAlertDefinitions is a handler for retrieving alert definitions of specific organisation.
|
// GetOrgAlertDefinitions is a handler for retrieving alert definitions of specific organisation.
|
||||||
func (st storeImpl) getOrgAlertDefinitions(query *listAlertDefinitionsQuery) error {
|
func (st DBstore) GetOrgAlertDefinitions(query *models.ListAlertDefinitionsQuery) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
alertDefinitions := make([]*AlertDefinition, 0)
|
alertDefinitions := make([]*models.AlertDefinition, 0)
|
||||||
q := "SELECT * FROM alert_definition WHERE org_id = ?"
|
q := "SELECT * FROM alert_definition WHERE org_id = ?"
|
||||||
if err := sess.SQL(q, query.OrgID).Find(&alertDefinitions); err != nil {
|
if err := sess.SQL(q, query.OrgID).Find(&alertDefinitions); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -230,9 +245,11 @@ func (st storeImpl) getOrgAlertDefinitions(query *listAlertDefinitionsQuery) err
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st storeImpl) getAlertDefinitions(query *listAlertDefinitionsQuery) error {
|
// 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 {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
alerts := make([]*AlertDefinition, 0)
|
alerts := make([]*models.AlertDefinition, 0)
|
||||||
q := "SELECT uid, org_id, interval_seconds, version, paused FROM alert_definition"
|
q := "SELECT uid, org_id, interval_seconds, version, paused FROM alert_definition"
|
||||||
if err := sess.SQL(q).Find(&alerts); err != nil {
|
if err := sess.SQL(q).Find(&alerts); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -243,7 +260,8 @@ func (st storeImpl) getAlertDefinitions(query *listAlertDefinitionsQuery) error
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st storeImpl) updateAlertDefinitionPaused(cmd *updateAlertDefinitionPausedCommand) error {
|
// 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 {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
if len(cmd.UIDs) == 0 {
|
if len(cmd.UIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@ -282,7 +300,7 @@ func generateNewAlertDefinitionUID(sess *sqlstore.DBSession, orgID int64) (strin
|
|||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
uid := util.GenerateShortUID()
|
uid := util.GenerateShortUID()
|
||||||
|
|
||||||
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&AlertDefinition{})
|
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&models.AlertDefinition{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -292,5 +310,32 @@ func generateNewAlertDefinitionUID(sess *sqlstore.DBSession, orgID int64) (strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", errAlertDefinitionFailedGenerateUniqueUID
|
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 ErrEmptyTitleError
|
||||||
|
}
|
||||||
|
|
||||||
|
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,20 +1,20 @@
|
|||||||
package ngalert
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getAlertInstance is a handler for retrieving an alert instance based on OrgId, AlertDefintionID, and
|
// GetAlertInstance is a handler for retrieving an alert instance based on OrgId, AlertDefintionID, and
|
||||||
// the hash of the labels.
|
// the hash of the labels.
|
||||||
// nolint:unused
|
// nolint:unused
|
||||||
func (st storeImpl) getAlertInstance(cmd *getAlertInstanceQuery) error {
|
func (st DBstore) GetAlertInstance(cmd *models.GetAlertInstanceQuery) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
instance := AlertInstance{}
|
instance := models.AlertInstance{}
|
||||||
s := strings.Builder{}
|
s := strings.Builder{}
|
||||||
s.WriteString(`SELECT * FROM alert_instance
|
s.WriteString(`SELECT * FROM alert_instance
|
||||||
WHERE
|
WHERE
|
||||||
@ -43,11 +43,11 @@ func (st storeImpl) getAlertInstance(cmd *getAlertInstanceQuery) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// listAlertInstances is a handler for retrieving alert instances within specific organisation
|
// ListAlertInstances is a handler for retrieving alert instances within specific organisation
|
||||||
// based on various filters.
|
// based on various filters.
|
||||||
func (st storeImpl) listAlertInstances(cmd *listAlertInstancesQuery) error {
|
func (st DBstore) ListAlertInstances(cmd *models.ListAlertInstancesQuery) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
alertInstances := make([]*listAlertInstancesQueryResult, 0)
|
alertInstances := make([]*models.ListAlertInstancesQueryResult, 0)
|
||||||
|
|
||||||
s := strings.Builder{}
|
s := strings.Builder{}
|
||||||
params := make([]interface{}, 0)
|
params := make([]interface{}, 0)
|
||||||
@ -76,26 +76,26 @@ func (st storeImpl) listAlertInstances(cmd *listAlertInstancesQuery) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveAlertDefinition is a handler for saving a new alert definition.
|
// SaveAlertInstance is a handler for saving a new alert instance.
|
||||||
// nolint:unused
|
// nolint:unused
|
||||||
func (st storeImpl) saveAlertInstance(cmd *saveAlertInstanceCommand) error {
|
func (st DBstore) SaveAlertInstance(cmd *models.SaveAlertInstanceCommand) error {
|
||||||
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||||
labelTupleJSON, labelsHash, err := cmd.Labels.StringAndHash()
|
labelTupleJSON, labelsHash, err := cmd.Labels.StringAndHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
alertInstance := &AlertInstance{
|
alertInstance := &models.AlertInstance{
|
||||||
DefinitionOrgID: cmd.DefinitionOrgID,
|
DefinitionOrgID: cmd.DefinitionOrgID,
|
||||||
DefinitionUID: cmd.DefinitionUID,
|
DefinitionUID: cmd.DefinitionUID,
|
||||||
Labels: cmd.Labels,
|
Labels: cmd.Labels,
|
||||||
LabelsHash: labelsHash,
|
LabelsHash: labelsHash,
|
||||||
CurrentState: cmd.State,
|
CurrentState: cmd.State,
|
||||||
CurrentStateSince: time.Now(),
|
CurrentStateSince: TimeNow(),
|
||||||
LastEvalTime: cmd.LastEvalTime,
|
LastEvalTime: cmd.LastEvalTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateAlertInstance(alertInstance); err != nil {
|
if err := models.ValidateAlertInstance(alertInstance); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
// +build integration
|
// +build integration
|
||||||
|
|
||||||
package ngalert
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -8,15 +8,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/grafana/grafana/pkg/registry"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const baseIntervalSeconds = 10
|
||||||
|
|
||||||
func mockTimeNow() {
|
func mockTimeNow() {
|
||||||
var timeSeed int64
|
var timeSeed int64
|
||||||
timeNow = func() time.Time {
|
store.TimeNow = func() time.Time {
|
||||||
fakeNow := time.Unix(timeSeed, 0).UTC()
|
fakeNow := time.Unix(timeSeed, 0).UTC()
|
||||||
timeSeed++
|
timeSeed++
|
||||||
return fakeNow
|
return fakeNow
|
||||||
@ -24,13 +28,16 @@ func mockTimeNow() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resetTimeNow() {
|
func resetTimeNow() {
|
||||||
timeNow = time.Now
|
store.TimeNow = time.Now
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreatingAlertDefinition(t *testing.T) {
|
func TestCreatingAlertDefinition(t *testing.T) {
|
||||||
mockTimeNow()
|
mockTimeNow()
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
|
|
||||||
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
var customIntervalSeconds int64 = 120
|
var customIntervalSeconds int64 = 120
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
@ -45,7 +52,7 @@ func TestCreatingAlertDefinition(t *testing.T) {
|
|||||||
desc: "should create successfully an alert definition with default interval",
|
desc: "should create successfully an alert definition with default interval",
|
||||||
inputIntervalSeconds: nil,
|
inputIntervalSeconds: nil,
|
||||||
inputTitle: "a name",
|
inputTitle: "a name",
|
||||||
expectedInterval: defaultIntervalSeconds,
|
expectedInterval: dbstore.DefaultIntervalSeconds,
|
||||||
expectedUpdated: time.Unix(0, 0).UTC(),
|
expectedUpdated: time.Unix(0, 0).UTC(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -58,28 +65,25 @@ func TestCreatingAlertDefinition(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "should fail to create an alert definition with too big name",
|
desc: "should fail to create an alert definition with too big name",
|
||||||
inputIntervalSeconds: &customIntervalSeconds,
|
inputIntervalSeconds: &customIntervalSeconds,
|
||||||
inputTitle: getLongString(alertDefinitionMaxTitleLength + 1),
|
inputTitle: getLongString(store.AlertDefinitionMaxTitleLength + 1),
|
||||||
expectedError: errors.New(""),
|
expectedError: errors.New(""),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "should fail to create an alert definition with empty title",
|
desc: "should fail to create an alert definition with empty title",
|
||||||
inputIntervalSeconds: &customIntervalSeconds,
|
inputIntervalSeconds: &customIntervalSeconds,
|
||||||
inputTitle: "",
|
inputTitle: "",
|
||||||
expectedError: errEmptyTitleError,
|
expectedError: store.ErrEmptyTitleError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
|
||||||
t.Cleanup(registry.ClearOverrides)
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
|
||||||
q := saveAlertDefinitionCommand{
|
q := models.SaveAlertDefinitionCommand{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
Title: tc.inputTitle,
|
Title: tc.inputTitle,
|
||||||
Condition: "B",
|
Condition: "B",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
@ -87,9 +91,9 @@ func TestCreatingAlertDefinition(t *testing.T) {
|
|||||||
"expression":"2 + 3 > 1"
|
"expression":"2 + 3 > 1"
|
||||||
}`),
|
}`),
|
||||||
RefID: "B",
|
RefID: "B",
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(time.Duration(5) * time.Hour),
|
From: models.Duration(time.Duration(5) * time.Hour),
|
||||||
To: eval.Duration(time.Duration(3) * time.Hour),
|
To: models.Duration(time.Duration(3) * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -97,7 +101,7 @@ func TestCreatingAlertDefinition(t *testing.T) {
|
|||||||
if tc.inputIntervalSeconds != nil {
|
if tc.inputIntervalSeconds != nil {
|
||||||
q.IntervalSeconds = tc.inputIntervalSeconds
|
q.IntervalSeconds = tc.inputIntervalSeconds
|
||||||
}
|
}
|
||||||
err := store.saveAlertDefinition(&q)
|
err := dbstore.SaveAlertDefinition(&q)
|
||||||
switch {
|
switch {
|
||||||
case tc.expectedError != nil:
|
case tc.expectedError != nil:
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@ -113,14 +117,14 @@ func TestCreatingAlertDefinition(t *testing.T) {
|
|||||||
}
|
}
|
||||||
func TestCreatingConflictionAlertDefinition(t *testing.T) {
|
func TestCreatingConflictionAlertDefinition(t *testing.T) {
|
||||||
t.Run("Should fail to create alert definition with conflicting org_id, title", func(t *testing.T) {
|
t.Run("Should fail to create alert definition with conflicting org_id, title", func(t *testing.T) {
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
q := saveAlertDefinitionCommand{
|
q := models.SaveAlertDefinitionCommand{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
Title: "title",
|
Title: "title",
|
||||||
Condition: "B",
|
Condition: "B",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
@ -128,20 +132,20 @@ func TestCreatingConflictionAlertDefinition(t *testing.T) {
|
|||||||
"expression":"2 + 3 > 1"
|
"expression":"2 + 3 > 1"
|
||||||
}`),
|
}`),
|
||||||
RefID: "B",
|
RefID: "B",
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(time.Duration(5) * time.Hour),
|
From: models.Duration(time.Duration(5) * time.Hour),
|
||||||
To: eval.Duration(time.Duration(3) * time.Hour),
|
To: models.Duration(time.Duration(3) * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.saveAlertDefinition(&q)
|
err := dbstore.SaveAlertDefinition(&q)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = store.saveAlertDefinition(&q)
|
err = dbstore.SaveAlertDefinition(&q)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, store.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
assert.True(t, dbstore.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,15 +154,15 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
mockTimeNow()
|
mockTimeNow()
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
|
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
q := updateAlertDefinitionCommand{
|
q := models.UpdateAlertDefinitionCommand{
|
||||||
UID: "unknown",
|
UID: "unknown",
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
Title: "something completely different",
|
Title: "something completely different",
|
||||||
Condition: "A",
|
Condition: "A",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
@ -166,15 +170,15 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
"expression":"2 + 2 > 1"
|
"expression":"2 + 2 > 1"
|
||||||
}`),
|
}`),
|
||||||
RefID: "A",
|
RefID: "A",
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(time.Duration(5) * time.Hour),
|
From: models.Duration(time.Duration(5) * time.Hour),
|
||||||
To: eval.Duration(time.Duration(3) * time.Hour),
|
To: models.Duration(time.Duration(3) * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.updateAlertDefinition(&q)
|
err := dbstore.UpdateAlertDefinition(&q)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -182,11 +186,11 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
mockTimeNow()
|
mockTimeNow()
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
|
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
var initialInterval int64 = 120
|
var initialInterval int64 = 120
|
||||||
alertDefinition := createTestAlertDefinition(t, store, initialInterval)
|
alertDefinition := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||||
created := alertDefinition.Updated
|
created := alertDefinition.Updated
|
||||||
|
|
||||||
var customInterval int64 = 30
|
var customInterval int64 = 30
|
||||||
@ -231,7 +235,7 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
desc: "should not update alert definition if the title it's too big",
|
desc: "should not update alert definition if the title it's too big",
|
||||||
inputInterval: &customInterval,
|
inputInterval: &customInterval,
|
||||||
inputOrgID: 0,
|
inputOrgID: 0,
|
||||||
inputTitle: getLongString(alertDefinitionMaxTitleLength + 1),
|
inputTitle: getLongString(store.AlertDefinitionMaxTitleLength + 1),
|
||||||
expectedError: errors.New(""),
|
expectedError: errors.New(""),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -245,10 +249,10 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
q := updateAlertDefinitionCommand{
|
q := models.UpdateAlertDefinitionCommand{
|
||||||
UID: (*alertDefinition).UID,
|
UID: (*alertDefinition).UID,
|
||||||
Condition: "B",
|
Condition: "B",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
@ -256,9 +260,9 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
"expression":"2 + 3 > 1"
|
"expression":"2 + 3 > 1"
|
||||||
}`),
|
}`),
|
||||||
RefID: "B",
|
RefID: "B",
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(5 * time.Hour),
|
From: models.Duration(5 * time.Hour),
|
||||||
To: eval.Duration(3 * time.Hour),
|
To: models.Duration(3 * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -275,7 +279,7 @@ func TestUpdatingAlertDefinition(t *testing.T) {
|
|||||||
q.OrgID = tc.inputOrgID
|
q.OrgID = tc.inputOrgID
|
||||||
}
|
}
|
||||||
q.Title = tc.inputTitle
|
q.Title = tc.inputTitle
|
||||||
err := store.updateAlertDefinition(&q)
|
err := dbstore.UpdateAlertDefinition(&q)
|
||||||
switch {
|
switch {
|
||||||
case tc.expectedError != nil:
|
case tc.expectedError != nil:
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
@ -321,18 +325,18 @@ func TestUpdatingConflictingAlertDefinition(t *testing.T) {
|
|||||||
mockTimeNow()
|
mockTimeNow()
|
||||||
defer resetTimeNow()
|
defer resetTimeNow()
|
||||||
|
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
var initialInterval int64 = 120
|
var initialInterval int64 = 120
|
||||||
alertDef1 := createTestAlertDefinition(t, store, initialInterval)
|
alertDef1 := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||||
alertDef2 := createTestAlertDefinition(t, store, initialInterval)
|
alertDef2 := createTestAlertDefinition(t, dbstore, initialInterval)
|
||||||
|
|
||||||
q := updateAlertDefinitionCommand{
|
q := models.UpdateAlertDefinitionCommand{
|
||||||
UID: (*alertDef2).UID,
|
UID: (*alertDef2).UID,
|
||||||
Title: alertDef1.Title,
|
Title: alertDef1.Title,
|
||||||
Condition: "B",
|
Condition: "B",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
@ -340,70 +344,70 @@ func TestUpdatingConflictingAlertDefinition(t *testing.T) {
|
|||||||
"expression":"2 + 3 > 1"
|
"expression":"2 + 3 > 1"
|
||||||
}`),
|
}`),
|
||||||
RefID: "B",
|
RefID: "B",
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(5 * time.Hour),
|
From: models.Duration(5 * time.Hour),
|
||||||
To: eval.Duration(3 * time.Hour),
|
To: models.Duration(3 * time.Hour),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.updateAlertDefinition(&q)
|
err := dbstore.UpdateAlertDefinition(&q)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.True(t, store.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
assert.True(t, dbstore.SQLStore.Dialect.IsUniqueConstraintViolation(err))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeletingAlertDefinition(t *testing.T) {
|
func TestDeletingAlertDefinition(t *testing.T) {
|
||||||
t.Run("zero rows affected when deleting unknown alert", func(t *testing.T) {
|
t.Run("zero rows affected when deleting unknown alert", func(t *testing.T) {
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
q := deleteAlertDefinitionByUIDCommand{
|
q := models.DeleteAlertDefinitionByUIDCommand{
|
||||||
UID: "unknown",
|
UID: "unknown",
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := store.deleteAlertDefinitionByUID(&q)
|
err := dbstore.DeleteAlertDefinitionByUID(&q)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deleting successfully existing alert", func(t *testing.T) {
|
t.Run("deleting successfully existing alert", func(t *testing.T) {
|
||||||
_, store := setupTestEnv(t, baseIntervalSeconds)
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
alertDefinition := createTestAlertDefinition(t, store, 60)
|
alertDefinition := createTestAlertDefinition(t, dbstore, 60)
|
||||||
|
|
||||||
q := deleteAlertDefinitionByUIDCommand{
|
q := models.DeleteAlertDefinitionByUIDCommand{
|
||||||
UID: (*alertDefinition).UID,
|
UID: (*alertDefinition).UID,
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
// save an instance for the definition
|
// save an instance for the definition
|
||||||
saveCmd := &saveAlertInstanceCommand{
|
saveCmd := &models.SaveAlertInstanceCommand{
|
||||||
DefinitionOrgID: alertDefinition.OrgID,
|
DefinitionOrgID: alertDefinition.OrgID,
|
||||||
DefinitionUID: alertDefinition.UID,
|
DefinitionUID: alertDefinition.UID,
|
||||||
State: InstanceStateFiring,
|
State: models.InstanceStateFiring,
|
||||||
Labels: InstanceLabels{"test": "testValue"},
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
}
|
}
|
||||||
err := store.saveAlertInstance(saveCmd)
|
err := dbstore.SaveAlertInstance(saveCmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
listCommand := &listAlertInstancesQuery{
|
listQuery := &models.ListAlertInstancesQuery{
|
||||||
DefinitionOrgID: alertDefinition.OrgID,
|
DefinitionOrgID: alertDefinition.OrgID,
|
||||||
DefinitionUID: alertDefinition.UID,
|
DefinitionUID: alertDefinition.UID,
|
||||||
}
|
}
|
||||||
err = store.listAlertInstances(listCommand)
|
err = dbstore.ListAlertInstances(listQuery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, listCommand.Result, 1)
|
require.Len(t, listQuery.Result, 1)
|
||||||
|
|
||||||
err = store.deleteAlertDefinitionByUID(&q)
|
err = dbstore.DeleteAlertDefinitionByUID(&q)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// assert that alert instance is deleted
|
// assert that alert instance is deleted
|
||||||
err = store.listAlertInstances(listCommand)
|
err = dbstore.ListAlertInstances(listQuery)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Len(t, listCommand.Result, 0)
|
require.Len(t, listQuery.Result, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
165
pkg/services/ngalert/tests/instance_database_test.go
Normal file
165
pkg/services/ngalert/tests/instance_database_test.go
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
// +build integration
|
||||||
|
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertInstanceOperations(t *testing.T) {
|
||||||
|
dbstore := setupTestEnv(t, baseIntervalSeconds)
|
||||||
|
|
||||||
|
alertDefinition1 := createTestAlertDefinition(t, dbstore, 60)
|
||||||
|
orgID := alertDefinition1.OrgID
|
||||||
|
|
||||||
|
alertDefinition2 := createTestAlertDefinition(t, dbstore, 60)
|
||||||
|
require.Equal(t, orgID, alertDefinition2.OrgID)
|
||||||
|
|
||||||
|
alertDefinition3 := createTestAlertDefinition(t, dbstore, 60)
|
||||||
|
require.Equal(t, orgID, alertDefinition3.OrgID)
|
||||||
|
|
||||||
|
alertDefinition4 := createTestAlertDefinition(t, dbstore, 60)
|
||||||
|
require.Equal(t, orgID, alertDefinition4.OrgID)
|
||||||
|
|
||||||
|
t.Run("can save and read new alert instance", func(t *testing.T) {
|
||||||
|
saveCmd := &models.SaveAlertInstanceCommand{
|
||||||
|
DefinitionOrgID: alertDefinition1.OrgID,
|
||||||
|
DefinitionUID: alertDefinition1.UID,
|
||||||
|
State: models.InstanceStateFiring,
|
||||||
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
|
}
|
||||||
|
err := dbstore.SaveAlertInstance(saveCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getCmd := &models.GetAlertInstanceQuery{
|
||||||
|
DefinitionOrgID: saveCmd.DefinitionOrgID,
|
||||||
|
DefinitionUID: saveCmd.DefinitionUID,
|
||||||
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbstore.GetAlertInstance(getCmd)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
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,
|
||||||
|
State: models.InstanceStateNormal,
|
||||||
|
}
|
||||||
|
err := dbstore.SaveAlertInstance(saveCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
getCmd := &models.GetAlertInstanceQuery{
|
||||||
|
DefinitionOrgID: saveCmd.DefinitionOrgID,
|
||||||
|
DefinitionUID: saveCmd.DefinitionUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 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,
|
||||||
|
State: models.InstanceStateFiring,
|
||||||
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dbstore.SaveAlertInstance(saveCmdOne)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
saveCmdTwo := &models.SaveAlertInstanceCommand{
|
||||||
|
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
||||||
|
DefinitionUID: saveCmdOne.DefinitionUID,
|
||||||
|
State: models.InstanceStateFiring,
|
||||||
|
Labels: models.InstanceLabels{"test": "meow"},
|
||||||
|
}
|
||||||
|
err = dbstore.SaveAlertInstance(saveCmdTwo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
listQuery := &models.ListAlertInstancesQuery{
|
||||||
|
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
||||||
|
DefinitionUID: saveCmdOne.DefinitionUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbstore.ListAlertInstances(listQuery)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, listQuery.Result, 2)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can list all added instances in org", func(t *testing.T) {
|
||||||
|
listQuery := &models.ListAlertInstancesQuery{
|
||||||
|
DefinitionOrgID: orgID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dbstore.ListAlertInstances(listQuery)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, listQuery.Result, 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("can list all added instances in org filtered by current state", func(t *testing.T) {
|
||||||
|
listQuery := &models.ListAlertInstancesQuery{
|
||||||
|
DefinitionOrgID: orgID,
|
||||||
|
State: models.InstanceStateNormal,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dbstore.ListAlertInstances(listQuery)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, listQuery.Result, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
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,
|
||||||
|
State: models.InstanceStateFiring,
|
||||||
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dbstore.SaveAlertInstance(saveCmdOne)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
saveCmdTwo := &models.SaveAlertInstanceCommand{
|
||||||
|
DefinitionOrgID: saveCmdOne.DefinitionOrgID,
|
||||||
|
DefinitionUID: saveCmdOne.DefinitionUID,
|
||||||
|
State: models.InstanceStateNormal,
|
||||||
|
Labels: models.InstanceLabels{"test": "testValue"},
|
||||||
|
}
|
||||||
|
err = dbstore.SaveAlertInstance(saveCmdTwo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
listQuery := &models.ListAlertInstancesQuery{
|
||||||
|
DefinitionOrgID: alertDefinition4.OrgID,
|
||||||
|
DefinitionUID: alertDefinition4.UID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dbstore.ListAlertInstances(listQuery)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, listQuery.Result, 1)
|
||||||
|
|
||||||
|
require.Equal(t, saveCmdTwo.DefinitionOrgID, listQuery.Result[0].DefinitionOrgID)
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -8,7 +8,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
@ -16,47 +21,49 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type evalAppliedInfo struct {
|
type evalAppliedInfo struct {
|
||||||
alertDefKey alertDefinitionKey
|
alertDefKey models.AlertDefinitionKey
|
||||||
now time.Time
|
now time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAlertingTicker(t *testing.T) {
|
func TestAlertingTicker(t *testing.T) {
|
||||||
ng, store := setupTestEnv(t, 1)
|
dbstore := setupTestEnv(t, 1)
|
||||||
t.Cleanup(registry.ClearOverrides)
|
t.Cleanup(registry.ClearOverrides)
|
||||||
|
|
||||||
alerts := make([]*AlertDefinition, 0)
|
alerts := make([]*models.AlertDefinition, 0)
|
||||||
// create alert definition with zero interval (should never run)
|
// create alert definition with zero interval (should never run)
|
||||||
alerts = append(alerts, createTestAlertDefinition(t, store, 0))
|
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 0))
|
||||||
|
|
||||||
// create alert definition with one second interval
|
// create alert definition with one second interval
|
||||||
alerts = append(alerts, createTestAlertDefinition(t, store, 1))
|
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 1))
|
||||||
|
|
||||||
evalAppliedCh := make(chan evalAppliedInfo, len(alerts))
|
evalAppliedCh := make(chan evalAppliedInfo, len(alerts))
|
||||||
stopAppliedCh := make(chan alertDefinitionKey, len(alerts))
|
stopAppliedCh := make(chan models.AlertDefinitionKey, len(alerts))
|
||||||
|
|
||||||
mockedClock := clock.NewMock()
|
mockedClock := clock.NewMock()
|
||||||
baseInterval := time.Second
|
baseInterval := time.Second
|
||||||
|
|
||||||
schefCfg := schedulerCfg{
|
schefCfg := schedule.SchedulerCfg{
|
||||||
c: mockedClock,
|
C: mockedClock,
|
||||||
baseInterval: baseInterval,
|
BaseInterval: baseInterval,
|
||||||
evalAppliedFunc: func(alertDefKey alertDefinitionKey, now time.Time) {
|
EvalAppliedFunc: func(alertDefKey models.AlertDefinitionKey, now time.Time) {
|
||||||
evalAppliedCh <- evalAppliedInfo{alertDefKey: alertDefKey, now: now}
|
evalAppliedCh <- evalAppliedInfo{alertDefKey: alertDefKey, now: now}
|
||||||
},
|
},
|
||||||
stopAppliedFunc: func(alertDefKey alertDefinitionKey) {
|
StopAppliedFunc: func(alertDefKey models.AlertDefinitionKey) {
|
||||||
stopAppliedCh <- alertDefKey
|
stopAppliedCh <- alertDefKey
|
||||||
},
|
},
|
||||||
|
Store: dbstore,
|
||||||
|
Logger: log.New("ngalert schedule test"),
|
||||||
}
|
}
|
||||||
ng.schedule.overrideCfg(schefCfg)
|
sched := schedule.NewScheduler(schefCfg, nil)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
go func() {
|
go func() {
|
||||||
err := ng.schedule.Ticker(ctx)
|
err := sched.Ticker(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}()
|
}()
|
||||||
runtime.Gosched()
|
runtime.Gosched()
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated := []alertDefinitionKey{alerts[1].getKey()}
|
expectedAlertDefinitionsEvaluated := []models.AlertDefinitionKey{alerts[1].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 1st tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 1st tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
@ -64,93 +71,93 @@ func TestAlertingTicker(t *testing.T) {
|
|||||||
|
|
||||||
// change alert definition interval to three seconds
|
// change alert definition interval to three seconds
|
||||||
var threeSecInterval int64 = 3
|
var threeSecInterval int64 = 3
|
||||||
err := store.updateAlertDefinition(&updateAlertDefinitionCommand{
|
err := dbstore.UpdateAlertDefinition(&models.UpdateAlertDefinitionCommand{
|
||||||
UID: alerts[0].UID,
|
UID: alerts[0].UID,
|
||||||
IntervalSeconds: &threeSecInterval,
|
IntervalSeconds: &threeSecInterval,
|
||||||
OrgID: alerts[0].OrgID,
|
OrgID: alerts[0].OrgID,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("alert definition: %v interval reset to: %d", alerts[0].getKey(), threeSecInterval)
|
t.Logf("alert definition: %v interval reset to: %d", alerts[0].GetKey(), threeSecInterval)
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[1].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 2nd tick alert definition: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 2nd tick alert definition: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[1].getKey(), alerts[0].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey(), alerts[0].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 3rd tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 3rd tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[1].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[1].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 4th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 4th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
err = store.deleteAlertDefinitionByUID(&deleteAlertDefinitionByUIDCommand{UID: alerts[1].UID, OrgID: alerts[1].OrgID})
|
err = dbstore.DeleteAlertDefinitionByUID(&models.DeleteAlertDefinitionByUIDCommand{UID: alerts[1].UID, OrgID: alerts[1].OrgID})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("alert definition: %v deleted", alerts[1].getKey())
|
t.Logf("alert definition: %v deleted", alerts[1].GetKey())
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{}
|
||||||
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
expectedAlertDefinitionsStopped := []alertDefinitionKey{alerts[1].getKey()}
|
expectedAlertDefinitionsStopped := []models.AlertDefinitionKey{alerts[1].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 5th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
|
||||||
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
|
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
|
||||||
})
|
})
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[0].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[0].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 6th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 6th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
// create alert definition with one second interval
|
// create alert definition with one second interval
|
||||||
alerts = append(alerts, createTestAlertDefinition(t, store, 1))
|
alerts = append(alerts, createTestAlertDefinition(t, dbstore, 1))
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[2].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[2].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 7th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 7th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
// pause alert definition
|
// pause alert definition
|
||||||
err = store.updateAlertDefinitionPaused(&updateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: true})
|
err = dbstore.UpdateAlertDefinitionPaused(&models.UpdateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: true})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("alert definition: %v paused", alerts[2].getKey())
|
t.Logf("alert definition: %v paused", alerts[2].GetKey())
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{}
|
||||||
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
|
|
||||||
expectedAlertDefinitionsStopped = []alertDefinitionKey{alerts[2].getKey()}
|
expectedAlertDefinitionsStopped = []models.AlertDefinitionKey{alerts[2].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 8th tick alert definitions: %s should be stopped", concatenate(expectedAlertDefinitionsStopped)), func(t *testing.T) {
|
||||||
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
|
assertStopRun(t, stopAppliedCh, expectedAlertDefinitionsStopped...)
|
||||||
})
|
})
|
||||||
|
|
||||||
// unpause alert definition
|
// unpause alert definition
|
||||||
err = store.updateAlertDefinitionPaused(&updateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: false})
|
err = dbstore.UpdateAlertDefinitionPaused(&models.UpdateAlertDefinitionPausedCommand{UIDs: []string{alerts[2].UID}, OrgID: alerts[2].OrgID, Paused: false})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("alert definition: %v unpaused", alerts[2].getKey())
|
t.Logf("alert definition: %v unpaused", alerts[2].GetKey())
|
||||||
|
|
||||||
expectedAlertDefinitionsEvaluated = []alertDefinitionKey{alerts[0].getKey(), alerts[2].getKey()}
|
expectedAlertDefinitionsEvaluated = []models.AlertDefinitionKey{alerts[0].GetKey(), alerts[2].GetKey()}
|
||||||
t.Run(fmt.Sprintf("on 9th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
t.Run(fmt.Sprintf("on 9th tick alert definitions: %s should be evaluated", concatenate(expectedAlertDefinitionsEvaluated)), func(t *testing.T) {
|
||||||
tick := advanceClock(t, mockedClock)
|
tick := advanceClock(t, mockedClock)
|
||||||
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
assertEvalRun(t, evalAppliedCh, tick, expectedAlertDefinitionsEvaluated...)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys ...alertDefinitionKey) {
|
func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys ...models.AlertDefinitionKey) {
|
||||||
timeout := time.After(time.Second)
|
timeout := time.After(time.Second)
|
||||||
|
|
||||||
expected := make(map[alertDefinitionKey]struct{}, len(keys))
|
expected := make(map[models.AlertDefinitionKey]struct{}, len(keys))
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
expected[k] = struct{}{}
|
expected[k] = struct{}{}
|
||||||
}
|
}
|
||||||
@ -175,10 +182,10 @@ func assertEvalRun(t *testing.T, ch <-chan evalAppliedInfo, tick time.Time, keys
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertStopRun(t *testing.T, ch <-chan alertDefinitionKey, keys ...alertDefinitionKey) {
|
func assertStopRun(t *testing.T, ch <-chan models.AlertDefinitionKey, keys ...models.AlertDefinitionKey) {
|
||||||
timeout := time.After(time.Second)
|
timeout := time.After(time.Second)
|
||||||
|
|
||||||
expected := make(map[alertDefinitionKey]struct{}, len(keys))
|
expected := make(map[models.AlertDefinitionKey]struct{}, len(keys))
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
expected[k] = struct{}{}
|
expected[k] = struct{}{}
|
||||||
}
|
}
|
||||||
@ -208,7 +215,7 @@ func advanceClock(t *testing.T, mockedClock *clock.Mock) time.Time {
|
|||||||
// t.Logf("Tick: %v", mockedClock.Now())
|
// t.Logf("Tick: %v", mockedClock.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func concatenate(keys []alertDefinitionKey) string {
|
func concatenate(keys []models.AlertDefinitionKey) string {
|
||||||
s := make([]string, len(keys))
|
s := make([]string, len(keys))
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
s = append(s, k.String())
|
s = append(s, k.String())
|
@ -1,4 +1,4 @@
|
|||||||
package ngalert
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -7,18 +7,25 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"github.com/grafana/grafana/pkg/api/routing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupTestEnv(t *testing.T, baseIntervalSeconds int64) (AlertNG, *storeImpl) {
|
// setupTestEnv initializes a store to used by the tests.
|
||||||
|
func setupTestEnv(t *testing.T, baseIntervalSeconds int64) *store.DBstore {
|
||||||
cfg := setting.NewCfg()
|
cfg := setting.NewCfg()
|
||||||
|
// AlertNG is disabled by default and only if it's enabled
|
||||||
|
// its database migrations run and the relative database tables are created
|
||||||
cfg.FeatureToggles = map[string]bool{"ngalert": true}
|
cfg.FeatureToggles = map[string]bool{"ngalert": true}
|
||||||
|
|
||||||
ng := overrideAlertNGInRegistry(t, cfg)
|
ng := overrideAlertNGInRegistry(t, cfg)
|
||||||
@ -26,18 +33,20 @@ func setupTestEnv(t *testing.T, baseIntervalSeconds int64) (AlertNG, *storeImpl)
|
|||||||
|
|
||||||
err := ng.Init()
|
err := ng.Init()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return ng, &storeImpl{SQLStore: ng.SQLStore, baseInterval: time.Duration(baseIntervalSeconds) * time.Second}
|
return &store.DBstore{SQLStore: ng.SQLStore, BaseInterval: time.Duration(baseIntervalSeconds) * time.Second}
|
||||||
}
|
}
|
||||||
|
|
||||||
func overrideAlertNGInRegistry(t *testing.T, cfg *setting.Cfg) AlertNG {
|
func overrideAlertNGInRegistry(t *testing.T, cfg *setting.Cfg) ngalert.AlertNG {
|
||||||
ng := AlertNG{
|
ng := ngalert.AlertNG{
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
RouteRegister: routing.NewRouteRegister(),
|
RouteRegister: routing.NewRouteRegister(),
|
||||||
log: log.New("ngalert-test"),
|
Log: log.New("ngalert-test"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hook for initialising the service after the Cfg is populated
|
||||||
|
// so that database migrations will run
|
||||||
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
overrideServiceFunc := func(descriptor registry.Descriptor) (*registry.Descriptor, bool) {
|
||||||
if _, ok := descriptor.Instance.(*AlertNG); ok {
|
if _, ok := descriptor.Instance.(*ngalert.AlertNG); ok {
|
||||||
return ®istry.Descriptor{
|
return ®istry.Descriptor{
|
||||||
Name: descriptor.Name,
|
Name: descriptor.Name,
|
||||||
Instance: &ng,
|
Instance: &ng,
|
||||||
@ -52,29 +61,30 @@ func overrideAlertNGInRegistry(t *testing.T, cfg *setting.Cfg) AlertNG {
|
|||||||
return ng
|
return ng
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestAlertDefinition(t *testing.T, store *storeImpl, intervalSeconds int64) *AlertDefinition {
|
// createTestAlertDefinition creates a dummy alert definition to be used by the tests.
|
||||||
cmd := saveAlertDefinitionCommand{
|
func createTestAlertDefinition(t *testing.T, store *store.DBstore, intervalSeconds int64) *models.AlertDefinition {
|
||||||
|
cmd := models.SaveAlertDefinitionCommand{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
Title: fmt.Sprintf("an alert definition %d", rand.Intn(1000)),
|
Title: fmt.Sprintf("an alert definition %d", rand.Intn(1000)),
|
||||||
Condition: "A",
|
Condition: "A",
|
||||||
Data: []eval.AlertQuery{
|
Data: []models.AlertQuery{
|
||||||
{
|
{
|
||||||
Model: json.RawMessage(`{
|
Model: json.RawMessage(`{
|
||||||
"datasource": "__expr__",
|
"datasource": "__expr__",
|
||||||
"type":"math",
|
"type":"math",
|
||||||
"expression":"2 + 2 > 1"
|
"expression":"2 + 2 > 1"
|
||||||
}`),
|
}`),
|
||||||
RelativeTimeRange: eval.RelativeTimeRange{
|
RelativeTimeRange: models.RelativeTimeRange{
|
||||||
From: eval.Duration(5 * time.Hour),
|
From: models.Duration(5 * time.Hour),
|
||||||
To: eval.Duration(3 * time.Hour),
|
To: models.Duration(3 * time.Hour),
|
||||||
},
|
},
|
||||||
RefID: "A",
|
RefID: "A",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
IntervalSeconds: &intervalSeconds,
|
IntervalSeconds: &intervalSeconds,
|
||||||
}
|
}
|
||||||
err := store.saveAlertDefinition(&cmd)
|
err := store.SaveAlertDefinition(&cmd)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
t.Logf("alert definition: %v with interval: %d created", cmd.Result.getKey(), intervalSeconds)
|
t.Logf("alert definition: %v with interval: %d created", cmd.Result.GetKey(), intervalSeconds)
|
||||||
return cmd.Result
|
return cmd.Result
|
||||||
}
|
}
|
@ -1,79 +0,0 @@
|
|||||||
package ngalert
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
|
||||||
)
|
|
||||||
|
|
||||||
const alertDefinitionMaxTitleLength = 190
|
|
||||||
|
|
||||||
var errEmptyTitleError = errors.New("title is empty")
|
|
||||||
|
|
||||||
// validateAlertDefinition validates the alert definition interval and organisation.
|
|
||||||
// If requireData is true checks that it contains at least one alert query
|
|
||||||
func (st storeImpl) validateAlertDefinition(alertDefinition *AlertDefinition, requireData bool) error {
|
|
||||||
if !requireData && len(alertDefinition.Data) == 0 {
|
|
||||||
return fmt.Errorf("no queries or expressions are found")
|
|
||||||
}
|
|
||||||
|
|
||||||
if alertDefinition.Title == "" {
|
|
||||||
return errEmptyTitleError
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateCondition validates that condition queries refer to existing datasources
|
|
||||||
func (api *apiImpl) validateCondition(c eval.Condition, user *models.SignedInUser, skipCache bool) error {
|
|
||||||
var refID string
|
|
||||||
|
|
||||||
if len(c.QueriesAndExpressions) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, query := range c.QueriesAndExpressions {
|
|
||||||
if c.RefID == query.RefID {
|
|
||||||
refID = c.RefID
|
|
||||||
}
|
|
||||||
|
|
||||||
datasourceUID, err := query.GetDatasource()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isExpression, err := query.IsExpression()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if isExpression {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = api.DatasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to get datasource: %s: %w", datasourceUID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if refID == "" {
|
|
||||||
return fmt.Errorf("condition %s not found in any query or expression", c.RefID)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user