Alerting: Return 400 if the Prometheus rule has unsupported options (#101616)
This commit is contained in:
@@ -98,10 +98,8 @@ func NewConverter(cfg Config) (*Converter, error) {
|
||||
|
||||
// PrometheusRulesToGrafana converts a Prometheus rule group into Grafana Alerting rule group.
|
||||
func (p *Converter) PrometheusRulesToGrafana(orgID int64, namespaceUID string, group PrometheusRuleGroup) (*models.AlertRuleGroup, error) {
|
||||
for _, rule := range group.Rules {
|
||||
if err := rule.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := group.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grafanaGroup, err := p.convertRuleGroup(orgID, namespaceUID, group)
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
promGroup PrometheusRuleGroup
|
||||
config Config
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid rule group",
|
||||
@@ -68,6 +69,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "keep_firing_for is not supported",
|
||||
},
|
||||
{
|
||||
name: "rule group with empty interval",
|
||||
@@ -100,6 +102,63 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "rule group with query_offset is not supported",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
QueryOffset: func() *prommodel.Duration {
|
||||
d := prommodel.Duration(30 * time.Second)
|
||||
return &d
|
||||
}(),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "query_offset is not supported",
|
||||
},
|
||||
{
|
||||
name: "rule group with limit is not supported",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
Limit: 5,
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "limit is not supported",
|
||||
},
|
||||
{
|
||||
name: "rule group with labels is not supported",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
Labels: map[string]string{"team": "devops"},
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "labels are not supported",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@@ -114,6 +173,9 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err, tc.name)
|
||||
if tc.errorMsg != "" {
|
||||
require.Contains(t, err.Error(), tc.errorMsg, tc.name)
|
||||
}
|
||||
return
|
||||
}
|
||||
require.NoError(t, err, tc.name)
|
||||
|
||||
@@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPrometheusRuleValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleInvalid")
|
||||
ErrPrometheusRuleValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleInvalid")
|
||||
ErrPrometheusRuleGroupValidationFailed = errutil.ValidationFailed("alerting.prometheusRuleGroupInvalid")
|
||||
)
|
||||
|
||||
type PrometheusRulesFile struct {
|
||||
@@ -15,9 +16,34 @@ type PrometheusRulesFile struct {
|
||||
}
|
||||
|
||||
type PrometheusRuleGroup struct {
|
||||
Name string `yaml:"name"`
|
||||
Interval prommodel.Duration `yaml:"interval"`
|
||||
Rules []PrometheusRule `yaml:"rules"`
|
||||
Name string `yaml:"name"`
|
||||
Interval prommodel.Duration `yaml:"interval"`
|
||||
QueryOffset *prommodel.Duration `yaml:"query_offset,omitempty"`
|
||||
Limit int `yaml:"limit,omitempty"`
|
||||
Rules []PrometheusRule `yaml:"rules"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func (g *PrometheusRuleGroup) Validate() error {
|
||||
if g.QueryOffset != nil {
|
||||
return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset is not supported")
|
||||
}
|
||||
|
||||
if g.Limit != 0 {
|
||||
return ErrPrometheusRuleGroupValidationFailed.Errorf("limit is not supported")
|
||||
}
|
||||
|
||||
if len(g.Labels) > 0 {
|
||||
return ErrPrometheusRuleGroupValidationFailed.Errorf("labels are not supported")
|
||||
}
|
||||
|
||||
for _, rule := range g.Rules {
|
||||
if err := rule.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type PrometheusRule struct {
|
||||
|
||||
@@ -7,8 +7,140 @@ import (
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func TestPrometheusRuleGroup_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
group PrometheusRuleGroup
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid group with no unsupported fields",
|
||||
group: PrometheusRuleGroup{
|
||||
Name: "test_group",
|
||||
Interval: prommodel.Duration(60),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "test_alert",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid group with query_offset",
|
||||
group: PrometheusRuleGroup{
|
||||
Name: "test_group",
|
||||
Interval: prommodel.Duration(60),
|
||||
QueryOffset: util.Pointer(prommodel.Duration(10)),
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "query_offset is not supported",
|
||||
},
|
||||
{
|
||||
name: "invalid group with limit",
|
||||
group: PrometheusRuleGroup{
|
||||
Name: "test_group",
|
||||
Interval: prommodel.Duration(60),
|
||||
Limit: 10,
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "limit is not supported",
|
||||
},
|
||||
{
|
||||
name: "invalid group with labels",
|
||||
group: PrometheusRuleGroup{
|
||||
Name: "test_group",
|
||||
Interval: prommodel.Duration(60),
|
||||
Labels: map[string]string{"foo": "bar"},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "labels are not supported",
|
||||
},
|
||||
{
|
||||
name: "invalid group with invalid rule",
|
||||
group: PrometheusRuleGroup{
|
||||
Name: "test_group",
|
||||
Interval: prommodel.Duration(60),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "test_alert",
|
||||
Expr: "up == 0",
|
||||
KeepFiringFor: util.Pointer(prommodel.Duration(10)),
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "keep_firing_for is not supported",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.group.Validate()
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.errorMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheusRule_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
rule PrometheusRule
|
||||
expectError bool
|
||||
errorMsg string
|
||||
}{
|
||||
{
|
||||
name: "valid alert rule",
|
||||
rule: PrometheusRule{
|
||||
Alert: "test_alert",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "valid recording rule",
|
||||
rule: PrometheusRule{
|
||||
Record: "test_recording",
|
||||
Expr: "sum(up)",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "invalid rule with keep_firing_for",
|
||||
rule: PrometheusRule{
|
||||
Alert: "test_alert",
|
||||
Expr: "up == 0",
|
||||
KeepFiringFor: util.Pointer(prommodel.Duration(10)),
|
||||
},
|
||||
expectError: true,
|
||||
errorMsg: "keep_firing_for is not supported",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.rule.Validate()
|
||||
if tt.expectError {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.errorMsg)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheusRulesFileYAML(t *testing.T) {
|
||||
interval := prommodel.Duration(5 * time.Minute)
|
||||
alertFor := prommodel.Duration(10 * time.Minute)
|
||||
|
||||
Reference in New Issue
Block a user