mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Allow specifying uid for new rules added to groups (#99858)
When modifying rule groups the `uid` can be specified but only if the rule already existed in the DB. If the rule is new the update would be rejected. This updates the RuleGroup provisioning apis to allow specifying the `uid` when creating/updating rule groups. Additionally, the RuleGroupIdx was not being updated when rules were reordered in the group. Context: https://github.com/grafana/terraform-provider-grafana/pull/1971#issuecomment-2599223897 Relates to: https://github.com/grafana/terraform-provider-grafana/issues/1928 Fixes: #98283
This commit is contained in:
parent
55e7c4ae6d
commit
7dee4d1808
@ -693,6 +693,7 @@ func TestCheckMuteTimes(t *testing.T) {
|
||||
}
|
||||
|
||||
func gettableMuteIntervals(t *testing.T, muteTimeIntervals []amConfig.MuteTimeInterval, provenances map[string]definitions.Provenance) definitions.GettableUserConfig {
|
||||
t.Helper()
|
||||
return definitions.GettableUserConfig{
|
||||
AlertmanagerConfig: definitions.GettableApiAlertingConfig{
|
||||
MuteTimeProvenances: provenances,
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/cmputil"
|
||||
)
|
||||
|
||||
@ -571,6 +572,9 @@ func (alertRule *AlertRule) PreSave(timeNow func() time.Time, userUID *UserUID)
|
||||
|
||||
// ValidateAlertRule validates various alert rule fields.
|
||||
func (alertRule *AlertRule) ValidateAlertRule(cfg setting.UnifiedAlertingSettings) error {
|
||||
if err := util.ValidateUID(alertRule.UID); err != nil {
|
||||
return errors.Join(ErrAlertRuleFailedValidation, fmt.Errorf("cannot create rule with UID '%s': %w", alertRule.UID, err))
|
||||
}
|
||||
if len(alertRule.Data) == 0 {
|
||||
return fmt.Errorf("%w: no queries or expressions are found", ErrAlertRuleFailedValidation)
|
||||
}
|
||||
|
@ -368,6 +368,16 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user iden
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rule := range group.Rules {
|
||||
if rule.UID == "" {
|
||||
// if empty the UID will be generated before save
|
||||
continue
|
||||
}
|
||||
if err := util.ValidateUID(rule.UID); err != nil {
|
||||
return fmt.Errorf("%w: cannot create rule with UID %q: %w", models.ErrAlertRuleFailedValidation, rule.UID, err)
|
||||
}
|
||||
}
|
||||
|
||||
delta, err := service.calcDelta(ctx, user, group)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -575,6 +585,10 @@ func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, user ident
|
||||
// No changes to the rule.
|
||||
return rule, nil
|
||||
}
|
||||
// new rules not allowed in update for a single rule
|
||||
if len(delta.New) > 0 {
|
||||
return models.AlertRule{}, fmt.Errorf("failed to update rule with UID %s because %w", rule.UID, models.ErrAlertRuleNotFound)
|
||||
}
|
||||
for _, d := range delta.Update {
|
||||
if d.Existing.GetKey() == rule.GetKey() {
|
||||
storedRule = d.Existing
|
||||
@ -817,6 +831,7 @@ func syncGroupRuleFields(group *models.AlertRuleGroup, orgID int64) *models.Aler
|
||||
group.Rules[i].RuleGroup = group.Title
|
||||
group.Rules[i].NamespaceUID = group.FolderUID
|
||||
group.Rules[i].OrgID = orgID
|
||||
group.Rules[i].RuleGroupIndex = i
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
@ -113,10 +113,9 @@ func calculateChanges(ctx context.Context, ruleReader RuleReader, groupKey model
|
||||
}
|
||||
loadedRulesByUID[rule.UID] = rule
|
||||
}
|
||||
if existing == nil {
|
||||
return nil, fmt.Errorf("failed to update rule with UID %s because %w", r.UID, models.ErrAlertRuleNotFound)
|
||||
if existing != nil {
|
||||
affectedGroups[existing.GetGroupKey()] = ruleList
|
||||
}
|
||||
affectedGroups[existing.GetGroupKey()] = ruleList
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,18 +125,14 @@ func calculateChanges(ctx context.Context, ruleReader RuleReader, groupKey model
|
||||
}
|
||||
|
||||
models.PatchPartialAlertRule(existing, r)
|
||||
|
||||
diff := existing.Diff(&r.AlertRule, AlertRuleFieldsToIgnoreInDiff[:]...)
|
||||
if len(diff) == 0 {
|
||||
continue
|
||||
if len(diff) > 0 {
|
||||
toUpdate = append(toUpdate, RuleDelta{
|
||||
Existing: existing,
|
||||
New: &r.AlertRule,
|
||||
Diff: diff,
|
||||
})
|
||||
}
|
||||
|
||||
toUpdate = append(toUpdate, RuleDelta{
|
||||
Existing: existing,
|
||||
New: &r.AlertRule,
|
||||
Diff: diff,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
toDelete := make([]*models.AlertRule, 0, len(existingGroupRulesUIDs))
|
||||
|
@ -240,14 +240,19 @@ func TestCalculateChanges(t *testing.T) {
|
||||
require.Len(t, changes.AffectedGroups[sourceGroupKey], len(inDatabase))
|
||||
})
|
||||
|
||||
t.Run("should fail when submitted rule has UID that does not exist in db", func(t *testing.T) {
|
||||
t.Run("should add rule when submitted rule has UID that does not exist in db", func(t *testing.T) {
|
||||
fakeStore := fakes.NewRuleStore(t)
|
||||
groupKey := models.GenerateGroupKey(orgId)
|
||||
submitted := gen.With(gen.WithOrgID(orgId), simulateSubmitted).Generate()
|
||||
require.NotEqual(t, "", submitted.UID)
|
||||
|
||||
_, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
|
||||
require.Error(t, err)
|
||||
diff, err := CalculateChanges(context.Background(), fakeStore, groupKey, []*models.AlertRuleWithOptionals{{AlertRule: submitted}})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, diff.New, 1)
|
||||
require.Empty(t, diff.Delete)
|
||||
require.Empty(t, diff.Update)
|
||||
require.Equal(t, submitted, *diff.New[0])
|
||||
})
|
||||
|
||||
t.Run("should fail if cannot fetch current rules in the group", func(t *testing.T) {
|
||||
|
@ -508,7 +508,34 @@ func TestIntegrationProvisioning(t *testing.T) {
|
||||
|
||||
t.Run("when provisioning alert rules", func(t *testing.T) {
|
||||
url := fmt.Sprintf("http://%s/api/v1/provisioning/alert-rules", grafanaListedAddr)
|
||||
body := `{"orgID":1,"folderUID":"default","ruleGroup":"Test Group","title":"Provisioned","condition":"A","data":[{"refId":"A","queryType":"","relativeTimeRange":{"from":600,"to":0},"datasourceUid":"f558c85f-66ad-4fd1-b31d-7979e6c93db4","model":{"editorMode":"code","exemplar":false,"expr":"sum(rate(low_card[5m])) \u003e 0","format":"time_series","instant":true,"intervalMs":1000,"legendFormat":"__auto","maxDataPoints":43200,"range":false,"refId":"A"}}],"noDataState":"NoData","execErrState":"Error","for":"0s"}`
|
||||
body := `
|
||||
{
|
||||
"orgID":1,
|
||||
"folderUID":"default",
|
||||
"ruleGroup":"Test Group",
|
||||
"title":"Provisioned",
|
||||
"condition":"A",
|
||||
"data":[{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{"from":600,"to":0},
|
||||
"datasourceUid":"f558c85f-66ad-4fd1-b31d-7979e6c93db4",
|
||||
"model":{
|
||||
"editorMode":"code",
|
||||
"exemplar":false,
|
||||
"expr":"sum(rate(low_card[5m])) \u003e 0",
|
||||
"format":"time_series",
|
||||
"instant":true,
|
||||
"intervalMs":1000,
|
||||
"legendFormat":"__auto",
|
||||
"maxDataPoints":43200,
|
||||
"range":false,"refId":"A"
|
||||
}
|
||||
}],
|
||||
"noDataState":"NoData",
|
||||
"execErrState":"Error",
|
||||
"for":"0s"
|
||||
}`
|
||||
req := createTestRequest("POST", url, "admin", body)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
@ -535,6 +562,149 @@ func TestIntegrationProvisioning(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationProvisioningRules(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||
|
||||
// Create a users to make authenticated requests
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleViewer),
|
||||
Password: "viewer",
|
||||
Login: "viewer",
|
||||
})
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "editor",
|
||||
Login: "editor",
|
||||
})
|
||||
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "admin",
|
||||
Login: "admin",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "editor", "editor")
|
||||
// Create the namespace we'll save our alerts to.
|
||||
namespaceUID := "default"
|
||||
apiClient.CreateFolder(t, namespaceUID, namespaceUID)
|
||||
|
||||
t.Run("when provisioning alert rules", func(t *testing.T) {
|
||||
originalRuleGroup := definitions.AlertRuleGroup{
|
||||
Title: "TestGroup",
|
||||
Interval: 60,
|
||||
FolderUID: "default",
|
||||
Rules: []definitions.ProvisionedAlertRule{
|
||||
{
|
||||
UID: "rule1",
|
||||
Title: "Rule1",
|
||||
OrgID: 1,
|
||||
RuleGroup: "TestGroup",
|
||||
Condition: "A",
|
||||
NoDataState: definitions.Alerting,
|
||||
ExecErrState: definitions.AlertingErrState,
|
||||
For: model.Duration(time.Duration(60) * time.Second),
|
||||
Data: []definitions.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: definitions.RelativeTimeRange{
|
||||
From: definitions.Duration(time.Duration(5) * time.Hour),
|
||||
To: definitions.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UID: "rule2",
|
||||
Title: "Rule2",
|
||||
OrgID: 1,
|
||||
RuleGroup: "TestGroup",
|
||||
Condition: "A",
|
||||
NoDataState: definitions.Alerting,
|
||||
ExecErrState: definitions.AlertingErrState,
|
||||
For: model.Duration(time.Duration(60) * time.Second),
|
||||
Data: []definitions.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: definitions.RelativeTimeRange{
|
||||
From: definitions.Duration(time.Duration(5) * time.Hour),
|
||||
To: definitions.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UID: "rule3",
|
||||
Title: "Rule3",
|
||||
OrgID: 1,
|
||||
RuleGroup: "TestGroup",
|
||||
Condition: "A",
|
||||
NoDataState: definitions.Alerting,
|
||||
ExecErrState: definitions.AlertingErrState,
|
||||
For: model.Duration(time.Duration(60) * time.Second),
|
||||
Data: []definitions.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: definitions.RelativeTimeRange{
|
||||
From: definitions.Duration(time.Duration(5) * time.Hour),
|
||||
To: definitions.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, originalRuleGroup)
|
||||
t.Run("should create a new rule group with UIDs specified", func(t *testing.T) {
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Equal(t, originalRuleGroup, result)
|
||||
})
|
||||
|
||||
t.Run("should remove a rule when updating group with a rule removed", func(t *testing.T) {
|
||||
existingRuleGroup, status, raw := apiClient.GetRuleGroupProvisioning(t, "default", "TestGroup")
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Len(t, existingRuleGroup.Rules, 3)
|
||||
|
||||
updatedRuleGroup := existingRuleGroup
|
||||
updatedRuleGroup.Rules = updatedRuleGroup.Rules[:2]
|
||||
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, updatedRuleGroup)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Equal(t, updatedRuleGroup, result)
|
||||
|
||||
// Check that the rule was removed
|
||||
rules, status, raw := apiClient.GetRuleGroupProvisioning(t, existingRuleGroup.FolderUID, existingRuleGroup.Title)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Len(t, rules.Rules, 2)
|
||||
})
|
||||
|
||||
t.Run("should recreate a rule when updating group with the rule added back", func(t *testing.T) {
|
||||
result, status, raw := apiClient.CreateOrUpdateRuleGroupProvisioning(t, originalRuleGroup)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Equal(t, originalRuleGroup, result)
|
||||
require.Len(t, result.Rules, 3)
|
||||
|
||||
// Check that the rule was re-added
|
||||
rules, status, raw := apiClient.GetRuleGroupProvisioning(t, originalRuleGroup.FolderUID, originalRuleGroup.Title)
|
||||
requireStatusCode(t, http.StatusOK, status, raw)
|
||||
require.Len(t, rules.Rules, 3)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestMuteTimings(t *testing.T) {
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
|
@ -3098,6 +3098,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
assert.Empty(t, resp.Deleted)
|
||||
}
|
||||
|
||||
createdRuleUIDs := make(map[string]string)
|
||||
// With the rules created, let's make sure that rule definition is stored correctly.
|
||||
{
|
||||
u := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr)
|
||||
@ -3228,9 +3229,11 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
]
|
||||
}`
|
||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||
createdRuleUIDs["AlwaysFiring"] = generatedUIDs[0]
|
||||
createdRuleUIDs["AlwaysFiringButSilenced"] = generatedUIDs[1]
|
||||
}
|
||||
|
||||
// try to update by pass an invalid UID
|
||||
// validate that a rulegroup with a new rule with a user specified UID can be created while others updated
|
||||
{
|
||||
interval, err := model.ParseDuration("30s")
|
||||
require.NoError(t, err)
|
||||
@ -3238,6 +3241,57 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
Name: "arulegroup",
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: &interval,
|
||||
Labels: map[string]string{"label1": "val1"},
|
||||
Annotations: map[string]string{"annotation1": "val1"},
|
||||
},
|
||||
// this rule does not explicitly set no data and error states
|
||||
// therefore it should get the default values
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiring",
|
||||
UID: createdRuleUIDs["AlwaysFiring"],
|
||||
Condition: "A",
|
||||
Data: []apimodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiringButSilenced",
|
||||
UID: createdRuleUIDs["AlwaysFiringButSilenced"],
|
||||
Condition: "A",
|
||||
Data: []apimodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
},
|
||||
},
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: &interval,
|
||||
@ -3276,31 +3330,83 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
_, status, body := apiClient.PostRulesGroupWithStatus(t, "default", &rules)
|
||||
assert.Equal(t, http.StatusNotFound, status)
|
||||
var res map[string]any
|
||||
assert.NoError(t, json.Unmarshal([]byte(body), &res))
|
||||
require.Equal(t, "failed to update rule group: failed to update rule with UID unknown because could not find alert rule", res["message"])
|
||||
response, status, _ := apiClient.PostRulesGroupWithStatus(t, "default", &rules)
|
||||
assert.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
// let's make sure that rule definitions are not affected by the failed POST request.
|
||||
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 := io.ReadAll(resp.Body)
|
||||
require.Len(t, response.Created, 1)
|
||||
require.Len(t, response.Updated, 2)
|
||||
require.Len(t, response.Deleted, 0)
|
||||
}
|
||||
|
||||
// remove the added rule and set the interval back to 1m
|
||||
{
|
||||
interval, err := model.ParseDuration("1m")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, resp.StatusCode, 202)
|
||||
rules := apimodels.PostableRuleGroupConfig{
|
||||
Name: "arulegroup",
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: &interval,
|
||||
Labels: map[string]string{"label1": "val1"},
|
||||
Annotations: map[string]string{"annotation1": "val1"},
|
||||
},
|
||||
// this rule does not explicitly set no data and error states
|
||||
// therefore it should get the default values
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiring",
|
||||
UID: createdRuleUIDs["AlwaysFiring"],
|
||||
Condition: "A",
|
||||
Data: []apimodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
Title: "AlwaysFiringButSilenced",
|
||||
UID: createdRuleUIDs["AlwaysFiringButSilenced"],
|
||||
Condition: "A",
|
||||
Data: []apimodels.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
RelativeTimeRange: apimodels.RelativeTimeRange{
|
||||
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
||||
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
||||
},
|
||||
DatasourceUID: expr.DatasourceUID,
|
||||
Model: json.RawMessage(`{
|
||||
"type": "math",
|
||||
"expression": "2 + 3 > 1"
|
||||
}`),
|
||||
},
|
||||
},
|
||||
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
||||
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
||||
},
|
||||
},
|
||||
},
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
body, m := rulesNamespaceWithoutVariableValues(t, b)
|
||||
returnedUIDs, ok := m["default,arulegroup"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 2, len(returnedUIDs))
|
||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||
response, status, _ := apiClient.PostRulesGroupWithStatus(t, "default", &rules)
|
||||
assert.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
require.Len(t, response.Created, 0)
|
||||
require.Len(t, response.Updated, 2)
|
||||
require.Len(t, response.Deleted, 1)
|
||||
}
|
||||
|
||||
// try to update by pass two rules with conflicting UIDs
|
||||
@ -3406,6 +3512,111 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
returnedUIDs, ok := m["default,arulegroup"]
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 2, len(returnedUIDs))
|
||||
expectedGetNamespaceResponseBody = `
|
||||
{
|
||||
"default":[
|
||||
{
|
||||
"name":"arulegroup",
|
||||
"interval":"1m",
|
||||
"rules":[
|
||||
{
|
||||
"annotations": {
|
||||
"annotation1": "val1"
|
||||
},
|
||||
"expr":"",
|
||||
"for": "1m",
|
||||
"labels": {
|
||||
"label1": "val1"
|
||||
},
|
||||
"grafana_alert":{
|
||||
"title":"AlwaysFiring",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"__expr__",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003e 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"updated_by": {
|
||||
"uid": "uid",
|
||||
"name": "grafana"
|
||||
},
|
||||
"intervalSeconds":60,
|
||||
"is_paused": false,
|
||||
"version":3,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"NoData",
|
||||
"exec_err_state":"Alerting",
|
||||
"metadata": {
|
||||
"editor_settings": {
|
||||
"simplified_query_and_expressions_section": false,
|
||||
"simplified_notifications_section": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"expr":"",
|
||||
"for": "0s",
|
||||
"grafana_alert":{
|
||||
"title":"AlwaysFiringButSilenced",
|
||||
"condition":"A",
|
||||
"data":[
|
||||
{
|
||||
"refId":"A",
|
||||
"queryType":"",
|
||||
"relativeTimeRange":{
|
||||
"from":18000,
|
||||
"to":10800
|
||||
},
|
||||
"datasourceUid":"__expr__",
|
||||
"model":{
|
||||
"expression":"2 + 3 \u003e 1",
|
||||
"intervalMs":1000,
|
||||
"maxDataPoints":43200,
|
||||
"type":"math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated":"2021-02-21T01:10:30Z",
|
||||
"updated_by": {
|
||||
"uid": "uid",
|
||||
"name": "grafana"
|
||||
},
|
||||
"intervalSeconds":60,
|
||||
"is_paused": false,
|
||||
"version":3,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"rule_group":"arulegroup",
|
||||
"no_data_state":"Alerting",
|
||||
"exec_err_state":"Alerting",
|
||||
"metadata": {
|
||||
"editor_settings": {
|
||||
"simplified_query_and_expressions_section": false,
|
||||
"simplified_notifications_section": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
assert.JSONEq(t, expectedGetNamespaceResponseBody, body)
|
||||
}
|
||||
|
||||
@ -3525,7 +3736,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
},
|
||||
"intervalSeconds":60,
|
||||
"is_paused": false,
|
||||
"version":2,
|
||||
"version":4,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"rule_group":"arulegroup",
|
||||
@ -3642,7 +3853,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
},
|
||||
"intervalSeconds":60,
|
||||
"is_paused":false,
|
||||
"version":3,
|
||||
"version":5,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"rule_group":"arulegroup",
|
||||
@ -3738,7 +3949,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
},
|
||||
"intervalSeconds":60,
|
||||
"is_paused":false,
|
||||
"version":3,
|
||||
"version":5,
|
||||
"uid":"uid",
|
||||
"namespace_uid":"nsuid",
|
||||
"rule_group":"arulegroup",
|
||||
|
@ -687,6 +687,30 @@ func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRu
|
||||
return resp.StatusCode, string(b)
|
||||
}
|
||||
|
||||
func (a apiClient) GetRuleGroupProvisioning(t *testing.T, folderUID string, groupName string) (apimodels.AlertRuleGroup, int, string) {
|
||||
t.Helper()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, folderUID, groupName), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
return sendRequest[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) CreateOrUpdateRuleGroupProvisioning(t *testing.T, group apimodels.AlertRuleGroup) (apimodels.AlertRuleGroup, int, string) {
|
||||
t.Helper()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(group)
|
||||
require.NoError(t, err)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/folder/%s/rule-groups/%s", a.url, group.FolderUID, group.Title), &buf)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
return sendRequest[apimodels.AlertRuleGroup](t, req, http.StatusOK)
|
||||
}
|
||||
|
||||
func (a apiClient) SubmitRuleForBacktesting(t *testing.T, config apimodels.BacktestConfig) (int, string) {
|
||||
t.Helper()
|
||||
buf := bytes.Buffer{}
|
||||
|
Loading…
Reference in New Issue
Block a user