Alerting: Fix file import/export of recording rules with target datasource uid (#115663)
Alerting: Fix export of recording rules with target datasource uid
This commit is contained in:
@@ -173,4 +173,49 @@ resource "grafana_rule_group" "rule_group_d3e8424bfbf66bc3" {
|
||||
from = "condition"
|
||||
}
|
||||
}
|
||||
rule {
|
||||
name = "recording rule with target"
|
||||
|
||||
data {
|
||||
ref_id = "query"
|
||||
|
||||
relative_time_range {
|
||||
from = 18000
|
||||
to = 10800
|
||||
}
|
||||
|
||||
datasource_uid = "000000002"
|
||||
model = "{\"expr\":\"rate(http_requests_total[5m])\",\"hide\":false,\"interval\":\"\",\"intervalMs\":1000,\"legendFormat\":\"\",\"maxDataPoints\":100,\"refId\":\"query\"}"
|
||||
}
|
||||
data {
|
||||
ref_id = "reduced"
|
||||
|
||||
relative_time_range {
|
||||
from = 18000
|
||||
to = 10800
|
||||
}
|
||||
|
||||
datasource_uid = "__expr__"
|
||||
model = "{\"expression\":\"query\",\"hide\":false,\"intervalMs\":1000,\"maxDataPoints\":100,\"reducer\":\"mean\",\"refId\":\"reduced\",\"type\":\"reduce\"}"
|
||||
}
|
||||
data {
|
||||
ref_id = "condition"
|
||||
|
||||
relative_time_range {
|
||||
from = 18000
|
||||
to = 10800
|
||||
}
|
||||
|
||||
datasource_uid = "__expr__"
|
||||
model = "{\"expression\":\"$reduced > 5\",\"hide\":false,\"intervalMs\":1000,\"maxDataPoints\":100,\"refId\":\"condition\",\"type\":\"math\"}"
|
||||
}
|
||||
|
||||
is_paused = false
|
||||
|
||||
record {
|
||||
metric = "http_requests_rate"
|
||||
from = "condition"
|
||||
target_datasource_uid = "000000003"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,67 @@
|
||||
"metric": "test_metric",
|
||||
"from": "condition"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "recording rule with target",
|
||||
"data": [
|
||||
{
|
||||
"refId": "query",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "000000002",
|
||||
"model": {
|
||||
"expr": "rate(http_requests_total[5m])",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"intervalMs": 1000,
|
||||
"legendFormat": "",
|
||||
"maxDataPoints": 100,
|
||||
"refId": "query"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "reduced",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "query",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"reducer": "mean",
|
||||
"refId": "reduced",
|
||||
"type": "reduce"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "condition",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "$reduced \u003e 5",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"refId": "condition",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"isPaused": false,
|
||||
"record": {
|
||||
"metric": "http_requests_rate",
|
||||
"from": "condition",
|
||||
"targetDatasourceUid": "000000003"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -187,3 +187,48 @@ groups:
|
||||
record:
|
||||
metric: test_metric
|
||||
from: condition
|
||||
- title: recording rule with target
|
||||
data:
|
||||
- refId: query
|
||||
relativeTimeRange:
|
||||
from: 18000
|
||||
to: 10800
|
||||
datasourceUid: "000000002"
|
||||
model:
|
||||
expr: rate(http_requests_total[5m])
|
||||
hide: false
|
||||
interval: ""
|
||||
intervalMs: 1000
|
||||
legendFormat: ""
|
||||
maxDataPoints: 100
|
||||
refId: query
|
||||
- refId: reduced
|
||||
relativeTimeRange:
|
||||
from: 18000
|
||||
to: 10800
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
expression: query
|
||||
hide: false
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 100
|
||||
reducer: mean
|
||||
refId: reduced
|
||||
type: reduce
|
||||
- refId: condition
|
||||
relativeTimeRange:
|
||||
from: 18000
|
||||
to: 10800
|
||||
datasourceUid: __expr__
|
||||
model:
|
||||
expression: $reduced > 5
|
||||
hide: false
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 100
|
||||
refId: condition
|
||||
type: math
|
||||
isPaused: false
|
||||
record:
|
||||
metric: http_requests_rate
|
||||
from: condition
|
||||
targetDatasourceUid: "000000003"
|
||||
|
||||
@@ -240,6 +240,71 @@
|
||||
"from": "condition"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"grafana_alert": {
|
||||
"title": "recording rule with target",
|
||||
"data": [
|
||||
{
|
||||
"refId": "query",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "000000002",
|
||||
"model": {
|
||||
"expr": "rate(http_requests_total[5m])",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"intervalMs": 1000,
|
||||
"legendFormat": "",
|
||||
"maxDataPoints": 100,
|
||||
"refId": "query"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "reduced",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "query",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"reducer": "mean",
|
||||
"refId": "reduced",
|
||||
"type": "reduce"
|
||||
}
|
||||
},
|
||||
{
|
||||
"refId": "condition",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 18000,
|
||||
"to": 10800
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "$reduced > 5",
|
||||
"hide": false,
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 100,
|
||||
"refId": "condition",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"record": {
|
||||
"metric": "http_requests_rate",
|
||||
"from": "condition",
|
||||
"target_datasource_uid": "000000003"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
testFileDasboardTypoSupport = "./testdata/alert_rules/dasboard-typo-support"
|
||||
testFileMultipleRules = "./testdata/alert_rules/multiple-rules"
|
||||
testFileMultipleFiles = "./testdata/alert_rules/multiple-files"
|
||||
testFileRecordingRules = "./testdata/alert_rules/recording-rules"
|
||||
testFileCorrectProperties_cp = "./testdata/contact_points/correct-properties"
|
||||
testFileCorrectPropertiesWithOrg_cp = "./testdata/contact_points/correct-properties-with-org"
|
||||
testFileEmptyUID = "./testdata/contact_points/empty-uid"
|
||||
@@ -188,4 +189,43 @@ func TestConfigReader(t *testing.T) {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("recording rules should parse correctly", func(t *testing.T) {
|
||||
ruleFiles, err := configReader.readConfig(ctx, testFileRecordingRules)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, ruleFiles, 2)
|
||||
|
||||
findRule := func(title string) *AlertingFile {
|
||||
for _, rf := range ruleFiles {
|
||||
if rf.Groups[0].Title == title {
|
||||
return rf
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
ruleWithTarget := findRule("recording_rules_group")
|
||||
require.NotNil(t, ruleWithTarget)
|
||||
|
||||
require.Len(t, ruleWithTarget.Groups, 1)
|
||||
require.Len(t, ruleWithTarget.Groups[0].Rules, 1)
|
||||
|
||||
ruleWith := ruleWithTarget.Groups[0].Rules[0]
|
||||
require.NotNil(t, ruleWith.Record)
|
||||
require.Equal(t, "my_recorded_metric", ruleWith.Record.Metric)
|
||||
require.Equal(t, "A", ruleWith.Record.From)
|
||||
require.Equal(t, "mimir-uid", ruleWith.Record.TargetDatasourceUID)
|
||||
|
||||
ruleWithoutTarget := findRule("recording_rules_group_no_target")
|
||||
require.NotNil(t, ruleWithoutTarget)
|
||||
|
||||
require.Len(t, ruleWithoutTarget.Groups, 1)
|
||||
require.Len(t, ruleWithoutTarget.Groups[0].Rules, 1)
|
||||
|
||||
ruleWithout := ruleWithoutTarget.Groups[0].Rules[0]
|
||||
require.NotNil(t, ruleWithout.Record)
|
||||
require.Equal(t, "http_requests_rate", ruleWithout.Record.Metric)
|
||||
require.Equal(t, "A", ruleWithout.Record.From)
|
||||
require.Equal(t, "", ruleWithout.Record.TargetDatasourceUID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -303,13 +303,15 @@ func (nsV1 *NotificationSettingsV1) mapToModel() (models.NotificationSettings, e
|
||||
}
|
||||
|
||||
type RecordV1 struct {
|
||||
Metric values.StringValue `json:"metric" yaml:"metric"`
|
||||
From values.StringValue `json:"from" yaml:"from"`
|
||||
Metric values.StringValue `json:"metric" yaml:"metric"`
|
||||
From values.StringValue `json:"from" yaml:"from"`
|
||||
TargetDatasourceUID values.StringValue `json:"targetDatasourceUid" yaml:"targetDatasourceUid"`
|
||||
}
|
||||
|
||||
func (record *RecordV1) mapToModel() (models.Record, error) {
|
||||
return models.Record{
|
||||
Metric: record.Metric.Value(),
|
||||
From: record.From.Value(),
|
||||
Metric: record.Metric.Value(),
|
||||
From: record.From.Value(),
|
||||
TargetDatasourceUID: record.TargetDatasourceUID.Value(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -208,6 +208,15 @@ func TestRecordingRules(t *testing.T) {
|
||||
_, err := rule.mapToModel(1)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("a valid rule with empty targetDatasourceUid should not error", func(t *testing.T) {
|
||||
rule := validRecordingRuleV1(t)
|
||||
rule.Record.TargetDatasourceUID = stringToStringValue("")
|
||||
model, err := rule.mapToModel(1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, model.Record)
|
||||
require.Equal(t, "", model.Record.TargetDatasourceUID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNotificationsSettingsV1MapToModel(t *testing.T) {
|
||||
@@ -307,80 +316,43 @@ func TestNotificationsSettingsV1MapToModel(t *testing.T) {
|
||||
|
||||
func validRuleGroupV1(t *testing.T) AlertRuleGroupV1 {
|
||||
t.Helper()
|
||||
var (
|
||||
orgID values.Int64Value
|
||||
name values.StringValue
|
||||
folder values.StringValue
|
||||
interval values.StringValue
|
||||
)
|
||||
|
||||
var orgID values.Int64Value
|
||||
err := yaml.Unmarshal([]byte("1"), &orgID)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("Test"), &name)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("Test"), &folder)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("10s"), &interval)
|
||||
require.NoError(t, err)
|
||||
|
||||
return AlertRuleGroupV1{
|
||||
OrgID: orgID,
|
||||
Name: name,
|
||||
Folder: folder,
|
||||
Interval: interval,
|
||||
Name: stringToStringValue("Test"),
|
||||
Folder: stringToStringValue("Test"),
|
||||
Interval: stringToStringValue("10s"),
|
||||
Rules: []AlertRuleV1{},
|
||||
}
|
||||
}
|
||||
|
||||
func validRuleV1(t *testing.T) AlertRuleV1 {
|
||||
t.Helper()
|
||||
var (
|
||||
title values.StringValue
|
||||
uid values.StringValue
|
||||
forDuration values.StringValue
|
||||
condition values.StringValue
|
||||
)
|
||||
err := yaml.Unmarshal([]byte("test"), &title)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("test_uid"), &uid)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("10s"), &forDuration)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("A"), &condition)
|
||||
require.NoError(t, err)
|
||||
|
||||
return AlertRuleV1{
|
||||
Title: title,
|
||||
UID: uid,
|
||||
For: forDuration,
|
||||
Condition: condition,
|
||||
Title: stringToStringValue("test"),
|
||||
UID: stringToStringValue("test_uid"),
|
||||
For: stringToStringValue("10s"),
|
||||
Condition: stringToStringValue("A"),
|
||||
Data: []QueryV1{{}},
|
||||
}
|
||||
}
|
||||
|
||||
func validRecordingRuleV1(t *testing.T) AlertRuleV1 {
|
||||
t.Helper()
|
||||
var (
|
||||
title values.StringValue
|
||||
uid values.StringValue
|
||||
forDuration values.StringValue
|
||||
metric values.StringValue
|
||||
from values.StringValue
|
||||
)
|
||||
err := yaml.Unmarshal([]byte("test"), &title)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("test_uid"), &uid)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("10s"), &forDuration)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("test_metric"), &metric)
|
||||
require.NoError(t, err)
|
||||
err = yaml.Unmarshal([]byte("A"), &from)
|
||||
require.NoError(t, err)
|
||||
|
||||
return AlertRuleV1{
|
||||
Title: title,
|
||||
UID: uid,
|
||||
For: forDuration,
|
||||
Title: stringToStringValue("test"),
|
||||
UID: stringToStringValue("test_uid"),
|
||||
For: stringToStringValue("10s"),
|
||||
Record: &RecordV1{
|
||||
Metric: metric,
|
||||
From: from,
|
||||
Metric: stringToStringValue("test_metric"),
|
||||
From: stringToStringValue("A"),
|
||||
TargetDatasourceUID: stringToStringValue("test_target_datasource"),
|
||||
},
|
||||
Data: []QueryV1{{}},
|
||||
}
|
||||
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
apiVersion: 1
|
||||
groups:
|
||||
- name: recording_rules_group
|
||||
folder: my_folder
|
||||
interval: 1m
|
||||
rules:
|
||||
- uid: recording_rule_with_target
|
||||
title: my_recording_rule_with_target
|
||||
condition: A
|
||||
data:
|
||||
- refId: A
|
||||
queryType: ''
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: prometheus-uid
|
||||
model:
|
||||
expr: up{instance="localhost:9090"}
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: A
|
||||
record:
|
||||
metric: my_recorded_metric
|
||||
from: A
|
||||
targetDatasourceUid: mimir-uid
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
apiVersion: 1
|
||||
groups:
|
||||
- name: recording_rules_group_no_target
|
||||
folder: my_folder
|
||||
interval: 1m
|
||||
rules:
|
||||
- uid: recording_rule_without_target
|
||||
title: my_recording_rule_without_target
|
||||
condition: A
|
||||
data:
|
||||
- refId: A
|
||||
queryType: ''
|
||||
relativeTimeRange:
|
||||
from: 600
|
||||
to: 0
|
||||
datasourceUid: prometheus-uid
|
||||
model:
|
||||
expr: rate(http_requests_total[5m])
|
||||
instant: true
|
||||
intervalMs: 1000
|
||||
maxDataPoints: 43200
|
||||
refId: A
|
||||
record:
|
||||
metric: http_requests_rate
|
||||
from: A
|
||||
@@ -72,6 +72,33 @@
|
||||
},
|
||||
"isPaused": false,
|
||||
"missing_series_evals_to_resolve": 2
|
||||
},
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "RecordingRule1",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "1 + 1",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"refId": "A",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"isPaused": false,
|
||||
"record": {
|
||||
"metric": "test_metric",
|
||||
"from": "A",
|
||||
"targetDatasourceUid": "test-datasource-uid"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,6 +97,44 @@
|
||||
},
|
||||
"missing_series_evals_to_resolve": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"expr": "",
|
||||
"for": "0s",
|
||||
"keep_firing_for": "0s",
|
||||
"grafana_alert": {
|
||||
"title": "RecordingRule1",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "1 + 1",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"rule_group": "Group1",
|
||||
"is_paused": false,
|
||||
"record": {
|
||||
"metric": "test_metric",
|
||||
"from": "A",
|
||||
"target_datasource_uid": "test-datasource-uid"
|
||||
},
|
||||
"metadata": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -53,6 +53,26 @@
|
||||
"exec_err_state": "Alerting",
|
||||
"missing_series_evals_to_resolve": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"grafana_alert": {
|
||||
"title": "RecordingRule1",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "1 + 1",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"record": {
|
||||
"metric": "test_metric",
|
||||
"from": "A",
|
||||
"target_datasource_uid": "test-datasource-uid"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRul
|
||||
ExecErrState: gettable.ExecErrState,
|
||||
IsPaused: &gettable.IsPaused,
|
||||
NotificationSettings: gettable.NotificationSettings,
|
||||
Record: gettable.Record,
|
||||
Metadata: gettable.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user