You can now get alert rules for a dashboard or a panel using /api/v1/rules endpoints. (#39476)

Get alert rules for a dashboard and panel in /api/v1/rules
This commit is contained in:
George Robinson
2021-10-04 16:33:55 +01:00
committed by GitHub
parent a3783a4eb7
commit 2a4c1b1aa6
14 changed files with 918 additions and 17 deletions
+24 -1
View File
@@ -2,8 +2,11 @@ package api
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
@@ -51,6 +54,13 @@ func (srv PrometheusSrv) RouteGetAlertStatuses(c *models.ReqContext) response.Re
return response.JSON(http.StatusOK, alertResponse)
}
func getPanelIDFromRequest(r *http.Request) (int64, error) {
if s := strings.TrimSpace(r.URL.Query().Get("panel_id")); s != "" {
return strconv.ParseInt(s, 10, 64)
}
return 0, nil
}
func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Response {
ruleResponse := apimodels.RuleResponse{
DiscoveryBase: apimodels.DiscoveryBase{
@@ -71,9 +81,20 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
namespaceUIDs = append(namespaceUIDs, k)
}
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "invalid panel_id")
}
if dashboardUID == "" && panelID != 0 {
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
}
ruleGroupQuery := ngmodels.ListOrgRuleGroupsQuery{
OrgID: c.SignedInUser.OrgId,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
}
if err := srv.store.GetOrgRuleGroups(&ruleGroupQuery); err != nil {
ruleResponse.DiscoveryBase.Status = "error"
@@ -83,7 +104,9 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *models.ReqContext) response.Res
}
alertRuleQuery := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
OrgID: c.SignedInUser.OrgId,
DashboardUID: dashboardUID,
PanelID: panelID,
}
if err := srv.store.GetOrgAlertRules(&alertRuleQuery); err != nil {
ruleResponse.DiscoveryBase.Status = "error"
+12 -1
View File
@@ -158,9 +158,20 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
namespaceUIDs = append(namespaceUIDs, k)
}
dashboardUID := c.Query("dashboard_uid")
panelID, err := getPanelIDFromRequest(c.Req)
if err != nil {
return ErrResp(http.StatusBadRequest, err, "invalid panel_id")
}
if dashboardUID == "" && panelID != 0 {
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
}
q := ngmodels.ListAlertRulesQuery{
OrgID: c.SignedInUser.OrgId,
NamespaceUIDs: namespaceUIDs,
DashboardUID: dashboardUID,
PanelID: panelID,
}
if err := srv.store.GetOrgAlertRules(&q); err != nil {
@@ -210,7 +221,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
result[namespace] = append(result[namespace], ruleGroupConfig)
}
}
return response.JSON(http.StatusAccepted, result)
return response.JSON(http.StatusOK, result)
}
func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig) response.Response {
@@ -183,6 +183,14 @@ type GetSilencesParams struct {
Filter []string `json:"filter"`
}
// swagger:parameters RouteGetRuleStatuses
type GetRuleStatusesParams struct {
// in: query
DashboardUID string
// in: query
PanelID int64
}
// swagger:model
type GettableStatus struct {
// cluster
@@ -86,6 +86,14 @@ type PathRouleGroupConfig struct {
Groupname string
}
// swagger:parameters RouteGetRulesConfig
type PathGetRulesParams struct {
// in: query
DashboardUID string
// in: query
PanelID int64
}
// swagger:model
type RuleGroupConfigResponse struct {
GettableRuleGroupConfig
+24 -3
View File
@@ -2869,6 +2869,7 @@
"type": "array"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"properties": {
"comment": {
"description": "comment",
@@ -2920,9 +2921,7 @@
"status",
"updatedAt"
],
"type": "object",
"x-go-name": "GettableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models"
"type": "object"
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
@@ -3800,6 +3799,17 @@
"description": "gets the evaluation statuses of all rules",
"operationId": "RouteGetRuleStatuses",
"parameters": [
{
"in": "query",
"name": "DashboardUID",
"type": "string"
},
{
"format": "int64",
"in": "query",
"name": "PanelID",
"type": "integer"
},
{
"description": "Recipient should be \"grafana\" for requests to be handled by grafana\nand the numeric datasource id for requests to be forwarded to a datasource",
"in": "path",
@@ -3832,6 +3842,17 @@
"name": "Recipient",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "DashboardUID",
"type": "string"
},
{
"format": "int64",
"in": "query",
"name": "PanelID",
"type": "integer"
}
],
"produces": [
+23 -2
View File
@@ -592,6 +592,17 @@
],
"operationId": "RouteGetRuleStatuses",
"parameters": [
{
"type": "string",
"name": "DashboardUID",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"name": "PanelID",
"in": "query"
},
{
"type": "string",
"description": "Recipient should be \"grafana\" for requests to be handled by grafana\nand the numeric datasource id for requests to be forwarded to a datasource",
@@ -627,6 +638,17 @@
"name": "Recipient",
"in": "path",
"required": true
},
{
"type": "string",
"name": "DashboardUID",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"name": "PanelID",
"in": "query"
}
],
"responses": {
@@ -3877,6 +3899,7 @@
"$ref": "#/definitions/gettableAlerts"
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"type": "object",
"required": [
"comment",
@@ -3929,8 +3952,6 @@
"x-go-name": "UpdatedAt"
}
},
"x-go-name": "GettableSilence",
"x-go-package": "github.com/prometheus/alertmanager/api/v2/models",
"$ref": "#/definitions/gettableSilence"
},
"gettableSilences": {
+19 -2
View File
@@ -58,8 +58,10 @@ type AlertRule struct {
Updated time.Time
IntervalSeconds int64
Version int64
UID string `xorm:"uid"`
NamespaceUID string `xorm:"namespace_uid"`
UID string `xorm:"uid"`
NamespaceUID string `xorm:"namespace_uid"`
DashboardUID *string `xorm:"dashboard_uid"`
PanelID *int64 `xorm:"panel_id"`
RuleGroup string
NoDataState NoDataState
ExecErrState ExecutionErrorState
@@ -137,6 +139,11 @@ type ListAlertRulesQuery struct {
NamespaceUIDs []string
ExcludeOrgs []int64
// DashboardUID and PanelID are optional and allow filtering rules
// to return just those for a dashboard and panel.
DashboardUID string
PanelID int64
Result []*AlertRule
}
@@ -156,6 +163,11 @@ type ListRuleGroupAlertRulesQuery struct {
NamespaceUID string
RuleGroup string
// DashboardUID and PanelID are optional and allow filtering rules
// to return just those for a dashboard and panel.
DashboardUID string
PanelID int64
Result []*AlertRule
}
@@ -164,6 +176,11 @@ type ListOrgRuleGroupsQuery struct {
OrgID int64
NamespaceUIDs []string
// DashboardUID and PanelID are optional and allow filtering rules
// to return just those for a dashboard and panel.
DashboardUID string
PanelID int64
Result [][]string
}
+60 -4
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"time"
@@ -332,6 +333,17 @@ func (st DBstore) GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error {
q = fmt.Sprintf("%s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
}
if query.DashboardUID != "" {
params = append(params, query.DashboardUID)
q = fmt.Sprintf("%s AND dashboard_uid = ?", q)
if query.PanelID != 0 {
params = append(params, query.PanelID)
q = fmt.Sprintf("%s AND panel_id = ?", q)
}
}
q = fmt.Sprintf("%s ORDER BY id ASC", q)
if err := sess.SQL(q, params...).Find(&alertRules); err != nil {
return err
}
@@ -359,10 +371,20 @@ func (st DBstore) GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRules
// GetRuleGroupAlertRules is a handler for retrieving rule group alert rules of specific organisation.
func (st DBstore) GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
alertRules := make([]*ngmodels.AlertRule, 0)
q := "SELECT * FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?"
if err := sess.SQL(q, query.OrgID, query.NamespaceUID, query.RuleGroup).Find(&alertRules); err != nil {
args := []interface{}{query.OrgID, query.NamespaceUID, query.RuleGroup}
if query.DashboardUID != "" {
q = fmt.Sprintf("%s and dashboard_uid = ?", q)
args = append(args, query.DashboardUID)
if query.PanelID != 0 {
q = fmt.Sprintf("%s and panel_id = ?", q)
args = append(args, query.PanelID)
}
}
alertRules := make([]*ngmodels.AlertRule, 0)
if err := sess.SQL(q, args...).Find(&alertRules); err != nil {
return err
}
@@ -528,6 +550,18 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
new.Labels = r.ApiRuleNode.Labels
}
if s := new.Annotations["__dashboardUid__"]; s != "" {
new.DashboardUID = &s
}
if s := new.Annotations["__panelId__"]; s != "" {
panelID, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("the __panelId__ annotation does not contain a valid Panel ID: %w", err)
}
new.PanelID = &panelID
}
upsertRule := UpsertRule{
New: new,
}
@@ -569,7 +603,19 @@ func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error {
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
var ruleGroups [][]string
q := "SELECT DISTINCT rule_group, namespace_uid, (select title from dashboard where org_id = alert_rule.org_id and uid = alert_rule.namespace_uid) AS namespace_title FROM alert_rule WHERE org_id = ?"
q := `
SELECT DISTINCT
rule_group,
namespace_uid,
(
SELECT title
FROM dashboard
WHERE
org_id = alert_rule.org_id AND
uid = alert_rule.namespace_uid
) AS namespace_title
FROM alert_rule
WHERE org_id = ?`
params := []interface{}{query.OrgID}
if len(query.NamespaceUIDs) > 0 {
@@ -580,6 +626,16 @@ func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error
}
q = fmt.Sprintf(" %s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
}
if query.DashboardUID != "" {
q = fmt.Sprintf("%s and dashboard_uid = ?", q)
params = append(params, query.DashboardUID)
if query.PanelID != 0 {
q = fmt.Sprintf("%s and panel_id = ?", q)
params = append(params, query.PanelID)
}
}
q = fmt.Sprintf(" %s ORDER BY namespace_title", q)
if err := sess.SQL(q, params...).Find(&ruleGroups); err != nil {
@@ -54,6 +54,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
ualert.RerunDashAlertMigration(mg)
addSecretsMigration(mg)
addKVStoreMigrations(mg)
ualert.AddDashboardUIDPanelIDMigration(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
@@ -204,6 +204,33 @@ func AddAlertRuleMigrations(mg *migrator.Migrator, defaultIntervalSeconds int64)
mg.AddMigration("add index in alert_rule on org_id, namespase_uid and title columns", migrator.NewAddIndexMigration(alertRule, &migrator.Index{
Cols: []string{"org_id", "namespace_uid", "title"}, Type: migrator.UniqueIndex,
}))
mg.AddMigration("add dashboard_uid column to alert_rule", migrator.NewAddColumnMigration(
migrator.Table{Name: "alert_rule"},
&migrator.Column{
Name: "dashboard_uid",
Type: migrator.DB_NVarchar,
Length: 40,
Nullable: true,
},
))
mg.AddMigration("add panel_id column to alert_rule", migrator.NewAddColumnMigration(
migrator.Table{Name: "alert_rule"},
&migrator.Column{
Name: "panel_id",
Type: migrator.DB_BigInt,
Nullable: true,
},
))
mg.AddMigration("add index in alert_rule on org_id, dashboard_uid and panel_id columns", migrator.NewAddIndexMigration(
migrator.Table{Name: "alert_rule"},
&migrator.Index{
Name: "IDX_alert_rule_org_id_dashboard_uid_panel_id",
Cols: []string{"org_id", "dashboard_uid", "panel_id"},
},
))
}
func AddAlertRuleVersionMigrations(mg *migrator.Migrator) {
@@ -101,6 +101,73 @@ func RerunDashAlertMigration(mg *migrator.Migrator) {
}
}
func AddDashboardUIDPanelIDMigration(mg *migrator.Migrator) {
logs, err := mg.GetMigrationLog()
if err != nil {
mg.Logger.Crit("alert migration failure: could not get migration log", "error", err)
os.Exit(1)
}
migrationID := "update dashboard_uid and panel_id from existing annotations"
_, migrationRun := logs[migrationID]
ngEnabled := mg.Cfg.UnifiedAlerting.Enabled
undoMigrationID := "undo " + migrationID
if ngEnabled && !migrationRun {
// If ngalert is enabled and the migration has not been run then run it.
mg.AddMigration(migrationID, &updateDashboardUIDPanelIDMigration{})
} else if !ngEnabled && migrationRun {
// If ngalert is disabled and the migration has been run then remove it
// from the migration log so it will run if ngalert is re-enabled.
mg.AddMigration(undoMigrationID, &clearMigrationEntry{
migrationID: migrationID,
})
}
}
// updateDashboardUIDPanelIDMigration sets the dashboard_uid and panel_id columns
// from the __dashboardUid__ and __panelId__ annotations.
type updateDashboardUIDPanelIDMigration struct {
migrator.MigrationBase
}
func (m *updateDashboardUIDPanelIDMigration) SQL(_ migrator.Dialect) string {
return "set dashboard_uid and panel_id migration"
}
func (m *updateDashboardUIDPanelIDMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
var results []struct {
ID int64 `xorm:"id"`
Annotations map[string]string `xorm:"annotations"`
}
if err := sess.SQL(`SELECT id, annotations FROM alert_rule`).Find(&results); err != nil {
return fmt.Errorf("failed to get annotations for all alert rules: %w", err)
}
for _, next := range results {
var (
dashboardUID *string
panelID *int64
)
if s, ok := next.Annotations["__dashboardUid__"]; ok {
dashboardUID = &s
}
if s, ok := next.Annotations["__panelId__"]; ok {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf("the __panelId__ annotation does not contain a valid Panel ID: %w", err)
}
panelID = &i
}
if _, err := sess.Exec(`UPDATE alert_rule SET dashboard_uid = ?, panel_id = ? WHERE id = ?`,
dashboardUID,
panelID,
next.ID); err != nil {
return fmt.Errorf("failed to set dashboard_uid and panel_id for alert rule: %w", err)
}
}
return nil
}
// clearMigrationEntry removes an entry fromt the migration_log table.
// This migration is not recorded in the migration_log so that it can re-run several times.
type clearMigrationEntry struct {