Alerting: Allow importing Prometheus rules with keep_firing_for (#103557)
This commit is contained in:
@@ -196,6 +196,11 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom
|
|||||||
forInterval = time.Duration(*rule.For)
|
forInterval = time.Duration(*rule.For)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var keepFiringFor time.Duration
|
||||||
|
if rule.KeepFiringFor != nil {
|
||||||
|
keepFiringFor = time.Duration(*rule.KeepFiringFor)
|
||||||
|
}
|
||||||
|
|
||||||
var query []models.AlertQuery
|
var query []models.AlertQuery
|
||||||
var title string
|
var title string
|
||||||
var isPaused bool
|
var isPaused bool
|
||||||
@@ -236,19 +241,20 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := models.AlertRule{
|
result := models.AlertRule{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
NamespaceUID: namespaceUID,
|
NamespaceUID: namespaceUID,
|
||||||
Title: title,
|
Title: title,
|
||||||
Data: query,
|
Data: query,
|
||||||
Condition: query[len(query)-1].RefID,
|
Condition: query[len(query)-1].RefID,
|
||||||
NoDataState: p.cfg.NoDataState,
|
NoDataState: p.cfg.NoDataState,
|
||||||
ExecErrState: p.cfg.ExecErrState,
|
ExecErrState: p.cfg.ExecErrState,
|
||||||
Annotations: rule.Annotations,
|
Annotations: rule.Annotations,
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
For: forInterval,
|
For: forInterval,
|
||||||
RuleGroup: promGroup.Name,
|
KeepFiringFor: keepFiringFor,
|
||||||
IsPaused: isPaused,
|
RuleGroup: promGroup.Name,
|
||||||
Record: record,
|
IsPaused: isPaused,
|
||||||
|
Record: record,
|
||||||
|
|
||||||
// MissingSeriesEvalsToResolve is set to 1 to match the Prometheus behaviour.
|
// MissingSeriesEvalsToResolve is set to 1 to match the Prometheus behaviour.
|
||||||
// Prometheus resolves alerts as soon as the series disappears.
|
// Prometheus resolves alerts as soon as the series disappears.
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
|||||||
QueryOffset: util.Pointer(prommodel.Duration(1 * time.Minute)),
|
QueryOffset: util.Pointer(prommodel.Duration(1 * time.Minute)),
|
||||||
Rules: []PrometheusRule{
|
Rules: []PrometheusRule{
|
||||||
{
|
{
|
||||||
Alert: "alert-1",
|
Alert: "alert-1",
|
||||||
Expr: "cpu_usage > 80",
|
Expr: "cpu_usage > 80",
|
||||||
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
|
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
|
||||||
|
KeepFiringFor: util.Pointer(prommodel.Duration(60 * time.Second)),
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
"severity": "critical",
|
"severity": "critical",
|
||||||
},
|
},
|
||||||
@@ -127,24 +128,6 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
errorMsg: "invalid target datasource type: non-prometheus-datasource, must be prometheus",
|
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",
|
name: "rule group with empty interval",
|
||||||
orgID: 1,
|
orgID: 1,
|
||||||
@@ -330,6 +313,12 @@ func TestPrometheusRulesToGrafana(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.Equal(t, expectedFor, grafanaRule.For, tc.name)
|
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))
|
expectedLabels := make(map[string]string, len(promRule.Labels)+len(tc.promGroup.Labels))
|
||||||
maps.Copy(expectedLabels, tc.promGroup.Labels)
|
maps.Copy(expectedLabels, tc.promGroup.Labels)
|
||||||
maps.Copy(expectedLabels, promRule.Labels)
|
maps.Copy(expectedLabels, promRule.Labels)
|
||||||
|
|||||||
@@ -33,12 +33,6 @@ func (g *PrometheusRuleGroup) Validate() error {
|
|||||||
return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset must be >= 0")
|
return ErrPrometheusRuleGroupValidationFailed.Errorf("query_offset must be >= 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rule := range g.Rules {
|
|
||||||
if err := rule.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +45,3 @@ type PrometheusRule struct {
|
|||||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||||
Record string `yaml:"record,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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) {
|
|||||||
QueryOffset: util.Pointer(prommodel.Duration(time.Duration(1) * time.Second)),
|
QueryOffset: util.Pointer(prommodel.Duration(time.Duration(1) * time.Second)),
|
||||||
Rules: []PrometheusRule{
|
Rules: []PrometheusRule{
|
||||||
{
|
{
|
||||||
Alert: "test_alert",
|
Alert: "test_alert",
|
||||||
Expr: "up == 0",
|
Expr: "up == 0",
|
||||||
|
KeepFiringFor: util.Pointer(prommodel.Duration(10)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -56,22 +57,6 @@ func TestPrometheusRuleGroup_Validate(t *testing.T) {
|
|||||||
expectError: true,
|
expectError: true,
|
||||||
errorMsg: "limit is not supported",
|
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 {
|
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) {
|
func TestPrometheusRulesFileYAML(t *testing.T) {
|
||||||
interval := prommodel.Duration(5 * time.Minute)
|
interval := prommodel.Duration(5 * time.Minute)
|
||||||
alertFor := prommodel.Duration(10 * time.Minute)
|
alertFor := prommodel.Duration(10 * time.Minute)
|
||||||
|
|||||||
Reference in New Issue
Block a user