Chore: Sql store split for legacy alerting (#52901)
Moves ~20 sqlstore methods for legacy alerting out of sqlstore (sqlstore.Store interface) and into alerting.
This commit is contained in:
@@ -29,7 +29,7 @@ type UsageStatsQuerier interface {
|
||||
// configured in Grafana.
|
||||
func (e *AlertEngine) QueryUsageStats(ctx context.Context) (*UsageStats, error) {
|
||||
cmd := &models.GetAllAlertsQuery{}
|
||||
err := e.sqlStore.GetAllAlertQueryHandler(ctx, cmd)
|
||||
err := e.AlertStore.GetAllAlertQueryHandler(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func (e *AlertEngine) mapRulesToUsageStats(ctx context.Context, rules []*models.
|
||||
result := map[string]int{}
|
||||
for k, v := range typeCount {
|
||||
query := &datasources.GetDataSourceQuery{Id: k}
|
||||
err := e.sqlStore.GetDataSource(ctx, query)
|
||||
err := e.datasourceService.GetDataSource(ctx, query)
|
||||
if err != nil {
|
||||
return map[string]int{}, nil
|
||||
}
|
||||
|
||||
@@ -12,12 +12,22 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fd "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
)
|
||||
|
||||
func TestAlertingUsageStats(t *testing.T) {
|
||||
store := &AlertStoreMock{}
|
||||
dsMock := &fd.FakeDataSourceService{
|
||||
DataSources: []*datasources.DataSource{
|
||||
{Id: 1, Type: datasources.DS_INFLUXDB},
|
||||
{Id: 2, Type: datasources.DS_GRAPHITE},
|
||||
{Id: 3, Type: datasources.DS_PROMETHEUS},
|
||||
{Id: 4, Type: datasources.DS_PROMETHEUS},
|
||||
},
|
||||
}
|
||||
ae := &AlertEngine{
|
||||
sqlStore: store,
|
||||
AlertStore: store,
|
||||
datasourceService: dsMock,
|
||||
}
|
||||
|
||||
store.getAllAlerts = func(ctx context.Context, query *models.GetAllAlertsQuery) error {
|
||||
@@ -41,23 +51,6 @@ func TestAlertingUsageStats(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
store.getDataSource = func(ctx context.Context, query *datasources.GetDataSourceQuery) error {
|
||||
ds := map[int64]*datasources.DataSource{
|
||||
1: {Type: "influxdb"},
|
||||
2: {Type: "graphite"},
|
||||
3: {Type: "prometheus"},
|
||||
4: {Type: "prometheus"},
|
||||
}
|
||||
|
||||
r, exist := ds[query.Id]
|
||||
if !exist {
|
||||
return datasources.ErrDataSourceNotFound
|
||||
}
|
||||
|
||||
query.Result = r
|
||||
return nil
|
||||
}
|
||||
|
||||
result, err := ae.QueryUsageStats(context.Background())
|
||||
require.NoError(t, err, "getAlertingUsage should not return error")
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange l
|
||||
OrgId: context.Rule.OrgID,
|
||||
}
|
||||
|
||||
if err := context.Store.GetDataSource(context.Ctx, getDsInfo); err != nil {
|
||||
if err := context.GetDataSource(context.Ctx, getDsInfo); err != nil {
|
||||
return nil, fmt.Errorf("could not find datasource: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fd "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
|
||||
@@ -138,7 +139,6 @@ func (rh fakeIntervalTestReqHandler) HandleRequest(ctx context.Context, dsInfo *
|
||||
func applyScenario(t *testing.T, timeRange string, dataSourceJsonData *simplejson.Json, queryModel string, verifier func(query legacydata.DataSubQuery)) {
|
||||
t.Run("desc", func(t *testing.T) {
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
store.ExpectedDatasource = &datasources.DataSource{Id: 1, Type: "graphite", JsonData: dataSourceJsonData}
|
||||
|
||||
ctx := &queryIntervalTestContext{}
|
||||
ctx.result = &alerting.EvalContext{
|
||||
@@ -146,6 +146,11 @@ func applyScenario(t *testing.T, timeRange string, dataSourceJsonData *simplejso
|
||||
Rule: &alerting.Rule{},
|
||||
RequestValidator: &validations.OSSPluginRequestValidator{},
|
||||
Store: store,
|
||||
DatasourceService: &fd.FakeDataSourceService{
|
||||
DataSources: []*datasources.DataSource{
|
||||
{Id: 1, Type: datasources.DS_GRAPHITE, JsonData: dataSourceJsonData},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
jsonModel, err := simplejson.NewJson([]byte(`{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fd "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
@@ -37,7 +38,6 @@ func TestQueryCondition(t *testing.T) {
|
||||
setup := func() *queryConditionTestContext {
|
||||
ctx := &queryConditionTestContext{}
|
||||
store := mockstore.NewSQLStoreMock()
|
||||
store.ExpectedDatasource = &datasources.DataSource{Id: 1, Type: "graphite"}
|
||||
|
||||
ctx.reducer = `{"type":"avg"}`
|
||||
ctx.evaluator = `{"type":"gt","params":[100]}`
|
||||
@@ -46,6 +46,11 @@ func TestQueryCondition(t *testing.T) {
|
||||
Rule: &alerting.Rule{},
|
||||
RequestValidator: &validations.OSSPluginRequestValidator{},
|
||||
Store: store,
|
||||
DatasourceService: &fd.FakeDataSourceService{
|
||||
DataSources: []*datasources.DataSource{
|
||||
{Id: 1, Type: datasources.DS_GRAPHITE},
|
||||
},
|
||||
},
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
@@ -25,19 +26,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
)
|
||||
|
||||
// AlertStore is a subset of SQLStore API to satisfy the needs of the alerting service.
|
||||
// A subset is needed to make it easier to mock during the tests.
|
||||
type AlertStore interface {
|
||||
GetAllAlertQueryHandler(context.Context, *models.GetAllAlertsQuery) error
|
||||
GetDataSource(context.Context, *datasources.GetDataSourceQuery) error
|
||||
SetAlertNotificationStateToCompleteCommand(context.Context, *models.SetAlertNotificationStateToCompleteCommand) error
|
||||
SetAlertNotificationStateToPendingCommand(context.Context, *models.SetAlertNotificationStateToPendingCommand) error
|
||||
GetAlertNotificationUidWithId(context.Context, *models.GetAlertNotificationUidQuery) error
|
||||
GetAlertNotificationsWithUidToSend(context.Context, *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
GetOrCreateAlertNotificationState(context.Context, *models.GetOrCreateNotificationStateQuery) error
|
||||
SetAlertState(context.Context, *models.SetAlertStateCommand) error
|
||||
}
|
||||
|
||||
// AlertEngine is the background process that
|
||||
// schedules alert evaluations and makes sure notifications
|
||||
// are sent.
|
||||
@@ -56,9 +44,10 @@ type AlertEngine struct {
|
||||
resultHandler resultHandler
|
||||
usageStatsService usagestats.Service
|
||||
tracer tracing.Tracer
|
||||
sqlStore AlertStore
|
||||
AlertStore AlertStore
|
||||
dashAlertExtractor DashAlertExtractor
|
||||
dashboardService dashboards.DashboardService
|
||||
datasourceService datasources.DataSourceService
|
||||
}
|
||||
|
||||
// IsDisabled returns true if the alerting service is disabled for this instance.
|
||||
@@ -69,8 +58,8 @@ func (e *AlertEngine) IsDisabled() bool {
|
||||
// ProvideAlertEngine returns a new AlertEngine.
|
||||
func ProvideAlertEngine(renderer rendering.Service, requestValidator models.PluginRequestValidator,
|
||||
dataService legacydata.RequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Internal,
|
||||
notificationService *notifications.NotificationService, tracer tracing.Tracer, sqlStore AlertStore, cfg *setting.Cfg,
|
||||
dashAlertExtractor DashAlertExtractor, dashboardService dashboards.DashboardService) *AlertEngine {
|
||||
notificationService *notifications.NotificationService, tracer tracing.Tracer, store AlertStore, cfg *setting.Cfg,
|
||||
dashAlertExtractor DashAlertExtractor, dashboardService dashboards.DashboardService, cacheService *localcache.CacheService, dsService datasources.DataSourceService) *AlertEngine {
|
||||
e := &AlertEngine{
|
||||
Cfg: cfg,
|
||||
RenderService: renderer,
|
||||
@@ -78,16 +67,17 @@ func ProvideAlertEngine(renderer rendering.Service, requestValidator models.Plug
|
||||
DataService: dataService,
|
||||
usageStatsService: usageStatsService,
|
||||
tracer: tracer,
|
||||
sqlStore: sqlStore,
|
||||
AlertStore: store,
|
||||
dashAlertExtractor: dashAlertExtractor,
|
||||
dashboardService: dashboardService,
|
||||
datasourceService: dsService,
|
||||
}
|
||||
e.execQueue = make(chan *Job, 1000)
|
||||
e.scheduler = newScheduler()
|
||||
e.evalHandler = NewEvalHandler(e.DataService)
|
||||
e.ruleReader = newRuleReader(sqlStore)
|
||||
e.ruleReader = newRuleReader(store)
|
||||
e.log = log.New("alerting.engine")
|
||||
e.resultHandler = newResultHandler(e.RenderService, sqlStore, notificationService, encryptionService.GetDecryptedValue)
|
||||
e.resultHandler = newResultHandler(e.RenderService, store, notificationService, encryptionService.GetDecryptedValue)
|
||||
|
||||
e.registerUsageMetrics()
|
||||
|
||||
@@ -203,7 +193,7 @@ func (e *AlertEngine) processJob(attemptID int, attemptChan chan int, cancelChan
|
||||
alertCtx, cancelFn := context.WithTimeout(context.Background(), setting.AlertingEvaluationTimeout)
|
||||
cancelChan <- cancelFn
|
||||
alertCtx, span := e.tracer.Start(alertCtx, "alert execution")
|
||||
evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator, e.sqlStore, e.dashboardService)
|
||||
evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator, e.AlertStore, e.dashboardService, e.datasourceService)
|
||||
evalContext.Ctx = alertCtx
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
datasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
|
||||
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -32,7 +34,8 @@ func TestIntegrationEngineTimeouts(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, nil, cfg, nil, nil)
|
||||
dsMock := &datasources.FakeDataSourceService{}
|
||||
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, nil, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock)
|
||||
setting.AlertingNotificationTimeout = 30 * time.Second
|
||||
setting.AlertingMaxAttempts = 3
|
||||
engine.resultHandler = &FakeResultHandler{}
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fd "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
|
||||
encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@@ -46,15 +48,11 @@ func (handler *FakeResultHandler) handle(evalContext *EvalContext) error {
|
||||
// A mock implementation of the AlertStore interface, allowing to override certain methods individually
|
||||
type AlertStoreMock struct {
|
||||
getAllAlerts func(context.Context, *models.GetAllAlertsQuery) error
|
||||
getDataSource func(context.Context, *datasources.GetDataSourceQuery) error
|
||||
getAlertNotificationsWithUidToSend func(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
getOrCreateNotificationState func(ctx context.Context, query *models.GetOrCreateNotificationStateQuery) error
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetDataSource(c context.Context, cmd *datasources.GetDataSourceQuery) error {
|
||||
if a.getDataSource != nil {
|
||||
return a.getDataSource(c, cmd)
|
||||
}
|
||||
func (a *AlertStoreMock) GetAlertById(c context.Context, cmd *models.GetAlertByIdQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -99,6 +97,22 @@ func (a *AlertStoreMock) SetAlertState(_ context.Context, _ *models.SetAlertStat
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) GetAlertStatesForDashboard(_ context.Context, _ *models.GetAlertStatesForDashboardQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) HandleAlertsQuery(context.Context, *models.GetAlertsQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) PauseAlert(context.Context, *models.PauseAlertCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AlertStoreMock) PauseAllAlerts(context.Context, *models.PauseAllAlertCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEngineProcessJob(t *testing.T) {
|
||||
usMock := &usagestats.UsageStatsMock{T: t}
|
||||
|
||||
@@ -111,7 +125,10 @@ func TestEngineProcessJob(t *testing.T) {
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
|
||||
store := &AlertStoreMock{}
|
||||
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, store, cfg, nil, nil)
|
||||
dsMock := &fd.FakeDataSourceService{
|
||||
DataSources: []*datasources.DataSource{{Id: 1, Type: datasources.DS_PROMETHEUS}},
|
||||
}
|
||||
engine := ProvideAlertEngine(nil, nil, nil, usMock, encService, nil, tracer, store, setting.NewCfg(), nil, nil, localcache.New(time.Minute, time.Minute), dsMock)
|
||||
setting.AlertingEvaluationTimeout = 30 * time.Second
|
||||
setting.AlertingNotificationTimeout = 30 * time.Second
|
||||
setting.AlertingMaxAttempts = 3
|
||||
@@ -128,11 +145,6 @@ func TestEngineProcessJob(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
store.getDataSource = func(ctx context.Context, q *datasources.GetDataSourceQuery) error {
|
||||
q.Result = &datasources.DataSource{Id: 1, Type: datasources.DS_PROMETHEUS}
|
||||
return nil
|
||||
}
|
||||
|
||||
report, err := usMock.GetUsageReport(context.Background())
|
||||
require.Nil(t, err)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
@@ -38,25 +39,27 @@ type EvalContext struct {
|
||||
|
||||
Ctx context.Context
|
||||
|
||||
Store AlertStore
|
||||
dashboardService dashboards.DashboardService
|
||||
Store AlertStore
|
||||
dashboardService dashboards.DashboardService
|
||||
DatasourceService datasources.DataSourceService
|
||||
}
|
||||
|
||||
// NewEvalContext is the EvalContext constructor.
|
||||
func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator,
|
||||
sqlStore AlertStore, dashboardService dashboards.DashboardService) *EvalContext {
|
||||
alertStore AlertStore, dashboardService dashboards.DashboardService, dsService datasources.DataSourceService) *EvalContext {
|
||||
return &EvalContext{
|
||||
Ctx: alertCtx,
|
||||
StartTime: time.Now(),
|
||||
Rule: rule,
|
||||
Logs: make([]*ResultLogEntry, 0),
|
||||
EvalMatches: make([]*EvalMatch, 0),
|
||||
AllMatches: make([]*EvalMatch, 0),
|
||||
Log: log.New("alerting.evalContext"),
|
||||
PrevAlertState: rule.State,
|
||||
RequestValidator: requestValidator,
|
||||
Store: sqlStore,
|
||||
dashboardService: dashboardService,
|
||||
Ctx: alertCtx,
|
||||
StartTime: time.Now(),
|
||||
Rule: rule,
|
||||
Logs: make([]*ResultLogEntry, 0),
|
||||
EvalMatches: make([]*EvalMatch, 0),
|
||||
AllMatches: make([]*EvalMatch, 0),
|
||||
Log: log.New("alerting.evalContext"),
|
||||
PrevAlertState: rule.State,
|
||||
RequestValidator: requestValidator,
|
||||
Store: alertStore,
|
||||
dashboardService: dashboardService,
|
||||
DatasourceService: dsService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +223,10 @@ func (c *EvalContext) evaluateNotificationTemplateFields() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *EvalContext) GetDataSource(ctx context.Context, q *datasources.GetDataSourceQuery) error {
|
||||
return c.DatasourceService.GetDataSource(ctx, q)
|
||||
}
|
||||
|
||||
// getTemplateMatches returns the values we should use to parse the templates
|
||||
func (c *EvalContext) getTemplateMatches() []*EvalMatch {
|
||||
// EvalMatches represent series violating the rule threshold,
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestStateIsUpdatedWhenNeeded(t *testing.T) {
|
||||
ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
ctx := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
t.Run("ok -> alerting", func(t *testing.T) {
|
||||
ctx.PrevAlertState = models.AlertStateOK
|
||||
@@ -199,7 +199,7 @@ func TestGetStateFromEvalContext(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
tc.applyFn(evalContext)
|
||||
newState := evalContext.GetNewState()
|
||||
@@ -391,7 +391,7 @@ func TestEvaluateNotificationTemplateFields(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(tt *testing.T) {
|
||||
evalContext := NewEvalContext(context.Background(), &Rule{Name: "Rule name: ${value1}", Message: "Rule message: ${value2}",
|
||||
Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.EvalMatches = test.evalMatches
|
||||
evalContext.AllMatches = test.allMatches
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Conditions: []Condition{&conditionStub{
|
||||
firing: true,
|
||||
}},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -39,7 +39,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
t.Run("Show return triggered with single passing condition2", func(t *testing.T) {
|
||||
context := NewEvalContext(context.Background(), &Rule{
|
||||
Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -52,7 +52,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -65,7 +65,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -78,7 +78,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -92,7 +92,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -106,7 +106,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -120,7 +120,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "and"},
|
||||
&conditionStub{firing: true, operator: "and"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -134,7 +134,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
&conditionStub{firing: true, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, true, context.Firing)
|
||||
@@ -148,7 +148,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
&conditionStub{firing: false, operator: "or"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -163,7 +163,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.False(t, context.NoDataFound)
|
||||
@@ -174,7 +174,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
Conditions: []Condition{
|
||||
&conditionStub{operator: "and", noData: true},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.Equal(t, false, context.Firing)
|
||||
@@ -187,7 +187,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "and", noData: true},
|
||||
&conditionStub{operator: "and", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.True(t, context.NoDataFound)
|
||||
@@ -199,7 +199,7 @@ func TestAlertingEvaluationHandler(t *testing.T) {
|
||||
&conditionStub{operator: "or", noData: true},
|
||||
&conditionStub{operator: "or", noData: false},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
handler.Eval(context)
|
||||
require.True(t, context.NoDataFound)
|
||||
|
||||
@@ -26,11 +26,11 @@ type DashAlertExtractorService struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideDashAlertExtractorService(datasourcePermissionsService permissions.DatasourcePermissionsService, datasourceService datasources.DataSourceService, alertStore AlertStore) *DashAlertExtractorService {
|
||||
func ProvideDashAlertExtractorService(datasourcePermissionsService permissions.DatasourcePermissionsService, datasourceService datasources.DataSourceService, store AlertStore) *DashAlertExtractorService {
|
||||
return &DashAlertExtractorService{
|
||||
datasourcePermissionsService: datasourcePermissionsService,
|
||||
datasourceService: datasourceService,
|
||||
alertStore: alertStore,
|
||||
alertStore: store,
|
||||
log: log.New("alerting.extractor"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ func TestAlertRuleExtraction(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Alert notifications are in DB", func(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
sqlStore := sqlStore{db: sqlstore.InitTestDB(t)}
|
||||
|
||||
firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
|
||||
err = sqlStore.CreateAlertNotificationCommand(context.Background(), &firstNotification)
|
||||
|
||||
@@ -20,18 +20,18 @@ import (
|
||||
func TestNotificationService(t *testing.T) {
|
||||
testRule := &Rule{Name: "Test", Message: "Something is bad"}
|
||||
store := &AlertStoreMock{}
|
||||
evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}, store, nil)
|
||||
evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}, store, nil, nil)
|
||||
|
||||
testRuleTemplated := &Rule{Name: "Test latency ${quantile}", Message: "Something is bad on instance ${instance}"}
|
||||
|
||||
evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil)
|
||||
evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil)
|
||||
evalCtxWithMatch.EvalMatches = []*EvalMatch{{
|
||||
Tags: map[string]string{
|
||||
"instance": "localhost:3000",
|
||||
"quantile": "0.99",
|
||||
},
|
||||
}}
|
||||
evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil)
|
||||
evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}, store, nil, nil)
|
||||
|
||||
notificationServiceScenario(t, "Given alert rule with upload image enabled should render and upload image and send notification",
|
||||
evalCtx, true, func(sc *scenarioContext) {
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) {
|
||||
am := &AlertmanagerNotifier{log: log.New("test.logger")}
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
State: tc.prevState,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
evalContext.Rule.State = tc.newState
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestShouldSendAlertNotification(t *testing.T) {
|
||||
for _, tc := range tcs {
|
||||
evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{
|
||||
State: tc.prevState,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
if tc.state == nil {
|
||||
tc.state = &models.AlertNotificationState{}
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestDingDingNotifier(t *testing.T) {
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
Message: `{host="localhost"}`,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
_, err = notifier.genBody(evalContext, "")
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
@@ -154,7 +154,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil, nil, nil)
|
||||
}, nil, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
@@ -203,7 +203,7 @@ func TestOpsGenieNotifier(t *testing.T) {
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
AlertRuleTags: tagPairs,
|
||||
}, nil, nil, nil)
|
||||
}, nil, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
tags := make([]string, 0)
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
@@ -198,7 +198,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
ID: 0,
|
||||
Name: "someRule",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext)
|
||||
@@ -256,7 +256,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
Name: "someRule",
|
||||
Message: "someMessage",
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
evalContext.EvalMatches = []*alerting.EvalMatch{
|
||||
{
|
||||
@@ -335,7 +335,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
{Key: "severity", Value: "warning"},
|
||||
{Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
@@ -414,7 +414,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "info"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
@@ -493,7 +493,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
||||
{Key: "component", Value: "aComponent"},
|
||||
{Key: "severity", Value: "llama"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png"
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ func TestGenPushoverBody(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateAlerting,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
|
||||
require.Nil(t, err)
|
||||
@@ -87,7 +87,7 @@ func TestGenPushoverBody(t *testing.T) {
|
||||
evalContext := alerting.NewEvalContext(context.Background(),
|
||||
&alerting.Rule{
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
_, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "")
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
@@ -61,7 +61,7 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "")
|
||||
require.LessOrEqual(t, len(caption), 1024)
|
||||
@@ -77,7 +77,7 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
@@ -95,7 +95,7 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
@@ -112,7 +112,7 @@ func TestTelegramNotifier(t *testing.T) {
|
||||
Name: "This is an alarm",
|
||||
Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri",
|
||||
State: models.AlertStateOK,
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
|
||||
caption := generateImageCaption(evalContext,
|
||||
"http://grafa.url/foo",
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
@@ -141,7 +141,7 @@ func TestVictoropsNotifier(t *testing.T) {
|
||||
{Key: "keyOnly"},
|
||||
{Key: "severity", Value: "warning"},
|
||||
},
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil)
|
||||
}, &validations.OSSPluginRequestValidator{}, nil, nil, nil)
|
||||
evalContext.IsTestRun = true
|
||||
|
||||
payload, err := victoropsNotifier.buildEventPayload(evalContext)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/tsdb/legacydata"
|
||||
@@ -84,7 +85,7 @@ func TestAlertRuleForParsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlertRuleModel(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
sqlStore := &sqlStore{db: sqlstore.InitTestDB(t), cache: localcache.New(time.Minute, time.Minute)}
|
||||
RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) {
|
||||
return &FakeCondition{}, nil
|
||||
})
|
||||
|
||||
@@ -7,21 +7,21 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
"github.com/grafana/grafana/pkg/services/notifications"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type AlertNotificationService struct {
|
||||
SQLStore *sqlstore.SQLStore
|
||||
SQLStore AlertNotificationStore
|
||||
EncryptionService encryption.Internal
|
||||
NotificationService *notifications.NotificationService
|
||||
}
|
||||
|
||||
func ProvideService(store *sqlstore.SQLStore, encryptionService encryption.Internal,
|
||||
func ProvideService(store db.DB, encryptionService encryption.Internal,
|
||||
notificationService *notifications.NotificationService) *AlertNotificationService {
|
||||
s := &AlertNotificationService{
|
||||
SQLStore: store,
|
||||
SQLStore: &sqlStore{db: store},
|
||||
EncryptionService: encryptionService,
|
||||
NotificationService: notificationService,
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
encryptionprovider "github.com/grafana/grafana/pkg/services/encryption/provider"
|
||||
@@ -17,7 +20,11 @@ import (
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
sqlStore := sqlstore.InitTestDB(t)
|
||||
sqlStore := &sqlStore{
|
||||
db: sqlstore.InitTestDB(t),
|
||||
log: &log.ConcreteLogger{},
|
||||
cache: localcache.New(time.Minute, time.Minute),
|
||||
}
|
||||
|
||||
nType := "test"
|
||||
registerTestNotifier(nType)
|
||||
@@ -30,7 +37,7 @@ func TestService(t *testing.T) {
|
||||
encService, err := encryptionservice.ProvideEncryptionService(encProvider, usMock, settings)
|
||||
require.NoError(t, err)
|
||||
|
||||
s := ProvideService(sqlStore, encService, nil)
|
||||
s := ProvideService(sqlStore.db, encService, nil)
|
||||
|
||||
origSecret := setting.SecretKey
|
||||
setting.SecretKey = "alert_notification_service_test"
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
||||
)
|
||||
|
||||
// AlertStore is a subset of SQLStore API to satisfy the needs of the alerting service.
|
||||
// A subset is needed to make it easier to mock during the tests.
|
||||
type AlertStore interface {
|
||||
GetAlertById(context.Context, *models.GetAlertByIdQuery) error
|
||||
GetAllAlertQueryHandler(context.Context, *models.GetAllAlertsQuery) error
|
||||
GetAlertStatesForDashboard(context.Context, *models.GetAlertStatesForDashboardQuery) error
|
||||
HandleAlertsQuery(context.Context, *models.GetAlertsQuery) error
|
||||
SetAlertNotificationStateToCompleteCommand(context.Context, *models.SetAlertNotificationStateToCompleteCommand) error
|
||||
SetAlertNotificationStateToPendingCommand(context.Context, *models.SetAlertNotificationStateToPendingCommand) error
|
||||
GetAlertNotificationUidWithId(context.Context, *models.GetAlertNotificationUidQuery) error
|
||||
GetAlertNotificationsWithUidToSend(context.Context, *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
GetOrCreateAlertNotificationState(context.Context, *models.GetOrCreateNotificationStateQuery) error
|
||||
SetAlertState(context.Context, *models.SetAlertStateCommand) error
|
||||
PauseAlert(context.Context, *models.PauseAlertCommand) error
|
||||
PauseAllAlerts(context.Context, *models.PauseAllAlertCommand) error
|
||||
}
|
||||
|
||||
type sqlStore struct {
|
||||
db db.DB
|
||||
cache *localcache.CacheService
|
||||
log *log.ConcreteLogger
|
||||
}
|
||||
|
||||
func ProvideAlertStore(
|
||||
db db.DB,
|
||||
cacheService *localcache.CacheService) AlertStore {
|
||||
return &sqlStore{
|
||||
db: db,
|
||||
cache: cacheService,
|
||||
log: log.New("alerting.store"),
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertById(ctx context.Context, query *models.GetAlertByIdQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alert := models.Alert{}
|
||||
has, err := sess.ID(query.Id).Get(&alert)
|
||||
if !has {
|
||||
return fmt.Errorf("could not find alert")
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = &alert
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAllAlertQueryHandler(ctx context.Context, query *models.GetAllAlertsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var alerts []*models.Alert
|
||||
err := sess.SQL("select * from alert").Find(&alerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = alerts
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func deleteAlertByIdInternal(alertId int64, reason string, sess *sqlstore.DBSession, log *log.ConcreteLogger) error {
|
||||
log.Debug("Deleting alert", "id", alertId, "reason", reason)
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM alert WHERE id = ?", alertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM annotation WHERE alert_id = ?", alertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM alert_notification_state WHERE alert_id = ?", alertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alertId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
builder := sqlstore.SQLBuilder{}
|
||||
|
||||
builder.Write(`SELECT
|
||||
alert.id,
|
||||
alert.dashboard_id,
|
||||
alert.panel_id,
|
||||
alert.name,
|
||||
alert.state,
|
||||
alert.new_state_date,
|
||||
alert.eval_data,
|
||||
alert.eval_date,
|
||||
alert.execution_error,
|
||||
dashboard.uid as dashboard_uid,
|
||||
dashboard.slug as dashboard_slug
|
||||
FROM alert
|
||||
INNER JOIN dashboard on dashboard.id = alert.dashboard_id `)
|
||||
|
||||
builder.Write(`WHERE alert.org_id = ?`, query.OrgId)
|
||||
|
||||
if len(strings.TrimSpace(query.Query)) > 0 {
|
||||
builder.Write(" AND alert.name "+ss.db.GetDialect().LikeStr()+" ?", "%"+query.Query+"%")
|
||||
}
|
||||
|
||||
if len(query.DashboardIDs) > 0 {
|
||||
builder.Write(` AND alert.dashboard_id IN (?` + strings.Repeat(",?", len(query.DashboardIDs)-1) + `) `)
|
||||
|
||||
for _, dbID := range query.DashboardIDs {
|
||||
builder.AddParams(dbID)
|
||||
}
|
||||
}
|
||||
|
||||
if query.PanelId != 0 {
|
||||
builder.Write(` AND alert.panel_id = ?`, query.PanelId)
|
||||
}
|
||||
|
||||
if len(query.State) > 0 && query.State[0] != "all" {
|
||||
builder.Write(` AND (`)
|
||||
for i, v := range query.State {
|
||||
if i > 0 {
|
||||
builder.Write(" OR ")
|
||||
}
|
||||
if strings.HasPrefix(v, "not_") {
|
||||
builder.Write("state <> ? ")
|
||||
v = strings.TrimPrefix(v, "not_")
|
||||
} else {
|
||||
builder.Write("state = ? ")
|
||||
}
|
||||
builder.AddParams(v)
|
||||
}
|
||||
builder.Write(")")
|
||||
}
|
||||
|
||||
if query.User.OrgRole != models.ROLE_ADMIN {
|
||||
builder.WriteDashboardPermissionFilter(query.User, models.PERMISSION_VIEW)
|
||||
}
|
||||
|
||||
builder.Write(" ORDER BY name ASC")
|
||||
|
||||
if query.Limit != 0 {
|
||||
builder.Write(ss.db.GetDialect().Limit(query.Limit))
|
||||
}
|
||||
|
||||
alerts := make([]*models.AlertListItemDTO, 0)
|
||||
if err := sess.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&alerts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range alerts {
|
||||
if alerts[i].ExecutionError == " " {
|
||||
alerts[i].ExecutionError = ""
|
||||
}
|
||||
}
|
||||
|
||||
query.Result = alerts
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
existingAlerts, err := GetAlertsByDashboardId2(dashID, sess)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := updateAlerts(existingAlerts, alerts, sess, ss.log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := deleteMissingAlerts(existingAlerts, alerts, sess, ss.log); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess *sqlstore.DBSession, log *log.ConcreteLogger) error {
|
||||
for _, alert := range alerts {
|
||||
update := false
|
||||
var alertToUpdate *models.Alert
|
||||
|
||||
for _, k := range existingAlerts {
|
||||
if alert.PanelId == k.PanelId {
|
||||
update = true
|
||||
alert.Id = k.Id
|
||||
alertToUpdate = k
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if update {
|
||||
if alertToUpdate.ContainsUpdates(alert) {
|
||||
alert.Updated = timeNow()
|
||||
alert.State = alertToUpdate.State
|
||||
sess.MustCols("message", "for")
|
||||
|
||||
_, err := sess.ID(alert.Id).Update(alert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Alert updated", "name", alert.Name, "id", alert.Id)
|
||||
}
|
||||
} else {
|
||||
alert.Updated = timeNow()
|
||||
alert.Created = timeNow()
|
||||
alert.State = models.AlertStateUnknown
|
||||
alert.NewStateDate = timeNow()
|
||||
|
||||
_, err := sess.Insert(alert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Alert inserted", "name", alert.Name, "id", alert.Id)
|
||||
}
|
||||
tags := alert.GetTagsFromSettings()
|
||||
if _, err := sess.Exec("DELETE FROM alert_rule_tag WHERE alert_id = ?", alert.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
if tags != nil {
|
||||
tags, err := sqlstore.EnsureTagsExist(sess, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tag := range tags {
|
||||
if _, err := sess.Exec("INSERT INTO alert_rule_tag (alert_id, tag_id) VALUES(?,?)", alert.Id, tag.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *sqlstore.DBSession, log *log.ConcreteLogger) error {
|
||||
for _, missingAlert := range alerts {
|
||||
missing := true
|
||||
|
||||
for _, k := range existingAlerts {
|
||||
if missingAlert.PanelId == k.PanelId {
|
||||
missing = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if missing {
|
||||
if err := deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess, log); err != nil {
|
||||
// No use trying to delete more, since we're in a transaction and it will be
|
||||
// rolled back on error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAlertsByDashboardId2(dashboardId int64, sess *sqlstore.DBSession) ([]*models.Alert, error) {
|
||||
alerts := make([]*models.Alert, 0)
|
||||
err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
|
||||
|
||||
if err != nil {
|
||||
return []*models.Alert{}, err
|
||||
}
|
||||
|
||||
return alerts, nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
alert := models.Alert{}
|
||||
|
||||
if has, err := sess.ID(cmd.AlertId).Get(&alert); err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return fmt.Errorf("could not find alert")
|
||||
}
|
||||
|
||||
if alert.State == models.AlertStatePaused {
|
||||
return models.ErrCannotChangeStateOnPausedAlert
|
||||
}
|
||||
|
||||
if alert.State == cmd.State {
|
||||
return models.ErrRequiresNewState
|
||||
}
|
||||
|
||||
alert.State = cmd.State
|
||||
alert.StateChanges++
|
||||
alert.NewStateDate = timeNow()
|
||||
alert.EvalData = cmd.EvalData
|
||||
|
||||
if cmd.Error == "" {
|
||||
alert.ExecutionError = " " // without this space, xorm skips updating this field
|
||||
} else {
|
||||
alert.ExecutionError = cmd.Error
|
||||
}
|
||||
|
||||
_, err := sess.ID(alert.Id).Update(&alert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = alert
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) PauseAlert(ctx context.Context, cmd *models.PauseAlertCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if len(cmd.AlertIds) == 0 {
|
||||
return fmt.Errorf("command contains no alertids")
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
buffer.WriteString(`UPDATE alert SET state = ?, new_state_date = ?`)
|
||||
if cmd.Paused {
|
||||
params = append(params, string(models.AlertStatePaused))
|
||||
params = append(params, timeNow().UTC())
|
||||
} else {
|
||||
params = append(params, string(models.AlertStateUnknown))
|
||||
params = append(params, timeNow().UTC())
|
||||
}
|
||||
|
||||
buffer.WriteString(` WHERE id IN (?` + strings.Repeat(",?", len(cmd.AlertIds)-1) + `)`)
|
||||
for _, v := range cmd.AlertIds {
|
||||
params = append(params, v)
|
||||
}
|
||||
|
||||
sqlOrArgs := append([]interface{}{buffer.String()}, params...)
|
||||
|
||||
res, err := sess.Exec(sqlOrArgs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.ResultCount, _ = res.RowsAffected()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) PauseAllAlerts(ctx context.Context, cmd *models.PauseAllAlertCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var newState string
|
||||
if cmd.Paused {
|
||||
newState = string(models.AlertStatePaused)
|
||||
} else {
|
||||
newState = string(models.AlertStateUnknown)
|
||||
}
|
||||
|
||||
res, err := sess.Exec(`UPDATE alert SET state = ?, new_state_date = ?`, newState, timeNow().UTC())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.ResultCount, _ = res.RowsAffected()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertStatesForDashboard(ctx context.Context, query *models.GetAlertStatesForDashboardQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var rawSQL = `SELECT
|
||||
id,
|
||||
dashboard_id,
|
||||
panel_id,
|
||||
state,
|
||||
new_state_date
|
||||
FROM alert
|
||||
WHERE org_id = ? AND dashboard_id = ?`
|
||||
|
||||
query.Result = make([]*models.AlertStateInfoDTO, 0)
|
||||
err := sess.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,604 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type AlertNotificationStore interface {
|
||||
DeleteAlertNotification(ctx context.Context, cmd *models.DeleteAlertNotificationCommand) error
|
||||
DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error
|
||||
GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error
|
||||
GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error
|
||||
GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error
|
||||
GetAllAlertNotifications(ctx context.Context, query *models.GetAllAlertNotificationsQuery) error
|
||||
GetAlertNotificationsWithUidToSend(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error
|
||||
CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error
|
||||
UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error
|
||||
UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error
|
||||
SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error
|
||||
SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error
|
||||
GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error
|
||||
}
|
||||
|
||||
// timeNow makes it possible to test usage of time
|
||||
var timeNow = time.Now
|
||||
|
||||
func (ss *sqlStore) DeleteAlertNotification(ctx context.Context, cmd *models.DeleteAlertNotificationCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
sql := "DELETE FROM alert_notification WHERE alert_notification.org_id = ? AND alert_notification.id = ?"
|
||||
res, err := sess.Exec(sql, cmd.OrgId, cmd.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return models.ErrAlertNotificationNotFound
|
||||
}
|
||||
|
||||
if _, err := sess.Exec("DELETE FROM alert_notification_state WHERE alert_notification_state.org_id = ? AND alert_notification_state.notifier_id = ?", cmd.OrgId, cmd.Id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error {
|
||||
existingNotification := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid}
|
||||
if err := getAlertNotificationWithUidInternal(ctx, existingNotification, ss.db.NewSession(ctx)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existingNotification.Result == nil {
|
||||
return models.ErrAlertNotificationNotFound
|
||||
}
|
||||
|
||||
cmd.DeletedAlertNotificationId = existingNotification.Result.Id
|
||||
deleteCommand := &models.DeleteAlertNotificationCommand{
|
||||
Id: existingNotification.Result.Id,
|
||||
OrgId: existingNotification.Result.OrgId,
|
||||
}
|
||||
if err := ss.DeleteAlertNotification(ctx, deleteCommand); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error {
|
||||
return getAlertNotificationInternal(ctx, query, ss.db.NewSession(ctx))
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error {
|
||||
cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id)
|
||||
|
||||
if cached, found := ss.cache.Get(cacheKey); found {
|
||||
query.Result = cached.(string)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := getAlertNotificationUidInternal(ctx, query, ss.db.NewSession(ctx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ss.cache.Set(cacheKey, query.Result, -1) // Infinite, never changes
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newAlertNotificationUidCacheKey(orgID, notificationId int64) string {
|
||||
return fmt.Sprintf("notification-uid-by-org-%d-and-id-%d", orgID, notificationId)
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error {
|
||||
return getAlertNotificationWithUidInternal(ctx, query, ss.db.NewSession(ctx))
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAllAlertNotifications(ctx context.Context, query *models.GetAllAlertNotificationsQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
results := make([]*models.AlertNotification, 0)
|
||||
if err := sess.Where("org_id = ?", query.OrgId).Asc("name").Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = results
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetAlertNotificationsWithUidToSend(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
alert_notification.id,
|
||||
alert_notification.uid,
|
||||
alert_notification.org_id,
|
||||
alert_notification.name,
|
||||
alert_notification.type,
|
||||
alert_notification.created,
|
||||
alert_notification.updated,
|
||||
alert_notification.settings,
|
||||
alert_notification.secure_settings,
|
||||
alert_notification.is_default,
|
||||
alert_notification.disable_resolve_message,
|
||||
alert_notification.send_reminder,
|
||||
alert_notification.frequency
|
||||
FROM alert_notification
|
||||
`)
|
||||
|
||||
sql.WriteString(` WHERE alert_notification.org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
sql.WriteString(` AND ((alert_notification.is_default = ?)`)
|
||||
params = append(params, ss.db.GetDialect().BooleanStr(true))
|
||||
|
||||
if len(query.Uids) > 0 {
|
||||
sql.WriteString(` OR alert_notification.uid IN (?` + strings.Repeat(",?", len(query.Uids)-1) + ")")
|
||||
for _, v := range query.Uids {
|
||||
params = append(params, v)
|
||||
}
|
||||
}
|
||||
sql.WriteString(`)`)
|
||||
|
||||
results := make([]*models.AlertNotification, 0)
|
||||
if err := sess.SQL(sql.String(), params...).Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = results
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getAlertNotificationUidInternal(ctx context.Context, query *models.GetAlertNotificationUidQuery, sess *sqlstore.DBSession) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
alert_notification.uid
|
||||
FROM alert_notification
|
||||
`)
|
||||
|
||||
sql.WriteString(` WHERE alert_notification.org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
sql.WriteString(` AND alert_notification.id = ?`)
|
||||
params = append(params, query.Id)
|
||||
|
||||
results := make([]string, 0)
|
||||
if err := sess.SQL(sql.String(), params...).Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
return models.ErrAlertNotificationFailedTranslateUniqueID
|
||||
}
|
||||
|
||||
query.Result = results[0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAlertNotificationInternal(ctx context.Context, query *models.GetAlertNotificationsQuery, sess *sqlstore.DBSession) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
alert_notification.id,
|
||||
alert_notification.uid,
|
||||
alert_notification.org_id,
|
||||
alert_notification.name,
|
||||
alert_notification.type,
|
||||
alert_notification.created,
|
||||
alert_notification.updated,
|
||||
alert_notification.settings,
|
||||
alert_notification.secure_settings,
|
||||
alert_notification.is_default,
|
||||
alert_notification.disable_resolve_message,
|
||||
alert_notification.send_reminder,
|
||||
alert_notification.frequency
|
||||
FROM alert_notification
|
||||
`)
|
||||
|
||||
sql.WriteString(` WHERE alert_notification.org_id = ?`)
|
||||
params = append(params, query.OrgId)
|
||||
|
||||
if query.Name != "" || query.Id != 0 {
|
||||
if query.Name != "" {
|
||||
sql.WriteString(` AND alert_notification.name = ?`)
|
||||
params = append(params, query.Name)
|
||||
}
|
||||
|
||||
if query.Id != 0 {
|
||||
sql.WriteString(` AND alert_notification.id = ?`)
|
||||
params = append(params, query.Id)
|
||||
}
|
||||
}
|
||||
|
||||
results := make([]*models.AlertNotification, 0)
|
||||
if err := sess.SQL(sql.String(), params...).Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
query.Result = nil
|
||||
} else {
|
||||
query.Result = results[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAlertNotificationWithUidInternal(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery, sess *sqlstore.DBSession) error {
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
alert_notification.id,
|
||||
alert_notification.uid,
|
||||
alert_notification.org_id,
|
||||
alert_notification.name,
|
||||
alert_notification.type,
|
||||
alert_notification.created,
|
||||
alert_notification.updated,
|
||||
alert_notification.settings,
|
||||
alert_notification.secure_settings,
|
||||
alert_notification.is_default,
|
||||
alert_notification.disable_resolve_message,
|
||||
alert_notification.send_reminder,
|
||||
alert_notification.frequency
|
||||
FROM alert_notification
|
||||
`)
|
||||
|
||||
sql.WriteString(` WHERE alert_notification.org_id = ? AND alert_notification.uid = ?`)
|
||||
params = append(params, query.OrgId, query.Uid)
|
||||
|
||||
results := make([]*models.AlertNotification, 0)
|
||||
if err := sess.SQL(sql.String(), params...).Find(&results); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
query.Result = nil
|
||||
} else {
|
||||
query.Result = results[0]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
if cmd.Uid == "" {
|
||||
uid, uidGenerationErr := generateNewAlertNotificationUid(ctx, sess, cmd.OrgId)
|
||||
if uidGenerationErr != nil {
|
||||
return uidGenerationErr
|
||||
}
|
||||
|
||||
cmd.Uid = uid
|
||||
}
|
||||
existingQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid}
|
||||
err := getAlertNotificationWithUidInternal(ctx, existingQuery, sess)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if existingQuery.Result != nil {
|
||||
return models.ErrAlertNotificationWithSameUIDExists
|
||||
}
|
||||
|
||||
// check if name exists
|
||||
sameNameQuery := &models.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name}
|
||||
if err := getAlertNotificationInternal(ctx, sameNameQuery, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sameNameQuery.Result != nil {
|
||||
return models.ErrAlertNotificationWithSameNameExists
|
||||
}
|
||||
|
||||
var frequency time.Duration
|
||||
if cmd.SendReminder {
|
||||
if cmd.Frequency == "" {
|
||||
return models.ErrNotificationFrequencyNotFound
|
||||
}
|
||||
|
||||
frequency, err = time.ParseDuration(cmd.Frequency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// delete empty keys
|
||||
for k, v := range cmd.SecureSettings {
|
||||
if v == "" {
|
||||
delete(cmd.SecureSettings, k)
|
||||
}
|
||||
}
|
||||
|
||||
alertNotification := &models.AlertNotification{
|
||||
Uid: cmd.Uid,
|
||||
OrgId: cmd.OrgId,
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
Settings: cmd.Settings,
|
||||
SecureSettings: cmd.EncryptedSecureSettings,
|
||||
SendReminder: cmd.SendReminder,
|
||||
DisableResolveMessage: cmd.DisableResolveMessage,
|
||||
Frequency: frequency,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
IsDefault: cmd.IsDefault,
|
||||
}
|
||||
|
||||
if _, err = sess.MustCols("send_reminder").Insert(alertNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = alertNotification
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func generateNewAlertNotificationUid(ctx context.Context, sess *sqlstore.DBSession, orgId int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&models.AlertNotification{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", models.ErrAlertNotificationFailedGenerateUniqueUid
|
||||
}
|
||||
|
||||
func (ss *sqlStore) UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) (err error) {
|
||||
current := models.AlertNotification{}
|
||||
|
||||
if _, err = sess.ID(cmd.Id).Get(¤t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if current.Id == 0 {
|
||||
return models.ErrAlertNotificationNotFound
|
||||
}
|
||||
|
||||
// check if name exists
|
||||
sameNameQuery := &models.GetAlertNotificationsQuery{OrgId: cmd.OrgId, Name: cmd.Name}
|
||||
if err := getAlertNotificationInternal(ctx, sameNameQuery, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sameNameQuery.Result != nil && sameNameQuery.Result.Id != current.Id {
|
||||
return fmt.Errorf("alert notification name %q already exists", cmd.Name)
|
||||
}
|
||||
|
||||
// delete empty keys
|
||||
for k, v := range cmd.SecureSettings {
|
||||
if v == "" {
|
||||
delete(cmd.SecureSettings, k)
|
||||
}
|
||||
}
|
||||
|
||||
current.Updated = time.Now()
|
||||
current.Settings = cmd.Settings
|
||||
current.SecureSettings = cmd.EncryptedSecureSettings
|
||||
current.Name = cmd.Name
|
||||
current.Type = cmd.Type
|
||||
current.IsDefault = cmd.IsDefault
|
||||
current.SendReminder = cmd.SendReminder
|
||||
current.DisableResolveMessage = cmd.DisableResolveMessage
|
||||
|
||||
if cmd.Uid != "" {
|
||||
current.Uid = cmd.Uid
|
||||
}
|
||||
|
||||
if current.SendReminder {
|
||||
if cmd.Frequency == "" {
|
||||
return models.ErrNotificationFrequencyNotFound
|
||||
}
|
||||
|
||||
frequency, err := time.ParseDuration(cmd.Frequency)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
current.Frequency = frequency
|
||||
}
|
||||
|
||||
sess.UseBool("is_default", "send_reminder", "disable_resolve_message")
|
||||
|
||||
if affected, err := sess.ID(cmd.Id).Update(current); err != nil {
|
||||
return err
|
||||
} else if affected == 0 {
|
||||
return fmt.Errorf("could not update alert notification")
|
||||
}
|
||||
|
||||
cmd.Result = ¤t
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error {
|
||||
getAlertNotificationWithUidQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid}
|
||||
|
||||
if err := getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, ss.db.NewSession(ctx)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
current := getAlertNotificationWithUidQuery.Result
|
||||
|
||||
if current == nil {
|
||||
return models.ErrAlertNotificationNotFound
|
||||
}
|
||||
|
||||
if cmd.NewUid == "" {
|
||||
cmd.NewUid = cmd.Uid
|
||||
}
|
||||
|
||||
updateNotification := &models.UpdateAlertNotificationCommand{
|
||||
Id: current.Id,
|
||||
Uid: cmd.NewUid,
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
SendReminder: cmd.SendReminder,
|
||||
DisableResolveMessage: cmd.DisableResolveMessage,
|
||||
Frequency: cmd.Frequency,
|
||||
IsDefault: cmd.IsDefault,
|
||||
Settings: cmd.Settings,
|
||||
SecureSettings: cmd.SecureSettings,
|
||||
|
||||
OrgId: cmd.OrgId,
|
||||
}
|
||||
|
||||
if err := ss.UpdateAlertNotification(ctx, updateNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = updateNotification.Result
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *sqlStore) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
version := cmd.Version
|
||||
var current models.AlertNotificationState
|
||||
if _, err := sess.ID(cmd.Id).Get(¤t); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newVersion := cmd.Version + 1
|
||||
sql := `UPDATE alert_notification_state SET
|
||||
state = ?,
|
||||
version = ?,
|
||||
updated_at = ?
|
||||
WHERE
|
||||
id = ?`
|
||||
|
||||
_, err := sess.Exec(sql, models.AlertNotificationStateCompleted, newVersion, timeNow().Unix(), cmd.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if current.Version != version {
|
||||
ss.log.Error("notification state out of sync. the notification is marked as complete but has been modified between set as pending and completion.", "notifierId", current.NotifierId)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error {
|
||||
return ss.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
newVersion := cmd.Version + 1
|
||||
sql := `UPDATE alert_notification_state SET
|
||||
state = ?,
|
||||
version = ?,
|
||||
updated_at = ?,
|
||||
alert_rule_state_updated_version = ?
|
||||
WHERE
|
||||
id = ? AND
|
||||
(version = ? OR alert_rule_state_updated_version < ?)`
|
||||
|
||||
res, err := sess.Exec(sql,
|
||||
models.AlertNotificationStatePending,
|
||||
newVersion,
|
||||
timeNow().Unix(),
|
||||
cmd.AlertRuleStateUpdatedVersion,
|
||||
cmd.Id,
|
||||
cmd.Version,
|
||||
cmd.AlertRuleStateUpdatedVersion)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
affected, _ := res.RowsAffected()
|
||||
if affected == 0 {
|
||||
return models.ErrAlertNotificationStateVersionConflict
|
||||
}
|
||||
|
||||
cmd.ResultVersion = newVersion
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error {
|
||||
return ss.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
nj := &models.AlertNotificationState{}
|
||||
|
||||
exist, err := getAlertNotificationState(ctx, sess, cmd, nj)
|
||||
|
||||
// if exists, return it, otherwise create it with default values
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exist {
|
||||
cmd.Result = nj
|
||||
return nil
|
||||
}
|
||||
|
||||
notificationState := &models.AlertNotificationState{
|
||||
OrgId: cmd.OrgId,
|
||||
AlertId: cmd.AlertId,
|
||||
NotifierId: cmd.NotifierId,
|
||||
State: models.AlertNotificationStateUnknown,
|
||||
UpdatedAt: timeNow().Unix(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(notificationState); err != nil {
|
||||
if ss.db.GetDialect().IsUniqueConstraintViolation(err) {
|
||||
exist, err = getAlertNotificationState(ctx, sess, cmd, nj)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return errors.New("should not happen")
|
||||
}
|
||||
|
||||
cmd.Result = nj
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = notificationState
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getAlertNotificationState(ctx context.Context, sess *sqlstore.DBSession, cmd *models.GetOrCreateNotificationStateQuery, nj *models.AlertNotificationState) (bool, error) {
|
||||
return sess.
|
||||
Where("alert_notification_state.org_id = ?", cmd.OrgId).
|
||||
Where("alert_notification_state.alert_id = ?", cmd.AlertId).
|
||||
Where("alert_notification_state.notifier_id = ?", cmd.NotifierId).
|
||||
Get(nj)
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIntegrationAlertNotificationSQLAccess(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
var store *sqlStore
|
||||
setup := func() {
|
||||
store = &sqlStore{
|
||||
db: sqlstore.InitTestDB(t),
|
||||
log: log.New(),
|
||||
cache: localcache.New(time.Minute, time.Minute)}
|
||||
}
|
||||
|
||||
t.Run("Alert notification state", func(t *testing.T) {
|
||||
setup()
|
||||
var alertID int64 = 7
|
||||
var orgID int64 = 5
|
||||
var notifierID int64 = 10
|
||||
oldTimeNow := timeNow
|
||||
now := time.Date(2018, 9, 30, 0, 0, 0, 0, time.UTC)
|
||||
timeNow = func() time.Time { return now }
|
||||
|
||||
defer func() { timeNow = oldTimeNow }()
|
||||
|
||||
t.Run("Get no existing state should create a new state", func(t *testing.T) {
|
||||
query := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID}
|
||||
err := store.GetOrCreateAlertNotificationState(context.Background(), query)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, query.Result)
|
||||
require.Equal(t, models.AlertNotificationStateUnknown, query.Result.State)
|
||||
require.Equal(t, int64(0), query.Result.Version)
|
||||
require.Equal(t, now.Unix(), query.Result.UpdatedAt)
|
||||
|
||||
t.Run("Get existing state should not create a new state", func(t *testing.T) {
|
||||
query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID}
|
||||
err := store.GetOrCreateAlertNotificationState(context.Background(), query2)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, query2.Result)
|
||||
require.Equal(t, query.Result.Id, query2.Result.Id)
|
||||
require.Equal(t, now.Unix(), query2.Result.UpdatedAt)
|
||||
})
|
||||
|
||||
t.Run("Update existing state to pending with correct version should update database", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
|
||||
cmd := models.SetAlertNotificationStateToPendingCommand{
|
||||
Id: s.Id,
|
||||
Version: s.Version,
|
||||
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
|
||||
}
|
||||
|
||||
err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(1), cmd.ResultVersion)
|
||||
|
||||
query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID}
|
||||
err = store.GetOrCreateAlertNotificationState(context.Background(), query2)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(1), query2.Result.Version)
|
||||
require.Equal(t, models.AlertNotificationStatePending, query2.Result.State)
|
||||
require.Equal(t, now.Unix(), query2.Result.UpdatedAt)
|
||||
|
||||
t.Run("Update existing state to completed should update database", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
setStateCmd := models.SetAlertNotificationStateToCompleteCommand{
|
||||
Id: s.Id,
|
||||
Version: cmd.ResultVersion,
|
||||
}
|
||||
err := store.SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID}
|
||||
err = store.GetOrCreateAlertNotificationState(context.Background(), query3)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, int64(2), query3.Result.Version)
|
||||
require.Equal(t, models.AlertNotificationStateCompleted, query3.Result.State)
|
||||
require.Equal(t, now.Unix(), query3.Result.UpdatedAt)
|
||||
})
|
||||
|
||||
t.Run("Update existing state to completed should update database. regardless of version", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
unknownVersion := int64(1000)
|
||||
cmd := models.SetAlertNotificationStateToCompleteCommand{
|
||||
Id: s.Id,
|
||||
Version: unknownVersion,
|
||||
}
|
||||
err := store.SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID}
|
||||
err = store.GetOrCreateAlertNotificationState(context.Background(), query3)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, unknownVersion+1, query3.Result.Version)
|
||||
require.Equal(t, models.AlertNotificationStateCompleted, query3.Result.State)
|
||||
require.Equal(t, now.Unix(), query3.Result.UpdatedAt)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Update existing state to pending with incorrect version should return version mismatch error", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
s.Version = 1000
|
||||
cmd := models.SetAlertNotificationStateToPendingCommand{
|
||||
Id: s.NotifierId,
|
||||
Version: s.Version,
|
||||
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
|
||||
}
|
||||
err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
|
||||
require.Equal(t, models.ErrAlertNotificationStateVersionConflict, err)
|
||||
})
|
||||
|
||||
t.Run("Updating existing state to pending with incorrect version since alert rule state update version is higher", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
cmd := models.SetAlertNotificationStateToPendingCommand{
|
||||
Id: s.Id,
|
||||
Version: s.Version,
|
||||
AlertRuleStateUpdatedVersion: 1000,
|
||||
}
|
||||
err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, int64(1), cmd.ResultVersion)
|
||||
})
|
||||
|
||||
t.Run("different version and same alert state change version should return error", func(t *testing.T) {
|
||||
s := *query.Result
|
||||
s.Version = 1000
|
||||
cmd := models.SetAlertNotificationStateToPendingCommand{
|
||||
Id: s.Id,
|
||||
Version: s.Version,
|
||||
AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion,
|
||||
}
|
||||
err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd)
|
||||
require.Error(t, err)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Alert notifications should be empty", func(t *testing.T) {
|
||||
setup()
|
||||
cmd := &models.GetAlertNotificationsQuery{
|
||||
OrgId: 2,
|
||||
Name: "email",
|
||||
}
|
||||
|
||||
err := store.GetAlertNotifications(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
require.Nil(t, cmd.Result)
|
||||
})
|
||||
|
||||
t.Run("Cannot save alert notifier with send reminder = true", func(t *testing.T) {
|
||||
setup()
|
||||
cmd := &models.CreateAlertNotificationCommand{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
OrgId: 1,
|
||||
SendReminder: true,
|
||||
Settings: simplejson.New(),
|
||||
}
|
||||
|
||||
t.Run("and missing frequency", func(t *testing.T) {
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Equal(t, models.ErrNotificationFrequencyNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("invalid frequency", func(t *testing.T) {
|
||||
cmd.Frequency = "invalid duration"
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString(
|
||||
err.Error()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Cannot update alert notifier with send reminder = false", func(t *testing.T) {
|
||||
setup()
|
||||
cmd := &models.CreateAlertNotificationCommand{
|
||||
Name: "ops update",
|
||||
Type: "email",
|
||||
OrgId: 1,
|
||||
SendReminder: false,
|
||||
Settings: simplejson.New(),
|
||||
}
|
||||
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
updateCmd := &models.UpdateAlertNotificationCommand{
|
||||
Id: cmd.Result.Id,
|
||||
SendReminder: true,
|
||||
}
|
||||
|
||||
t.Run("and missing frequency", func(t *testing.T) {
|
||||
err := store.UpdateAlertNotification(context.Background(), updateCmd)
|
||||
require.Equal(t, models.ErrNotificationFrequencyNotFound, err)
|
||||
})
|
||||
|
||||
t.Run("invalid frequency", func(t *testing.T) {
|
||||
updateCmd.Frequency = "invalid duration"
|
||||
|
||||
err := store.UpdateAlertNotification(context.Background(), updateCmd)
|
||||
require.Error(t, err)
|
||||
require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString(
|
||||
err.Error()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Can save Alert Notification", func(t *testing.T) {
|
||||
setup()
|
||||
cmd := &models.CreateAlertNotificationCommand{
|
||||
Name: "ops",
|
||||
Type: "email",
|
||||
OrgId: 1,
|
||||
SendReminder: true,
|
||||
Frequency: "10s",
|
||||
Settings: simplejson.New(),
|
||||
}
|
||||
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
require.NotEqual(t, 0, cmd.Result.Id)
|
||||
require.NotEqual(t, 0, cmd.Result.OrgId)
|
||||
require.Equal(t, "email", cmd.Result.Type)
|
||||
require.Equal(t, 10*time.Second, cmd.Result.Frequency)
|
||||
require.False(t, cmd.Result.DisableResolveMessage)
|
||||
require.NotEmpty(t, cmd.Result.Uid)
|
||||
|
||||
t.Run("Cannot save Alert Notification with the same name", func(t *testing.T) {
|
||||
err = store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Cannot save Alert Notification with the same name and another uid", func(t *testing.T) {
|
||||
anotherUidCmd := &models.CreateAlertNotificationCommand{
|
||||
Name: cmd.Name,
|
||||
Type: cmd.Type,
|
||||
OrgId: 1,
|
||||
SendReminder: cmd.SendReminder,
|
||||
Frequency: cmd.Frequency,
|
||||
Settings: cmd.Settings,
|
||||
Uid: "notifier1",
|
||||
}
|
||||
err = store.CreateAlertNotificationCommand(context.Background(), anotherUidCmd)
|
||||
require.Error(t, err)
|
||||
})
|
||||
t.Run("Can save Alert Notification with another name and another uid", func(t *testing.T) {
|
||||
anotherUidCmd := &models.CreateAlertNotificationCommand{
|
||||
Name: "another ops",
|
||||
Type: cmd.Type,
|
||||
OrgId: 1,
|
||||
SendReminder: cmd.SendReminder,
|
||||
Frequency: cmd.Frequency,
|
||||
Settings: cmd.Settings,
|
||||
Uid: "notifier2",
|
||||
}
|
||||
err = store.CreateAlertNotificationCommand(context.Background(), anotherUidCmd)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Can update alert notification", func(t *testing.T) {
|
||||
newCmd := &models.UpdateAlertNotificationCommand{
|
||||
Name: "NewName",
|
||||
Type: "webhook",
|
||||
OrgId: cmd.Result.OrgId,
|
||||
SendReminder: true,
|
||||
DisableResolveMessage: true,
|
||||
Frequency: "60s",
|
||||
Settings: simplejson.New(),
|
||||
Id: cmd.Result.Id,
|
||||
}
|
||||
err := store.UpdateAlertNotification(context.Background(), newCmd)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "NewName", newCmd.Result.Name)
|
||||
require.Equal(t, 60*time.Second, newCmd.Result.Frequency)
|
||||
require.True(t, newCmd.Result.DisableResolveMessage)
|
||||
})
|
||||
|
||||
t.Run("Can update alert notification to disable sending of reminders", func(t *testing.T) {
|
||||
newCmd := &models.UpdateAlertNotificationCommand{
|
||||
Name: "NewName",
|
||||
Type: "webhook",
|
||||
OrgId: cmd.Result.OrgId,
|
||||
SendReminder: false,
|
||||
Settings: simplejson.New(),
|
||||
Id: cmd.Result.Id,
|
||||
}
|
||||
err := store.UpdateAlertNotification(context.Background(), newCmd)
|
||||
require.Nil(t, err)
|
||||
require.False(t, newCmd.Result.SendReminder)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Can search using an array of ids", func(t *testing.T) {
|
||||
setup()
|
||||
cmd1 := models.CreateAlertNotificationCommand{Name: "nagios", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
|
||||
cmd2 := models.CreateAlertNotificationCommand{Name: "slack", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
|
||||
cmd3 := models.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
|
||||
cmd4 := models.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
|
||||
|
||||
otherOrg := models.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
|
||||
|
||||
require.Nil(t, store.CreateAlertNotificationCommand(context.Background(), &cmd1))
|
||||
require.Nil(t, store.CreateAlertNotificationCommand(context.Background(), &cmd2))
|
||||
require.Nil(t, store.CreateAlertNotificationCommand(context.Background(), &cmd3))
|
||||
require.Nil(t, store.CreateAlertNotificationCommand(context.Background(), &cmd4))
|
||||
require.Nil(t, store.CreateAlertNotificationCommand(context.Background(), &otherOrg))
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
query := &models.GetAlertNotificationsWithUidToSendQuery{
|
||||
Uids: []string{cmd1.Result.Uid, cmd2.Result.Uid, "112341231"},
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := store.GetAlertNotificationsWithUidToSend(context.Background(), query)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 3, len(query.Result))
|
||||
})
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
query := &models.GetAllAlertNotificationsQuery{
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err := store.GetAllAlertNotifications(context.Background(), query)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 4, len(query.Result))
|
||||
require.Equal(t, cmd4.Name, query.Result[0].Name)
|
||||
require.Equal(t, cmd1.Name, query.Result[1].Name)
|
||||
require.Equal(t, cmd3.Name, query.Result[2].Name)
|
||||
require.Equal(t, cmd2.Name, query.Result[3].Name)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Notification Uid by Id Caching", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
notification := &models.CreateAlertNotificationCommand{Uid: "aNotificationUid", OrgId: 1, Name: "aNotificationUid"}
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), notification)
|
||||
require.Nil(t, err)
|
||||
|
||||
byUidQuery := &models.GetAlertNotificationsWithUidQuery{
|
||||
Uid: notification.Uid,
|
||||
OrgId: notification.OrgId,
|
||||
}
|
||||
|
||||
notificationByUidErr := store.GetAlertNotificationsWithUid(context.Background(), byUidQuery)
|
||||
require.Nil(t, notificationByUidErr)
|
||||
|
||||
t.Run("Can cache notification Uid", func(t *testing.T) {
|
||||
byIdQuery := &models.GetAlertNotificationUidQuery{
|
||||
Id: byUidQuery.Result.Id,
|
||||
OrgId: byUidQuery.Result.OrgId,
|
||||
}
|
||||
|
||||
cacheKey := newAlertNotificationUidCacheKey(byIdQuery.OrgId, byIdQuery.Id)
|
||||
|
||||
resultBeforeCaching, foundBeforeCaching := store.cache.Get(cacheKey)
|
||||
require.False(t, foundBeforeCaching)
|
||||
require.Nil(t, resultBeforeCaching)
|
||||
|
||||
notificationByIdErr := store.GetAlertNotificationUidWithId(context.Background(), byIdQuery)
|
||||
require.Nil(t, notificationByIdErr)
|
||||
|
||||
resultAfterCaching, foundAfterCaching := store.cache.Get(cacheKey)
|
||||
require.True(t, foundAfterCaching)
|
||||
require.Equal(t, notification.Uid, resultAfterCaching)
|
||||
})
|
||||
|
||||
t.Run("Retrieves from cache when exists", func(t *testing.T) {
|
||||
query := &models.GetAlertNotificationUidQuery{
|
||||
Id: 999,
|
||||
OrgId: 100,
|
||||
}
|
||||
cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id)
|
||||
store.cache.Set(cacheKey, "a-cached-uid", -1)
|
||||
|
||||
err := store.GetAlertNotificationUidWithId(context.Background(), query)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "a-cached-uid", query.Result)
|
||||
})
|
||||
|
||||
t.Run("Returns an error without populating cache when the notification doesn't exist in the database", func(t *testing.T) {
|
||||
query := &models.GetAlertNotificationUidQuery{
|
||||
Id: -1,
|
||||
OrgId: 100,
|
||||
}
|
||||
|
||||
err := store.GetAlertNotificationUidWithId(context.Background(), query)
|
||||
require.Equal(t, "", query.Result)
|
||||
require.Error(t, err)
|
||||
require.True(t, errors.Is(err, models.ErrAlertNotificationFailedTranslateUniqueID))
|
||||
|
||||
cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id)
|
||||
result, found := store.cache.Get(cacheKey)
|
||||
require.False(t, found)
|
||||
require.Nil(t, result)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Cannot update non-existing Alert Notification", func(t *testing.T) {
|
||||
setup()
|
||||
updateCmd := &models.UpdateAlertNotificationCommand{
|
||||
Name: "NewName",
|
||||
Type: "webhook",
|
||||
OrgId: 1,
|
||||
SendReminder: true,
|
||||
DisableResolveMessage: true,
|
||||
Frequency: "60s",
|
||||
Settings: simplejson.New(),
|
||||
Id: 1,
|
||||
}
|
||||
err := store.UpdateAlertNotification(context.Background(), updateCmd)
|
||||
require.Equal(t, models.ErrAlertNotificationNotFound, err)
|
||||
|
||||
t.Run("using UID", func(t *testing.T) {
|
||||
updateWithUidCmd := &models.UpdateAlertNotificationWithUidCommand{
|
||||
Name: "NewName",
|
||||
Type: "webhook",
|
||||
OrgId: 1,
|
||||
SendReminder: true,
|
||||
DisableResolveMessage: true,
|
||||
Frequency: "60s",
|
||||
Settings: simplejson.New(),
|
||||
Uid: "uid",
|
||||
NewUid: "newUid",
|
||||
}
|
||||
err := store.UpdateAlertNotificationWithUid(context.Background(), updateWithUidCmd)
|
||||
require.Equal(t, models.ErrAlertNotificationNotFound, err)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Can delete Alert Notification", func(t *testing.T) {
|
||||
setup()
|
||||
cmd := &models.CreateAlertNotificationCommand{
|
||||
Name: "ops update",
|
||||
Type: "email",
|
||||
OrgId: 1,
|
||||
SendReminder: false,
|
||||
Settings: simplejson.New(),
|
||||
}
|
||||
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
deleteCmd := &models.DeleteAlertNotificationCommand{
|
||||
Id: cmd.Result.Id,
|
||||
OrgId: 1,
|
||||
}
|
||||
err = store.DeleteAlertNotification(context.Background(), deleteCmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("using UID", func(t *testing.T) {
|
||||
err := store.CreateAlertNotificationCommand(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
|
||||
deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{
|
||||
Uid: cmd.Result.Uid,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err = store.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, cmd.Result.Id, deleteWithUidCmd.DeletedAlertNotificationId)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Cannot delete non-existing Alert Notification", func(t *testing.T) {
|
||||
setup()
|
||||
deleteCmd := &models.DeleteAlertNotificationCommand{
|
||||
Id: 1,
|
||||
OrgId: 1,
|
||||
}
|
||||
err := store.DeleteAlertNotification(context.Background(), deleteCmd)
|
||||
require.Equal(t, models.ErrAlertNotificationNotFound, err)
|
||||
|
||||
t.Run("using UID", func(t *testing.T) {
|
||||
deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{
|
||||
Uid: "uid",
|
||||
OrgId: 1,
|
||||
}
|
||||
err = store.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd)
|
||||
require.Equal(t, models.ErrAlertNotificationNotFound, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/db"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func mockTimeNow() {
|
||||
var timeSeed int64
|
||||
timeNow = func() time.Time {
|
||||
loc := time.FixedZone("MockZoneUTC-5", -5*60*60)
|
||||
fakeNow := time.Unix(timeSeed, 0).In(loc)
|
||||
timeSeed++
|
||||
return fakeNow
|
||||
}
|
||||
}
|
||||
|
||||
func resetTimeNow() {
|
||||
timeNow = time.Now
|
||||
}
|
||||
|
||||
func TestIntegrationAlertingDataAccess(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
var store *sqlStore
|
||||
var testDash *models.Dashboard
|
||||
var items []*models.Alert
|
||||
|
||||
setup := func(t *testing.T) {
|
||||
store = &sqlStore{
|
||||
db: sqlstore.InitTestDB(t),
|
||||
log: log.New(),
|
||||
}
|
||||
|
||||
testDash = insertTestDashboard(t, store.db, "dashboard with alerts", 1, 0, false, "alert")
|
||||
evalData, err := simplejson.NewJson([]byte(`{"test": "test"}`))
|
||||
require.Nil(t, err)
|
||||
items = []*models.Alert{
|
||||
{
|
||||
PanelId: 1,
|
||||
DashboardId: testDash.Id,
|
||||
OrgId: testDash.OrgId,
|
||||
Name: "Alerting title",
|
||||
Message: "Alerting message",
|
||||
Settings: simplejson.New(),
|
||||
Frequency: 1,
|
||||
EvalData: evalData,
|
||||
},
|
||||
}
|
||||
|
||||
err = store.SaveAlerts(context.Background(), testDash.Id, items)
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
t.Run("Can set new states", func(t *testing.T) {
|
||||
setup(t)
|
||||
|
||||
// Get alert so we can use its ID in tests
|
||||
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &alertQuery)
|
||||
require.Nil(t, err2)
|
||||
|
||||
insertedAlert := alertQuery.Result[0]
|
||||
|
||||
t.Run("new state ok", func(t *testing.T) {
|
||||
cmd := &models.SetAlertStateCommand{
|
||||
AlertId: insertedAlert.Id,
|
||||
State: models.AlertStateOK,
|
||||
}
|
||||
|
||||
err := store.SetAlertState(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
alert, _ := getAlertById(t, insertedAlert.Id, store)
|
||||
stateDateBeforePause := alert.NewStateDate
|
||||
|
||||
t.Run("can pause all alerts", func(t *testing.T) {
|
||||
err := store.pauseAllAlerts(t, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("cannot updated paused alert", func(t *testing.T) {
|
||||
cmd := &models.SetAlertStateCommand{
|
||||
AlertId: insertedAlert.Id,
|
||||
State: models.AlertStateOK,
|
||||
}
|
||||
|
||||
err = store.SetAlertState(context.Background(), cmd)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("alert is paused", func(t *testing.T) {
|
||||
alert, _ = getAlertById(t, insertedAlert.Id, store)
|
||||
currentState := alert.State
|
||||
require.Equal(t, models.AlertStatePaused, currentState)
|
||||
})
|
||||
|
||||
t.Run("pausing alerts should update their NewStateDate", func(t *testing.T) {
|
||||
alert, _ = getAlertById(t, insertedAlert.Id, store)
|
||||
stateDateAfterPause := alert.NewStateDate
|
||||
require.True(t, stateDateBeforePause.Before(stateDateAfterPause))
|
||||
})
|
||||
|
||||
t.Run("unpausing alerts should update their NewStateDate again", func(t *testing.T) {
|
||||
err := store.pauseAllAlerts(t, false)
|
||||
require.Nil(t, err)
|
||||
alert, _ = getAlertById(t, insertedAlert.Id, store)
|
||||
stateDateAfterUnpause := alert.NewStateDate
|
||||
require.True(t, stateDateBeforePause.Before(stateDateAfterUnpause))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Can read properties", func(t *testing.T) {
|
||||
setup(t)
|
||||
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &alertQuery)
|
||||
|
||||
alert := alertQuery.Result[0]
|
||||
require.Nil(t, err2)
|
||||
require.Greater(t, alert.Id, int64(0))
|
||||
require.Equal(t, testDash.Id, alert.DashboardId)
|
||||
require.Equal(t, int64(1), alert.PanelId)
|
||||
require.Equal(t, "Alerting title", alert.Name)
|
||||
require.Equal(t, models.AlertStateUnknown, alert.State)
|
||||
require.NotNil(t, alert.NewStateDate)
|
||||
require.NotNil(t, alert.EvalData)
|
||||
require.Equal(t, "test", alert.EvalData.Get("test").MustString())
|
||||
require.NotNil(t, alert.EvalDate)
|
||||
require.Equal(t, "", alert.ExecutionError)
|
||||
require.NotNil(t, alert.DashboardUid)
|
||||
require.Equal(t, "dashboard-with-alerts", alert.DashboardSlug)
|
||||
})
|
||||
|
||||
t.Run("Viewer can read alerts", func(t *testing.T) {
|
||||
setup(t)
|
||||
viewerUser := &models.SignedInUser{OrgRole: models.ROLE_VIEWER, OrgId: 1}
|
||||
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: viewerUser}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &alertQuery)
|
||||
|
||||
require.Nil(t, err2)
|
||||
require.Equal(t, 1, len(alertQuery.Result))
|
||||
})
|
||||
|
||||
t.Run("Alerts with same dashboard id and panel id should update", func(t *testing.T) {
|
||||
setup(t)
|
||||
modifiedItems := items
|
||||
modifiedItems[0].Name = "Name"
|
||||
|
||||
err := store.SaveAlerts(context.Background(), testDash.Id, items)
|
||||
|
||||
t.Run("Can save alerts with same dashboard and panel id", func(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
})
|
||||
|
||||
t.Run("Alerts should be updated", func(t *testing.T) {
|
||||
query := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &query)
|
||||
|
||||
require.Nil(t, err2)
|
||||
require.Equal(t, 1, len(query.Result))
|
||||
require.Equal(t, "Name", query.Result[0].Name)
|
||||
|
||||
t.Run("Alert state should not be updated", func(t *testing.T) {
|
||||
require.Equal(t, models.AlertStateUnknown, query.Result[0].State)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Updates without changes should be ignored", func(t *testing.T) {
|
||||
err3 := store.SaveAlerts(context.Background(), testDash.Id, items)
|
||||
require.Nil(t, err3)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Multiple alerts per dashboard", func(t *testing.T) {
|
||||
setup(t)
|
||||
multipleItems := []*models.Alert{
|
||||
{
|
||||
DashboardId: testDash.Id,
|
||||
PanelId: 1,
|
||||
Name: "1",
|
||||
OrgId: 1,
|
||||
Settings: simplejson.New(),
|
||||
},
|
||||
{
|
||||
DashboardId: testDash.Id,
|
||||
PanelId: 2,
|
||||
Name: "2",
|
||||
OrgId: 1,
|
||||
Settings: simplejson.New(),
|
||||
},
|
||||
{
|
||||
DashboardId: testDash.Id,
|
||||
PanelId: 3,
|
||||
Name: "3",
|
||||
OrgId: 1,
|
||||
Settings: simplejson.New(),
|
||||
},
|
||||
}
|
||||
|
||||
err := store.SaveAlerts(context.Background(), testDash.Id, multipleItems)
|
||||
|
||||
t.Run("Should save 3 dashboards", func(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
|
||||
queryForDashboard := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &queryForDashboard)
|
||||
|
||||
require.Nil(t, err2)
|
||||
require.Equal(t, 3, len(queryForDashboard.Result))
|
||||
})
|
||||
|
||||
t.Run("should updated two dashboards and delete one", func(t *testing.T) {
|
||||
missingOneAlert := multipleItems[:2]
|
||||
|
||||
err = store.SaveAlerts(context.Background(), testDash.Id, missingOneAlert)
|
||||
|
||||
t.Run("should delete the missing alert", func(t *testing.T) {
|
||||
query := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &query)
|
||||
require.Nil(t, err2)
|
||||
require.Equal(t, 2, len(query.Result))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("When dashboard is removed", func(t *testing.T) {
|
||||
setup(t)
|
||||
items := []*models.Alert{
|
||||
{
|
||||
PanelId: 1,
|
||||
DashboardId: testDash.Id,
|
||||
Name: "Alerting title",
|
||||
Message: "Alerting message",
|
||||
},
|
||||
}
|
||||
|
||||
err := store.SaveAlerts(context.Background(), testDash.Id, items)
|
||||
require.Nil(t, err)
|
||||
|
||||
err = store.db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
dash := models.Dashboard{Id: testDash.Id, OrgId: 1}
|
||||
_, err := sess.Delete(dash)
|
||||
return err
|
||||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("Alerts should be removed", func(t *testing.T) {
|
||||
query := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := store.HandleAlertsQuery(context.Background(), &query)
|
||||
|
||||
require.Nil(t, err2)
|
||||
require.Equal(t, 0, len(query.Result))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationPausingAlerts(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
mockTimeNow()
|
||||
defer resetTimeNow()
|
||||
|
||||
t.Run("Given an alert", func(t *testing.T) {
|
||||
sqlStore := sqlStore{db: sqlstore.InitTestDB(t), log: log.New()}
|
||||
|
||||
testDash := insertTestDashboard(t, sqlStore.db, "dashboard with alerts", 1, 0, false, "alert")
|
||||
alert, err := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New(), sqlStore)
|
||||
require.Nil(t, err)
|
||||
|
||||
stateDateBeforePause := alert.NewStateDate
|
||||
stateDateAfterPause := stateDateBeforePause
|
||||
|
||||
// Get alert so we can use its ID in tests
|
||||
alertQuery := models.GetAlertsQuery{DashboardIDs: []int64{testDash.Id}, PanelId: 1, OrgId: 1, User: &models.SignedInUser{OrgRole: models.ROLE_ADMIN}}
|
||||
err2 := sqlStore.HandleAlertsQuery(context.Background(), &alertQuery)
|
||||
require.Nil(t, err2)
|
||||
|
||||
insertedAlert := alertQuery.Result[0]
|
||||
|
||||
t.Run("when paused", func(t *testing.T) {
|
||||
_, err := sqlStore.pauseAlert(t, testDash.OrgId, insertedAlert.Id, true)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("the NewStateDate should be updated", func(t *testing.T) {
|
||||
alert, err := getAlertById(t, insertedAlert.Id, &sqlStore)
|
||||
require.Nil(t, err)
|
||||
|
||||
stateDateAfterPause = alert.NewStateDate
|
||||
require.True(t, stateDateBeforePause.Before(stateDateAfterPause))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when unpaused", func(t *testing.T) {
|
||||
_, err := sqlStore.pauseAlert(t, testDash.OrgId, insertedAlert.Id, false)
|
||||
require.Nil(t, err)
|
||||
|
||||
t.Run("the NewStateDate should be updated again", func(t *testing.T) {
|
||||
alert, err := getAlertById(t, insertedAlert.Id, &sqlStore)
|
||||
require.Nil(t, err)
|
||||
|
||||
stateDateAfterUnpause := alert.NewStateDate
|
||||
require.True(t, stateDateAfterPause.Before(stateDateAfterUnpause))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *sqlStore) pauseAlert(t *testing.T, orgId int64, alertId int64, pauseState bool) (int64, error) {
|
||||
cmd := &models.PauseAlertCommand{
|
||||
OrgId: orgId,
|
||||
AlertIds: []int64{alertId},
|
||||
Paused: pauseState,
|
||||
}
|
||||
err := ss.PauseAlert(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
return cmd.ResultCount, err
|
||||
}
|
||||
|
||||
func insertTestAlert(title string, message string, orgId int64, dashId int64, settings *simplejson.Json, ss sqlStore) (*models.Alert, error) {
|
||||
items := []*models.Alert{
|
||||
{
|
||||
PanelId: 1,
|
||||
DashboardId: dashId,
|
||||
OrgId: orgId,
|
||||
Name: title,
|
||||
Message: message,
|
||||
Settings: settings,
|
||||
Frequency: 1,
|
||||
},
|
||||
}
|
||||
|
||||
err := ss.SaveAlerts(context.Background(), dashId, items)
|
||||
return items[0], err
|
||||
}
|
||||
|
||||
func getAlertById(t *testing.T, id int64, ss *sqlStore) (*models.Alert, error) {
|
||||
q := &models.GetAlertByIdQuery{
|
||||
Id: id,
|
||||
}
|
||||
err := ss.GetAlertById(context.Background(), q)
|
||||
require.Nil(t, err)
|
||||
return q.Result, err
|
||||
}
|
||||
|
||||
func (ss *sqlStore) pauseAllAlerts(t *testing.T, pauseState bool) error {
|
||||
cmd := &models.PauseAllAlertCommand{
|
||||
Paused: pauseState,
|
||||
}
|
||||
err := ss.PauseAllAlerts(context.Background(), cmd)
|
||||
require.Nil(t, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func insertTestDashboard(t *testing.T, db db.DB, title string, orgId int64,
|
||||
folderId int64, isFolder bool, tags ...interface{}) *models.Dashboard {
|
||||
t.Helper()
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
|
||||
var dash *models.Dashboard
|
||||
err := db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
dash = cmd.GetDashboardModel()
|
||||
dash.SetVersion(1)
|
||||
dash.Created = time.Now()
|
||||
dash.Updated = time.Now()
|
||||
dash.Uid = util.GenerateShortUID()
|
||||
_, err := sess.Insert(dash)
|
||||
return err
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, dash)
|
||||
dash.Data.Set("id", dash.Id)
|
||||
dash.Data.Set("uid", dash.Uid)
|
||||
|
||||
err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
dashVersion := &dashver.DashboardVersion{
|
||||
DashboardID: dash.Id,
|
||||
ParentVersion: dash.Version,
|
||||
RestoredFrom: cmd.RestoredFrom,
|
||||
Version: dash.Version,
|
||||
Created: time.Now(),
|
||||
CreatedBy: dash.UpdatedBy,
|
||||
Message: cmd.Message,
|
||||
Data: dash.Data,
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if affectedRows, err := sess.Insert(dashVersion); err != nil {
|
||||
return err
|
||||
} else if affectedRows == 0 {
|
||||
return dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return dash
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext {
|
||||
ID: rand.Int63(),
|
||||
}
|
||||
|
||||
ctx := NewEvalContext(context.Background(), testRule, fakeRequestValidator{}, nil, nil)
|
||||
ctx := NewEvalContext(context.Background(), testRule, fakeRequestValidator{}, nil, nil, nil)
|
||||
if cmd.Settings.Get("uploadImage").MustBool(true) {
|
||||
ctx.ImagePublicURL = "https://grafana.com/assets/img/blog/mixed_styles.png"
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ func (e *AlertEngine) AlertTest(orgID int64, dashboard *simplejson.Json, panelID
|
||||
if alert.PanelId != panelID {
|
||||
continue
|
||||
}
|
||||
rule, err := NewRuleFromDBAlert(context.Background(), e.sqlStore, alert, true)
|
||||
rule, err := NewRuleFromDBAlert(context.Background(), e.AlertStore, alert, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler := NewEvalHandler(e.DataService)
|
||||
|
||||
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, e.sqlStore, nil)
|
||||
context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}, e.AlertStore, nil, e.datasourceService)
|
||||
context.IsTestRun = true
|
||||
context.IsDebug = true
|
||||
|
||||
|
||||
Reference in New Issue
Block a user