mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
Alerting: Fix updating alert rule properties with missing/zero values (#35512)
* Fix deleting labels and annotations * Add test * Keep no data and error start if not provided * Allow setting interval and for to zero during rule updates
This commit is contained in:
parent
89d8dedece
commit
e5a5b8e3fe
@ -249,26 +249,18 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
||||
r.New.Data = r.Existing.Data
|
||||
}
|
||||
|
||||
if r.New.IntervalSeconds == 0 {
|
||||
r.New.IntervalSeconds = r.Existing.IntervalSeconds
|
||||
}
|
||||
|
||||
r.New.ID = r.Existing.ID
|
||||
r.New.OrgID = r.Existing.OrgID
|
||||
r.New.NamespaceUID = r.Existing.NamespaceUID
|
||||
r.New.RuleGroup = r.Existing.RuleGroup
|
||||
r.New.Version = r.Existing.Version + 1
|
||||
|
||||
if r.New.For == 0 {
|
||||
r.New.For = r.Existing.For
|
||||
if r.New.ExecErrState == "" {
|
||||
r.New.ExecErrState = r.Existing.ExecErrState
|
||||
}
|
||||
|
||||
if len(r.New.Annotations) == 0 {
|
||||
r.New.Annotations = r.Existing.Annotations
|
||||
}
|
||||
|
||||
if len(r.New.Labels) == 0 {
|
||||
r.New.Labels = r.Existing.Labels
|
||||
if r.New.NoDataState == "" {
|
||||
r.New.NoDataState = r.Existing.NoDataState
|
||||
}
|
||||
|
||||
if err := st.validateAlertRule(r.New); err != nil {
|
||||
@ -280,7 +272,7 @@ func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
||||
}
|
||||
|
||||
// no way to update multiple rules at once
|
||||
if _, err := sess.ID(r.Existing.ID).Update(r.New); err != nil {
|
||||
if _, err := sess.ID(r.Existing.ID).AllCols().Update(r.New); err != nil {
|
||||
return fmt.Errorf("failed to update rule %s: %w", r.New.Title, err)
|
||||
}
|
||||
|
||||
|
@ -1258,7 +1258,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
|
||||
// update the first rule and completely remove the other
|
||||
{
|
||||
interval, err := model.ParseDuration("30s")
|
||||
forValue, err := model.ParseDuration("30s")
|
||||
require.NoError(t, err)
|
||||
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
@ -1266,14 +1266,16 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: interval,
|
||||
For: forValue,
|
||||
Labels: map[string]string{
|
||||
"label1": "val42",
|
||||
"foo": "bar",
|
||||
// delete foo label
|
||||
"label1": "val1", // update label value
|
||||
"label2": "val2", // new label
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"annotation1": "val42",
|
||||
"foo": "bar",
|
||||
// delete foo annotation
|
||||
"annotation1": "val1", // update annotation value
|
||||
"annotation2": "val2", // new annotation
|
||||
},
|
||||
},
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
@ -1299,6 +1301,7 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Interval: interval,
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
@ -1347,14 +1350,14 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
"rules":[
|
||||
{
|
||||
"annotations": {
|
||||
"annotation1": "val42",
|
||||
"foo": "bar"
|
||||
"annotation1": "val1",
|
||||
"annotation2": "val2"
|
||||
},
|
||||
"expr":"",
|
||||
"for": "30s",
|
||||
"labels": {
|
||||
"foo": "bar",
|
||||
"label1": "val42"
|
||||
"label1": "val1",
|
||||
"label2": "val2"
|
||||
},
|
||||
"grafana_alert":{
|
||||
"id":1,
|
||||
@ -1395,6 +1398,229 @@ func TestAlertRuleCRUD(t *testing.T) {
|
||||
}`, body)
|
||||
}
|
||||
|
||||
// update the rule; delete labels and annotations
|
||||
{
|
||||
forValue, err := model.ParseDuration("30s")
|
||||
require.NoError(t, err)
|
||||
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
Name: "arulegroup",
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: forValue,
|
||||
},
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
UID: ruleUID, // Including the UID in the payload makes the endpoint update the existing rule.
|
||||
Title: "AlwaysNormal",
|
||||
Condition: "A",
|
||||
Data: []ngmodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: ngmodels.RelativeTimeRange{
|
||||
From: ngmodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: ngmodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: "-100",
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 < 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
},
|
||||
},
|
||||
},
|
||||
Interval: interval,
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err = enc.Encode(&rules)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Post(u, "application/json", &buf)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
||||
|
||||
// let's make sure that rule definitions are updated correctly.
|
||||
u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err = http.Get(u)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
|
||||
body, m := rulesNamespaceWithoutVariableValues(t, b)
|
||||
returnedUIDs, ok := m["default,arulegroup"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, len(returnedUIDs))
|
||||
assert.Equal(t, ruleUID, returnedUIDs[0])
|
||||
assert.JSONEq(t, `
|
||||
{
|
||||
"default":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"expr":"",
|
||||
"for": "30s",
|
||||
"grafana_alert":{
|
||||
"id":1,
|
||||
"orgId":1,
|
||||
"title":"AlwaysNormal",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"-100",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003C 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"intervalSeconds":60,
|
||||
"version":3,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"namespace_id":1,
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"Alerting",
|
||||
"exec_err_state":"Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, body)
|
||||
}
|
||||
|
||||
// update the rule; keep title, condition, no data state, error state, queries and expressions if not provided
|
||||
{
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
Name: "arulegroup",
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
UID: ruleUID, // Including the UID in the payload makes the endpoint update the existing rule.
|
||||
},
|
||||
},
|
||||
},
|
||||
Interval: interval,
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err = enc.Encode(&rules)
|
||||
require.NoError(t, err)
|
||||
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err := http.Post(u, "application/json", &buf)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b))
|
||||
|
||||
// let's make sure that rule definitions are updated correctly.
|
||||
u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
// nolint:gosec
|
||||
resp, err = http.Get(u)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
|
||||
body, m := rulesNamespaceWithoutVariableValues(t, b)
|
||||
returnedUIDs, ok := m["default,arulegroup"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 1, len(returnedUIDs))
|
||||
assert.Equal(t, ruleUID, returnedUIDs[0])
|
||||
assert.JSONEq(t, `
|
||||
{
|
||||
"default":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"expr":"",
|
||||
"grafana_alert":{
|
||||
"id":1,
|
||||
"orgId":1,
|
||||
"title":"AlwaysNormal",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"-100",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003C 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"intervalSeconds":60,
|
||||
"version":4,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"namespace_id":1,
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"Alerting",
|
||||
"exec_err_state":"Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, body)
|
||||
}
|
||||
|
||||
client := &http.Client{}
|
||||
// Finally, make sure we can delete it.
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user