mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Add traceability headers for alert queries (#57127)
* Define EvaluationContext * Refactor ConditionEval to use new context struct * Refactor QueriesAndExpressionsEval to use EvaluationContext * Remove dead field from AlertExecCtx * Refactor Validate to use EvaluationContext * Get rid of privately used AlertExecCtx * Move EvaluationContext to new file and add helper * Add builder pattern and bind rule info to context * Extract header logic and add rule UID header * Fix missing call
This commit is contained in:
parent
85cda0db69
commit
4eb8e4ff66
@ -10,10 +10,10 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
@ -30,7 +30,7 @@ import (
|
|||||||
|
|
||||||
type ConditionValidator interface {
|
type ConditionValidator interface {
|
||||||
// Validate validates that the condition is correct. Returns nil if the condition is correct. Otherwise, error that describes the failure
|
// Validate validates that the condition is correct. Returns nil if the condition is correct. Otherwise, error that describes the failure
|
||||||
Validate(ctx context.Context, user *user.SignedInUser, condition ngmodels.Condition) error
|
Validate(ctx eval.EvaluationContext, condition ngmodels.Condition) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type RulerSrv struct {
|
type RulerSrv struct {
|
||||||
@ -308,7 +308,7 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
|||||||
}
|
}
|
||||||
|
|
||||||
rules, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.OrgID, namespace, func(condition ngmodels.Condition) error {
|
rules, err := validateRuleGroup(&ruleGroupConfig, c.SignedInUser.OrgID, namespace, func(condition ngmodels.Condition) error {
|
||||||
return srv.conditionValidator.Validate(c.Req.Context(), c.SignedInUser, condition)
|
return srv.conditionValidator.Validate(eval.Context(c.Req.Context(), c.SignedInUser), condition)
|
||||||
}, srv.cfg)
|
}, srv.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrResp(http.StatusBadRequest, err, "")
|
return ErrResp(http.StatusBadRequest, err, "")
|
||||||
|
@ -42,17 +42,18 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body a
|
|||||||
Condition: body.GrafanaManagedCondition.Condition,
|
Condition: body.GrafanaManagedCondition.Condition,
|
||||||
Data: body.GrafanaManagedCondition.Data,
|
Data: body.GrafanaManagedCondition.Data,
|
||||||
}
|
}
|
||||||
|
ctx := eval.Context(c.Req.Context(), c.SignedInUser)
|
||||||
|
|
||||||
if err := srv.evaluator.Validate(c.Req.Context(), c.SignedInUser, evalCond); err != nil {
|
if err := srv.evaluator.Validate(ctx, evalCond); err != nil {
|
||||||
return ErrResp(http.StatusBadRequest, err, "invalid condition")
|
return ErrResp(http.StatusBadRequest, err, "invalid condition")
|
||||||
}
|
}
|
||||||
|
|
||||||
now := body.GrafanaManagedCondition.Now
|
ctx = ctx.When(body.GrafanaManagedCondition.Now)
|
||||||
if now.IsZero() {
|
if ctx.At.IsZero() {
|
||||||
now = timeNow()
|
ctx = ctx.When(timeNow())
|
||||||
}
|
}
|
||||||
|
|
||||||
evalResults := srv.evaluator.ConditionEval(c.Req.Context(), c.SignedInUser, evalCond, now)
|
evalResults := srv.evaluator.ConditionEval(ctx, evalCond)
|
||||||
|
|
||||||
frame := evalResults.AsDataFrame()
|
frame := evalResults.AsDataFrame()
|
||||||
return response.JSONStreaming(http.StatusOK, util.DynMap{
|
return response.JSONStreaming(http.StatusOK, util.DynMap{
|
||||||
@ -99,18 +100,18 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
|
func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
|
||||||
now := cmd.Now
|
|
||||||
if now.IsZero() {
|
|
||||||
now = timeNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: cmd.Data}, func(evaluator accesscontrol.Evaluator) bool {
|
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: cmd.Data}, func(evaluator accesscontrol.Evaluator) bool {
|
||||||
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
|
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), "")
|
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "")
|
||||||
}
|
}
|
||||||
|
|
||||||
evalResults, err := srv.evaluator.QueriesAndExpressionsEval(c.Req.Context(), c.SignedInUser, cmd.Data, now)
|
ctx := eval.Context(c.Req.Context(), c.SignedInUser).When(cmd.Now)
|
||||||
|
if ctx.At.IsZero() {
|
||||||
|
ctx = ctx.When(timeNow())
|
||||||
|
}
|
||||||
|
|
||||||
|
evalResults, err := srv.evaluator.QueriesAndExpressionsEval(ctx, cmd.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrResp(http.StatusBadRequest, err, "Failed to evaluate queries and expressions")
|
return ErrResp(http.StatusBadRequest, err, "Failed to evaluate queries and expressions")
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,8 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
|||||||
|
|
||||||
evaluator := &eval.FakeEvaluator{}
|
evaluator := &eval.FakeEvaluator{}
|
||||||
var result []eval.Result
|
var result []eval.Result
|
||||||
evaluator.EXPECT().Validate(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
evaluator.EXPECT().Validate(mock.Anything, mock.Anything).Return(nil)
|
||||||
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result)
|
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything).Return(result)
|
||||||
|
|
||||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||||
|
|
||||||
@ -112,8 +112,8 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
|
|||||||
|
|
||||||
evaluator := &eval.FakeEvaluator{}
|
evaluator := &eval.FakeEvaluator{}
|
||||||
var result []eval.Result
|
var result []eval.Result
|
||||||
evaluator.EXPECT().Validate(mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
evaluator.EXPECT().Validate(mock.Anything, mock.Anything).Return(nil)
|
||||||
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result)
|
evaluator.EXPECT().ConditionEval(mock.Anything, mock.Anything).Return(result)
|
||||||
|
|
||||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result, nil)
|
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||||
|
|
||||||
@ -246,7 +246,7 @@ func TestRouteEvalQueries(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(result, nil)
|
evaluator.EXPECT().QueriesAndExpressionsEval(mock.Anything, mock.Anything).Return(result, nil)
|
||||||
|
|
||||||
srv := createTestingApiSrv(ds, ac, evaluator)
|
srv := createTestingApiSrv(ds, ac, evaluator)
|
||||||
|
|
||||||
|
43
pkg/services/ngalert/eval/context.go
Normal file
43
pkg/services/ngalert/eval/context.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package eval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvaluationContext represents the context in which a condition is evaluated.
|
||||||
|
type EvaluationContext struct {
|
||||||
|
Ctx context.Context
|
||||||
|
User *user.SignedInUser
|
||||||
|
At time.Time
|
||||||
|
RuleUID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Context(ctx context.Context, user *user.SignedInUser) EvaluationContext {
|
||||||
|
return EvaluationContext{
|
||||||
|
Ctx: ctx,
|
||||||
|
User: user,
|
||||||
|
At: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c EvaluationContext) When(t time.Time) EvaluationContext {
|
||||||
|
c.At = t
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c EvaluationContext) WithRule(r *models.AlertRule) EvaluationContext {
|
||||||
|
if r != nil {
|
||||||
|
c.RuleUID = r.UID
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c EvaluationContext) WithTimeout(timeout time.Duration) (EvaluationContext, context.CancelFunc) {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(c.Ctx, timeout)
|
||||||
|
c.Ctx = timeoutCtx
|
||||||
|
return c, cancel
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
package eval
|
package eval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
@ -17,7 +16,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
@ -27,11 +25,11 @@ import (
|
|||||||
//go:generate mockery --name Evaluator --structname FakeEvaluator --inpackage --filename evaluator_mock.go --with-expecter
|
//go:generate mockery --name Evaluator --structname FakeEvaluator --inpackage --filename evaluator_mock.go --with-expecter
|
||||||
type Evaluator interface {
|
type Evaluator interface {
|
||||||
// ConditionEval executes conditions and evaluates the result.
|
// ConditionEval executes conditions and evaluates the result.
|
||||||
ConditionEval(ctx context.Context, user *user.SignedInUser, condition models.Condition, now time.Time) Results
|
ConditionEval(ctx EvaluationContext, condition models.Condition) Results
|
||||||
// QueriesAndExpressionsEval executes queries and expressions and returns the result.
|
// QueriesAndExpressionsEval executes queries and expressions and returns the result.
|
||||||
QueriesAndExpressionsEval(ctx context.Context, user *user.SignedInUser, data []models.AlertQuery, now time.Time) (*backend.QueryDataResponse, error)
|
QueriesAndExpressionsEval(ctx EvaluationContext, data []models.AlertQuery) (*backend.QueryDataResponse, error)
|
||||||
// Validate validates that the condition is correct. Returns nil if the condition is correct. Otherwise, error that describes the failure
|
// Validate validates that the condition is correct. Returns nil if the condition is correct. Otherwise, error that describes the failure
|
||||||
Validate(ctx context.Context, user *user.SignedInUser, condition models.Condition) error
|
Validate(ctx EvaluationContext, condition models.Condition) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type evaluatorImpl struct {
|
type evaluatorImpl struct {
|
||||||
@ -159,24 +157,31 @@ func (s State) String() string {
|
|||||||
return [...]string{"Normal", "Alerting", "Pending", "NoData", "Error"}[s]
|
return [...]string{"Normal", "Alerting", "Pending", "NoData", "Error"}[s]
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlertExecCtx is the context provided for executing an alert condition.
|
func buildDatasourceHeaders(ctx EvaluationContext) map[string]string {
|
||||||
type AlertExecCtx struct {
|
headers := map[string]string{
|
||||||
User *user.SignedInUser
|
// Many data sources check this in query method as sometimes alerting needs special considerations.
|
||||||
ExpressionsEnabled bool
|
// Several existing systems also compare against the value of this header. Altering this constitutes a breaking change.
|
||||||
Log log.Logger
|
//
|
||||||
|
// Note: The spelling of this headers is intentionally degenerate from the others for compatibility reasons.
|
||||||
|
// When sent over a network, the key of this header is canonicalized to "Fromalert".
|
||||||
|
// However, some datasources still compare against the string "FromAlert".
|
||||||
|
"FromAlert": "true",
|
||||||
|
|
||||||
Ctx context.Context
|
"X-Cache-Skip": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.RuleUID != "" {
|
||||||
|
headers["X-Rule-Uid"] = ctx.RuleUID
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
// getExprRequest validates the condition, gets the datasource information and creates an expr.Request from it.
|
// getExprRequest validates the condition, gets the datasource information and creates an expr.Request from it.
|
||||||
func getExprRequest(ctx AlertExecCtx, data []models.AlertQuery, now time.Time, dsCacheService datasources.CacheService) (*expr.Request, error) {
|
func getExprRequest(ctx EvaluationContext, data []models.AlertQuery, dsCacheService datasources.CacheService) (*expr.Request, error) {
|
||||||
req := &expr.Request{
|
req := &expr.Request{
|
||||||
OrgId: ctx.User.OrgID,
|
OrgId: ctx.User.OrgID,
|
||||||
Headers: map[string]string{
|
Headers: buildDatasourceHeaders(ctx),
|
||||||
// Some data sources check this in query method as sometimes alerting needs special considerations.
|
|
||||||
"FromAlert": "true",
|
|
||||||
"X-Cache-Skip": "true",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datasources := make(map[string]*datasources.DataSource, len(data))
|
datasources := make(map[string]*datasources.DataSource, len(data))
|
||||||
@ -211,8 +216,8 @@ func getExprRequest(ctx AlertExecCtx, data []models.AlertQuery, now time.Time, d
|
|||||||
|
|
||||||
req.Queries = append(req.Queries, expr.Query{
|
req.Queries = append(req.Queries, expr.Query{
|
||||||
TimeRange: expr.TimeRange{
|
TimeRange: expr.TimeRange{
|
||||||
From: q.RelativeTimeRange.ToTimeRange(now).From,
|
From: q.RelativeTimeRange.ToTimeRange(ctx.At).From,
|
||||||
To: q.RelativeTimeRange.ToTimeRange(now).To,
|
To: q.RelativeTimeRange.ToTimeRange(ctx.At).To,
|
||||||
},
|
},
|
||||||
DataSource: ds,
|
DataSource: ds,
|
||||||
JSON: model,
|
JSON: model,
|
||||||
@ -311,10 +316,10 @@ func queryDataResponseToExecutionResults(c models.Condition, execResp *backend.Q
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func executeQueriesAndExpressions(ctx AlertExecCtx, data []models.AlertQuery, now time.Time, exprService *expr.Service, dsCacheService datasources.CacheService) (resp *backend.QueryDataResponse, err error) {
|
func executeQueriesAndExpressions(ctx EvaluationContext, data []models.AlertQuery, exprService *expr.Service, dsCacheService datasources.CacheService, log log.Logger) (resp *backend.QueryDataResponse, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
if e := recover(); e != nil {
|
||||||
ctx.Log.Error("alert rule panic", "error", e, "stack", string(debug.Stack()))
|
log.Error("alert rule panic", "error", e, "stack", string(debug.Stack()))
|
||||||
panicErr := fmt.Errorf("alert rule panic; please check the logs for the full stack")
|
panicErr := fmt.Errorf("alert rule panic; please check the logs for the full stack")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("queries and expressions execution failed: %w; %v", err, panicErr.Error())
|
err = fmt.Errorf("queries and expressions execution failed: %w; %v", err, panicErr.Error())
|
||||||
@ -324,7 +329,7 @@ func executeQueriesAndExpressions(ctx AlertExecCtx, data []models.AlertQuery, no
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
queryDataReq, err := getExprRequest(ctx, data, now, dsCacheService)
|
queryDataReq, err := getExprRequest(ctx, data, dsCacheService)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -560,25 +565,23 @@ func (evalResults Results) AsDataFrame() data.Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConditionEval executes conditions and evaluates the result.
|
// ConditionEval executes conditions and evaluates the result.
|
||||||
func (e *evaluatorImpl) ConditionEval(ctx context.Context, user *user.SignedInUser, condition models.Condition, now time.Time) Results {
|
func (e *evaluatorImpl) ConditionEval(ctx EvaluationContext, condition models.Condition) Results {
|
||||||
execResp, err := e.QueriesAndExpressionsEval(ctx, user, condition.Data, now)
|
execResp, err := e.QueriesAndExpressionsEval(ctx, condition.Data)
|
||||||
var execResults ExecutionResults
|
var execResults ExecutionResults
|
||||||
if err != nil {
|
if err != nil {
|
||||||
execResults = ExecutionResults{Error: err}
|
execResults = ExecutionResults{Error: err}
|
||||||
} else {
|
} else {
|
||||||
execResults = queryDataResponseToExecutionResults(condition, execResp)
|
execResults = queryDataResponseToExecutionResults(condition, execResp)
|
||||||
}
|
}
|
||||||
return evaluateExecutionResult(execResults, now)
|
return evaluateExecutionResult(execResults, ctx.At)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueriesAndExpressionsEval executes queries and expressions and returns the result.
|
// QueriesAndExpressionsEval executes queries and expressions and returns the result.
|
||||||
func (e *evaluatorImpl) QueriesAndExpressionsEval(ctx context.Context, user *user.SignedInUser, data []models.AlertQuery, now time.Time) (*backend.QueryDataResponse, error) {
|
func (e *evaluatorImpl) QueriesAndExpressionsEval(ctx EvaluationContext, data []models.AlertQuery) (*backend.QueryDataResponse, error) {
|
||||||
alertCtx, cancelFn := context.WithTimeout(ctx, e.cfg.UnifiedAlerting.EvaluationTimeout)
|
timeoutCtx, cancelFn := ctx.WithTimeout(e.cfg.UnifiedAlerting.EvaluationTimeout)
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
|
|
||||||
alertExecCtx := AlertExecCtx{User: user, Ctx: alertCtx, ExpressionsEnabled: e.cfg.ExpressionsEnabled, Log: e.log}
|
execResult, err := executeQueriesAndExpressions(timeoutCtx, data, e.expressionService, e.dataSourceCache, e.log)
|
||||||
|
|
||||||
execResult, err := executeQueriesAndExpressions(alertExecCtx, data, now, e.expressionService, e.dataSourceCache)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute conditions: %w", err)
|
return nil, fmt.Errorf("failed to execute conditions: %w", err)
|
||||||
}
|
}
|
||||||
@ -586,14 +589,7 @@ func (e *evaluatorImpl) QueriesAndExpressionsEval(ctx context.Context, user *use
|
|||||||
return execResult, nil
|
return execResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *evaluatorImpl) Validate(ctx context.Context, user *user.SignedInUser, condition models.Condition) error {
|
func (e *evaluatorImpl) Validate(ctx EvaluationContext, condition models.Condition) error {
|
||||||
evalctx := AlertExecCtx{
|
|
||||||
User: user,
|
|
||||||
ExpressionsEnabled: e.cfg.ExpressionsEnabled,
|
|
||||||
Log: e.log,
|
|
||||||
Ctx: ctx,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(condition.Data) == 0 {
|
if len(condition.Data) == 0 {
|
||||||
return errors.New("expression list is empty. must be at least 1 expression")
|
return errors.New("expression list is empty. must be at least 1 expression")
|
||||||
}
|
}
|
||||||
@ -601,7 +597,9 @@ func (e *evaluatorImpl) Validate(ctx context.Context, user *user.SignedInUser, c
|
|||||||
return errors.New("condition must not be empty")
|
return errors.New("condition must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := getExprRequest(evalctx, condition.Data, time.Now(), e.dataSourceCache)
|
ctx.At = time.Now()
|
||||||
|
|
||||||
|
req, err := getExprRequest(ctx, condition.Data, e.dataSourceCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -445,8 +445,9 @@ func TestValidate(t *testing.T) {
|
|||||||
condition := testCase.condition(cacheService)
|
condition := testCase.condition(cacheService)
|
||||||
|
|
||||||
evaluator := NewEvaluator(&setting.Cfg{ExpressionsEnabled: true}, log.New("test"), cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil))
|
evaluator := NewEvaluator(&setting.Cfg{ExpressionsEnabled: true}, log.New("test"), cacheService, expr.ProvideService(&setting.Cfg{ExpressionsEnabled: true}, nil, nil))
|
||||||
|
evalCtx := Context(context.Background(), u)
|
||||||
|
|
||||||
err := evaluator.Validate(context.Background(), u, condition)
|
err := evaluator.Validate(evalCtx, condition)
|
||||||
if testCase.error {
|
if testCase.error {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
// Code generated by mockery v2.10.0. DO NOT EDIT.
|
// Code generated by mockery v2.12.0. DO NOT EDIT.
|
||||||
|
|
||||||
package eval
|
package eval
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
|
||||||
|
|
||||||
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
|
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
|
||||||
time "time"
|
testing "testing"
|
||||||
|
|
||||||
user "github.com/grafana/grafana/pkg/services/user"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// FakeEvaluator is an autogenerated mock type for the Evaluator type
|
// FakeEvaluator is an autogenerated mock type for the Evaluator type
|
||||||
@ -29,13 +24,13 @@ func (_m *FakeEvaluator) EXPECT() *FakeEvaluator_Expecter {
|
|||||||
return &FakeEvaluator_Expecter{mock: &_m.Mock}
|
return &FakeEvaluator_Expecter{mock: &_m.Mock}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConditionEval provides a mock function with given fields: ctx, _a1, condition, now
|
// ConditionEval provides a mock function with given fields: ctx, condition
|
||||||
func (_m *FakeEvaluator) ConditionEval(ctx context.Context, _a1 *user.SignedInUser, condition models.Condition, now time.Time) Results {
|
func (_m *FakeEvaluator) ConditionEval(ctx EvaluationContext, condition models.Condition) Results {
|
||||||
ret := _m.Called(ctx, _a1, condition, now)
|
ret := _m.Called(ctx, condition)
|
||||||
|
|
||||||
var r0 Results
|
var r0 Results
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, models.Condition, time.Time) Results); ok {
|
if rf, ok := ret.Get(0).(func(EvaluationContext, models.Condition) Results); ok {
|
||||||
r0 = rf(ctx, _a1, condition, now)
|
r0 = rf(ctx, condition)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(Results)
|
r0 = ret.Get(0).(Results)
|
||||||
@ -51,17 +46,15 @@ type FakeEvaluator_ConditionEval_Call struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConditionEval is a helper method to define mock.On call
|
// ConditionEval is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx EvaluationContext
|
||||||
// - _a1 *user.SignedInUser
|
// - condition models.Condition
|
||||||
// - condition models.Condition
|
func (_e *FakeEvaluator_Expecter) ConditionEval(ctx interface{}, condition interface{}) *FakeEvaluator_ConditionEval_Call {
|
||||||
// - now time.Time
|
return &FakeEvaluator_ConditionEval_Call{Call: _e.mock.On("ConditionEval", ctx, condition)}
|
||||||
func (_e *FakeEvaluator_Expecter) ConditionEval(ctx interface{}, _a1 interface{}, condition interface{}, now interface{}) *FakeEvaluator_ConditionEval_Call {
|
|
||||||
return &FakeEvaluator_ConditionEval_Call{Call: _e.mock.On("ConditionEval", ctx, _a1, condition, now)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *FakeEvaluator_ConditionEval_Call) Run(run func(ctx context.Context, _a1 *user.SignedInUser, condition models.Condition, now time.Time)) *FakeEvaluator_ConditionEval_Call {
|
func (_c *FakeEvaluator_ConditionEval_Call) Run(run func(ctx EvaluationContext, condition models.Condition)) *FakeEvaluator_ConditionEval_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(*user.SignedInUser), args[2].(models.Condition), args[3].(time.Time))
|
run(args[0].(EvaluationContext), args[1].(models.Condition))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@ -71,13 +64,13 @@ func (_c *FakeEvaluator_ConditionEval_Call) Return(_a0 Results) *FakeEvaluator_C
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueriesAndExpressionsEval provides a mock function with given fields: ctx, _a1, data, now
|
// QueriesAndExpressionsEval provides a mock function with given fields: ctx, data
|
||||||
func (_m *FakeEvaluator) QueriesAndExpressionsEval(ctx context.Context, _a1 *user.SignedInUser, data []models.AlertQuery, now time.Time) (*backend.QueryDataResponse, error) {
|
func (_m *FakeEvaluator) QueriesAndExpressionsEval(ctx EvaluationContext, data []models.AlertQuery) (*backend.QueryDataResponse, error) {
|
||||||
ret := _m.Called(ctx, _a1, data, now)
|
ret := _m.Called(ctx, data)
|
||||||
|
|
||||||
var r0 *backend.QueryDataResponse
|
var r0 *backend.QueryDataResponse
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, []models.AlertQuery, time.Time) *backend.QueryDataResponse); ok {
|
if rf, ok := ret.Get(0).(func(EvaluationContext, []models.AlertQuery) *backend.QueryDataResponse); ok {
|
||||||
r0 = rf(ctx, _a1, data, now)
|
r0 = rf(ctx, data)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*backend.QueryDataResponse)
|
r0 = ret.Get(0).(*backend.QueryDataResponse)
|
||||||
@ -85,8 +78,8 @@ func (_m *FakeEvaluator) QueriesAndExpressionsEval(ctx context.Context, _a1 *use
|
|||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, *user.SignedInUser, []models.AlertQuery, time.Time) error); ok {
|
if rf, ok := ret.Get(1).(func(EvaluationContext, []models.AlertQuery) error); ok {
|
||||||
r1 = rf(ctx, _a1, data, now)
|
r1 = rf(ctx, data)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@ -100,17 +93,15 @@ type FakeEvaluator_QueriesAndExpressionsEval_Call struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// QueriesAndExpressionsEval is a helper method to define mock.On call
|
// QueriesAndExpressionsEval is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx EvaluationContext
|
||||||
// - _a1 *user.SignedInUser
|
// - data []models.AlertQuery
|
||||||
// - data []models.AlertQuery
|
func (_e *FakeEvaluator_Expecter) QueriesAndExpressionsEval(ctx interface{}, data interface{}) *FakeEvaluator_QueriesAndExpressionsEval_Call {
|
||||||
// - now time.Time
|
return &FakeEvaluator_QueriesAndExpressionsEval_Call{Call: _e.mock.On("QueriesAndExpressionsEval", ctx, data)}
|
||||||
func (_e *FakeEvaluator_Expecter) QueriesAndExpressionsEval(ctx interface{}, _a1 interface{}, data interface{}, now interface{}) *FakeEvaluator_QueriesAndExpressionsEval_Call {
|
|
||||||
return &FakeEvaluator_QueriesAndExpressionsEval_Call{Call: _e.mock.On("QueriesAndExpressionsEval", ctx, _a1, data, now)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *FakeEvaluator_QueriesAndExpressionsEval_Call) Run(run func(ctx context.Context, _a1 *user.SignedInUser, data []models.AlertQuery, now time.Time)) *FakeEvaluator_QueriesAndExpressionsEval_Call {
|
func (_c *FakeEvaluator_QueriesAndExpressionsEval_Call) Run(run func(ctx EvaluationContext, data []models.AlertQuery)) *FakeEvaluator_QueriesAndExpressionsEval_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(*user.SignedInUser), args[2].([]models.AlertQuery), args[3].(time.Time))
|
run(args[0].(EvaluationContext), args[1].([]models.AlertQuery))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@ -120,13 +111,13 @@ func (_c *FakeEvaluator_QueriesAndExpressionsEval_Call) Return(_a0 *backend.Quer
|
|||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate provides a mock function with given fields: ctx, _a1, condition
|
// Validate provides a mock function with given fields: ctx, condition
|
||||||
func (_m *FakeEvaluator) Validate(ctx context.Context, _a1 *user.SignedInUser, condition models.Condition) error {
|
func (_m *FakeEvaluator) Validate(ctx EvaluationContext, condition models.Condition) error {
|
||||||
ret := _m.Called(ctx, _a1, condition)
|
ret := _m.Called(ctx, condition)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, *user.SignedInUser, models.Condition) error); ok {
|
if rf, ok := ret.Get(0).(func(EvaluationContext, models.Condition) error); ok {
|
||||||
r0 = rf(ctx, _a1, condition)
|
r0 = rf(ctx, condition)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
@ -140,16 +131,15 @@ type FakeEvaluator_Validate_Call struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate is a helper method to define mock.On call
|
// Validate is a helper method to define mock.On call
|
||||||
// - ctx context.Context
|
// - ctx EvaluationContext
|
||||||
// - _a1 *user.SignedInUser
|
// - condition models.Condition
|
||||||
// - condition models.Condition
|
func (_e *FakeEvaluator_Expecter) Validate(ctx interface{}, condition interface{}) *FakeEvaluator_Validate_Call {
|
||||||
func (_e *FakeEvaluator_Expecter) Validate(ctx interface{}, _a1 interface{}, condition interface{}) *FakeEvaluator_Validate_Call {
|
return &FakeEvaluator_Validate_Call{Call: _e.mock.On("Validate", ctx, condition)}
|
||||||
return &FakeEvaluator_Validate_Call{Call: _e.mock.On("Validate", ctx, _a1, condition)}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *FakeEvaluator_Validate_Call) Run(run func(ctx context.Context, _a1 *user.SignedInUser, condition models.Condition)) *FakeEvaluator_Validate_Call {
|
func (_c *FakeEvaluator_Validate_Call) Run(run func(ctx EvaluationContext, condition models.Condition)) *FakeEvaluator_Validate_Call {
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
run(args[0].(context.Context), args[1].(*user.SignedInUser), args[2].(models.Condition))
|
run(args[0].(EvaluationContext), args[1].(models.Condition))
|
||||||
})
|
})
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
@ -158,3 +148,13 @@ func (_c *FakeEvaluator_Validate_Call) Return(_a0 error) *FakeEvaluator_Validate
|
|||||||
_c.Call.Return(_a0)
|
_c.Call.Return(_a0)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFakeEvaluator creates a new instance of FakeEvaluator. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||||
|
func NewFakeEvaluator(t testing.TB) *FakeEvaluator {
|
||||||
|
mock := &FakeEvaluator{}
|
||||||
|
mock.Mock.Test(t)
|
||||||
|
|
||||||
|
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||||
|
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
@ -340,8 +340,9 @@ func (sch *schedule) ruleRoutine(grafanaCtx context.Context, key ngmodels.AlertR
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
evalCtx := eval.Context(ctx, schedulerUser).When(e.scheduledAt).WithRule(e.rule)
|
||||||
|
|
||||||
results := sch.evaluator.ConditionEval(ctx, schedulerUser, e.rule.GetEvalCondition(), e.scheduledAt)
|
results := sch.evaluator.ConditionEval(evalCtx, e.rule.GetEvalCondition())
|
||||||
dur := sch.clock.Now().Sub(start)
|
dur := sch.clock.Now().Sub(start)
|
||||||
evalTotal.Inc()
|
evalTotal.Inc()
|
||||||
evalDuration.Observe(dur.Seconds())
|
evalDuration.Observe(dur.Seconds())
|
||||||
|
Loading…
Reference in New Issue
Block a user