diff --git a/pkg/services/datasources/fake_cache_service.go b/pkg/services/datasources/fake_cache_service.go new file mode 100644 index 00000000000..e4c2ed9193b --- /dev/null +++ b/pkg/services/datasources/fake_cache_service.go @@ -0,0 +1,31 @@ +package datasources + +import ( + "context" + + "github.com/grafana/grafana/pkg/models" +) + +type FakeCacheService struct { + DataSources []*models.DataSource +} + +var _ CacheService = &FakeCacheService{} + +func (c *FakeCacheService) GetDatasource(ctx context.Context, datasourceID int64, user *models.SignedInUser, skipCache bool) (*models.DataSource, error) { + for _, datasource := range c.DataSources { + if datasource.Id == datasourceID { + return datasource, nil + } + } + return nil, models.ErrDataSourceNotFound +} + +func (c *FakeCacheService) GetDatasourceByUID(ctx context.Context, datasourceUID string, user *models.SignedInUser, skipCache bool) (*models.DataSource, error) { + for _, datasource := range c.DataSources { + if datasource.Uid == datasourceUID { + return datasource, nil + } + } + return nil, models.ErrDataSourceNotFound +} diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 843fa582914..234b45f75d8 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" @@ -112,11 +113,11 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { api.RegisterTestingApiEndpoints(NewForkedTestingApi( &TestingApiSrv{ AlertingProxy: proxy, - Cfg: api.Cfg, ExpressionService: api.ExpressionService, DatasourceCache: api.DatasourceCache, - secretsService: api.SecretsService, log: logger, + accessControl: api.AccessControl, + evaluator: eval.NewEvaluator(api.Cfg, log.New("ngalert.eval"), api.DatasourceCache, api.SecretsService), }), m) api.RegisterConfigurationApiEndpoints(NewForkedConfiguration( &AdminSrv{ diff --git a/pkg/services/ngalert/api/api_testing.go b/pkg/services/ngalert/api/api_testing.go index 22a01417304..3df26ad261c 100644 --- a/pkg/services/ngalert/api/api_testing.go +++ b/pkg/services/ngalert/api/api_testing.go @@ -8,32 +8,65 @@ import ( "net/url" "strconv" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/datasources" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" - "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/setting" + ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) type TestingApiSrv struct { *AlertingProxy - Cfg *setting.Cfg ExpressionService *expr.Service DatasourceCache datasources.CacheService log log.Logger - secretsService secrets.Service + accessControl accesscontrol.AccessControl + evaluator eval.Evaluator } func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { if body.Type() != apimodels.GrafanaBackend || body.GrafanaManagedCondition == nil { return ErrResp(http.StatusBadRequest, errors.New("unexpected payload"), "") } - return conditionEval(c, *body.GrafanaManagedCondition, srv.DatasourceCache, srv.ExpressionService, srv.secretsService, srv.Cfg, srv.log) + + if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: body.GrafanaManagedCondition.Data}, func(evaluator accesscontrol.Evaluator) bool { + return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator) + }) { + return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "") + } + + evalCond := ngmodels.Condition{ + Condition: body.GrafanaManagedCondition.Condition, + OrgID: c.SignedInUser.OrgId, + Data: body.GrafanaManagedCondition.Data, + } + + if err := validateCondition(c.Req.Context(), evalCond, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil { + return ErrResp(http.StatusBadRequest, err, "invalid condition") + } + + now := body.GrafanaManagedCondition.Now + if now.IsZero() { + now = timeNow() + } + + evalResults, err := srv.evaluator.ConditionEval(&evalCond, now, srv.ExpressionService) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "Failed to evaluate conditions") + } + + frame := evalResults.AsDataFrame() + return response.JSONStreaming(http.StatusOK, util.DynMap{ + "instances": []*data.Frame{&frame}, + }) } func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { @@ -84,12 +117,17 @@ func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.Ev now = timeNow() } + if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: cmd.Data}, func(evaluator accesscontrol.Evaluator) bool { + return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator) + }) { + return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "") + } + if _, err := validateQueriesAndExpressions(c.Req.Context(), cmd.Data, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil { return ErrResp(http.StatusBadRequest, err, "invalid queries or expressions") } - evaluator := eval.NewEvaluator(srv.Cfg, srv.log, srv.DatasourceCache, srv.secretsService) - evalResults, err := evaluator.QueriesAndExpressionsEval(c.SignedInUser.OrgId, cmd.Data, now, srv.ExpressionService) + evalResults, err := srv.evaluator.QueriesAndExpressionsEval(c.SignedInUser.OrgId, cmd.Data, now, srv.ExpressionService) if err != nil { return ErrResp(http.StatusBadRequest, err, "Failed to evaluate queries and expressions") } diff --git a/pkg/services/ngalert/api/api_testing_test.go b/pkg/services/ngalert/api/api_testing_test.go new file mode 100644 index 00000000000..91b976b7364 --- /dev/null +++ b/pkg/services/ngalert/api/api_testing_test.go @@ -0,0 +1,278 @@ +package api + +import ( + "net/http" + "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + models2 "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/accesscontrol" + acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/eval" + "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/web" +) + +func TestRouteTestGrafanaRuleConfig(t *testing.T) { + t.Run("when fine-grained access is enabled", func(t *testing.T) { + rc := &models2.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + SignedInUser: &models2.SignedInUser{ + OrgId: 1, + }, + } + + t.Run("should return 401 if user cannot query a data source", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + data2 := models.GenerateAlertQuery() + + ac := acMock.New().WithPermissions([]*accesscontrol.Permission{ + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, + }) + + srv := createTestingApiSrv(nil, ac, nil) + + response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{ + Expr: "", + GrafanaManagedCondition: &models.EvalAlertConditionCommand{ + Condition: data1.RefID, + Data: []models.AlertQuery{data1, data2}, + Now: time.Time{}, + }, + }) + + require.Equal(t, http.StatusUnauthorized, response.Status()) + }) + + t.Run("should return 200 if user can query all data sources", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + data2 := models.GenerateAlertQuery() + + ac := acMock.New().WithPermissions([]*accesscontrol.Permission{ + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)}, + }) + + ds := &datasources.FakeCacheService{DataSources: []*models2.DataSource{ + {Uid: data1.DatasourceUID}, + {Uid: data2.DatasourceUID}, + }} + + evaluator := &eval.FakeEvaluator{} + var result []eval.Result + evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything, mock.Anything).Return(result, nil) + + srv := createTestingApiSrv(ds, ac, evaluator) + + response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{ + Expr: "", + GrafanaManagedCondition: &models.EvalAlertConditionCommand{ + Condition: data1.RefID, + Data: []models.AlertQuery{data1, data2}, + Now: time.Time{}, + }, + }) + + require.Equal(t, http.StatusOK, response.Status()) + + evaluator.AssertCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything) + }) + }) + + t.Run("when fine-grained access is disabled", func(t *testing.T) { + rc := &models2.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + IsSignedIn: false, + SignedInUser: &models2.SignedInUser{ + OrgId: 1, + }, + } + ac := acMock.New().WithDisabled() + + t.Run("should require user to be signed in", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + + ds := &datasources.FakeCacheService{DataSources: []*models2.DataSource{ + {Uid: data1.DatasourceUID}, + }} + + evaluator := &eval.FakeEvaluator{} + var result []eval.Result + evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything, mock.Anything).Return(result, nil) + + srv := createTestingApiSrv(ds, ac, evaluator) + + response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{ + Expr: "", + GrafanaManagedCondition: &models.EvalAlertConditionCommand{ + Condition: data1.RefID, + Data: []models.AlertQuery{data1}, + Now: time.Time{}, + }, + }) + + require.Equal(t, http.StatusUnauthorized, response.Status()) + evaluator.AssertNotCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything) + + rc.IsSignedIn = true + + response = srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{ + Expr: "", + GrafanaManagedCondition: &models.EvalAlertConditionCommand{ + Condition: data1.RefID, + Data: []models.AlertQuery{data1}, + Now: time.Time{}, + }, + }) + + require.Equal(t, http.StatusOK, response.Status()) + + evaluator.AssertCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything) + }) + }) +} + +func TestRouteEvalQueries(t *testing.T) { + t.Run("when fine-grained access is enabled", func(t *testing.T) { + rc := &models2.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + SignedInUser: &models2.SignedInUser{ + OrgId: 1, + }, + } + + t.Run("should return 401 if user cannot query a data source", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + data2 := models.GenerateAlertQuery() + + ac := acMock.New().WithPermissions([]*accesscontrol.Permission{ + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, + }) + + srv := &TestingApiSrv{ + accessControl: ac, + } + + response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{ + Data: []models.AlertQuery{data1, data2}, + Now: time.Time{}, + }) + + require.Equal(t, http.StatusUnauthorized, response.Status()) + }) + + t.Run("should return 200 if user can query all data sources", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + data2 := models.GenerateAlertQuery() + + ac := acMock.New().WithPermissions([]*accesscontrol.Permission{ + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)}, + {Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)}, + }) + + ds := &datasources.FakeCacheService{DataSources: []*models2.DataSource{ + {Uid: data1.DatasourceUID}, + {Uid: data2.DatasourceUID}, + }} + + evaluator := &eval.FakeEvaluator{} + result := &backend.QueryDataResponse{ + Responses: map[string]backend.DataResponse{ + "test": { + Frames: nil, + Error: nil, + }, + }, + } + evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result, nil) + + srv := createTestingApiSrv(ds, ac, evaluator) + + response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{ + Data: []models.AlertQuery{data1, data2}, + Now: time.Time{}, + }) + + require.Equal(t, http.StatusOK, response.Status()) + + evaluator.AssertCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything) + }) + }) + + t.Run("when fine-grained access is disabled", func(t *testing.T) { + rc := &models2.ReqContext{ + Context: &web.Context{ + Req: &http.Request{}, + }, + IsSignedIn: false, + SignedInUser: &models2.SignedInUser{ + OrgId: 1, + }, + } + ac := acMock.New().WithDisabled() + + t.Run("should require user to be signed in", func(t *testing.T) { + data1 := models.GenerateAlertQuery() + + ds := &datasources.FakeCacheService{DataSources: []*models2.DataSource{ + {Uid: data1.DatasourceUID}, + }} + + evaluator := &eval.FakeEvaluator{} + result := &backend.QueryDataResponse{ + Responses: map[string]backend.DataResponse{ + "test": { + Frames: nil, + Error: nil, + }, + }, + } + evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result, nil) + + srv := createTestingApiSrv(ds, ac, evaluator) + + response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{ + Data: []models.AlertQuery{data1}, + Now: time.Time{}, + }) + + require.Equal(t, http.StatusUnauthorized, response.Status()) + evaluator.AssertNotCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything) + + rc.IsSignedIn = true + + response = srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{ + Data: []models.AlertQuery{data1}, + Now: time.Time{}, + }) + + require.Equal(t, http.StatusOK, response.Status()) + + evaluator.AssertCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything) + }) + }) +} + +func createTestingApiSrv(ds *datasources.FakeCacheService, ac *acMock.Mock, evaluator *eval.FakeEvaluator) *TestingApiSrv { + if ac == nil { + ac = acMock.New().WithDisabled() + } + + return &TestingApiSrv{ + DatasourceCache: ds, + accessControl: ac, + evaluator: evaluator, + } +} diff --git a/pkg/services/ngalert/api/util.go b/pkg/services/ngalert/api/util.go index 186af0d99e1..76808313bae 100644 --- a/pkg/services/ngalert/api/util.go +++ b/pkg/services/ngalert/api/util.go @@ -12,22 +12,15 @@ import ( "strconv" "strings" - "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/pkg/errors" "gopkg.in/yaml.v3" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/expr" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - "github.com/grafana/grafana/pkg/services/ngalert/eval" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" - "github.com/grafana/grafana/pkg/services/secrets" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -239,33 +232,6 @@ func validateQueriesAndExpressions(ctx context.Context, data []ngmodels.AlertQue return refIDs, nil } -func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand, datasourceCache datasources.CacheService, expressionService *expr.Service, secretsService secrets.Service, cfg *setting.Cfg, log log.Logger) response.Response { - evalCond := ngmodels.Condition{ - Condition: cmd.Condition, - OrgID: c.SignedInUser.OrgId, - Data: cmd.Data, - } - if err := validateCondition(c.Req.Context(), evalCond, c.SignedInUser, c.SkipCache, datasourceCache); err != nil { - return ErrResp(http.StatusBadRequest, err, "invalid condition") - } - - now := cmd.Now - if now.IsZero() { - now = timeNow() - } - - evaluator := eval.NewEvaluator(cfg, log, datasourceCache, secretsService) - evalResults, err := evaluator.ConditionEval(&evalCond, now, expressionService) - if err != nil { - return ErrResp(http.StatusBadRequest, err, "Failed to evaluate conditions") - } - - frame := evalResults.AsDataFrame() - return response.JSONStreaming(http.StatusOK, util.DynMap{ - "instances": []*data.Frame{&frame}, - }) -} - // ErrorResp creates a response with a visible error func ErrResp(status int, err error, msg string, args ...interface{}) *response.NormalResponse { if msg != "" { diff --git a/pkg/services/ngalert/eval/eval.go b/pkg/services/ngalert/eval/eval.go index 7d93132ef5a..7ad329a0095 100644 --- a/pkg/services/ngalert/eval/eval.go +++ b/pkg/services/ngalert/eval/eval.go @@ -22,10 +22,19 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr" ) -type Evaluator struct { +//go:generate mockery --name Evaluator --structname FakeEvaluator --inpackage --filename evaluator_mock.go --with-expecter +type Evaluator interface { + // ConditionEval executes conditions and evaluates the result. + ConditionEval(condition *models.Condition, now time.Time, expressionService *expr.Service) (Results, error) + // QueriesAndExpressionsEval executes queries and expressions and returns the result. + QueriesAndExpressionsEval(orgID int64, data []models.AlertQuery, now time.Time, expressionService *expr.Service) (*backend.QueryDataResponse, error) +} + +type evaluatorImpl struct { cfg *setting.Cfg log log.Logger dataSourceCache datasources.CacheService @@ -36,8 +45,8 @@ func NewEvaluator( cfg *setting.Cfg, log log.Logger, datasourceCache datasources.CacheService, - secretsService secrets.Service) *Evaluator { - return &Evaluator{ + secretsService secrets.Service) Evaluator { + return &evaluatorImpl{ cfg: cfg, log: log, dataSourceCache: datasourceCache, @@ -576,7 +585,7 @@ func (evalResults Results) AsDataFrame() data.Frame { } // ConditionEval executes conditions and evaluates the result. -func (e *Evaluator) ConditionEval(condition *models.Condition, now time.Time, expressionService *expr.Service) (Results, error) { +func (e *evaluatorImpl) ConditionEval(condition *models.Condition, now time.Time, expressionService *expr.Service) (Results, error) { alertCtx, cancelFn := context.WithTimeout(context.Background(), e.cfg.UnifiedAlerting.EvaluationTimeout) defer cancelFn() @@ -589,7 +598,7 @@ func (e *Evaluator) ConditionEval(condition *models.Condition, now time.Time, ex } // QueriesAndExpressionsEval executes queries and expressions and returns the result. -func (e *Evaluator) QueriesAndExpressionsEval(orgID int64, data []models.AlertQuery, now time.Time, expressionService *expr.Service) (*backend.QueryDataResponse, error) { +func (e *evaluatorImpl) QueriesAndExpressionsEval(orgID int64, data []models.AlertQuery, now time.Time, expressionService *expr.Service) (*backend.QueryDataResponse, error) { alertCtx, cancelFn := context.WithTimeout(context.Background(), e.cfg.UnifiedAlerting.EvaluationTimeout) defer cancelFn() diff --git a/pkg/services/ngalert/eval/evaluator_mock.go b/pkg/services/ngalert/eval/evaluator_mock.go new file mode 100644 index 00000000000..70ff9b14871 --- /dev/null +++ b/pkg/services/ngalert/eval/evaluator_mock.go @@ -0,0 +1,124 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package eval + +import ( + backend "github.com/grafana/grafana-plugin-sdk-go/backend" + expr "github.com/grafana/grafana/pkg/expr" + + mock "github.com/stretchr/testify/mock" + + models "github.com/grafana/grafana/pkg/services/ngalert/models" + + time "time" +) + +// FakeEvaluator is an autogenerated mock type for the Evaluator type +type FakeEvaluator struct { + mock.Mock +} + +type FakeEvaluator_Expecter struct { + mock *mock.Mock +} + +func (_m *FakeEvaluator) EXPECT() *FakeEvaluator_Expecter { + return &FakeEvaluator_Expecter{mock: &_m.Mock} +} + +// ConditionEval provides a mock function with given fields: condition, now, expressionService +func (_m *FakeEvaluator) ConditionEval(condition *models.Condition, now time.Time, expressionService *expr.Service) (Results, error) { + ret := _m.Called(condition, now, expressionService) + + var r0 Results + if rf, ok := ret.Get(0).(func(*models.Condition, time.Time, *expr.Service) Results); ok { + r0 = rf(condition, now, expressionService) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(Results) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*models.Condition, time.Time, *expr.Service) error); ok { + r1 = rf(condition, now, expressionService) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FakeEvaluator_ConditionEval_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ConditionEval' +type FakeEvaluator_ConditionEval_Call struct { + *mock.Call +} + +// ConditionEval is a helper method to define mock.On call +// - condition *models.Condition +// - now time.Time +// - expressionService *expr.Service +func (_e *FakeEvaluator_Expecter) ConditionEval(condition interface{}, now interface{}, expressionService interface{}) *FakeEvaluator_ConditionEval_Call { + return &FakeEvaluator_ConditionEval_Call{Call: _e.mock.On("ConditionEval", condition, now, expressionService)} +} + +func (_c *FakeEvaluator_ConditionEval_Call) Run(run func(condition *models.Condition, now time.Time, expressionService *expr.Service)) *FakeEvaluator_ConditionEval_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*models.Condition), args[1].(time.Time), args[2].(*expr.Service)) + }) + return _c +} + +func (_c *FakeEvaluator_ConditionEval_Call) Return(_a0 Results, _a1 error) *FakeEvaluator_ConditionEval_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// QueriesAndExpressionsEval provides a mock function with given fields: orgID, data, now, expressionService +func (_m *FakeEvaluator) QueriesAndExpressionsEval(orgID int64, data []models.AlertQuery, now time.Time, expressionService *expr.Service) (*backend.QueryDataResponse, error) { + ret := _m.Called(orgID, data, now, expressionService) + + var r0 *backend.QueryDataResponse + if rf, ok := ret.Get(0).(func(int64, []models.AlertQuery, time.Time, *expr.Service) *backend.QueryDataResponse); ok { + r0 = rf(orgID, data, now, expressionService) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*backend.QueryDataResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int64, []models.AlertQuery, time.Time, *expr.Service) error); ok { + r1 = rf(orgID, data, now, expressionService) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FakeEvaluator_QueriesAndExpressionsEval_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueriesAndExpressionsEval' +type FakeEvaluator_QueriesAndExpressionsEval_Call struct { + *mock.Call +} + +// QueriesAndExpressionsEval is a helper method to define mock.On call +// - orgID int64 +// - data []models.AlertQuery +// - now time.Time +// - expressionService *expr.Service +func (_e *FakeEvaluator_Expecter) QueriesAndExpressionsEval(orgID interface{}, data interface{}, now interface{}, expressionService interface{}) *FakeEvaluator_QueriesAndExpressionsEval_Call { + return &FakeEvaluator_QueriesAndExpressionsEval_Call{Call: _e.mock.On("QueriesAndExpressionsEval", orgID, data, now, expressionService)} +} + +func (_c *FakeEvaluator_QueriesAndExpressionsEval_Call) Run(run func(orgID int64, data []models.AlertQuery, now time.Time, expressionService *expr.Service)) *FakeEvaluator_QueriesAndExpressionsEval_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(int64), args[1].([]models.AlertQuery), args[2].(time.Time), args[3].(*expr.Service)) + }) + return _c +} + +func (_c *FakeEvaluator_QueriesAndExpressionsEval_Call) Return(_a0 *backend.QueryDataResponse, _a1 error) *FakeEvaluator_QueriesAndExpressionsEval_Call { + _c.Call.Return(_a0, _a1) + return _c +} diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index 962daff8f90..a15438d5f8d 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -76,7 +76,7 @@ type schedule struct { log log.Logger - evaluator *eval.Evaluator + evaluator eval.Evaluator ruleStore store.RuleStore instanceStore store.InstanceStore @@ -109,7 +109,7 @@ type SchedulerCfg struct { EvalAppliedFunc func(models.AlertRuleKey, time.Time) MaxAttempts int64 StopAppliedFunc func(models.AlertRuleKey) - Evaluator *eval.Evaluator + Evaluator eval.Evaluator RuleStore store.RuleStore OrgStore store.OrgStore InstanceStore store.InstanceStore