Alerting: Allow importing Prometheus rules with keep_firing_for (#103557)

This commit is contained in:
Alexander Akhmetov
2025-04-07 22:33:07 +02:00
committed by GitHub
parent fa7ea6ee00
commit c4a2f99d8b
4 changed files with 32 additions and 114 deletions
+19 -13
View File
@@ -196,6 +196,11 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom
forInterval = time.Duration(*rule.For)
}
var keepFiringFor time.Duration
if rule.KeepFiringFor != nil {
keepFiringFor = time.Duration(*rule.KeepFiringFor)
}
var query []models.AlertQuery
var title string
var isPaused bool
@@ -236,19 +241,20 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom
}
result := models.AlertRule{
OrgID: orgID,
NamespaceUID: namespaceUID,
Title: title,
Data: query,
Condition: query[len(query)-1].RefID,
NoDataState: p.cfg.NoDataState,
ExecErrState: p.cfg.ExecErrState,
Annotations: rule.Annotations,
Labels: labels,
For: forInterval,
RuleGroup: promGroup.Name,
IsPaused: isPaused,
Record: record,
OrgID: orgID,
NamespaceUID: namespaceUID,
Title: title,
Data: query,
Condition: query[len(query)-1].RefID,
NoDataState: p.cfg.NoDataState,
ExecErrState: p.cfg.ExecErrState,
Annotations: rule.Annotations,
Labels: labels,
For: forInterval,
KeepFiringFor: keepFiringFor,
RuleGroup: promGroup.Name,
IsPaused: isPaused,
Record: record,
// MissingSeriesEvalsToResolve is set to 1 to match the Prometheus behaviour.
// Prometheus resolves alerts as soon as the series disappears.
+10 -21
View File
@@ -41,9 +41,10 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
QueryOffset: util.Pointer(prommodel.Duration(1 * time.Minute)),
Rules: []PrometheusRule{
{
Alert: "alert-1",
Expr: "cpu_usage > 80",
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
Alert: "alert-1",
Expr: "cpu_usage > 80",
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
KeepFiringFor: util.Pointer(prommodel.Duration(60 * time.Second)),
Labels: map[string]string{
"severity": "critical",
},
@@ -127,24 +128,6 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
expectError: true,
errorMsg: "invalid target datasource type: non-prometheus-datasource, must be prometheus",
},
{
name: "rules with keep_firing_for are not supported",
orgID: 1,
namespace: "namespaceUID",
promGroup: PrometheusRuleGroup{
Name: "test-group-1",
Interval: prommodel.Duration(1 * time.Minute),
Rules: []PrometheusRule{
{
Alert: "alert-1",
Expr: "up == 0",
KeepFiringFor: util.Pointer(prommodel.Duration(5 * time.Minute)),
},
},
},
expectError: true,
errorMsg: "keep_firing_for is not supported",
},
{
name: "rule group with empty interval",
orgID: 1,
@@ -330,6 +313,12 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
}
require.Equal(t, expectedFor, grafanaRule.For, tc.name)
var expectedKeepFiringFor time.Duration
if promRule.KeepFiringFor != nil {
expectedKeepFiringFor = time.Duration(*promRule.KeepFiringFor)
}
require.Equal(t, expectedKeepFiringFor, grafanaRule.KeepFiringFor, tc.name)
expectedLabels := make(map[string]string, len(promRule.Labels)+len(tc.promGroup.Labels))
maps.Copy(expectedLabels, tc.promGroup.Labels)
maps.Copy(expectedLabels, promRule.Labels)
-14
View File
@@ -33,12 +33,6 @@ func (g *PrometheusRuleGroup) Validate() error {
return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset must be >= 0")
}
for _, rule := range g.Rules {
if err := rule.Validate(); err != nil {
return err
}
}
return nil
}
@@ -51,11 +45,3 @@ type PrometheusRule struct {
Annotations map[string]string `yaml:"annotations,omitempty"`
Record string `yaml:"record,omitempty"`
}
func (r *PrometheusRule) Validate() error {
if r.KeepFiringFor != nil {
return ErrPrometheusRuleValidationFailed.Errorf("keep_firing_for is not supported")
}
return nil
}
+3 -66
View File
@@ -29,8 +29,9 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) {
QueryOffset: util.Pointer(prommodel.Duration(time.Duration(1) * time.Second)),
Rules: []PrometheusRule{
{
Alert: "test_alert",
Expr: "up == 0",
Alert: "test_alert",
Expr: "up == 0",
KeepFiringFor: util.Pointer(prommodel.Duration(10)),
},
},
},
@@ -56,22 +57,6 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) {
expectError: true,
errorMsg: "limit is 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 {
@@ -87,54 +72,6 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) {
}
}
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)