Alerting: Condition evaluator with cached pipeline (#57479)
* create rule evaluator * load header from the context * init one factory * update scheduler
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
@@ -64,7 +63,6 @@ type API struct {
|
||||
DatasourceCache datasources.CacheService
|
||||
DatasourceService datasources.DataSourceService
|
||||
RouteRegister routing.RouteRegister
|
||||
ExpressionService *expr.Service
|
||||
QuotaService quota.Service
|
||||
Schedule schedule.ScheduleService
|
||||
TransactionManager provisioning.TransactionManager
|
||||
@@ -82,6 +80,7 @@ type API struct {
|
||||
MuteTimings *provisioning.MuteTimingService
|
||||
AlertRules *provisioning.AlertRuleService
|
||||
AlertsRouter *sender.AlertsRouter
|
||||
EvaluatorFactory eval.EvaluatorFactory
|
||||
}
|
||||
|
||||
// RegisterAPIEndpoints registers API handlers
|
||||
@@ -92,8 +91,6 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
ac: api.AccessControl,
|
||||
}
|
||||
|
||||
evaluator := eval.NewEvaluator(api.Cfg, api.DatasourceCache, api.ExpressionService)
|
||||
|
||||
// Register endpoints for proxying to Alertmanager-compatible backends.
|
||||
api.RegisterAlertmanagerApiEndpoints(NewForkingAM(
|
||||
api.DatasourceCache,
|
||||
@@ -111,7 +108,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
api.DatasourceCache,
|
||||
NewLotexRuler(proxy, logger),
|
||||
&RulerSrv{
|
||||
conditionValidator: evaluator,
|
||||
conditionValidator: api.EvaluatorFactory,
|
||||
QuotaService: api.QuotaService,
|
||||
scheduleService: api.Schedule,
|
||||
store: api.RuleStore,
|
||||
@@ -128,7 +125,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
DatasourceCache: api.DatasourceCache,
|
||||
log: logger,
|
||||
accessControl: api.AccessControl,
|
||||
evaluator: evaluator,
|
||||
evaluator: api.EvaluatorFactory,
|
||||
}), m)
|
||||
api.RegisterConfigurationApiEndpoints(NewConfiguration(
|
||||
&ConfigSrv{
|
||||
|
||||
@@ -24,7 +24,7 @@ type TestingApiSrv struct {
|
||||
DatasourceCache datasources.CacheService
|
||||
log log.Logger
|
||||
accessControl accesscontrol.AccessControl
|
||||
evaluator eval.Evaluator
|
||||
evaluator eval.EvaluatorFactory
|
||||
}
|
||||
|
||||
func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response {
|
||||
@@ -44,16 +44,20 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body a
|
||||
}
|
||||
ctx := eval.Context(c.Req.Context(), c.SignedInUser)
|
||||
|
||||
if err := srv.evaluator.Validate(ctx, evalCond); err != nil {
|
||||
conditionEval, err := srv.evaluator.Create(ctx, evalCond)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusBadRequest, err, "invalid condition")
|
||||
}
|
||||
|
||||
ctx = ctx.When(body.GrafanaManagedCondition.Now)
|
||||
if ctx.At.IsZero() {
|
||||
ctx = ctx.When(timeNow())
|
||||
now := body.GrafanaManagedCondition.Now
|
||||
if now.IsZero() {
|
||||
now = timeNow()
|
||||
}
|
||||
|
||||
evalResults := srv.evaluator.ConditionEval(ctx, evalCond)
|
||||
evalResults, err := conditionEval.Evaluate(c.Req.Context(), now)
|
||||
if err != nil {
|
||||
return ErrResp(500, err, "Failed to evaluate the rule")
|
||||
}
|
||||
|
||||
frame := evalResults.AsDataFrame()
|
||||
return response.JSONStreaming(http.StatusOK, util.DynMap{
|
||||
@@ -106,14 +110,28 @@ func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.Ev
|
||||
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "")
|
||||
}
|
||||
|
||||
ctx := eval.Context(c.Req.Context(), c.SignedInUser).When(cmd.Now)
|
||||
if ctx.At.IsZero() {
|
||||
ctx = ctx.When(timeNow())
|
||||
cond := ngmodels.Condition{
|
||||
Condition: "",
|
||||
Data: cmd.Data,
|
||||
}
|
||||
if len(cmd.Data) > 0 {
|
||||
cond.Condition = cmd.Data[0].RefID
|
||||
}
|
||||
evaluator, err := srv.evaluator.Create(eval.Context(c.Req.Context(), c.SignedInUser), cond)
|
||||
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusBadRequest, err, "Failed to build evaluator for queries and expressions")
|
||||
}
|
||||
|
||||
evalResults, err := srv.evaluator.QueriesAndExpressionsEval(ctx, cmd.Data)
|
||||
now := cmd.Now
|
||||
if now.IsZero() {
|
||||
now = timeNow()
|
||||
}
|
||||
|
||||
evalResults, err := evaluator.EvaluateRaw(c.Req.Context(), now)
|
||||
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusBadRequest, err, "Failed to evaluate queries and expressions")
|
||||
return ErrResp(http.StatusInternalServerError, err, "Failed to evaluate queries and expressions")
|
||||
}
|
||||
|
||||
return response.JSONStreaming(http.StatusOK, evalResults)
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
fakes "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"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/eval/eval_mocks"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@@ -58,6 +59,8 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
data2 := models.GenerateAlertQuery()
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
|
||||
@@ -68,26 +71,26 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
{Uid: data2.DatasourceUID},
|
||||
}}
|
||||
|
||||
evaluator := &eval.FakeEvaluator{}
|
||||
var result []eval.Result
|
||||
evaluator.EXPECT().Validate(mock.Anything, mock.Anything).Return(nil)
|
||||
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything).Return(result)
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||
evalFactory := eval_mocks.NewEvaluatorFactory(evaluator)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, evalFactory)
|
||||
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
|
||||
Expr: "",
|
||||
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
|
||||
Condition: data1.RefID,
|
||||
Data: []models.AlertQuery{data1, data2},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -109,26 +112,25 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
|
||||
{Uid: data1.DatasourceUID},
|
||||
}}
|
||||
currentTime := time.Now()
|
||||
|
||||
evaluator := &eval.FakeEvaluator{}
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
var result []eval.Result
|
||||
evaluator.EXPECT().Validate(mock.Anything, mock.Anything).Return(nil)
|
||||
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything).Return(result)
|
||||
evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
||||
|
||||
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
|
||||
Expr: "",
|
||||
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
|
||||
Condition: data1.RefID,
|
||||
Data: []models.AlertQuery{data1},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
evaluator.AssertNotCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertNotCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertNotCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
|
||||
rc.IsSignedIn = true
|
||||
|
||||
@@ -137,14 +139,13 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
||||
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
|
||||
Condition: data1.RefID,
|
||||
Data: []models.AlertQuery{data1},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "ConditionEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "Validate", mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -184,6 +185,8 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
data1 := models.GenerateAlertQuery()
|
||||
data2 := models.GenerateAlertQuery()
|
||||
|
||||
currentTime := time.Now()
|
||||
|
||||
ac := acMock.New().WithPermissions([]accesscontrol.Permission{
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data1.DatasourceUID)},
|
||||
{Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(data2.DatasourceUID)},
|
||||
@@ -194,7 +197,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
{Uid: data2.DatasourceUID},
|
||||
}}
|
||||
|
||||
evaluator := &eval.FakeEvaluator{}
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
result := &backend.QueryDataResponse{
|
||||
Responses: map[string]backend.DataResponse{
|
||||
"test": {
|
||||
@@ -203,18 +206,18 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything).Return(result, nil)
|
||||
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
||||
|
||||
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: []models.AlertQuery{data1, data2},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -237,7 +240,9 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
{Uid: data1.DatasourceUID},
|
||||
}}
|
||||
|
||||
evaluator := &eval.FakeEvaluator{}
|
||||
currentTime := time.Now()
|
||||
|
||||
evaluator := &eval_mocks.ConditionEvaluatorMock{}
|
||||
result := &backend.QueryDataResponse{
|
||||
Responses: map[string]backend.DataResponse{
|
||||
"test": {
|
||||
@@ -246,33 +251,33 @@ func TestRouteEvalQueries(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything).Return(result, nil)
|
||||
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
|
||||
|
||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
|
||||
|
||||
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: []models.AlertQuery{data1},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusUnauthorized, response.Status())
|
||||
evaluator.AssertNotCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertNotCalled(t, "EvaluateRaw", mock.Anything, mock.Anything)
|
||||
|
||||
rc.IsSignedIn = true
|
||||
|
||||
response = srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
|
||||
Data: []models.AlertQuery{data1},
|
||||
Now: time.Time{},
|
||||
Now: currentTime,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Status())
|
||||
|
||||
evaluator.AssertCalled(t, "QueriesAndExpressionsEval", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
|
||||
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createTestingApiSrv(ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator *eval.FakeEvaluator) *TestingApiSrv {
|
||||
func createTestingApiSrv(ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory) *TestingApiSrv {
|
||||
if ac == nil {
|
||||
ac = acMock.New().WithDisabled()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user