diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 6f49f6ba820..1599c8b3fdf 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -28,7 +28,7 @@ func (hs *HTTPServer) ValidateOrgAlert(c *models.ReqContext) { } query := models.GetAlertByIdQuery{Id: id} - if err := hs.SQLStore.GetAlertById(c.Req.Context(), &query); err != nil { + if err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query); err != nil { c.JsonApiErr(404, "Alert not found", nil) return } @@ -60,7 +60,7 @@ func (hs *HTTPServer) GetAlertStatesForDashboard(c *models.ReqContext) response. DashboardId: c.QueryInt64("dashboardId"), } - if err := hs.SQLStore.GetAlertStatesForDashboard(c.Req.Context(), &query); err != nil { + if err := hs.AlertEngine.AlertStore.GetAlertStatesForDashboard(c.Req.Context(), &query); err != nil { return response.Error(500, "Failed to fetch alert states", err) } @@ -141,7 +141,7 @@ func (hs *HTTPServer) GetAlerts(c *models.ReqContext) response.Response { query.State = states } - if err := hs.SQLStore.HandleAlertsQuery(c.Req.Context(), &query); err != nil { + if err := hs.AlertEngine.AlertStore.HandleAlertsQuery(c.Req.Context(), &query); err != nil { return response.Error(500, "List alerts failed", err) } @@ -223,7 +223,7 @@ func (hs *HTTPServer) GetAlert(c *models.ReqContext) response.Response { } query := models.GetAlertByIdQuery{Id: id} - if err := hs.SQLStore.GetAlertById(c.Req.Context(), &query); err != nil { + if err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query); err != nil { return response.Error(500, "List alerts failed", err) } @@ -689,7 +689,7 @@ func (hs *HTTPServer) PauseAlert(legacyAlertingEnabled *bool) func(c *models.Req result["alertId"] = alertID query := models.GetAlertByIdQuery{Id: alertID} - if err := hs.SQLStore.GetAlertById(c.Req.Context(), &query); err != nil { + if err := hs.AlertEngine.AlertStore.GetAlertById(c.Req.Context(), &query); err != nil { return response.Error(500, "Get Alert failed", err) } @@ -719,7 +719,7 @@ func (hs *HTTPServer) PauseAlert(legacyAlertingEnabled *bool) func(c *models.Req Paused: dto.Paused, } - if err := hs.SQLStore.PauseAlert(c.Req.Context(), &cmd); err != nil { + if err := hs.AlertEngine.AlertStore.PauseAlert(c.Req.Context(), &cmd); err != nil { return response.Error(500, "", err) } @@ -764,7 +764,7 @@ func (hs *HTTPServer) PauseAllAlerts(legacyAlertingEnabled *bool) func(c *models Paused: dto.Paused, } - if err := hs.SQLStore.PauseAllAlerts(c.Req.Context(), &updateCmd); err != nil { + if err := hs.AlertEngine.AlertStore.PauseAllAlerts(c.Req.Context(), &updateCmd); err != nil { return response.Error(500, "Failed to pause alerts", err) } diff --git a/pkg/api/alerting_test.go b/pkg/api/alerting_test.go deleted file mode 100644 index da3b80aaba1..00000000000 --- a/pkg/api/alerting_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package api - -import ( - "context" - "fmt" - "testing" - - "github.com/grafana/grafana/pkg/api/routing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/api/dtos" - "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/guardian" - "github.com/grafana/grafana/pkg/services/search" - "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" -) - -var ( - viewerRole = models.ROLE_VIEWER - editorRole = models.ROLE_EDITOR -) - -type setUpConf struct { - aclMockResp []*models.DashboardACLInfoDTO -} - -type mockSearchService struct{ ExpectedResult models.HitList } - -func (mss *mockSearchService) SearchHandler(_ context.Context, q *search.Query) error { - q.Result = mss.ExpectedResult - return nil -} -func (mss *mockSearchService) SortOptions() []models.SortOption { return nil } - -func setUp(confs ...setUpConf) *HTTPServer { - singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"} - store := mockstore.NewSQLStoreMock() - hs := &HTTPServer{SQLStore: store, SearchService: &mockSearchService{}} - store.ExpectedAlert = singleAlert - - aclMockResp := []*models.DashboardACLInfoDTO{} - for _, c := range confs { - if c.aclMockResp != nil { - aclMockResp = c.aclMockResp - } - } - store.ExpectedTeamsByUser = []*models.TeamDTO{} - dashSvc := &dashboards.FakeDashboardService{} - dashSvc.On("GetDashboardACLInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardACLInfoListQuery")).Run(func(args mock.Arguments) { - q := args.Get(1).(*models.GetDashboardACLInfoListQuery) - q.Result = aclMockResp - }).Return(nil) - guardian.InitLegacyGuardian(store, dashSvc) - return hs -} - -func TestAlertingAPIEndpoint(t *testing.T) { - t.Run("When user is editor and not in the ACL", func(t *testing.T) { - hs := setUp() - cmd := dtos.PauseAlertCommand{ - AlertId: 1, - Paused: true, - } - postAlertScenario(t, hs, "When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", - models.ROLE_EDITOR, cmd, func(sc *scenarioContext) { - setUp() - - callPauseAlert(sc) - assert.Equal(t, 403, sc.resp.Code) - }) - }) - - t.Run("When user is editor and dashboard has default ACL", func(t *testing.T) { - hs := setUp() - cmd := dtos.PauseAlertCommand{ - AlertId: 1, - Paused: true, - } - postAlertScenario(t, hs, "When calling POST on", "/api/alerts/1/pause", "/api/alerts/:alertId/pause", - models.ROLE_EDITOR, cmd, func(sc *scenarioContext) { - setUp(setUpConf{ - aclMockResp: []*models.DashboardACLInfoDTO{ - {Role: &viewerRole, Permission: models.PERMISSION_VIEW}, - {Role: &editorRole, Permission: models.PERMISSION_EDIT}, - }, - }) - - callPauseAlert(sc) - assert.Equal(t, 200, sc.resp.Code) - }) - }) - - t.Run("When calling GET", func(t *testing.T) { - hs := setUp() - loggedInUserScenarioWithRole(t, "When calling GET on", "GET", - "/api/alerts?dashboardId=1&dashboardId=2&folderId=3&dashboardTag=abc&dashboardQuery=dbQuery&limit=5&query=alertQuery", - "/api/alerts", models.ROLE_EDITOR, func(sc *scenarioContext) { - hs.SearchService.(*mockSearchService).ExpectedResult = models.HitList{ - &models.Hit{ID: 1}, - &models.Hit{ID: 2}, - } - - sc.handlerFunc = hs.GetAlerts - sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() - - getAlertsQuery := hs.SQLStore.(*mockstore.SQLStoreMock).LastGetAlertsQuery - - require.NotNil(t, getAlertsQuery) - assert.Equal(t, int64(1), getAlertsQuery.DashboardIDs[0]) - assert.Equal(t, int64(2), getAlertsQuery.DashboardIDs[1]) - assert.Equal(t, int64(5), getAlertsQuery.Limit) - assert.Equal(t, "alertQuery", getAlertsQuery.Query) - }, hs.SQLStore) - }) - - t.Run("When calling GET on alert-notifications", func(t *testing.T) { - hs := setUp() - loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/alert-notifications/1", - "/alert-notifications/:notificationId", models.ROLE_ADMIN, func(sc *scenarioContext) { - sc.handlerFunc = hs.GetAlertNotificationByID - sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() - assert.Equal(t, 404, sc.resp.Code) - }, hs.SQLStore) - }) -} - -func callPauseAlert(sc *scenarioContext) { - sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() -} - -func postAlertScenario(t *testing.T, hs *HTTPServer, desc string, url string, routePattern string, role models.RoleType, - cmd dtos.PauseAlertCommand, fn scenarioFunc) { - t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { - sc := setupScenarioContext(t, url) - sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response { - c.Req.Body = mockRequestBody(cmd) - c.Req.Header.Add("Content-Type", "application/json") - sc.context = c - sc.context.UserId = testUserID - sc.context.OrgId = testOrgID - sc.context.OrgRole = role - - legacyAlertingEnabled := new(bool) - *legacyAlertingEnabled = true - return hs.PauseAlert(legacyAlertingEnabled)(c) - }) - - sc.m.Post(routePattern, sc.defaultHandler) - - fn(sc) - }) -} diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 94af971ed47..b27d3d9765f 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -11,6 +11,7 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/response" @@ -33,6 +34,7 @@ import ( dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/login/loginservice" @@ -40,9 +42,11 @@ import ( "github.com/grafana/grafana/pkg/services/preference/preftest" "github.com/grafana/grafana/pkg/services/quota/quotaimpl" "github.com/grafana/grafana/pkg/services/rendering" + "github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/searchusers" "github.com/grafana/grafana/pkg/services/searchusers/filters" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" @@ -468,3 +472,42 @@ func SetupAPITestServer(t *testing.T, opts ...APITestServerOption) *webtest.Serv s := webtest.NewServer(t, hs.RouteRegister) return s } + +var ( + viewerRole = models.ROLE_VIEWER + editorRole = models.ROLE_EDITOR +) + +type setUpConf struct { + aclMockResp []*models.DashboardACLInfoDTO +} + +type mockSearchService struct{ ExpectedResult models.HitList } + +func (mss *mockSearchService) SearchHandler(_ context.Context, q *search.Query) error { + q.Result = mss.ExpectedResult + return nil +} +func (mss *mockSearchService) SortOptions() []models.SortOption { return nil } + +func setUp(confs ...setUpConf) *HTTPServer { + singleAlert := &models.Alert{Id: 1, DashboardId: 1, Name: "singlealert"} + store := mockstore.NewSQLStoreMock() + hs := &HTTPServer{SQLStore: store, SearchService: &mockSearchService{}} + store.ExpectedAlert = singleAlert + + aclMockResp := []*models.DashboardACLInfoDTO{} + for _, c := range confs { + if c.aclMockResp != nil { + aclMockResp = c.aclMockResp + } + } + store.ExpectedTeamsByUser = []*models.TeamDTO{} + dashSvc := &dashboards.FakeDashboardService{} + dashSvc.On("GetDashboardACLInfoList", mock.Anything, mock.AnythingOfType("*models.GetDashboardACLInfoListQuery")).Run(func(args mock.Arguments) { + q := args.Get(1).(*models.GetDashboardACLInfoListQuery) + q.Result = aclMockResp + }).Return(nil) + guardian.InitLegacyGuardian(store, dashSvc) + return hs +} diff --git a/pkg/cmd/grafana-cli/runner/wire.go b/pkg/cmd/grafana-cli/runner/wire.go index 0dc5666ccca..792c13fefcd 100644 --- a/pkg/cmd/grafana-cli/runner/wire.go +++ b/pkg/cmd/grafana-cli/runner/wire.go @@ -156,6 +156,7 @@ var wireSet = wire.NewSet( hooks.ProvideService, legacydataservice.ProvideService, wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)), + alerting.ProvideAlertStore, alerting.ProvideAlertEngine, wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)), api.ProvideHTTPServer, @@ -307,7 +308,6 @@ var wireSet = wire.NewSet( wire.Bind(new(secretsMigrations.SecretMigrationService), new(*secretsMigrations.SecretMigrationServiceImpl)), userauthimpl.ProvideService, ngmetrics.ProvideServiceForTest, - wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)), wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), notifications.MockNotificationService, wire.Bind(new(notifications.TempUserStore), new(*mockstore.SQLStoreMock)), diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 61daad69d66..17f2e479fd8 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -139,6 +139,7 @@ import ( var wireBasicSet = wire.NewSet( legacydataservice.ProvideService, wire.Bind(new(legacydata.RequestHandler), new(*legacydataservice.Service)), + alerting.ProvideAlertStore, alerting.ProvideAlertEngine, wire.Bind(new(alerting.UsageStatsQuerier), new(*alerting.AlertEngine)), setting.NewCfgFromArgs, @@ -316,7 +317,6 @@ var wireBasicSet = wire.NewSet( var wireSet = wire.NewSet( wireBasicSet, sqlstore.ProvideService, - wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)), wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), ngmetrics.ProvideService, wire.Bind(new(notifications.TempUserStore), new(*sqlstore.SQLStore)), @@ -333,7 +333,6 @@ var wireTestSet = wire.NewSet( ProvideTestEnv, sqlstore.ProvideServiceForTests, ngmetrics.ProvideServiceForTest, - wire.Bind(new(alerting.AlertStore), new(*sqlstore.SQLStore)), wire.Bind(new(sqlstore.TeamStore), new(*sqlstore.SQLStore)), notifications.MockNotificationService, diff --git a/pkg/services/alerting/alerting_usage.go b/pkg/services/alerting/alerting_usage.go index 4abe2749918..711337de037 100644 --- a/pkg/services/alerting/alerting_usage.go +++ b/pkg/services/alerting/alerting_usage.go @@ -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 } diff --git a/pkg/services/alerting/alerting_usage_test.go b/pkg/services/alerting/alerting_usage_test.go index a4a8c5a68f8..1392742541e 100644 --- a/pkg/services/alerting/alerting_usage_test.go +++ b/pkg/services/alerting/alerting_usage_test.go @@ -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") diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 334901ff9f0..dabb46e5eb8 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -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) } diff --git a/pkg/services/alerting/conditions/query_interval_test.go b/pkg/services/alerting/conditions/query_interval_test.go index 34d169ca4f5..e3d77bcc109 100644 --- a/pkg/services/alerting/conditions/query_interval_test.go +++ b/pkg/services/alerting/conditions/query_interval_test.go @@ -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(`{ diff --git a/pkg/services/alerting/conditions/query_test.go b/pkg/services/alerting/conditions/query_test.go index 0fc9a53a9ec..af302924d92 100644 --- a/pkg/services/alerting/conditions/query_test.go +++ b/pkg/services/alerting/conditions/query_test.go @@ -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 } diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 6777edae8aa..5ffe528735e 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -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() { diff --git a/pkg/services/alerting/engine_integration_test.go b/pkg/services/alerting/engine_integration_test.go index 2ef21f1bf22..669c11d226f 100644 --- a/pkg/services/alerting/engine_integration_test.go +++ b/pkg/services/alerting/engine_integration_test.go @@ -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{} diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index f3d57b2cb7e..5ba059b8d68 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -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) diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index f6a65389ca1..15745b49b16 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -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, diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index da02bf76547..c906f9ea5fe 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -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 diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index bf7b625d26d..7cb45874fe4 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -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) diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 93bb0cb68ed..b91a4578aa7 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -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"), } } diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index e8de776d570..fd178802e38 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -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) diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index 29858fb65fa..d53413c9822 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -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) { diff --git a/pkg/services/alerting/notifiers/alertmanager_test.go b/pkg/services/alerting/notifiers/alertmanager_test.go index 578bb8a86a9..fcbaf1909ef 100644 --- a/pkg/services/alerting/notifiers/alertmanager_test.go +++ b/pkg/services/alerting/notifiers/alertmanager_test.go @@ -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 diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 1ee355ea3bd..60ff43f068a 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -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{} diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index 64aba4fa618..27255a6d89b 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -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) }) diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index 23474a159a6..e6d3389ba8a 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -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) diff --git a/pkg/services/alerting/notifiers/pagerduty_test.go b/pkg/services/alerting/notifiers/pagerduty_test.go index 9f31f91bdd9..170d76adbb1 100644 --- a/pkg/services/alerting/notifiers/pagerduty_test.go +++ b/pkg/services/alerting/notifiers/pagerduty_test.go @@ -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 diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index d2de17dd6ed..9d342696493 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -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) diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index 30fe1cbc8b0..e2f91a9a180 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -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", diff --git a/pkg/services/alerting/notifiers/victorops_test.go b/pkg/services/alerting/notifiers/victorops_test.go index f975be9bae2..12cb2639a68 100644 --- a/pkg/services/alerting/notifiers/victorops_test.go +++ b/pkg/services/alerting/notifiers/victorops_test.go @@ -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) diff --git a/pkg/services/alerting/rule_test.go b/pkg/services/alerting/rule_test.go index 506b7cb03b6..fdf09b7ebd9 100644 --- a/pkg/services/alerting/rule_test.go +++ b/pkg/services/alerting/rule_test.go @@ -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 }) diff --git a/pkg/services/alerting/service.go b/pkg/services/alerting/service.go index 7c30b0a635f..e9f3d802545 100644 --- a/pkg/services/alerting/service.go +++ b/pkg/services/alerting/service.go @@ -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, } diff --git a/pkg/services/alerting/service_test.go b/pkg/services/alerting/service_test.go index c845999025f..05feef5d875 100644 --- a/pkg/services/alerting/service_test.go +++ b/pkg/services/alerting/service_test.go @@ -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" diff --git a/pkg/services/sqlstore/alert.go b/pkg/services/alerting/store.go similarity index 63% rename from pkg/services/sqlstore/alert.go rename to pkg/services/alerting/store.go index d4b07cb64db..1e9d2972e66 100644 --- a/pkg/services/sqlstore/alert.go +++ b/pkg/services/alerting/store.go @@ -1,20 +1,53 @@ -package sqlstore +package alerting import ( "bytes" "context" "fmt" "strings" - "time" + "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" ) -// timeNow makes it possible to test usage of time -var timeNow = time.Now +// 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.WithDbSession(ctx, func(sess *DBSession) error { +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 { @@ -29,8 +62,8 @@ func (ss *SQLStore) GetAlertById(ctx context.Context, query *models.GetAlertById }) } -func (ss *SQLStore) GetAllAlertQueryHandler(ctx context.Context, query *models.GetAllAlertsQuery) error { - return ss.WithDbSession(ctx, func(sess *DBSession) error { +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 { @@ -42,8 +75,8 @@ func (ss *SQLStore) GetAllAlertQueryHandler(ctx context.Context, query *models.G }) } -func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) error { - sqlog.Debug("Deleting alert", "id", alertId, "reason", reason) +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 @@ -64,9 +97,9 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro return nil } -func (ss *SQLStore) HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error { - return ss.WithDbSession(ctx, func(sess *DBSession) error { - builder := SQLBuilder{} +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, @@ -86,11 +119,11 @@ func (ss *SQLStore) HandleAlertsQuery(ctx context.Context, query *models.GetAler builder.Write(`WHERE alert.org_id = ?`, query.OrgId) if len(strings.TrimSpace(query.Query)) > 0 { - builder.Write(" AND alert.name "+dialect.LikeStr()+" ?", "%"+query.Query+"%") + builder.Write(" AND alert.name "+ss.db.GetDialect().LikeStr()+" ?", "%"+query.Query+"%") } if len(query.DashboardIDs) > 0 { - builder.sql.WriteString(` AND alert.dashboard_id IN (?` + strings.Repeat(",?", len(query.DashboardIDs)-1) + `) `) + builder.Write(` AND alert.dashboard_id IN (?` + strings.Repeat(",?", len(query.DashboardIDs)-1) + `) `) for _, dbID := range query.DashboardIDs { builder.AddParams(dbID) @@ -125,11 +158,11 @@ func (ss *SQLStore) HandleAlertsQuery(ctx context.Context, query *models.GetAler builder.Write(" ORDER BY name ASC") if query.Limit != 0 { - builder.Write(dialect.Limit(query.Limit)) + builder.Write(ss.db.GetDialect().Limit(query.Limit)) } alerts := make([]*models.AlertListItemDTO, 0) - if err := sess.SQL(builder.GetSQLString(), builder.params...).Find(&alerts); err != nil { + if err := sess.SQL(builder.GetSQLString(), builder.GetParams()...).Find(&alerts); err != nil { return err } @@ -144,18 +177,18 @@ func (ss *SQLStore) HandleAlertsQuery(ctx context.Context, query *models.GetAler }) } -func (ss *SQLStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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); err != nil { + if err := updateAlerts(existingAlerts, alerts, sess, ss.log); err != nil { return err } - if err := deleteMissingAlerts(existingAlerts, alerts, sess); err != nil { + if err := deleteMissingAlerts(existingAlerts, alerts, sess, ss.log); err != nil { return err } @@ -163,7 +196,7 @@ func (ss *SQLStore) SaveAlerts(ctx context.Context, dashID int64, alerts []*mode }) } -func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess *DBSession) error { +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 @@ -188,7 +221,7 @@ func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess * return err } - sqlog.Debug("Alert updated", "name", alert.Name, "id", alert.Id) + log.Debug("Alert updated", "name", alert.Name, "id", alert.Id) } } else { alert.Updated = timeNow() @@ -201,14 +234,14 @@ func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess * return err } - sqlog.Debug("Alert inserted", "name", alert.Name, "id", alert.Id) + 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 := EnsureTagsExist(sess, tags) + tags, err := sqlstore.EnsureTagsExist(sess, tags) if err != nil { return err } @@ -223,7 +256,7 @@ func updateAlerts(existingAlerts []*models.Alert, alerts []*models.Alert, sess * return nil } -func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *DBSession) error { +func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, sess *sqlstore.DBSession, log *log.ConcreteLogger) error { for _, missingAlert := range alerts { missing := true @@ -235,7 +268,7 @@ func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, } if missing { - if err := deleteAlertByIdInternal(missingAlert.Id, "Removed from dashboard", sess); err != nil { + 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 @@ -246,7 +279,7 @@ func deleteMissingAlerts(alerts []*models.Alert, existingAlerts []*models.Alert, return nil } -func GetAlertsByDashboardId2(dashboardId int64, sess *DBSession) ([]*models.Alert, error) { +func GetAlertsByDashboardId2(dashboardId int64, sess *sqlstore.DBSession) ([]*models.Alert, error) { alerts := make([]*models.Alert, 0) err := sess.Where("dashboard_id = ?", dashboardId).Find(&alerts) @@ -257,8 +290,8 @@ func GetAlertsByDashboardId2(dashboardId int64, sess *DBSession) ([]*models.Aler return alerts, nil } -func (ss *SQLStore) SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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 { @@ -296,8 +329,8 @@ func (ss *SQLStore) SetAlertState(ctx context.Context, cmd *models.SetAlertState }) } -func (ss *SQLStore) PauseAlert(ctx context.Context, cmd *models.PauseAlertCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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") } @@ -330,8 +363,8 @@ func (ss *SQLStore) PauseAlert(ctx context.Context, cmd *models.PauseAlertComman }) } -func (ss *SQLStore) PauseAllAlerts(ctx context.Context, cmd *models.PauseAllAlertCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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) @@ -348,8 +381,8 @@ func (ss *SQLStore) PauseAllAlerts(ctx context.Context, cmd *models.PauseAllAler }) } -func (ss *SQLStore) GetAlertStatesForDashboard(ctx context.Context, query *models.GetAlertStatesForDashboardQuery) error { - return ss.WithDbSession(ctx, func(sess *DBSession) error { +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, diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/alerting/store_notification.go similarity index 84% rename from pkg/services/sqlstore/alert_notification.go rename to pkg/services/alerting/store_notification.go index 24aa1a6aacd..9423108896a 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/alerting/store_notification.go @@ -1,4 +1,4 @@ -package sqlstore +package alerting import ( "bytes" @@ -9,6 +9,7 @@ import ( "time" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" ) @@ -28,8 +29,11 @@ type AlertNotificationStore interface { GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error } -func (ss *SQLStore) DeleteAlertNotification(ctx context.Context, cmd *models.DeleteAlertNotificationCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) 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 { @@ -52,9 +56,9 @@ func (ss *SQLStore) DeleteAlertNotification(ctx context.Context, cmd *models.Del }) } -func (ss *SQLStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error { +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.newSession(ctx)); err != nil { + if err := getAlertNotificationWithUidInternal(ctx, existingNotification, ss.db.NewSession(ctx)); err != nil { return err } @@ -74,24 +78,24 @@ func (ss *SQLStore) DeleteAlertNotificationWithUid(ctx context.Context, cmd *mod return nil } -func (ss *SQLStore) GetAlertNotifications(ctx context.Context, query *models.GetAlertNotificationsQuery) error { - return getAlertNotificationInternal(ctx, query, ss.newSession(ctx)) +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 { +func (ss *sqlStore) GetAlertNotificationUidWithId(ctx context.Context, query *models.GetAlertNotificationUidQuery) error { cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id) - if cached, found := ss.CacheService.Get(cacheKey); found { + if cached, found := ss.cache.Get(cacheKey); found { query.Result = cached.(string) return nil } - err := getAlertNotificationUidInternal(ctx, query, ss.newSession(ctx)) + err := getAlertNotificationUidInternal(ctx, query, ss.db.NewSession(ctx)) if err != nil { return err } - ss.CacheService.Set(cacheKey, query.Result, -1) // Infinite, never changes + ss.cache.Set(cacheKey, query.Result, -1) // Infinite, never changes return nil } @@ -100,12 +104,12 @@ 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.newSession(ctx)) +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.WithDbSession(ctx, func(sess *DBSession) error { +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 @@ -116,8 +120,8 @@ func (ss *SQLStore) GetAllAlertNotifications(ctx context.Context, query *models. }) } -func (ss *SQLStore) GetAlertNotificationsWithUidToSend(ctx context.Context, query *models.GetAlertNotificationsWithUidToSendQuery) error { - return ss.WithDbSession(ctx, func(sess *DBSession) error { +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) @@ -142,7 +146,7 @@ func (ss *SQLStore) GetAlertNotificationsWithUidToSend(ctx context.Context, quer params = append(params, query.OrgId) sql.WriteString(` AND ((alert_notification.is_default = ?)`) - params = append(params, dialect.BooleanStr(true)) + 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) + ")") @@ -162,7 +166,7 @@ func (ss *SQLStore) GetAlertNotificationsWithUidToSend(ctx context.Context, quer }) } -func getAlertNotificationUidInternal(ctx context.Context, query *models.GetAlertNotificationUidQuery, sess *DBSession) error { +func getAlertNotificationUidInternal(ctx context.Context, query *models.GetAlertNotificationUidQuery, sess *sqlstore.DBSession) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -191,7 +195,7 @@ func getAlertNotificationUidInternal(ctx context.Context, query *models.GetAlert return nil } -func getAlertNotificationInternal(ctx context.Context, query *models.GetAlertNotificationsQuery, sess *DBSession) error { +func getAlertNotificationInternal(ctx context.Context, query *models.GetAlertNotificationsQuery, sess *sqlstore.DBSession) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -241,7 +245,7 @@ func getAlertNotificationInternal(ctx context.Context, query *models.GetAlertNot return nil } -func getAlertNotificationWithUidInternal(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery, sess *DBSession) error { +func getAlertNotificationWithUidInternal(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery, sess *sqlstore.DBSession) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -279,8 +283,8 @@ func getAlertNotificationWithUidInternal(ctx context.Context, query *models.GetA return nil } -func (ss *SQLStore) CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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 { @@ -353,7 +357,7 @@ func (ss *SQLStore) CreateAlertNotificationCommand(ctx context.Context, cmd *mod }) } -func generateNewAlertNotificationUid(ctx context.Context, sess *DBSession, orgId int64) (string, error) { +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{}) @@ -369,8 +373,8 @@ func generateNewAlertNotificationUid(ctx context.Context, sess *DBSession, orgId return "", models.ErrAlertNotificationFailedGenerateUniqueUid } -func (ss *SQLStore) UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) (err error) { +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 { @@ -437,10 +441,10 @@ func (ss *SQLStore) UpdateAlertNotification(ctx context.Context, cmd *models.Upd }) } -func (ss *SQLStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error { +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.newSession(ctx)); err != nil { + if err := getAlertNotificationWithUidInternal(ctx, getAlertNotificationWithUidQuery, ss.db.NewSession(ctx)); err != nil { return err } @@ -478,8 +482,8 @@ func (ss *SQLStore) UpdateAlertNotificationWithUid(ctx context.Context, cmd *mod return nil } -func (ss *SQLStore) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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 { @@ -500,15 +504,15 @@ func (ss *SQLStore) SetAlertNotificationStateToCompleteCommand(ctx context.Conte } if current.Version != version { - sqlog.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) + 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.WithDbSession(ctx, func(sess *DBSession) error { +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 = ?, @@ -543,8 +547,8 @@ func (ss *SQLStore) SetAlertNotificationStateToPendingCommand(ctx context.Contex }) } -func (ss *SQLStore) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error { - return ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { +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) @@ -568,7 +572,7 @@ func (ss *SQLStore) GetOrCreateAlertNotificationState(ctx context.Context, cmd * } if _, err := sess.Insert(notificationState); err != nil { - if dialect.IsUniqueConstraintViolation(err) { + if ss.db.GetDialect().IsUniqueConstraintViolation(err) { exist, err = getAlertNotificationState(ctx, sess, cmd, nj) if err != nil { @@ -591,7 +595,7 @@ func (ss *SQLStore) GetOrCreateAlertNotificationState(ctx context.Context, cmd * }) } -func getAlertNotificationState(ctx context.Context, sess *DBSession, cmd *models.GetOrCreateNotificationStateQuery, nj *models.AlertNotificationState) (bool, error) { +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). diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/alerting/store_notification_test.go similarity index 78% rename from pkg/services/sqlstore/alert_notification_test.go rename to pkg/services/alerting/store_notification_test.go index 6bc2e4a83d2..37d74abf6e3 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/alerting/store_notification_test.go @@ -1,4 +1,4 @@ -package sqlstore +package alerting import ( "context" @@ -8,7 +8,10 @@ import ( "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" ) @@ -17,8 +20,13 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") } - var sqlStore *SQLStore - setup := func() { sqlStore = InitTestDB(t) } + 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() @@ -33,7 +41,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { 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 := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query) + err := store.GetOrCreateAlertNotificationState(context.Background(), query) require.Nil(t, err) require.NotNil(t, query.Result) require.Equal(t, models.AlertNotificationStateUnknown, query.Result.State) @@ -42,7 +50,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { 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 := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) + err := store.GetOrCreateAlertNotificationState(context.Background(), query2) require.Nil(t, err) require.NotNil(t, query2.Result) require.Equal(t, query.Result.Id, query2.Result.Id) @@ -58,12 +66,12 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + 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 = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) + 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) @@ -75,11 +83,11 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Id: s.Id, Version: cmd.ResultVersion, } - err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd) + err := store.SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd) require.Nil(t, err) query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) + 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) @@ -93,11 +101,11 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Id: s.Id, Version: unknownVersion, } - err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd) + err := store.SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd) require.Nil(t, err) query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) + 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) @@ -113,7 +121,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) require.Equal(t, models.ErrAlertNotificationStateVersionConflict, err) }) @@ -124,7 +132,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: 1000, } - err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) require.Nil(t, err) require.Equal(t, int64(1), cmd.ResultVersion) @@ -138,7 +146,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := store.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) require.Error(t, err) }) }) @@ -151,7 +159,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Name: "email", } - err := sqlStore.GetAlertNotifications(context.Background(), cmd) + err := store.GetAlertNotifications(context.Background(), cmd) require.Nil(t, err) require.Nil(t, cmd.Result) }) @@ -167,13 +175,13 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { } t.Run("and missing frequency", func(t *testing.T) { - err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + 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 := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + err := store.CreateAlertNotificationCommand(context.Background(), cmd) require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( err.Error())) }) @@ -189,7 +197,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + err := store.CreateAlertNotificationCommand(context.Background(), cmd) require.Nil(t, err) updateCmd := &models.UpdateAlertNotificationCommand{ @@ -198,14 +206,14 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { } t.Run("and missing frequency", func(t *testing.T) { - err := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) + 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 := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) + err := store.UpdateAlertNotification(context.Background(), updateCmd) require.Error(t, err) require.True(t, regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( err.Error())) @@ -223,7 +231,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + err := store.CreateAlertNotificationCommand(context.Background(), cmd) require.Nil(t, err) require.NotEqual(t, 0, cmd.Result.Id) require.NotEqual(t, 0, cmd.Result.OrgId) @@ -233,7 +241,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { require.NotEmpty(t, cmd.Result.Uid) t.Run("Cannot save Alert Notification with the same name", func(t *testing.T) { - err = sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + 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) { @@ -246,7 +254,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: cmd.Settings, Uid: "notifier1", } - err = sqlStore.CreateAlertNotificationCommand(context.Background(), anotherUidCmd) + 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) { @@ -259,7 +267,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: cmd.Settings, Uid: "notifier2", } - err = sqlStore.CreateAlertNotificationCommand(context.Background(), anotherUidCmd) + err = store.CreateAlertNotificationCommand(context.Background(), anotherUidCmd) require.Nil(t, err) }) @@ -274,7 +282,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: cmd.Result.Id, } - err := sqlStore.UpdateAlertNotification(context.Background(), newCmd) + 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) @@ -290,7 +298,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: cmd.Result.Id, } - err := sqlStore.UpdateAlertNotification(context.Background(), newCmd) + err := store.UpdateAlertNotification(context.Background(), newCmd) require.Nil(t, err) require.False(t, newCmd.Result.SendReminder) }) @@ -305,11 +313,11 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { otherOrg := models.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} - require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd1)) - require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd2)) - require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd3)) - require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &cmd4)) - require.Nil(t, sqlStore.CreateAlertNotificationCommand(context.Background(), &otherOrg)) + 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{ @@ -317,7 +325,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: 1, } - err := sqlStore.GetAlertNotificationsWithUidToSend(context.Background(), query) + err := store.GetAlertNotificationsWithUidToSend(context.Background(), query) require.Nil(t, err) require.Equal(t, 3, len(query.Result)) }) @@ -327,7 +335,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: 1, } - err := sqlStore.GetAllAlertNotifications(context.Background(), query) + 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) @@ -339,10 +347,9 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { t.Run("Notification Uid by Id Caching", func(t *testing.T) { setup() - ss := InitTestDB(t) notification := &models.CreateAlertNotificationCommand{Uid: "aNotificationUid", OrgId: 1, Name: "aNotificationUid"} - err := sqlStore.CreateAlertNotificationCommand(context.Background(), notification) + err := store.CreateAlertNotificationCommand(context.Background(), notification) require.Nil(t, err) byUidQuery := &models.GetAlertNotificationsWithUidQuery{ @@ -350,7 +357,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: notification.OrgId, } - notificationByUidErr := sqlStore.GetAlertNotificationsWithUid(context.Background(), byUidQuery) + notificationByUidErr := store.GetAlertNotificationsWithUid(context.Background(), byUidQuery) require.Nil(t, notificationByUidErr) t.Run("Can cache notification Uid", func(t *testing.T) { @@ -361,14 +368,14 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { cacheKey := newAlertNotificationUidCacheKey(byIdQuery.OrgId, byIdQuery.Id) - resultBeforeCaching, foundBeforeCaching := ss.CacheService.Get(cacheKey) + resultBeforeCaching, foundBeforeCaching := store.cache.Get(cacheKey) require.False(t, foundBeforeCaching) require.Nil(t, resultBeforeCaching) - notificationByIdErr := ss.GetAlertNotificationUidWithId(context.Background(), byIdQuery) + notificationByIdErr := store.GetAlertNotificationUidWithId(context.Background(), byIdQuery) require.Nil(t, notificationByIdErr) - resultAfterCaching, foundAfterCaching := ss.CacheService.Get(cacheKey) + resultAfterCaching, foundAfterCaching := store.cache.Get(cacheKey) require.True(t, foundAfterCaching) require.Equal(t, notification.Uid, resultAfterCaching) }) @@ -379,9 +386,9 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: 100, } cacheKey := newAlertNotificationUidCacheKey(query.OrgId, query.Id) - ss.CacheService.Set(cacheKey, "a-cached-uid", -1) + store.cache.Set(cacheKey, "a-cached-uid", -1) - err := ss.GetAlertNotificationUidWithId(context.Background(), query) + err := store.GetAlertNotificationUidWithId(context.Background(), query) require.Nil(t, err) require.Equal(t, "a-cached-uid", query.Result) }) @@ -392,13 +399,13 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: 100, } - err := ss.GetAlertNotificationUidWithId(context.Background(), query) + 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 := ss.CacheService.Get(cacheKey) + result, found := store.cache.Get(cacheKey) require.False(t, found) require.Nil(t, result) }) @@ -416,7 +423,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: 1, } - err := sqlStore.UpdateAlertNotification(context.Background(), updateCmd) + err := store.UpdateAlertNotification(context.Background(), updateCmd) require.Equal(t, models.ErrAlertNotificationNotFound, err) t.Run("using UID", func(t *testing.T) { @@ -431,7 +438,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Uid: "uid", NewUid: "newUid", } - err := sqlStore.UpdateAlertNotificationWithUid(context.Background(), updateWithUidCmd) + err := store.UpdateAlertNotificationWithUid(context.Background(), updateWithUidCmd) require.Equal(t, models.ErrAlertNotificationNotFound, err) }) }) @@ -446,18 +453,18 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + err := store.CreateAlertNotificationCommand(context.Background(), cmd) require.Nil(t, err) deleteCmd := &models.DeleteAlertNotificationCommand{ Id: cmd.Result.Id, OrgId: 1, } - err = sqlStore.DeleteAlertNotification(context.Background(), deleteCmd) + err = store.DeleteAlertNotification(context.Background(), deleteCmd) require.Nil(t, err) t.Run("using UID", func(t *testing.T) { - err := sqlStore.CreateAlertNotificationCommand(context.Background(), cmd) + err := store.CreateAlertNotificationCommand(context.Background(), cmd) require.Nil(t, err) deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{ @@ -465,7 +472,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { OrgId: 1, } - err = sqlStore.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) + err = store.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) require.Nil(t, err) require.Equal(t, cmd.Result.Id, deleteWithUidCmd.DeletedAlertNotificationId) }) @@ -477,7 +484,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Id: 1, OrgId: 1, } - err := sqlStore.DeleteAlertNotification(context.Background(), deleteCmd) + err := store.DeleteAlertNotification(context.Background(), deleteCmd) require.Equal(t, models.ErrAlertNotificationNotFound, err) t.Run("using UID", func(t *testing.T) { @@ -485,7 +492,7 @@ func TestIntegrationAlertNotificationSQLAccess(t *testing.T) { Uid: "uid", OrgId: 1, } - err = sqlStore.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) + err = store.DeleteAlertNotificationWithUid(context.Background(), deleteWithUidCmd) require.Equal(t, models.ErrAlertNotificationNotFound, err) }) }) diff --git a/pkg/services/sqlstore/alert_test.go b/pkg/services/alerting/store_test.go similarity index 69% rename from pkg/services/sqlstore/alert_test.go rename to pkg/services/alerting/store_test.go index cb5beb3ddb3..1508c521e9d 100644 --- a/pkg/services/sqlstore/alert_test.go +++ b/pkg/services/alerting/store_test.go @@ -1,4 +1,4 @@ -package sqlstore +package alerting import ( "context" @@ -6,7 +6,13 @@ import ( "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" ) @@ -32,14 +38,17 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { mockTimeNow() defer resetTimeNow() - var sqlStore *SQLStore + var store *sqlStore var testDash *models.Dashboard var items []*models.Alert setup := func(t *testing.T) { - sqlStore = InitTestDB(t) + store = &sqlStore{ + db: sqlstore.InitTestDB(t), + log: log.New(), + } - testDash = insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert") + 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{ @@ -55,7 +64,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { }, } - err = sqlStore.SaveAlerts(context.Background(), testDash.Id, items) + err = store.SaveAlerts(context.Background(), testDash.Id, items) require.Nil(t, err) } @@ -64,7 +73,7 @@ func TestIntegrationAlertingDataAccess(t *testing.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 := sqlStore.HandleAlertsQuery(context.Background(), &alertQuery) + err2 := store.HandleAlertsQuery(context.Background(), &alertQuery) require.Nil(t, err2) insertedAlert := alertQuery.Result[0] @@ -75,15 +84,15 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { State: models.AlertStateOK, } - err := sqlStore.SetAlertState(context.Background(), cmd) + err := store.SetAlertState(context.Background(), cmd) require.Nil(t, err) }) - alert, _ := getAlertById(t, insertedAlert.Id, sqlStore) + alert, _ := getAlertById(t, insertedAlert.Id, store) stateDateBeforePause := alert.NewStateDate t.Run("can pause all alerts", func(t *testing.T) { - err := sqlStore.pauseAllAlerts(t, true) + err := store.pauseAllAlerts(t, true) require.Nil(t, err) t.Run("cannot updated paused alert", func(t *testing.T) { @@ -92,26 +101,26 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { State: models.AlertStateOK, } - err = sqlStore.SetAlertState(context.Background(), cmd) + err = store.SetAlertState(context.Background(), cmd) require.Error(t, err) }) t.Run("alert is paused", func(t *testing.T) { - alert, _ = getAlertById(t, insertedAlert.Id, sqlStore) + 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, sqlStore) + 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 := sqlStore.pauseAllAlerts(t, false) + err := store.pauseAllAlerts(t, false) require.Nil(t, err) - alert, _ = getAlertById(t, insertedAlert.Id, sqlStore) + alert, _ = getAlertById(t, insertedAlert.Id, store) stateDateAfterUnpause := alert.NewStateDate require.True(t, stateDateBeforePause.Before(stateDateAfterUnpause)) }) @@ -121,7 +130,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { 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 := sqlStore.HandleAlertsQuery(context.Background(), &alertQuery) + err2 := store.HandleAlertsQuery(context.Background(), &alertQuery) alert := alertQuery.Result[0] require.Nil(t, err2) @@ -143,7 +152,7 @@ func TestIntegrationAlertingDataAccess(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 := sqlStore.HandleAlertsQuery(context.Background(), &alertQuery) + err2 := store.HandleAlertsQuery(context.Background(), &alertQuery) require.Nil(t, err2) require.Equal(t, 1, len(alertQuery.Result)) @@ -154,7 +163,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { modifiedItems := items modifiedItems[0].Name = "Name" - err := sqlStore.SaveAlerts(context.Background(), testDash.Id, items) + 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) @@ -162,7 +171,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { 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 := sqlStore.HandleAlertsQuery(context.Background(), &query) + err2 := store.HandleAlertsQuery(context.Background(), &query) require.Nil(t, err2) require.Equal(t, 1, len(query.Result)) @@ -174,7 +183,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { }) t.Run("Updates without changes should be ignored", func(t *testing.T) { - err3 := sqlStore.SaveAlerts(context.Background(), testDash.Id, items) + err3 := store.SaveAlerts(context.Background(), testDash.Id, items) require.Nil(t, err3) }) }) @@ -205,13 +214,13 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { }, } - err := sqlStore.SaveAlerts(context.Background(), testDash.Id, multipleItems) + 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 := sqlStore.HandleAlertsQuery(context.Background(), &queryForDashboard) + err2 := store.HandleAlertsQuery(context.Background(), &queryForDashboard) require.Nil(t, err2) require.Equal(t, 3, len(queryForDashboard.Result)) @@ -220,11 +229,11 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { t.Run("should updated two dashboards and delete one", func(t *testing.T) { missingOneAlert := multipleItems[:2] - err = sqlStore.SaveAlerts(context.Background(), testDash.Id, missingOneAlert) + 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 := sqlStore.HandleAlertsQuery(context.Background(), &query) + err2 := store.HandleAlertsQuery(context.Background(), &query) require.Nil(t, err2) require.Equal(t, 2, len(query.Result)) }) @@ -242,10 +251,10 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { }, } - err := sqlStore.SaveAlerts(context.Background(), testDash.Id, items) + err := store.SaveAlerts(context.Background(), testDash.Id, items) require.Nil(t, err) - err = sqlStore.WithDbSession(context.Background(), func(sess *DBSession) error { + 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 @@ -254,7 +263,7 @@ func TestIntegrationAlertingDataAccess(t *testing.T) { 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 := sqlStore.HandleAlertsQuery(context.Background(), &query) + err2 := store.HandleAlertsQuery(context.Background(), &query) require.Nil(t, err2) require.Equal(t, 0, len(query.Result)) @@ -270,9 +279,9 @@ func TestIntegrationPausingAlerts(t *testing.T) { defer resetTimeNow() t.Run("Given an alert", func(t *testing.T) { - sqlStore := InitTestDB(t) + sqlStore := sqlStore{db: sqlstore.InitTestDB(t), log: log.New()} - testDash := insertTestDashboard(t, sqlStore, "dashboard with alerts", 1, 0, false, "alert") + 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) @@ -291,7 +300,7 @@ func TestIntegrationPausingAlerts(t *testing.T) { require.Nil(t, err) t.Run("the NewStateDate should be updated", func(t *testing.T) { - alert, err := getAlertById(t, insertedAlert.Id, sqlStore) + alert, err := getAlertById(t, insertedAlert.Id, &sqlStore) require.Nil(t, err) stateDateAfterPause = alert.NewStateDate @@ -304,7 +313,7 @@ func TestIntegrationPausingAlerts(t *testing.T) { require.Nil(t, err) t.Run("the NewStateDate should be updated again", func(t *testing.T) { - alert, err := getAlertById(t, insertedAlert.Id, sqlStore) + alert, err := getAlertById(t, insertedAlert.Id, &sqlStore) require.Nil(t, err) stateDateAfterUnpause := alert.NewStateDate @@ -313,7 +322,8 @@ func TestIntegrationPausingAlerts(t *testing.T) { }) }) } -func (ss *SQLStore) pauseAlert(t *testing.T, orgId int64, alertId int64, pauseState bool) (int64, error) { + +func (ss *sqlStore) pauseAlert(t *testing.T, orgId int64, alertId int64, pauseState bool) (int64, error) { cmd := &models.PauseAlertCommand{ OrgId: orgId, AlertIds: []int64{alertId}, @@ -323,7 +333,8 @@ func (ss *SQLStore) pauseAlert(t *testing.T, orgId int64, alertId int64, pauseSt 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) { + +func insertTestAlert(title string, message string, orgId int64, dashId int64, settings *simplejson.Json, ss sqlStore) (*models.Alert, error) { items := []*models.Alert{ { PanelId: 1, @@ -340,7 +351,7 @@ func insertTestAlert(title string, message string, orgId int64, dashId int64, se return items[0], err } -func getAlertById(t *testing.T, id int64, ss *SQLStore) (*models.Alert, error) { +func getAlertById(t *testing.T, id int64, ss *sqlStore) (*models.Alert, error) { q := &models.GetAlertByIdQuery{ Id: id, } @@ -349,7 +360,7 @@ func getAlertById(t *testing.T, id int64, ss *SQLStore) (*models.Alert, error) { return q.Result, err } -func (ss *SQLStore) pauseAllAlerts(t *testing.T, pauseState bool) error { +func (ss *sqlStore) pauseAllAlerts(t *testing.T, pauseState bool) error { cmd := &models.PauseAllAlertCommand{ Paused: pauseState, } @@ -357,3 +368,59 @@ func (ss *SQLStore) pauseAllAlerts(t *testing.T, pauseState bool) error { 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 +} diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index a77cb3f3898..4c80f7c9153 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -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" } diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go index 8651146d54c..acb1db2a960 100644 --- a/pkg/services/alerting/test_rule.go +++ b/pkg/services/alerting/test_rule.go @@ -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 diff --git a/pkg/services/provisioning/notifiers/config_reader_test.go b/pkg/services/provisioning/notifiers/config_reader_test.go index 9e6ee7675e0..4237d7df3ac 100644 --- a/pkg/services/provisioning/notifiers/config_reader_test.go +++ b/pkg/services/provisioning/notifiers/config_reader_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting/notifiers" encryptionservice "github.com/grafana/grafana/pkg/services/encryption/service" + "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/stretchr/testify/require" @@ -31,6 +32,7 @@ var ( func TestNotificationAsConfig(t *testing.T) { var sqlStore *sqlstore.SQLStore + var ns *alerting.AlertNotificationService logger := log.New("fake.log") encryptionService := encryptionservice.SetupTestService(t) @@ -38,6 +40,8 @@ func TestNotificationAsConfig(t *testing.T) { t.Run("Testing notification as configuration", func(t *testing.T) { setup := func() { sqlStore = sqlstore.InitTestDB(t) + nm := ¬ifications.NotificationService{} + ns = alerting.ProvideService(sqlStore, encryptionService, nm) for i := 1; i < 5; i++ { orgCommand := models.CreateOrgCommand{Name: fmt.Sprintf("Main Org. %v", i)} @@ -158,11 +162,11 @@ func TestNotificationAsConfig(t *testing.T) { Uid: "notifier1", Type: "slack", } - err := sqlStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) + err := ns.SQLStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) require.NoError(t, err) require.NotNil(t, existingNotificationCmd.Result) notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} - err = sqlStore.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) + err = ns.SQLStore.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) require.NoError(t, err) require.NotNil(t, notificationsQuery.Result) require.Equal(t, len(notificationsQuery.Result), 1) @@ -175,6 +179,7 @@ func TestNotificationAsConfig(t *testing.T) { } }) }) + t.Run("Two notifications with is_default", func(t *testing.T) { setup() dc := newNotificationProvisioner(sqlStore, &fakeAlertNotification{}, encryptionService, nil, logger) @@ -194,7 +199,7 @@ func TestNotificationAsConfig(t *testing.T) { Uid: "notifier0", Type: "slack", } - err := sqlStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) + err := ns.SQLStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) require.NoError(t, err) existingNotificationCmd = models.CreateAlertNotificationCommand{ Name: "channel3", @@ -202,11 +207,11 @@ func TestNotificationAsConfig(t *testing.T) { Uid: "notifier3", Type: "slack", } - err = sqlStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) + err = ns.SQLStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) require.NoError(t, err) notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} - err = sqlStore.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) + err = ns.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) require.NoError(t, err) require.NotNil(t, notificationsQuery.Result) require.Equal(t, len(notificationsQuery.Result), 2) @@ -223,6 +228,7 @@ func TestNotificationAsConfig(t *testing.T) { t.Run("Can read correct properties with orgName instead of orgId", func(t *testing.T) { setup() + existingOrg1 := models.GetOrgByNameQuery{Name: "Main Org. 1"} err := sqlStore.GetOrgByNameHandler(context.Background(), &existingOrg1) require.NoError(t, err) @@ -238,7 +244,7 @@ func TestNotificationAsConfig(t *testing.T) { Uid: "notifier2", Type: "slack", } - err = sqlStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) + err = ns.SQLStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd) require.NoError(t, err) dc := newNotificationProvisioner(sqlStore, &fakeAlertNotification{}, encryptionService, nil, logger) @@ -270,7 +276,7 @@ func TestNotificationAsConfig(t *testing.T) { t.Fatalf("applyChanges return an error %v", err) } notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1} - err = sqlStore.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) + err = ns.GetAllAlertNotifications(context.Background(), ¬ificationsQuery) require.NoError(t, err) require.Empty(t, notificationsQuery.Result) }) diff --git a/pkg/services/sqlstore/db/db.go b/pkg/services/sqlstore/db/db.go index 4b43872d019..81ce357f032 100644 --- a/pkg/services/sqlstore/db/db.go +++ b/pkg/services/sqlstore/db/db.go @@ -10,5 +10,6 @@ import ( type DB interface { WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error + NewSession(ctx context.Context) *sqlstore.DBSession GetDialect() migrator.Dialect } diff --git a/pkg/services/sqlstore/store.go b/pkg/services/sqlstore/store.go index 1521c372cec..150554cab92 100644 --- a/pkg/services/sqlstore/store.go +++ b/pkg/services/sqlstore/store.go @@ -2,6 +2,7 @@ package sqlstore import ( "context" + "time" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/datasources" @@ -9,6 +10,8 @@ import ( "github.com/grafana/grafana/pkg/services/user" ) +var timeNow = time.Now + type Store interface { GetAdminStats(ctx context.Context, query *models.GetAdminStatsQuery) error GetAlertNotifiersUsageStats(ctx context.Context, query *models.GetAlertNotifierUsageStatsQuery) error @@ -84,13 +87,6 @@ type Store interface { SearchPlaylists(ctx context.Context, query *models.GetPlaylistsQuery) error // deprecated GetPlaylistItem(ctx context.Context, query *models.GetPlaylistItemsByUidQuery) error - GetAlertById(ctx context.Context, query *models.GetAlertByIdQuery) error - GetAllAlertQueryHandler(ctx context.Context, query *models.GetAllAlertsQuery) error - HandleAlertsQuery(ctx context.Context, query *models.GetAlertsQuery) error - SetAlertState(ctx context.Context, cmd *models.SetAlertStateCommand) error - PauseAlert(ctx context.Context, cmd *models.PauseAlertCommand) error - PauseAllAlerts(ctx context.Context, cmd *models.PauseAllAlertCommand) error - GetAlertStatesForDashboard(ctx context.Context, query *models.GetAlertStatesForDashboardQuery) error AddOrgUser(ctx context.Context, cmd *models.AddOrgUserCommand) error UpdateOrgUser(ctx context.Context, cmd *models.UpdateOrgUserCommand) error GetOrgUsers(ctx context.Context, query *models.GetOrgUsersQuery) error @@ -107,19 +103,6 @@ type Store interface { Sync() error Reset() error Quote(value string) string - 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 UpdateTempUserStatus(ctx context.Context, cmd *models.UpdateTempUserStatusCommand) error CreateTempUser(ctx context.Context, cmd *models.CreateTempUserCommand) error UpdateTempUserWithEmailSent(ctx context.Context, cmd *models.UpdateTempUserWithEmailSentCommand) error