[Alerting]: Several modifications in alert rules (#32983)
* [Alerting]: Use common properties for all rules * Add Labels in rules * Fix update ruleGroup API Return 400 Bad Request response when the request contains a UID that does not exist * Check permissions and return namespace id * Apply suggestions from code review Co-authored-by: gotjosh <josue@grafana.com>
This commit is contained in:
co-authored by
gotjosh
parent
34b4f7c717
commit
6bbb2fd4ba
@@ -100,7 +100,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
|
||||
State: "inactive",
|
||||
Name: rule.Title,
|
||||
Query: "", // TODO: get this from parsing AlertRule.Data
|
||||
Duration: time.Duration(rule.For).Seconds(),
|
||||
Duration: rule.For.Seconds(),
|
||||
Annotations: rule.Annotations,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
coreapi "github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -22,40 +23,41 @@ type RulerSrv struct {
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
||||
namespace := c.Params(":Namespace")
|
||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
namespaceTitle := c.Params(":Namespace")
|
||||
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
if err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespaceUID); err != nil {
|
||||
|
||||
if err := srv.store.DeleteNamespaceAlertRules(c.SignedInUser.OrgId, namespace.Uid); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "failed to delete namespace alert rules", err)
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "namespace rules deleted"})
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteDeleteRuleGroupConfig(c *models.ReqContext) response.Response {
|
||||
namespace := c.Params(":Namespace")
|
||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
namespaceTitle := c.Params(":Namespace")
|
||||
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
ruleGroup := c.Params(":Groupname")
|
||||
if err := srv.store.DeleteRuleGroupAlertRules(c.SignedInUser.OrgId, namespaceUID, ruleGroup); err != nil {
|
||||
if err := srv.store.DeleteRuleGroupAlertRules(c.SignedInUser.OrgId, namespace.Uid, ruleGroup); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "failed to delete group alert rules", err)
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group deleted"})
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
|
||||
namespace := c.Params(":Namespace")
|
||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
namespaceTitle := c.Params(":Namespace")
|
||||
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
q := ngmodels.ListNamespaceAlertRulesQuery{
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespaceUID,
|
||||
NamespaceUID: namespace.Uid,
|
||||
}
|
||||
if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
|
||||
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
||||
@@ -71,33 +73,33 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
|
||||
Name: r.RuleGroup,
|
||||
Interval: ruleGroupInterval,
|
||||
Rules: []apimodels.GettableExtendedRuleNode{
|
||||
toGettableExtendedRuleNode(*r),
|
||||
toGettableExtendedRuleNode(*r, namespace.Id),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r))
|
||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id))
|
||||
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
|
||||
}
|
||||
}
|
||||
|
||||
for _, ruleGroupConfig := range ruleGroupConfigs {
|
||||
result[namespace] = append(result[namespace], ruleGroupConfig)
|
||||
result[namespaceTitle] = append(result[namespaceTitle], ruleGroupConfig)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusAccepted, result)
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response {
|
||||
namespace := c.Params(":Namespace")
|
||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
namespaceTitle := c.Params(":Namespace")
|
||||
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
ruleGroup := c.Params(":Groupname")
|
||||
q := ngmodels.ListRuleGroupAlertRulesQuery{
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespaceUID,
|
||||
NamespaceUID: namespace.Uid,
|
||||
RuleGroup: ruleGroup,
|
||||
}
|
||||
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
|
||||
@@ -108,7 +110,7 @@ func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Resp
|
||||
ruleNodes := make([]apimodels.GettableExtendedRuleNode, 0, len(q.Result))
|
||||
for _, r := range q.Result {
|
||||
ruleGroupInterval = model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
||||
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r))
|
||||
ruleNodes = append(ruleNodes, toGettableExtendedRuleNode(*r, namespace.Id))
|
||||
}
|
||||
|
||||
result := apimodels.RuleGroupConfigResponse{
|
||||
@@ -131,10 +133,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
|
||||
configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig)
|
||||
for _, r := range q.Result {
|
||||
namespace, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
folder, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", r.NamespaceUID), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
namespace := folder.Title
|
||||
_, ok := configs[namespace]
|
||||
if !ok {
|
||||
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
|
||||
@@ -143,7 +146,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
Name: r.RuleGroup,
|
||||
Interval: ruleGroupInterval,
|
||||
Rules: []apimodels.GettableExtendedRuleNode{
|
||||
toGettableExtendedRuleNode(*r),
|
||||
toGettableExtendedRuleNode(*r, folder.Id),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
@@ -154,11 +157,11 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
Name: r.RuleGroup,
|
||||
Interval: ruleGroupInterval,
|
||||
Rules: []apimodels.GettableExtendedRuleNode{
|
||||
toGettableExtendedRuleNode(*r),
|
||||
toGettableExtendedRuleNode(*r, folder.Id),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r))
|
||||
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, folder.Id))
|
||||
configs[namespace][r.RuleGroup] = ruleGroupConfig
|
||||
}
|
||||
}
|
||||
@@ -174,10 +177,10 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
}
|
||||
|
||||
func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig) response.Response {
|
||||
namespace := c.Params(":Namespace")
|
||||
namespaceUID, err := srv.store.GetNamespaceUIDByTitle(namespace, c.SignedInUser.OrgId, c.SignedInUser)
|
||||
namespaceTitle := c.Params(":Namespace")
|
||||
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusInternalServerError, fmt.Sprintf("failed to get namespace: %s", namespace), err)
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
// TODO check permissions
|
||||
@@ -186,17 +189,20 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
||||
|
||||
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespaceUID,
|
||||
NamespaceUID: namespace.Uid,
|
||||
RuleGroupConfig: ruleGroupConfig,
|
||||
}); err != nil {
|
||||
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
||||
return response.Error(http.StatusNotFound, "failed to update rule group", err)
|
||||
}
|
||||
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
|
||||
}
|
||||
|
||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rule group updated successfully"})
|
||||
}
|
||||
|
||||
func toGettableExtendedRuleNode(r ngmodels.AlertRule) apimodels.GettableExtendedRuleNode {
|
||||
return apimodels.GettableExtendedRuleNode{
|
||||
func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64) apimodels.GettableExtendedRuleNode {
|
||||
gettableExtendedRuleNode := apimodels.GettableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
|
||||
ID: r.ID,
|
||||
OrgID: r.OrgID,
|
||||
@@ -208,17 +214,22 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule) apimodels.GettableExtended
|
||||
Version: r.Version,
|
||||
UID: r.UID,
|
||||
NamespaceUID: r.NamespaceUID,
|
||||
NamespaceID: namespaceID,
|
||||
RuleGroup: r.RuleGroup,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
For: r.For,
|
||||
Annotations: r.Annotations,
|
||||
},
|
||||
}
|
||||
gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||
For: model.Duration(r.For),
|
||||
Annotations: r.Annotations,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
return gettableExtendedRuleNode
|
||||
}
|
||||
|
||||
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
|
||||
return apimodels.PostableExtendedRuleNode{
|
||||
postableExtendedRuleNode := apimodels.PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
OrgID: r.OrgID,
|
||||
Title: r.Title,
|
||||
@@ -227,8 +238,22 @@ func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtended
|
||||
UID: r.UID,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
For: r.For,
|
||||
Annotations: r.Annotations,
|
||||
},
|
||||
}
|
||||
postableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||
For: model.Duration(r.For),
|
||||
Annotations: r.Annotations,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
return postableExtendedRuleNode
|
||||
}
|
||||
|
||||
func toNamespaceErrorResponse(err error) response.Response {
|
||||
if errors.Is(err, ngmodels.ErrCannotEditNamespace) {
|
||||
return response.Error(http.StatusForbidden, err.Error(), err)
|
||||
}
|
||||
if errors.Is(err, models.ErrDashboardIdentifierNotSet) {
|
||||
return response.Error(http.StatusBadRequest, err.Error(), err)
|
||||
}
|
||||
return coreapi.ToFolderErrorResponse(err)
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response {
|
||||
Condition: sseCond.Condition,
|
||||
NoDataState: *noDataSetting,
|
||||
ExecErrState: *execErrSetting,
|
||||
For: ngmodels.Duration(oldAlert.For),
|
||||
For: oldAlert.For,
|
||||
Annotations: ruleTags,
|
||||
}
|
||||
rgc := apimodels.PostableRuleGroupConfig{
|
||||
|
||||
@@ -3,13 +3,16 @@
|
||||
"interval": "10s",
|
||||
"rules": [
|
||||
{
|
||||
"for": "1m",
|
||||
"annotations": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "val1"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "prom query with SSE",
|
||||
"condition": "condition",
|
||||
"for": 5,
|
||||
"annotations": {
|
||||
"foo": "bar"
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"refId": "query",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
@grafanaRecipient = grafana
|
||||
|
||||
GET http://admin:admin@localhost:3000/api/prometheus/{{grafanaRecipient}}/api/v1/rules
|
||||
|
||||
###
|
||||
GET http://admin:admin@localhost:3000/api/prometheus/{{grafanaRecipient}}/api/v1/alerts
|
||||
@@ -31,6 +31,10 @@ GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rule
|
||||
// get group101 rules
|
||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}/group101
|
||||
|
||||
###
|
||||
// get group101 rules - empty namespace
|
||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules//group101
|
||||
|
||||
###
|
||||
// get namespace rules
|
||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
||||
@@ -244,3 +248,82 @@ GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rule
|
||||
###
|
||||
// get namespace rules
|
||||
GET http://admin:admin@localhost:3000/api/ruler/{{grafanaRecipient}}/api/v1/rules/{{namespace1}}
|
||||
|
||||
###
|
||||
// update rulegroup; Bad Request; not existing UID
|
||||
POST http://admin:admin@localhost:3000/api/ruler/grafana/api/v1/rules/{{namespace1}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "group42",
|
||||
"interval": "20s",
|
||||
"rules": [
|
||||
{
|
||||
"grafana_alert": {
|
||||
"title": "prom query with SSE",
|
||||
"condition": "condition",
|
||||
"uid": "unknown",
|
||||
"data": [
|
||||
{
|
||||
"refId": "query",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"model": {
|
||||
"datasource": "gdev-prometheus",
|
||||
"datasourceUid": "000000002",
|
||||
"expr": "http_request_duration_microseconds_count",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"intervalMs": 1000,
|
||||
"legendFormat": "",
|
||||
"maxDataPoints": 100,
|
||||
"refId": "query"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "reduced",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"model": {
|
||||
"datasource": "__expr__",
|
||||
"datasourceUid": "-100",
|
||||
"expression": "query",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"reducer": "mean",
|
||||
"refId": "reduced",
|
||||
"type": "reduce"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "condition",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"model": {
|
||||
"datasource": "__expr__",
|
||||
"datasourceUid": "-100",
|
||||
"expression": "$reduced > 42",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"refId": "condition",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user