From 81ba27e6c3dbe1d238ec6e605b727a05649d4efa Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Tue, 23 Feb 2021 19:13:04 +0200 Subject: [PATCH 1/2] Add testing endpoints --- pkg/api/testing.go | 110 +++++++++++++++++++++++ spec.json | 215 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 pkg/api/testing.go diff --git a/pkg/api/testing.go b/pkg/api/testing.go new file mode 100644 index 00000000000..51c95f61f72 --- /dev/null +++ b/pkg/api/testing.go @@ -0,0 +1,110 @@ +package api + +import ( + "time" + + "github.com/grafana/grafana/pkg/services/ngalert/eval" + "github.com/prometheus/alertmanager/config" +) + +// swagger:route Get /api/v1/receiver/test testing RouteTestReceiverConfig +// +// Test receiver +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// Responses: +// 200: Success +// 412: SmtpNotEnabled +// 500: Failure + +// swagger:route Get /api/v1/rule/test testing RouteTestRuleConfig +// +// Test rule +// +// Consumes: +// - application/json +// +// Produces: +// - application/json +// +// Responses: +// 200: TestRuleResponse + +// swagger:parameters RouteTestReceiverConfig +type TestReceiverRequest struct { + // in:body + Body ExtendedReceiver +} + +// swagger:parameters RouteTestRuleConfig +type TestRuleRequest struct { + // in:body + Body TestRulePayload +} + +// swagger:model +type TestRulePayload struct { + Expr LotexQuery `json:"expr,omitempty"` + // GrafanaManagedCondition for grafana alerts + GrafanaManagedCondition EvalAlertConditionCommand `json:"grafana_condition,omitempty"` +} + +// swagger:model +type LotexQuery struct { + // Example: (node_filesystem_avail_bytes{fstype!="",job="integrations/node_exporter"} node_filesystem_size_bytes{fstype!="",job="integrations/node_exporter"} * 100 < 5 and node_filesystem_readonly{fstype!="",job="integrations/node_exporter"} == 0) + Expr string + // DatasourceUID is required if the query will be sent to grafana to be executed + DatasourceUID string `json:"datasourceUid,omitempty"` +} + +// swagger:model +type EvalAlertConditionCommand struct { + Condition string `json:"condition"` + Data []eval.AlertQuery `json:"data"` + Now time.Time `json:"now"` +} + +// swagger:model +type TestRuleResponse struct { + Alerts interface{} `json:"alerts"` + GrafanaAlertInstances AlertInstancesResponse `json:"grafana_alert_instances"` +} + +// swagger:model +type AlertInstancesResponse struct { + // Instances is an array of arrow encoded dataframes + // each frame has a single row, and a column for each instance (alert identified by unique labels) with a boolean value (firing/not firing) + Instances [][]byte `json:"instances"` +} + +// swagger:model +type ExtendedReceiver struct { + EmailConfigs config.EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"` + PagerdutyConfigs config.PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"` + SlackConfigs config.SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"` + WebhookConfigs config.WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"` + OpsGenieConfigs config.OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"` + WechatConfigs config.WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"` + PushoverConfigs config.PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"` + VictorOpsConfigs config.VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"` + GrafanaReceiver GrafanaReceiver `yaml:"grafana_managed_receiver,omitempty" json:"grafana_managed_receiver,omitempty"` +} + +// swagger:model +type Success ResponseDetails + +// swagger:model +type SmtpNotEnabled ResponseDetails + +// swagger:model +type Failure ResponseDetails + +// swagger:model +type ResponseDetails struct { + Msg string `json:"msg"` +} diff --git a/spec.json b/spec.json index 902ef53a00e..b2c9b2c90ac 100644 --- a/spec.json +++ b/spec.json @@ -325,6 +325,82 @@ } } }, + "/api/v1/receiver/test": { + "get": { + "description": "Test receiver", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "testing" + ], + "operationId": "RouteTestReceiverConfig", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/ExtendedReceiver" + } + } + ], + "responses": { + "200": { + "description": "Success", + "schema": { + "$ref": "#/definitions/Success" + } + }, + "412": { + "description": "SmtpNotEnabled", + "schema": { + "$ref": "#/definitions/SmtpNotEnabled" + } + }, + "500": { + "description": "Failure", + "schema": { + "$ref": "#/definitions/Failure" + } + } + } + } + }, + "/api/v1/rule/test": { + "get": { + "description": "Test rule", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "testing" + ], + "operationId": "RouteTestRuleConfig", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/TestRulePayload" + } + } + ], + "responses": { + "200": { + "description": "TestRuleResponse", + "schema": { + "$ref": "#/definitions/TestRuleResponse" + } + } + } + } + }, "/api/v1/rules": { "get": { "description": "List rule groups", @@ -683,6 +759,24 @@ "AlertGroups": { "$ref": "#/definitions/alertGroups" }, + "AlertInstancesResponse": { + "type": "object", + "properties": { + "instances": { + "description": "Instances is an array of arrow encoded dataframes\neach frame has a single row, and a column for each instance (alert identified by unique labels) with a boolean value (firing/not firing)", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "integer", + "format": "uint8" + } + }, + "x-go-name": "Instances" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "AlertNotification": { "type": "object", "properties": { @@ -1203,6 +1297,61 @@ "title": "ErrorType models the different API error types.", "x-go-package": "github.com/prometheus/client_golang/api/prometheus/v1" }, + "EvalAlertConditionCommand": { + "type": "object", + "properties": { + "condition": { + "type": "string", + "x-go-name": "Condition" + }, + "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/alerting-api/pkg/api" + }, + "ExtendedReceiver": { + "type": "object", + "properties": { + "email_configs": { + "$ref": "#/definitions/EmailConfig" + }, + "grafana_managed_receiver": { + "$ref": "#/definitions/GrafanaReceiver" + }, + "opsgenie_configs": { + "$ref": "#/definitions/OpsGenieConfig" + }, + "pagerduty_configs": { + "$ref": "#/definitions/PagerdutyConfig" + }, + "pushover_configs": { + "$ref": "#/definitions/PushoverConfig" + }, + "slack_configs": { + "$ref": "#/definitions/SlackConfig" + }, + "victorops_configs": { + "$ref": "#/definitions/VictorOpsConfig" + }, + "webhook_configs": { + "$ref": "#/definitions/WebhookConfig" + }, + "wechat_configs": { + "$ref": "#/definitions/WechatConfig" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "ExtendedRuleNode": { "type": "object", "properties": { @@ -1295,6 +1444,9 @@ }, "x-go-package": "github.com/grafana/alerting-api/pkg/api" }, + "Failure": { + "$ref": "#/definitions/ResponseDetails" + }, "GetableSilence": { "$ref": "#/definitions/silence" }, @@ -1472,6 +1624,21 @@ }, "x-go-package": "github.com/prometheus/common/model" }, + "LotexQuery": { + "type": "object", + "properties": { + "Expr": { + "type": "string", + "example": "(node_filesystem_avail_bytes{fstype!=\"\",job=\"integrations/node_exporter\"} node_filesystem_size_bytes{fstype!=\"\",job=\"integrations/node_exporter\"} * 100 \u003c 5 and node_filesystem_readonly{fstype!=\"\",job=\"integrations/node_exporter\"} == 0)" + }, + "datasourceUid": { + "description": "DatasourceUID is required if the query will be sent to grafana to be executed", + "type": "string", + "x-go-name": "DatasourceUID" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "MatchRegexps": { "type": "object", "title": "MatchRegexps represents a map of Regexp.", @@ -1868,6 +2035,16 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/eval" }, + "ResponseDetails": { + "type": "object", + "properties": { + "msg": { + "type": "string", + "x-go-name": "Msg" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, "RoleType": { "type": "string", "x-go-package": "github.com/grafana/grafana/pkg/models" @@ -2328,6 +2505,12 @@ }, "x-go-package": "github.com/prometheus/alertmanager/config" }, + "SmtpNotEnabled": { + "$ref": "#/definitions/ResponseDetails" + }, + "Success": { + "$ref": "#/definitions/ResponseDetails" + }, "TLSConfig": { "type": "object", "title": "TLSConfig configures the options for TLS connections.", @@ -2355,9 +2538,35 @@ }, "x-go-package": "github.com/prometheus/common/config" }, - "URL": { + "TestRulePayload": { "type": "object", - "title": "URL is a custom URL type that allows validation at configuration load time.", + "properties": { + "expr": { + "$ref": "#/definitions/LotexQuery" + }, + "grafana_condition": { + "$ref": "#/definitions/EvalAlertConditionCommand" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, + "TestRuleResponse": { + "type": "object", + "properties": { + "alerts": { + "type": "object", + "x-go-name": "Alerts" + }, + "grafana_alert_instances": { + "$ref": "#/definitions/AlertInstancesResponse" + } + }, + "x-go-package": "github.com/grafana/alerting-api/pkg/api" + }, + "URL": { + "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", + "type": "object", + "title": "A URL represents a parsed URL (technically, a URI reference).", "properties": { "ForceQuery": { "type": "boolean" @@ -2390,7 +2599,7 @@ "$ref": "#/definitions/Userinfo" } }, - "x-go-package": "github.com/prometheus/common/config" + "x-go-package": "net/url" }, "UpdateDashboardAclCommand": { "type": "object", From 875a8e5bb37b04ef9321feaf9562a812b0dee746 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki Date: Wed, 24 Feb 2021 12:53:05 +0200 Subject: [PATCH 2/2] Change response type for Cortex/Loki alerts --- go.mod | 1 + pkg/api/testing.go | 3 +- spec.json | 70 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index d8502021986..6a83c09ce1b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/prometheus/alertmanager v0.21.1-0.20210211203738-a7ca7b1d2951 github.com/prometheus/client_golang v1.9.0 github.com/prometheus/common v0.15.0 + github.com/prometheus/prometheus v1.8.2-0.20201014093524-73e2ce1bd643 golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/oauth2 v0.0.0-20210210192628-66670185b0cd // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/pkg/api/testing.go b/pkg/api/testing.go index 51c95f61f72..21fe0cb4e0b 100644 --- a/pkg/api/testing.go +++ b/pkg/api/testing.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/prometheus/promql" ) // swagger:route Get /api/v1/receiver/test testing RouteTestReceiverConfig @@ -71,7 +72,7 @@ type EvalAlertConditionCommand struct { // swagger:model type TestRuleResponse struct { - Alerts interface{} `json:"alerts"` + Alerts promql.Vector `json:"alerts"` GrafanaAlertInstances AlertInstancesResponse `json:"grafana_alert_instances"` } diff --git a/spec.json b/spec.json index b2c9b2c90ac..78f7280390d 100644 --- a/spec.json +++ b/spec.json @@ -1611,6 +1611,17 @@ "type": "object", "x-go-package": "github.com/grafana/grafana/pkg/components/simplejson" }, + "Label": { + "type": "object", + "title": "Label is a key/value pair of strings.", + "properties": { + "Name": { + "type": "string", + "x-go-name": "Value" + } + }, + "x-go-package": "github.com/prometheus/prometheus/pkg/labels" + }, "LabelName": { "description": "A LabelName is a key for a LabelSet or Metric. It has a value associated\ntherewith.", "type": "string", @@ -1624,6 +1635,14 @@ }, "x-go-package": "github.com/prometheus/common/model" }, + "Labels": { + "description": "Labels is a sorted set of labels. Order has to be guaranteed upon\ninstantiation.", + "type": "array", + "items": { + "$ref": "#/definitions/Label" + }, + "x-go-package": "github.com/prometheus/prometheus/pkg/labels" + }, "LotexQuery": { "type": "object", "properties": { @@ -1895,6 +1914,21 @@ }, "x-go-package": "github.com/grafana/alerting-api/pkg/api" }, + "Point": { + "type": "object", + "title": "Point represents a single data point for a given timestamp.", + "properties": { + "T": { + "type": "integer", + "format": "int64" + }, + "V": { + "type": "number", + "format": "double" + } + }, + "x-go-package": "github.com/prometheus/prometheus/promql" + }, "PushoverConfig": { "type": "object", "properties": { @@ -2261,6 +2295,24 @@ "title": "RuleType models the type of a rule.", "x-go-package": "github.com/prometheus/client_golang/api/prometheus/v1" }, + "Sample": { + "type": "object", + "title": "Sample is a single sample belonging to a metric.", + "properties": { + "Metric": { + "$ref": "#/definitions/Labels" + }, + "T": { + "type": "integer", + "format": "int64" + }, + "V": { + "type": "number", + "format": "double" + } + }, + "x-go-package": "github.com/prometheus/prometheus/promql" + }, "Secret": { "type": "string", "title": "Secret special type for storing secrets.", @@ -2554,8 +2606,7 @@ "type": "object", "properties": { "alerts": { - "type": "object", - "x-go-name": "Alerts" + "$ref": "#/definitions/Vector" }, "grafana_alert_instances": { "$ref": "#/definitions/AlertInstancesResponse" @@ -2564,9 +2615,8 @@ "x-go-package": "github.com/grafana/alerting-api/pkg/api" }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "type": "object", - "title": "A URL represents a parsed URL (technically, a URI reference).", + "title": "URL is a custom URL type that allows validation at configuration load time.", "properties": { "ForceQuery": { "type": "boolean" @@ -2599,7 +2649,7 @@ "$ref": "#/definitions/Userinfo" } }, - "x-go-package": "net/url" + "x-go-package": "github.com/prometheus/common/config" }, "UpdateDashboardAclCommand": { "type": "object", @@ -2615,7 +2665,9 @@ "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" }, "UpsertAlertDefinitionCommand": { + "description": "https://github.com/grafana/grafana/blob/debb82e12417e82a0e2bd09e1a450065f884c1bc/pkg/services/ngalert/models.go#L85", "type": "object", + "title": "UpsertAlertDefinitionCommand is copy of the unexported struct:", "properties": { "condition": { "description": "Condition is the refID of the query or expression to be evaluated", @@ -2673,6 +2725,14 @@ }, "x-go-package": "github.com/grafana/alerting-api/pkg/api" }, + "Vector": { + "description": "Vector is basically only an alias for model.Samples, but the\ncontract is that in a Vector, all Samples have the same timestamp.", + "type": "array", + "items": { + "$ref": "#/definitions/Sample" + }, + "x-go-package": "github.com/prometheus/prometheus/promql" + }, "VictorOpsConfig": { "type": "object", "title": "VictorOpsConfig configures notifications via VictorOps.",