mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Update rule API to address folders by UID (#74600)
* Change ruler API to expect the folder UID as namespace * Update example requests * Fix tests * Update swagger * Modify FIle field in /api/prometheus/grafana/api/v1/rules * Fix ruler export * Modify folder in responses to be formatted as <parent UID>/<title> * Add alerting test with nested folders * Apply suggestion from code review * Alerting: use folder UID instead of title in rule API (#77166) Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com> * Drop a few more latent uses of namespace_id * move getNamespaceKey to models package * switch GetAlertRulesForScheduling to use folder table * update GetAlertRulesForScheduling to return folder titles in format `parent_uid/title`. * fi tests * add tests for GetAlertRulesForScheduling when parent uid * fix integration tests after merge * fix test after merge * change format of the namespace to JSON array this is needed for forward compatibility, when we migrate to full paths * update EF code to decode nested folder --------- Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com> Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com> Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com> Co-authored-by: Alex Weaver <weaver.alex.d@gmail.com> Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ec1d4274ed
commit
d1dab5828d
@@ -321,6 +321,362 @@ func TestIntegrationAlertRulePermissions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationAlertRuleNestedPermissions(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
// Setup Grafana and its Database
|
||||
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{featuremgmt.FlagNestedFolders},
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
DisableAnonymous: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, p)
|
||||
permissionsStore := resourcepermissions.NewStore(store, featuremgmt.WithFeatures())
|
||||
|
||||
// Create a user to make authenticated requests
|
||||
userID := createUser(t, store, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleEditor),
|
||||
Password: "password",
|
||||
Login: "grafana",
|
||||
})
|
||||
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder1", "folder1")
|
||||
// Create the namespace we'll save our alerts to.
|
||||
apiClient.CreateFolder(t, "folder2", "folder2")
|
||||
// Create a subfolder
|
||||
apiClient.CreateFolder(t, "subfolder", "subfolder", "folder1")
|
||||
|
||||
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group1 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group1))
|
||||
|
||||
// Create rule under folder1
|
||||
_, status, response := apiClient.PostRulesGroupWithStatus(t, "folder1", &group1)
|
||||
require.Equalf(t, http.StatusAccepted, status, response)
|
||||
|
||||
postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-2-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group2 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group2))
|
||||
|
||||
// Create rule under folder2
|
||||
_, status, response = apiClient.PostRulesGroupWithStatus(t, "folder2", &group2)
|
||||
require.Equalf(t, http.StatusAccepted, status, response)
|
||||
|
||||
postGroupRaw, err = testData.ReadFile(path.Join("test-data", "rulegroup-3-post.json"))
|
||||
require.NoError(t, err)
|
||||
var group3 apimodels.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &group3))
|
||||
|
||||
// Create rule under subfolder
|
||||
_, status, response = apiClient.PostRulesGroupWithStatus(t, "subfolder", &group3)
|
||||
require.Equalf(t, http.StatusAccepted, status, response)
|
||||
|
||||
// With the rules created, let's make sure that rule definitions are stored.
|
||||
allRules, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
status, allExportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var allExport apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(allExportRaw), &allExport))
|
||||
|
||||
t.Run("when user has all permissions", func(t *testing.T) {
|
||||
t.Run("Get all returns all rules", func(t *testing.T) {
|
||||
var group1, group2, group3 apimodels.GettableRuleGroupConfig
|
||||
|
||||
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-get.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup1Raw, &group1))
|
||||
getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-get.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup2Raw, &group2))
|
||||
getGroup3Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-3-get.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup3Raw, &group3))
|
||||
|
||||
nestedKey := ngmodels.GetNamespaceKey("folder1", "subfolder")
|
||||
|
||||
expected := apimodels.NamespaceConfigResponse{
|
||||
"folder1": []apimodels.GettableRuleGroupConfig{
|
||||
group1,
|
||||
},
|
||||
"folder2": []apimodels.GettableRuleGroupConfig{
|
||||
group2,
|
||||
},
|
||||
nestedKey: []apimodels.GettableRuleGroupConfig{
|
||||
group3,
|
||||
},
|
||||
}
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"GrafanaManagedAlert.Updated",
|
||||
"GrafanaManagedAlert.UID",
|
||||
"GrafanaManagedAlert.ID",
|
||||
"GrafanaManagedAlert.Data.Model",
|
||||
"GrafanaManagedAlert.NamespaceUID",
|
||||
"GrafanaManagedAlert.NamespaceID",
|
||||
}
|
||||
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(expected, allRules, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
|
||||
require.Empty(t, diff)
|
||||
|
||||
for _, rule := range allRules["folder1"][0].Rules {
|
||||
assert.Equal(t, "folder1", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
}
|
||||
|
||||
for _, rule := range allRules["folder2"][0].Rules {
|
||||
assert.Equal(t, "folder2", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
}
|
||||
|
||||
for _, rule := range allRules[nestedKey][0].Rules {
|
||||
assert.Equal(t, "subfolder", rule.GrafanaManagedAlert.NamespaceUID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
||||
rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder1")
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
require.Contains(t, rules, "folder1")
|
||||
require.Len(t, rules["folder1"], 1)
|
||||
require.Equal(t, allRules["folder1"], rules["folder1"])
|
||||
})
|
||||
|
||||
t.Run("Get group returns a single group", func(t *testing.T) {
|
||||
rules := apiClient.GetRulesGroup(t, "folder2", allRules["folder2"][0].Name)
|
||||
cmp.Diff(allRules["folder2"][0], rules.GettableRuleGroupConfig)
|
||||
})
|
||||
|
||||
t.Run("Get by folder returns groups in folder with nested folder format", func(t *testing.T) {
|
||||
rules, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "subfolder")
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
nestedKey := ngmodels.GetNamespaceKey("folder1", "subfolder")
|
||||
require.Contains(t, rules, nestedKey)
|
||||
require.Len(t, rules[nestedKey], 1)
|
||||
require.Equal(t, allRules[nestedKey], rules[nestedKey])
|
||||
})
|
||||
|
||||
t.Run("Export returns all rules", func(t *testing.T) {
|
||||
var group1File, group2File, group3File apimodels.AlertingFileExport
|
||||
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup1Raw, &group1File))
|
||||
getGroup2Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-2-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup2Raw, &group2File))
|
||||
getGroup3Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-3-export.json"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, json.Unmarshal(getGroup3Raw, &group3File))
|
||||
|
||||
group1File.Groups = append(group1File.Groups, group2File.Groups...)
|
||||
group1File.Groups = append(group1File.Groups, group3File.Groups...)
|
||||
expected := group1File
|
||||
|
||||
pathsToIgnore := []string{
|
||||
"Groups.Rules.UID",
|
||||
"Groups.Folder",
|
||||
}
|
||||
|
||||
// compare expected and actual and ignore the dynamic fields
|
||||
diff := cmp.Diff(expected, allExport, cmp.FilterPath(func(path cmp.Path) bool {
|
||||
for _, s := range pathsToIgnore {
|
||||
if strings.Contains(path.String(), s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, cmp.Ignore()))
|
||||
|
||||
require.Empty(t, diff)
|
||||
|
||||
require.Equal(t, "folder1", allExport.Groups[0].Folder)
|
||||
require.Equal(t, "folder2", allExport.Groups[1].Folder)
|
||||
require.Equal(t, "subfolder", allExport.Groups[2].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder1"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export from a subfolder", func(t *testing.T) {
|
||||
expected := allExport.Groups[2]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"subfolder"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export from one group", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder1"},
|
||||
GroupName: expected.Name,
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export from one group under subfolder", func(t *testing.T) {
|
||||
expected := allExport.Groups[2]
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"subfolder"},
|
||||
GroupName: expected.Name,
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
|
||||
t.Run("Export single rule", func(t *testing.T) {
|
||||
expected := allExport.Groups[0]
|
||||
expected.Rules = []apimodels.AlertRuleExport{
|
||||
expected.Rules[0],
|
||||
}
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
RuleUID: expected.Rules[0].UID,
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
t.Log(exportRaw)
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Len(t, export.Groups, 1)
|
||||
require.Equal(t, expected, export.Groups[0])
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when permissions for folder2 removed", func(t *testing.T) {
|
||||
// remove permissions for folder2
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder2")
|
||||
// remove permissions for subfolder (inherits from folder1)
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "subfolder")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
|
||||
t.Run("Get all returns all rules", func(t *testing.T) {
|
||||
newAll, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Contains(t, newAll, "folder1")
|
||||
require.NotContains(t, newAll, "folder2")
|
||||
require.Contains(t, newAll, ngmodels.GetNamespaceKey("folder1", "subfolder"))
|
||||
})
|
||||
|
||||
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
||||
_, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder2")
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Get group returns a single group", func(t *testing.T) {
|
||||
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/folder2/arulegroup", apiClient.url)
|
||||
// nolint:gosec
|
||||
resp, err := http.Get(u)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("Export returns all rules", func(t *testing.T) {
|
||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
var export apimodels.AlertingFileExport
|
||||
require.NoError(t, json.Unmarshal([]byte(exportRaw), &export))
|
||||
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Len(t, export.Groups, 2)
|
||||
require.Equal(t, "folder1", export.Groups[0].Folder)
|
||||
require.Equal(t, "subfolder", export.Groups[1].Folder)
|
||||
})
|
||||
|
||||
t.Run("Export from one folder", func(t *testing.T) {
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder2"},
|
||||
})
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Export from one group", func(t *testing.T) {
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
FolderUID: []string{"folder2"},
|
||||
GroupName: "arulegroup",
|
||||
})
|
||||
assert.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("Export single rule", func(t *testing.T) {
|
||||
uid := allRules["folder2"][0].Rules[0].GrafanaManagedAlert.UID
|
||||
status, _ := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
RuleUID: uid,
|
||||
})
|
||||
require.Equal(t, http.StatusForbidden, status)
|
||||
})
|
||||
|
||||
t.Run("when all permissions are revoked", func(t *testing.T) {
|
||||
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
||||
apiClient.ReloadCachedPermissions(t)
|
||||
|
||||
rules, status, _ := apiClient.GetAllRulesWithStatus(t)
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
require.Empty(t, rules)
|
||||
|
||||
status, _ = apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
||||
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
||||
})
|
||||
require.Equal(t, http.StatusNotFound, status)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createRule(t *testing.T, client apiClient, folder string) (apimodels.PostableRuleGroupConfig, string) {
|
||||
t.Helper()
|
||||
|
||||
@@ -895,19 +1251,21 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
|
||||
})
|
||||
|
||||
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
folder1Title := "folder1"
|
||||
client.CreateFolder(t, util.GenerateShortUID(), folder1Title)
|
||||
parentFolderUID := util.GenerateShortUID()
|
||||
client.CreateFolder(t, parentFolderUID, "parent")
|
||||
folderUID := util.GenerateShortUID()
|
||||
client.CreateFolder(t, folderUID, "folder1", parentFolderUID)
|
||||
|
||||
group1 := generateAlertRuleGroup(5, alertRuleGen())
|
||||
group2 := generateAlertRuleGroup(5, alertRuleGen())
|
||||
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folder1Title, &group1)
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &group1)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
_, status, _ = client.PostRulesGroupWithStatus(t, folder1Title, &group2)
|
||||
_, status, _ = client.PostRulesGroupWithStatus(t, folderUID, &group2)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
t.Run("should persist order of the rules in a group", func(t *testing.T) {
|
||||
group1Get := client.GetRulesGroup(t, folder1Title, group1.Name)
|
||||
group1Get := client.GetRulesGroup(t, folderUID, group1.Name)
|
||||
assert.Equal(t, group1.Name, group1Get.Name)
|
||||
assert.Equal(t, group1.Interval, group1Get.Interval)
|
||||
assert.Len(t, group1Get.Rules, len(group1.Rules))
|
||||
@@ -926,10 +1284,10 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
|
||||
for _, rule := range postableGroup1.Rules {
|
||||
expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID)
|
||||
}
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folder1Title, &postableGroup1)
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
group1Get = client.GetRulesGroup(t, folder1Title, group1.Name)
|
||||
group1Get = client.GetRulesGroup(t, folderUID, group1.Name)
|
||||
|
||||
require.Len(t, group1Get.Rules, len(postableGroup1.Rules))
|
||||
|
||||
@@ -941,8 +1299,8 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("should be able to move a rule from another group in a specific position", func(t *testing.T) {
|
||||
group1Get := client.GetRulesGroup(t, folder1Title, group1.Name)
|
||||
group2Get := client.GetRulesGroup(t, folder1Title, group2.Name)
|
||||
group1Get := client.GetRulesGroup(t, folderUID, group1.Name)
|
||||
group2Get := client.GetRulesGroup(t, folderUID, group2.Name)
|
||||
|
||||
movedRule := convertGettableRuleToPostable(group2Get.Rules[3])
|
||||
// now shuffle the rules
|
||||
@@ -952,10 +1310,10 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
|
||||
for _, rule := range postableGroup1.Rules {
|
||||
expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID)
|
||||
}
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folder1Title, &postableGroup1)
|
||||
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1)
|
||||
require.Equal(t, http.StatusAccepted, status)
|
||||
|
||||
group1Get = client.GetRulesGroup(t, folder1Title, group1.Name)
|
||||
group1Get = client.GetRulesGroup(t, folderUID, group1.Name)
|
||||
|
||||
require.Len(t, group1Get.Rules, len(postableGroup1.Rules))
|
||||
|
||||
@@ -965,7 +1323,7 @@ func TestIntegrationRuleGroupSequence(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, expectedUids, actualUids)
|
||||
|
||||
group2Get = client.GetRulesGroup(t, folder1Title, group2.Name)
|
||||
group2Get = client.GetRulesGroup(t, folderUID, group2.Name)
|
||||
assert.Len(t, group2Get.Rules, len(group2.Rules)-1)
|
||||
for _, rule := range group2Get.Rules {
|
||||
require.NotEqual(t, movedRule.GrafanaManagedAlert.UID, rule.GrafanaManagedAlert.UID)
|
||||
@@ -1019,26 +1377,26 @@ func TestIntegrationRuleUpdate(t *testing.T) {
|
||||
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
||||
|
||||
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
folder1Title := "folder1"
|
||||
client.CreateFolder(t, util.GenerateShortUID(), folder1Title)
|
||||
folderUID := util.GenerateShortUID()
|
||||
client.CreateFolder(t, folderUID, "folder1")
|
||||
|
||||
t.Run("should be able to reset 'for' to 0", func(t *testing.T) {
|
||||
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||
expected := model.Duration(10 * time.Second)
|
||||
group.Rules[0].ApiRuleNode.For = &expected
|
||||
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For)
|
||||
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
expected = 0
|
||||
group.Rules[0].ApiRuleNode.For = &expected
|
||||
_, status, body = client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
|
||||
getGroup = client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For)
|
||||
})
|
||||
t.Run("when data source missing", func(t *testing.T) {
|
||||
@@ -1047,10 +1405,10 @@ func TestIntegrationRuleUpdate(t *testing.T) {
|
||||
ds1 := adminClient.CreateTestDatasource(t)
|
||||
group := generateAlertRuleGroup(3, alertRuleGen(withDatasourceQuery(ds1.Body.Datasource.UID)))
|
||||
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
|
||||
require.Len(t, group.Rules, 3)
|
||||
@@ -1064,59 +1422,59 @@ func TestIntegrationRuleUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("noop should not fail", func(t *testing.T) {
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, groupName)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
||||
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
||||
})
|
||||
t.Run("should not let update rule if it does not fix datasource", func(t *testing.T) {
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, groupName)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
||||
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
|
||||
group.Rules[0].GrafanaManagedAlert.Title = uuid.NewString()
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
|
||||
if status == http.StatusAccepted {
|
||||
assert.Len(t, resp.Deleted, 1)
|
||||
getGroup = client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
assert.NotEqualf(t, group.Rules[0].GrafanaManagedAlert.Title, getGroup.Rules[0].GrafanaManagedAlert.Title, "group was updated")
|
||||
}
|
||||
require.Equalf(t, http.StatusBadRequest, status, "expected BadRequest. Response: %s", body)
|
||||
assert.Contains(t, body, "data source not found")
|
||||
})
|
||||
t.Run("should let delete broken rule", func(t *testing.T) {
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, groupName)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
||||
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
|
||||
// remove the last rule.
|
||||
group.Rules = group.Rules[0 : len(group.Rules)-1]
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to delete last rule from group. Response: %s", body)
|
||||
assert.Len(t, resp.Deleted, 1)
|
||||
|
||||
getGroup = client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
require.Len(t, group.Rules, 2)
|
||||
})
|
||||
t.Run("should let fix single rule", func(t *testing.T) {
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, groupName)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
||||
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
|
||||
ds2 := adminClient.CreateTestDatasource(t)
|
||||
withDatasourceQuery(ds2.Body.Datasource.UID)(&group.Rules[0])
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
||||
assert.Len(t, resp.Deleted, 0)
|
||||
assert.Len(t, resp.Updated, 2)
|
||||
assert.Len(t, resp.Created, 0)
|
||||
|
||||
getGroup = client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
require.Equal(t, ds2.Body.Datasource.UID, group.Rules[0].GrafanaManagedAlert.Data[0].DatasourceUID)
|
||||
})
|
||||
t.Run("should let delete group", func(t *testing.T) {
|
||||
status, body := client.DeleteRulesGroup(t, folder1Title, groupName)
|
||||
status, body := client.DeleteRulesGroup(t, folderUID, groupName)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
||||
})
|
||||
})
|
||||
@@ -1210,18 +1568,18 @@ func TestIntegrationRulePause(t *testing.T) {
|
||||
})
|
||||
|
||||
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
||||
folder1Title := "folder1"
|
||||
client.CreateFolder(t, util.GenerateShortUID(), folder1Title)
|
||||
folderUID := util.GenerateShortUID()
|
||||
client.CreateFolder(t, folderUID, "folder1")
|
||||
|
||||
t.Run("should create a paused rule if isPaused is true", func(t *testing.T) {
|
||||
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||
expectedIsPaused := true
|
||||
group.Rules[0].GrafanaManagedAlert.IsPaused = &expectedIsPaused
|
||||
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
require.Len(t, resp.Created, 1)
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
||||
require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
||||
})
|
||||
@@ -1231,10 +1589,10 @@ func TestIntegrationRulePause(t *testing.T) {
|
||||
expectedIsPaused := false
|
||||
group.Rules[0].GrafanaManagedAlert.IsPaused = &expectedIsPaused
|
||||
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
require.Len(t, resp.Created, 1)
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
||||
require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
||||
})
|
||||
@@ -1243,10 +1601,10 @@ func TestIntegrationRulePause(t *testing.T) {
|
||||
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||
group.Rules[0].GrafanaManagedAlert.IsPaused = nil
|
||||
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
require.Len(t, resp.Created, 1)
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
||||
require.False(t, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
||||
})
|
||||
@@ -1301,17 +1659,17 @@ func TestIntegrationRulePause(t *testing.T) {
|
||||
group := generateAlertRuleGroup(1, alertRuleGen())
|
||||
group.Rules[0].GrafanaManagedAlert.IsPaused = &tc.isPausedInDb
|
||||
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
getGroup := client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
||||
|
||||
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
||||
group.Rules[0].GrafanaManagedAlert.IsPaused = tc.isPausedInBody
|
||||
_, status, body = client.PostRulesGroupWithStatus(t, folder1Title, &group)
|
||||
_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group)
|
||||
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
||||
|
||||
getGroup = client.GetRulesGroup(t, folder1Title, group.Name)
|
||||
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
||||
require.Equal(t, tc.expectedIsPausedInDb, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
||||
})
|
||||
}
|
||||
|
||||
75
pkg/tests/api/alerting/test-data/rulegroup-3-export.json
Normal file
75
pkg/tests/api/alerting/test-data/rulegroup-3-export.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"apiVersion": 1,
|
||||
"groups": [
|
||||
{
|
||||
"orgId": 1,
|
||||
"name": "Group3",
|
||||
"folder": "<dynamic>",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 \u003e 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"noDataState": "NoData",
|
||||
"execErrState": "Alerting",
|
||||
"for": "5m",
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
},
|
||||
{
|
||||
"uid": "<dynamic>",
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"noDataState": "NoData",
|
||||
"execErrState": "Alerting",
|
||||
"for": "5m",
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"isPaused": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
90
pkg/tests/api/alerting/test-data/rulegroup-3-get.json
Normal file
90
pkg/tests/api/alerting/test-data/rulegroup-3-get.json
Normal file
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "Group3",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"expr": "",
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 1,
|
||||
"orgId": 1,
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 > 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"rule_group": "Group3",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting",
|
||||
"is_paused": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"expr": "",
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"id": 2,
|
||||
"orgId": 1,
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"queryType": "",
|
||||
"relativeTimeRange": {
|
||||
"from": 0,
|
||||
"to": 0
|
||||
},
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"updated": "2023-09-29T17:37:19Z",
|
||||
"intervalSeconds": 60,
|
||||
"version": 1,
|
||||
"uid": "<dynamic>",
|
||||
"namespace_uid": "<dynamic>",
|
||||
"rule_group": "Group3",
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting",
|
||||
"is_paused": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
56
pkg/tests/api/alerting/test-data/rulegroup-3-post.json
Normal file
56
pkg/tests/api/alerting/test-data/rulegroup-3-post.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "Group3",
|
||||
"interval": "1m",
|
||||
"rules": [
|
||||
{
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "Rule1",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 > 0",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
},
|
||||
{
|
||||
"for": "5m",
|
||||
"labels": {
|
||||
"label1": "test-label"
|
||||
},
|
||||
"annotations": {
|
||||
"annotation": "test-annotation"
|
||||
},
|
||||
"grafana_alert": {
|
||||
"title": "Rule2",
|
||||
"condition": "A",
|
||||
"data": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasourceUid": "__expr__",
|
||||
"model": {
|
||||
"expression": "0 == 0",
|
||||
"type": "math"
|
||||
}
|
||||
}
|
||||
],
|
||||
"no_data_state": "NoData",
|
||||
"exec_err_state": "Alerting"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/services/folder"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
@@ -260,9 +261,20 @@ func (a apiClient) ReloadCachedPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
// CreateFolder creates a folder for storing our alerts, and then refreshes the permission cache to make sure that following requests will be accepted
|
||||
func (a apiClient) CreateFolder(t *testing.T, uID string, title string) {
|
||||
func (a apiClient) CreateFolder(t *testing.T, uID string, title string, parentUID ...string) {
|
||||
t.Helper()
|
||||
payload := fmt.Sprintf(`{"uid": "%s","title": "%s"}`, uID, title)
|
||||
cmd := folder.CreateFolderCommand{
|
||||
UID: uID,
|
||||
Title: title,
|
||||
}
|
||||
if len(parentUID) > 0 {
|
||||
cmd.ParentUID = parentUID[0]
|
||||
}
|
||||
|
||||
blob, err := json.Marshal(cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
payload := string(blob)
|
||||
u := fmt.Sprintf("%s/api/folders", a.url)
|
||||
r := strings.NewReader(payload)
|
||||
// nolint:gosec
|
||||
|
||||
Reference in New Issue
Block a user