[Alerting]: Add alerting endpoint for Query Evaluation (#33174)
* [Alerting]: Add alerting endpoint for Query Evaluation * Fix passing down now parameter * Add validations and test * Fix eval queries and expressions test * Add eval tests
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"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/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@@ -77,3 +78,21 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodel
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
func (srv TestingApiSrv) RouteEvalQueries(c *models.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
|
||||
now := cmd.Now
|
||||
if now.IsZero() {
|
||||
now = timeNow()
|
||||
}
|
||||
if err := validateQueriesAndExpressions(cmd.Data, c.SignedInUser, c.SkipCache, srv.DatasourceCache); err != nil {
|
||||
return response.Error(http.StatusBadRequest, "invalid queries or expressions", err)
|
||||
}
|
||||
|
||||
evaluator := eval.Evaluator{Cfg: srv.Cfg}
|
||||
evalResults, err := evaluator.QueriesAndExpressionsEval(c.SignedInUser.OrgId, cmd.Data, now, srv.DataService)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "Failed to evaluate queries and expressions", err)
|
||||
}
|
||||
|
||||
return response.JSONStreaming(http.StatusOK, evalResults)
|
||||
}
|
||||
|
||||
@@ -18,12 +18,14 @@ import (
|
||||
)
|
||||
|
||||
type TestingApiService interface {
|
||||
RouteEvalQueries(*models.ReqContext, apimodels.EvalQueriesPayload) response.Response
|
||||
RouteTestReceiverConfig(*models.ReqContext, apimodels.ExtendedReceiver) response.Response
|
||||
RouteTestRuleConfig(*models.ReqContext, apimodels.TestRulePayload) response.Response
|
||||
}
|
||||
|
||||
func (api *API) RegisterTestingApiEndpoints(srv TestingApiService) {
|
||||
api.RouteRegister.Group("", func(group routing.RouteRegister) {
|
||||
group.Post(toMacaronPath("/api/v1/eval"), binding.Bind(apimodels.EvalQueriesPayload{}), routing.Wrap(srv.RouteEvalQueries))
|
||||
group.Post(toMacaronPath("/api/v1/receiver/test/{Recipient}"), binding.Bind(apimodels.ExtendedReceiver{}), routing.Wrap(srv.RouteTestReceiverConfig))
|
||||
group.Post(toMacaronPath("/api/v1/rule/test/{Recipient}"), binding.Bind(apimodels.TestRulePayload{}), routing.Wrap(srv.RouteTestRuleConfig))
|
||||
}, middleware.ReqSignedIn)
|
||||
|
||||
@@ -25,6 +25,52 @@ content-type: application/json
|
||||
}
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
POST http://admin:admin@localhost:3000/api/v1/eval
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"model": {
|
||||
"datasourceUid": "000000004",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"orgId": 0,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"model": {
|
||||
"datasourceUid": "-100",
|
||||
"expression": "$A",
|
||||
"intervalMs": 2000,
|
||||
"maxDataPoints": 200,
|
||||
"orgId": 0,
|
||||
"reducer": "mean",
|
||||
"refId": "B",
|
||||
"type": "reduce"
|
||||
}
|
||||
}
|
||||
],
|
||||
"now": "2021-04-11T14:38:14Z"
|
||||
}
|
||||
|
||||
###
|
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{lokiDatasourceID}}
|
||||
content-type: application/json
|
||||
|
||||
@@ -3,6 +3,9 @@ package definitions
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
@@ -37,6 +40,19 @@ import (
|
||||
// Responses:
|
||||
// 200: TestRuleResponse
|
||||
|
||||
// swagger:route Post /api/v1/eval testing RouteEvalQueries
|
||||
//
|
||||
// Test rule
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 200: EvalQueriesResponse
|
||||
|
||||
// swagger:parameters RouteTestReceiverConfig
|
||||
type TestReceiverRequest struct {
|
||||
// in:body
|
||||
@@ -57,6 +73,18 @@ type TestRulePayload struct {
|
||||
GrafanaManagedCondition *models.EvalAlertConditionCommand `json:"grafana_condition,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteEvalQueries
|
||||
type EvalQueriesRequest struct {
|
||||
// in:body
|
||||
Body EvalQueriesPayload
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type EvalQueriesPayload struct {
|
||||
Data []models.AlertQuery `json:"data"`
|
||||
Now time.Time `json:"now"`
|
||||
}
|
||||
|
||||
func (p *TestRulePayload) UnmarshalJSON(b []byte) error {
|
||||
type plain TestRulePayload
|
||||
if err := json.Unmarshal(b, (*plain)(p)); err != nil {
|
||||
@@ -96,6 +124,9 @@ type TestRuleResponse struct {
|
||||
GrafanaAlertInstances AlertInstancesResponse `json:"grafana_alert_instances"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type EvalQueriesResponse = backend.QueryDataResponse
|
||||
|
||||
// swagger:model
|
||||
type AlertInstancesResponse struct {
|
||||
// Instances is an array of arrow encoded dataframes
|
||||
|
||||
+3756
-3721
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,4 @@
|
||||
{
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "Package definitions includes the types required for generating or consuming an OpenAPI\nspec for the Unified Alerting API.\nDocumentation of the API.",
|
||||
@@ -717,6 +707,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/eval": {
|
||||
"post": {
|
||||
"description": "Test rule",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"testing"
|
||||
],
|
||||
"operationId": "RouteEvalQueries",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EvalQueriesPayload"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "EvalQueriesResponse",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/EvalQueriesResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/receiver/test/{Recipient}": {
|
||||
"post": {
|
||||
"description": "Test receiver",
|
||||
@@ -1332,6 +1354,27 @@
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
},
|
||||
"EvalQueriesPayload": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQuery"
|
||||
},
|
||||
"x-go-name": "Data"
|
||||
},
|
||||
"now": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"x-go-name": "Now"
|
||||
}
|
||||
},
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"EvalQueriesResponse": {
|
||||
"$ref": "#/definitions/EvalQueriesResponse"
|
||||
},
|
||||
"ExtendedReceiver": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1369,7 +1412,7 @@
|
||||
"$ref": "#/definitions/ResponseDetails"
|
||||
},
|
||||
"GettableAlert": {
|
||||
"$ref": "#/definitions/GettableAlert"
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
"GettableAlerts": {
|
||||
"$ref": "#/definitions/GettableAlerts"
|
||||
@@ -1638,7 +1681,7 @@
|
||||
"x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
},
|
||||
"GettableSilence": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
"$ref": "#/definitions/GettableSilence"
|
||||
},
|
||||
"GettableSilences": {
|
||||
"$ref": "#/definitions/GettableSilences"
|
||||
@@ -3204,7 +3247,7 @@
|
||||
"description": "alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
"$ref": "#/definitions/GettableAlert"
|
||||
},
|
||||
"x-go-name": "Alerts"
|
||||
},
|
||||
@@ -3775,10 +3818,5 @@
|
||||
"x-go-name": "VersionInfo",
|
||||
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"basic": {
|
||||
"type": "basic"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,33 @@ func validateCondition(c ngmodels.Condition, user *models.SignedInUser, skipCach
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateQueriesAndExpressions(data []ngmodels.AlertQuery, user *models.SignedInUser, skipCache bool, datasourceCache datasources.CacheService) error {
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, query := range data {
|
||||
datasourceUID, err := query.GetDatasource()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isExpression, err := query.IsExpression()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isExpression {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = datasourceCache.GetDatasourceByUID(datasourceUID, user, skipCache)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get datasource: %s: %w", datasourceUID, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand, datasourceCache datasources.CacheService, dataService *tsdb.Service, cfg *setting.Cfg) response.Response {
|
||||
evalCond := ngmodels.Condition{
|
||||
Condition: cmd.Condition,
|
||||
@@ -210,14 +237,14 @@ func conditionEval(c *models.ReqContext, cmd ngmodels.EvalAlertConditionCommand,
|
||||
}
|
||||
|
||||
evaluator := eval.Evaluator{Cfg: cfg}
|
||||
evalResults, err := evaluator.ConditionEval(&evalCond, timeNow(), dataService)
|
||||
evalResults, err := evaluator.ConditionEval(&evalCond, now, dataService)
|
||||
if err != nil {
|
||||
return response.Error(400, "Failed to evaluate conditions", err)
|
||||
return response.Error(http.StatusBadRequest, "Failed to evaluate conditions", err)
|
||||
}
|
||||
|
||||
frame := evalResults.AsDataFrame()
|
||||
|
||||
return response.JSONStreaming(200, util.DynMap{
|
||||
return response.JSONStreaming(http.StatusOK, util.DynMap{
|
||||
"instances": []*data.Frame{&frame},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user