mirror of https://github.com/grafana/grafana
[Alerting]: Implement test rule API route (#32837)
* [Alerting]: Implement test rule API route * Apply suggestions from code review * Call /query instead of /query_rangesarahzinger/aggregate-bug
parent
15978900a9
commit
e7ff04a167
@ -0,0 +1,79 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
"net/url" |
||||
"strconv" |
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api" |
||||
"github.com/grafana/grafana/pkg/api/response" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/datasources" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/grafana/grafana/pkg/tsdb" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
type TestingApiSrv struct { |
||||
*AlertingProxy |
||||
Cfg *setting.Cfg |
||||
DataService *tsdb.Service |
||||
DatasourceCache datasources.CacheService |
||||
log log.Logger |
||||
} |
||||
|
||||
func (srv TestingApiSrv) RouteTestReceiverConfig(c *models.ReqContext, body apimodels.ExtendedReceiver) response.Response { |
||||
srv.log.Info("RouteTestReceiverConfig: ", "body", body) |
||||
return response.JSON(http.StatusOK, util.DynMap{"message": "success"}) |
||||
} |
||||
|
||||
func (srv TestingApiSrv) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { |
||||
recipient := c.Params("Recipient") |
||||
if recipient == apimodels.GrafanaBackend.String() { |
||||
if body.Type() != apimodels.GrafanaBackend || body.GrafanaManagedCondition == nil { |
||||
return response.Error(http.StatusBadRequest, "unexpected payload", nil) |
||||
} |
||||
return conditionEval(c, *body.GrafanaManagedCondition, srv.DatasourceCache, srv.DataService, srv.Cfg) |
||||
} |
||||
|
||||
if body.Type() != apimodels.LoTexRulerBackend { |
||||
return response.Error(http.StatusBadRequest, "unexpected payload", nil) |
||||
} |
||||
|
||||
var path string |
||||
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil { |
||||
ds, err := srv.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache) |
||||
if err != nil { |
||||
return response.Error(http.StatusInternalServerError, "failed to get datasource", err) |
||||
} |
||||
|
||||
switch ds.Type { |
||||
case "loki": |
||||
path = "loki/api/v1/query" |
||||
case "prometheus": |
||||
path = "api/v1/query" |
||||
default: |
||||
return response.Error(http.StatusBadRequest, fmt.Sprintf("unexpected recipient type %s", ds.Type), nil) |
||||
} |
||||
} |
||||
|
||||
t := timeNow() |
||||
queryURL, err := url.Parse(path) |
||||
if err != nil { |
||||
return response.Error(http.StatusInternalServerError, "failed to parse url", err) |
||||
} |
||||
params := queryURL.Query() |
||||
params.Set("query", body.Expr) |
||||
params.Set("time", strconv.FormatInt(t.Unix(), 10)) |
||||
queryURL.RawQuery = params.Encode() |
||||
return srv.withReq( |
||||
c, |
||||
http.MethodGet, |
||||
queryURL, |
||||
nil, |
||||
jsonExtractor(nil), |
||||
nil, |
||||
) |
||||
} |
@ -1,32 +0,0 @@ |
||||
package api |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api" |
||||
"github.com/grafana/grafana/pkg/api/response" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/util" |
||||
) |
||||
|
||||
type TestingApiMock struct { |
||||
log log.Logger |
||||
} |
||||
|
||||
func (mock TestingApiMock) RouteTestReceiverConfig(c *models.ReqContext, body apimodels.ExtendedReceiver) response.Response { |
||||
mock.log.Info("RouteTestReceiverConfig: ", "body", body) |
||||
return response.JSON(http.StatusOK, util.DynMap{"message": "success"}) |
||||
} |
||||
|
||||
func (mock TestingApiMock) RouteTestRuleConfig(c *models.ReqContext, body apimodels.TestRulePayload) response.Response { |
||||
mock.log.Info("RouteTestRuleConfig: ", "body", body) |
||||
result := apimodels.TestRuleResponse{ |
||||
GrafanaAlertInstances: apimodels.AlertInstancesResponse{ |
||||
Instances: [][]byte{ |
||||
[]byte("QVJST1cxAAD/////+AAAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFAAAAACAAAAKAAAAAQAAACE////CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAKT///8IAAAADAAAAAAAAAAAAAAABAAAAG5hbWUAAAAAAQAAABgAAAAAABIAGAAUAAAAEwAMAAAACAAEABIAAAAUAAAAQAAAAEQAAAAAAAAGQAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAAAAAAEAAQABAAAAAAAAAAAAAAA/////4gAAAAUAAAAAAAAAAwAFgAUABMADAAEAAwAAAAIAAAAAAAAABQAAAAAAAADAwAKABgADAAIAAQACgAAABQAAAA4AAAAAQAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAQAAAADAAUABIADAAIAAQADAAAABAAAAAsAAAAPAAAAAAAAwABAAAACAEAAAAAAACQAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAKAAwAAAAIAAQACgAAAAgAAABQAAAAAgAAACgAAAAEAAAAhP///wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAACk////CAAAAAwAAAAAAAAAAAAAAAQAAABuYW1lAAAAAAEAAAAYAAAAAAASABgAFAAAABMADAAAAAgABAASAAAAFAAAAEAAAABEAAAAAAAABkAAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAMAAAAAAAAAAAAAAAEAAAAbmFtZQAAAAAAAAAABAAEAAQAAAAAAAAAAAAAACgBAABBUlJPVzE="), |
||||
}, |
||||
}, |
||||
} |
||||
return response.JSON(http.StatusOK, result) |
||||
} |
@ -0,0 +1,86 @@ |
||||
@grafanaRecipient = grafana |
||||
@lokiDatasourceID = 32 |
||||
@prometheusDatasourceID = 35 |
||||
|
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{grafanaRecipient}} |
||||
content-type: application/json |
||||
|
||||
{ |
||||
"grafana_condition": { |
||||
"condition": "A", |
||||
"data": [ |
||||
{ |
||||
"refId": "A", |
||||
"relativeTimeRange": { |
||||
"from": 18000, |
||||
"to": 10800 |
||||
}, |
||||
"model": { |
||||
"datasource": "__expr__", |
||||
"type":"math", |
||||
"expression":"1 < 2" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
} |
||||
|
||||
### |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{lokiDatasourceID}} |
||||
content-type: application/json |
||||
|
||||
{ |
||||
"expr": "rate({cluster=\"us-central1\", job=\"loki-prod/loki-canary\"}[1m]) > 0" |
||||
} |
||||
|
||||
### |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{prometheusDatasourceID}} |
||||
content-type: application/json |
||||
|
||||
{ |
||||
"expr": "http_request_duration_microseconds > 1" |
||||
} |
||||
|
||||
### loki recipient - empty payload |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{lokiDatasourceID}} |
||||
content-type: application/json |
||||
|
||||
{} |
||||
|
||||
### grafana recipient - empty payload |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{grafanaRecipient}} |
||||
content-type: application/json |
||||
|
||||
{} |
||||
|
||||
### loki recipient - grafana payload |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{lokiDatasourceID}} |
||||
content-type: application/json |
||||
|
||||
{ |
||||
"grafana_condition": { |
||||
"condition": "A", |
||||
"data": [ |
||||
{ |
||||
"refId": "A", |
||||
"relativeTimeRange": { |
||||
"from": 18000, |
||||
"to": 10800 |
||||
}, |
||||
"model": { |
||||
"datasource": "__expr__", |
||||
"type":"math", |
||||
"expression":"1 < 2" |
||||
} |
||||
} |
||||
] |
||||
} |
||||
}} |
||||
|
||||
### grafana recipient - lotex payload |
||||
POST http://admin:admin@localhost:3000/api/v1/rule/test/{{grafanaRecipient}} |
||||
content-type: application/json |
||||
|
||||
{ |
||||
"expr": "rate({cluster=\"us-central1\", job=\"loki-prod/loki-canary\"}[1m]) > 0" |
||||
} |
@ -0,0 +1,35 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"time" |
||||
) |
||||
|
||||
// EvalAlertConditionCommand is the command for evaluating a condition
|
||||
type EvalAlertConditionCommand struct { |
||||
Condition string `json:"condition"` |
||||
Data []AlertQuery `json:"data"` |
||||
Now time.Time `json:"now"` |
||||
} |
||||
|
||||
func (cmd *EvalAlertConditionCommand) UnmarshalJSON(b []byte) error { |
||||
type plain EvalAlertConditionCommand |
||||
if err := json.Unmarshal(b, (*plain)(cmd)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return cmd.validate() |
||||
} |
||||
|
||||
func (cmd *EvalAlertConditionCommand) validate() error { |
||||
if cmd.Condition == "" { |
||||
return fmt.Errorf("missing condition") |
||||
} |
||||
|
||||
if len(cmd.Data) == 0 { |
||||
return fmt.Errorf("missing data") |
||||
} |
||||
|
||||
return nil |
||||
} |
Loading…
Reference in new issue