Alerting: rule backtesting API (#57318)
* Implement backtesting engine that can process regular rule specification (with queries to datasource) as well as special kind of rules that have data frame instead of query. * declare a new API endpoint and model * add feature toggle `alertingBacktesting`
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
@@ -13,9 +15,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/backtesting"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@@ -25,6 +30,9 @@ type TestingApiSrv struct {
|
||||
log log.Logger
|
||||
accessControl accesscontrol.AccessControl
|
||||
evaluator eval.EvaluatorFactory
|
||||
cfg *setting.UnifiedAlertingSettings
|
||||
backtesting *backtesting.Engine
|
||||
featureManager featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response {
|
||||
@@ -136,3 +144,71 @@ func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.Ev
|
||||
|
||||
return response.JSONStreaming(http.StatusOK, evalResults)
|
||||
}
|
||||
|
||||
func (srv TestingApiSrv) BacktestAlertRule(c *models.ReqContext, cmd apimodels.BacktestConfig) response.Response {
|
||||
if !srv.featureManager.IsEnabled(featuremgmt.FlagAlertingBacktesting) {
|
||||
return ErrResp(http.StatusNotFound, nil, "Backgtesting API is not enabled")
|
||||
}
|
||||
|
||||
if cmd.From.After(cmd.To) {
|
||||
return ErrResp(400, nil, "From cannot be greater than To")
|
||||
}
|
||||
|
||||
noDataState, err := ngmodels.NoDataStateFromString(string(cmd.NoDataState))
|
||||
|
||||
if err != nil {
|
||||
return ErrResp(400, err, "")
|
||||
}
|
||||
forInterval := time.Duration(cmd.For)
|
||||
if forInterval < 0 {
|
||||
return ErrResp(400, nil, "Bad For interval")
|
||||
}
|
||||
|
||||
intervalSeconds, err := validateInterval(srv.cfg, time.Duration(cmd.Interval))
|
||||
if err != nil {
|
||||
return ErrResp(400, err, "")
|
||||
}
|
||||
|
||||
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: cmd.Data}, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
|
||||
}) {
|
||||
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
|
||||
}
|
||||
|
||||
rule := &ngmodels.AlertRule{
|
||||
// ID: 0,
|
||||
// Updated: time.Time{},
|
||||
// Version: 0,
|
||||
// NamespaceUID: "",
|
||||
// DashboardUID: nil,
|
||||
// PanelID: nil,
|
||||
// RuleGroup: "",
|
||||
// RuleGroupIndex: 0,
|
||||
// ExecErrState: "",
|
||||
Title: cmd.Title,
|
||||
// prefix backtesting- is to distinguish between executions of regular rule and backtesting in logs (like expression engine, evaluator, state manager etc)
|
||||
UID: "backtesting-" + util.GenerateShortUID(),
|
||||
OrgID: c.OrgID,
|
||||
Condition: cmd.Condition,
|
||||
Data: cmd.Data,
|
||||
IntervalSeconds: intervalSeconds,
|
||||
NoDataState: noDataState,
|
||||
For: forInterval,
|
||||
Annotations: cmd.Annotations,
|
||||
Labels: cmd.Labels,
|
||||
}
|
||||
|
||||
result, err := srv.backtesting.Test(c.Req.Context(), c.SignedInUser, rule, cmd.From, cmd.To)
|
||||
if err != nil {
|
||||
if errors.Is(err, backtesting.ErrInvalidInputData) {
|
||||
return ErrResp(400, err, "Failed to evaluate")
|
||||
}
|
||||
return ErrResp(500, err, "Failed to evaluate")
|
||||
}
|
||||
|
||||
body, err := data.FrameToJSON(result, data.IncludeAll)
|
||||
if err != nil {
|
||||
return ErrResp(500, err, "Failed to convert frame to JSON")
|
||||
}
|
||||
return response.JSON(http.StatusOK, body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user