The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/services/ngalert/api/api_ruler.go

260 lines
9.1 KiB

package api
import (
"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"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/util"
"github.com/prometheus/common/model"
)
type RulerSrv struct {
store store.RuleStore
log log.Logger
}
func (srv RulerSrv) RouteDeleteNamespaceRulesConfig(c *models.ReqContext) response.Response {
namespaceTitle := c.Params(":Namespace")
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
if err != nil {
return toNamespaceErrorResponse(err)
}
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 {
namespaceTitle := c.Params(":Namespace")
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
if err != nil {
return toNamespaceErrorResponse(err)
}
ruleGroup := c.Params(":Groupname")
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 {
namespaceTitle := c.Params(":Namespace")
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
if err != nil {
return toNamespaceErrorResponse(err)
}
q := ngmodels.ListNamespaceAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
NamespaceUID: namespace.Uid,
}
if err := srv.store.GetNamespaceAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to update rule group", err)
}
result := apimodels.NamespaceConfigResponse{}
ruleGroupConfigs := make(map[string]apimodels.GettableRuleGroupConfig)
for _, r := range q.Result {
ruleGroupConfig, ok := ruleGroupConfigs[r.RuleGroup]
if !ok {
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
ruleGroupConfigs[r.RuleGroup] = apimodels.GettableRuleGroupConfig{
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, namespace.Id),
},
}
} else {
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id))
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
}
}
for _, ruleGroupConfig := range ruleGroupConfigs {
result[namespaceTitle] = append(result[namespaceTitle], ruleGroupConfig)
}
return response.JSON(http.StatusAccepted, result)
}
func (srv RulerSrv) RouteGetRulegGroupConfig(c *models.ReqContext) response.Response {
namespaceTitle := c.Params(":Namespace")
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
if err != nil {
return toNamespaceErrorResponse(err)
}
ruleGroup := c.Params(":Groupname")
q := ngmodels.ListRuleGroupAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
NamespaceUID: namespace.Uid,
RuleGroup: ruleGroup,
}
if err := srv.store.GetRuleGroupAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to get group alert rules", err)
}
var ruleGroupInterval model.Duration
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, namespace.Id))
}
result := apimodels.RuleGroupConfigResponse{
GettableRuleGroupConfig: apimodels.GettableRuleGroupConfig{
Name: ruleGroup,
Interval: ruleGroupInterval,
Rules: ruleNodes,
},
}
return response.JSON(http.StatusAccepted, result)
}
func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response {
q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
}
if err := srv.store.GetOrgAlertRules(&q); err != nil {
return response.Error(http.StatusInternalServerError, "failed to get alert rules", err)
}
configs := make(map[string]map[string]apimodels.GettableRuleGroupConfig)
for _, r := range q.Result {
folder, err := srv.store.GetNamespaceByUID(r.NamespaceUID, c.SignedInUser.OrgId, c.SignedInUser)
if err != nil {
return toNamespaceErrorResponse(err)
}
namespace := folder.Title
_, ok := configs[namespace]
if !ok {
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
configs[namespace] = make(map[string]apimodels.GettableRuleGroupConfig)
configs[namespace][r.RuleGroup] = apimodels.GettableRuleGroupConfig{
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, folder.Id),
},
}
} else {
ruleGroupConfig, ok := configs[namespace][r.RuleGroup]
if !ok {
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
configs[namespace][r.RuleGroup] = apimodels.GettableRuleGroupConfig{
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, folder.Id),
},
}
} else {
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, folder.Id))
configs[namespace][r.RuleGroup] = ruleGroupConfig
}
}
}
result := apimodels.NamespaceConfigResponse{}
for namespace, m := range configs {
for _, ruleGroupConfig := range m {
result[namespace] = append(result[namespace], ruleGroupConfig)
}
}
return response.JSON(http.StatusAccepted, result)
}
func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig) response.Response {
namespaceTitle := c.Params(":Namespace")
namespace, err := srv.store.GetNamespaceByTitle(namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, true)
if err != nil {
return toNamespaceErrorResponse(err)
}
// TODO check permissions
// TODO check quota
// TODO validate UID uniqueness in the payload
if err := srv.store.UpdateRuleGroup(store.UpdateRuleGroupCmd{
OrgID: c.SignedInUser.OrgId,
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, namespaceID int64) apimodels.GettableExtendedRuleNode {
gettableExtendedRuleNode := apimodels.GettableExtendedRuleNode{
GrafanaManagedAlert: &apimodels.GettableGrafanaRule{
ID: r.ID,
OrgID: r.OrgID,
Title: r.Title,
Condition: r.Condition,
Data: r.Data,
Updated: r.Updated,
IntervalSeconds: r.IntervalSeconds,
Version: r.Version,
UID: r.UID,
NamespaceUID: r.NamespaceUID,
NamespaceID: namespaceID,
RuleGroup: r.RuleGroup,
NoDataState: apimodels.NoDataState(r.NoDataState),
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
},
}
gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
For: model.Duration(r.For),
Annotations: r.Annotations,
Labels: r.Labels,
}
return gettableExtendedRuleNode
}
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
postableExtendedRuleNode := apimodels.PostableExtendedRuleNode{
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
OrgID: r.OrgID,
Title: r.Title,
Condition: r.Condition,
Data: r.Data,
UID: r.UID,
NoDataState: apimodels.NoDataState(r.NoDataState),
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
},
}
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)
}