diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index 28bf9715858..df0adedbff5 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -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. diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 87a2ea9ff16..9fed80776e7 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -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) diff --git a/pkg/services/ngalert/prom/models.go b/pkg/services/ngalert/prom/models.go index 0ef0a301885..1ac5ad1d0d9 100644 --- a/pkg/services/ngalert/prom/models.go +++ b/pkg/services/ngalert/prom/models.go @@ -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 -} diff --git a/pkg/services/ngalert/prom/models_test.go b/pkg/services/ngalert/prom/models_test.go index 90be51c140c..f2184a4fffb 100644 --- a/pkg/services/ngalert/prom/models_test.go +++ b/pkg/services/ngalert/prom/models_test.go @@ -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)