mirror of https://github.com/grafana/grafana.git
Alerting: Generate stable UIDs for alert rules in Prometheus conversion (#100973)
This commit is contained in:
parent
436dc86a09
commit
5a6d9a99f3
|
|
@ -5,10 +5,19 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// ruleUIDLabel is a special label that can be used to set a custom UID for a Prometheus
|
||||
// alert rule when converting it to a Grafana alert rule. If this label is not present,
|
||||
// a stable UID will be generated automatically based on the rule's data.
|
||||
ruleUIDLabel = "__grafana_alert_rule_uid__"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
|
@ -114,6 +123,12 @@ func (p *Converter) convertRuleGroup(orgID int64, namespaceUID string, promGroup
|
|||
gr.Title = fmt.Sprintf("%s (%d)", gr.Title, val)
|
||||
}
|
||||
|
||||
uid, err := getUID(orgID, namespaceUID, promGroup.Name, i, rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate UID for rule '%s': %w", gr.Title, err)
|
||||
}
|
||||
gr.UID = uid
|
||||
|
||||
rules = append(rules, gr)
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +142,24 @@ func (p *Converter) convertRuleGroup(orgID int64, namespaceUID string, promGroup
|
|||
return result, nil
|
||||
}
|
||||
|
||||
// getUID returns a UID for a Prometheus rule.
|
||||
// If the rule has a special label its value is used.
|
||||
// Otherwise, a stable UUID is generated by using a hash of the rule's data.
|
||||
func getUID(orgID int64, namespaceUID string, group string, position int, promRule PrometheusRule) (string, error) {
|
||||
if uid, ok := promRule.Labels[ruleUIDLabel]; ok {
|
||||
if err := util.ValidateUID(uid); err != nil {
|
||||
return "", fmt.Errorf("invalid UID label value: %s; %w", uid, err)
|
||||
}
|
||||
return uid, nil
|
||||
}
|
||||
|
||||
// Generate stable UUID based on the orgID, namespace, group and position.
|
||||
uidData := fmt.Sprintf("%d|%s|%s|%d", orgID, namespaceUID, group, position)
|
||||
u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(uidData))
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (p *Converter) convertRule(orgID int64, namespaceUID, group string, rule PrometheusRule) (models.AlertRule, error) {
|
||||
var forInterval time.Duration
|
||||
if rule.For != nil {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
package prom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
|
|
@ -136,6 +139,10 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
|||
expectedLabels[k] = v
|
||||
}
|
||||
|
||||
uidData := fmt.Sprintf("%d|%s|%s|%d", tc.orgID, tc.namespace, tc.promGroup.Name, j)
|
||||
u := uuid.NewSHA1(uuid.NameSpaceOID, []byte(uidData))
|
||||
require.Equal(t, u.String(), grafanaRule.UID, tc.name)
|
||||
|
||||
require.Equal(t, expectedLabels, grafanaRule.Labels, tc.name)
|
||||
require.Equal(t, promRule.Annotations, grafanaRule.Annotations, tc.name)
|
||||
require.Equal(t, models.Duration(0*time.Minute), grafanaRule.Data[0].RelativeTimeRange.To)
|
||||
|
|
@ -190,3 +197,105 @@ func TestPrometheusRulesToGrafanaWithDuplicateRuleNames(t *testing.T) {
|
|||
require.Equal(t, "another alert", group.Rules[2].Title)
|
||||
require.Equal(t, "alert (3)", group.Rules[3].Title)
|
||||
}
|
||||
|
||||
func TestPrometheusRulesToGrafana_UID(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
namespace := "some-namespace"
|
||||
|
||||
promGroup := PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "cpu_usage > 80",
|
||||
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
ruleUIDLabel: "rule-uid-1",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "CPU usage is critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
converter, err := NewConverter(Config{
|
||||
DatasourceUID: "datasource-uid",
|
||||
DatasourceType: datasources.DS_PROMETHEUS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("if not specified, UID is generated based on the rule index", func(t *testing.T) {
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
firstUID := grafanaGroup.Rules[0].UID
|
||||
|
||||
// Convert again
|
||||
grafanaGroup, err = converter.PrometheusRulesToGrafana(orgID, namespace, promGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
secondUID := grafanaGroup.Rules[0].UID
|
||||
|
||||
// They must be equal
|
||||
require.NotEmpty(t, firstUID)
|
||||
require.Equal(t, firstUID, secondUID)
|
||||
})
|
||||
|
||||
t.Run("if the special label is specified", func(t *testing.T) {
|
||||
t.Run("and the label is valid it should be used", func(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
namespace := "some-namespace"
|
||||
|
||||
converter, err := NewConverter(Config{
|
||||
DatasourceUID: "datasource-uid",
|
||||
DatasourceType: datasources.DS_PROMETHEUS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
promGroup.Rules[0].Labels[ruleUIDLabel] = "rule-uid-1"
|
||||
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "rule-uid-1", grafanaGroup.Rules[0].UID)
|
||||
})
|
||||
|
||||
t.Run("and the label is invalid", func(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
namespace := "some-namespace"
|
||||
|
||||
converter, err := NewConverter(Config{
|
||||
DatasourceUID: "datasource-uid",
|
||||
DatasourceType: datasources.DS_PROMETHEUS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a string of 50 characters
|
||||
promGroup.Rules[0].Labels[ruleUIDLabel] = "aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmm" // too long
|
||||
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup)
|
||||
require.Errorf(t, err, "invalid UID label value")
|
||||
require.Nil(t, grafanaGroup)
|
||||
})
|
||||
|
||||
t.Run("and the label is empty", func(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
namespace := "some-namespace"
|
||||
|
||||
converter, err := NewConverter(Config{
|
||||
DatasourceUID: "datasource-uid",
|
||||
DatasourceType: datasources.DS_PROMETHEUS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
promGroup.Rules[0].Labels[ruleUIDLabel] = ""
|
||||
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(orgID, namespace, promGroup)
|
||||
require.Errorf(t, err, "invalid UID label value")
|
||||
require.Nil(t, grafanaGroup)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue