Alerting: Return 400 if the Prometheus rule has unsupported options (#101616)

pull/101624/head
Alexander Akhmetov 3 months ago committed by GitHub
parent 6389b801b9
commit ec7a0db54d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      pkg/services/ngalert/prom/convert.go
  2. 62
      pkg/services/ngalert/prom/convert_test.go
  3. 34
      pkg/services/ngalert/prom/models.go
  4. 132
      pkg/services/ngalert/prom/models_test.go

@ -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)

Loading…
Cancel
Save