2021-04-30 00:15:15 +08:00
|
|
|
package alerting
|
|
|
|
|
|
|
|
import (
|
2023-06-16 01:33:42 +08:00
|
|
|
"context"
|
2023-10-06 03:47:49 +08:00
|
|
|
"embed"
|
2021-04-30 00:15:15 +08:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2022-08-10 21:37:51 +08:00
|
|
|
"io"
|
2022-06-22 22:52:46 +08:00
|
|
|
"math/rand"
|
2021-04-30 00:15:15 +08:00
|
|
|
"net/http"
|
2023-10-06 03:47:49 +08:00
|
|
|
"path"
|
2024-02-15 22:45:10 +08:00
|
|
|
"slices"
|
2023-10-06 03:47:49 +08:00
|
|
|
"strings"
|
2021-04-30 00:15:15 +08:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
"github.com/google/go-cmp/cmp"
|
2023-06-16 01:33:42 +08:00
|
|
|
"github.com/google/uuid"
|
2024-01-05 00:47:13 +08:00
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
2024-02-15 22:45:10 +08:00
|
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
2022-02-24 00:30:04 +08:00
|
|
|
"github.com/prometheus/common/model"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2023-06-16 01:33:42 +08:00
|
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
2022-08-18 15:43:45 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
2023-06-16 01:33:42 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
|
|
datasourceService "github.com/grafana/grafana/pkg/services/datasources/service"
|
2023-07-21 22:23:01 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
2021-04-30 00:15:15 +08:00
|
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
2022-08-10 17:56:48 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2022-06-28 20:32:25 +08:00
|
|
|
"github.com/grafana/grafana/pkg/services/user"
|
2023-06-16 01:33:42 +08:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2021-04-30 00:15:15 +08:00
|
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
2022-06-22 22:52:46 +08:00
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2021-04-30 00:15:15 +08:00
|
|
|
)
|
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
//go:embed test-data/*.*
|
|
|
|
var testData embed.FS
|
|
|
|
|
2022-12-09 15:11:56 +08:00
|
|
|
func TestIntegrationAlertRulePermissions(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
2021-04-30 00:15:15 +08:00
|
|
|
// Setup Grafana and its Database
|
2023-10-06 03:47:49 +08:00
|
|
|
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
2021-09-29 22:16:40 +08:00
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
2022-02-09 17:26:06 +08:00
|
|
|
AppModeProduction: true,
|
2021-04-30 00:15:15 +08:00
|
|
|
})
|
2021-08-25 21:11:22 +08:00
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
2024-05-28 23:32:23 +08:00
|
|
|
permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures())
|
2021-04-30 00:15:15 +08:00
|
|
|
|
2021-05-05 00:16:28 +08:00
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
DefaultOrgRole: string(org.RoleEditor),
|
2021-08-12 21:04:09 +08:00
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
2021-05-05 00:16:28 +08:00
|
|
|
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
2021-04-30 00:15:15 +08:00
|
|
|
|
|
|
|
// Create the namespace we'll save our alerts to.
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient.CreateFolder(t, "folder1", "folder1")
|
|
|
|
// Create the namespace we'll save our alerts to.
|
|
|
|
apiClient.CreateFolder(t, "folder2", "folder2")
|
2021-04-30 00:15:15 +08:00
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
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))
|
|
|
|
|
2021-04-30 00:15:15 +08:00
|
|
|
// Create rule under folder1
|
2023-10-07 06:11:24 +08:00
|
|
|
_, status, response := apiClient.PostRulesGroupWithStatus(t, "folder1", &group1)
|
2023-10-06 03:47:49 +08:00
|
|
|
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))
|
2021-04-30 00:15:15 +08:00
|
|
|
|
|
|
|
// Create rule under folder2
|
2023-10-07 06:11:24 +08:00
|
|
|
_, status, response = apiClient.PostRulesGroupWithStatus(t, "folder2", &group2)
|
2023-10-06 03:47:49 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, response)
|
2021-04-30 00:15:15 +08:00
|
|
|
|
|
|
|
// With the rules created, let's make sure that rule definitions are stored.
|
2023-10-06 03:47:49 +08:00
|
|
|
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 apimodels.GettableRuleGroupConfig
|
|
|
|
|
|
|
|
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-get.json"))
|
2021-04-30 00:15:15 +08:00
|
|
|
require.NoError(t, err)
|
2023-10-06 03:47:49 +08:00
|
|
|
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))
|
|
|
|
|
|
|
|
expected := apimodels.NamespaceConfigResponse{
|
|
|
|
"folder1": []apimodels.GettableRuleGroupConfig{
|
|
|
|
group1,
|
|
|
|
},
|
|
|
|
"folder2": []apimodels.GettableRuleGroupConfig{
|
|
|
|
group2,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
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"])
|
2021-04-30 00:15:15 +08:00
|
|
|
})
|
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
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("Export returns all rules", func(t *testing.T) {
|
|
|
|
var group1File, group2File 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))
|
|
|
|
|
|
|
|
group1File.Groups = append(group1File.Groups, group2File.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
|
2021-04-30 00:15:15 +08:00
|
|
|
}
|
2023-10-06 03:47:49 +08:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}, cmp.Ignore()))
|
|
|
|
|
|
|
|
require.Empty(t, diff)
|
|
|
|
|
|
|
|
require.Equal(t, "folder1", allExport.Groups[0].Folder)
|
|
|
|
require.Equal(t, "folder2", allExport.Groups[1].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 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 single rule", func(t *testing.T) {
|
|
|
|
expected := allExport.Groups[0]
|
|
|
|
expected.Rules = []apimodels.AlertRuleExport{
|
|
|
|
expected.Rules[0],
|
2021-04-30 00:15:15 +08:00
|
|
|
}
|
2023-10-06 03:47:49 +08:00
|
|
|
status, exportRaw := apiClient.ExportRulesWithStatus(t, &apimodels.AlertRulesExportParameters{
|
|
|
|
ExportQueryParams: apimodels.ExportQueryParams{Format: "json"},
|
|
|
|
RuleUID: expected.Rules[0].UID,
|
|
|
|
})
|
2021-04-30 00:15:15 +08:00
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
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) {
|
2021-04-30 00:15:15 +08:00
|
|
|
// remove permissions from folder2
|
2022-08-10 17:56:48 +08:00
|
|
|
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder2")
|
2022-06-27 21:31:49 +08:00
|
|
|
apiClient.ReloadCachedPermissions(t)
|
2021-04-30 00:15:15 +08:00
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
t.Run("Get all returns all rules", func(t *testing.T) {
|
|
|
|
newAll, status, _ := apiClient.GetAllRulesWithStatus(t)
|
|
|
|
require.Equal(t, http.StatusOK, status)
|
|
|
|
require.NotContains(t, newAll, "folder2")
|
|
|
|
require.Contains(t, newAll, "folder1")
|
2021-04-30 00:15:15 +08:00
|
|
|
})
|
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
t.Run("Get by folder returns groups in folder", func(t *testing.T) {
|
|
|
|
_, status, _ := apiClient.GetAllRulesGroupInFolderWithStatus(t, "folder2")
|
|
|
|
require.Equal(t, http.StatusForbidden, status)
|
|
|
|
})
|
2021-11-08 21:26:08 +08:00
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
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)
|
2021-11-08 21:26:08 +08:00
|
|
|
require.NoError(t, err)
|
2023-10-06 03:47:49 +08:00
|
|
|
defer func() {
|
|
|
|
_ = resp.Body.Close()
|
|
|
|
}()
|
|
|
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
2021-11-08 21:26:08 +08:00
|
|
|
})
|
|
|
|
|
2023-10-06 03:47:49 +08:00
|
|
|
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, 1)
|
|
|
|
require.Equal(t, "folder1", export.Groups[0].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"},
|
|
|
|
})
|
2023-12-08 02:43:58 +08:00
|
|
|
assert.Equal(t, http.StatusForbidden, status)
|
2023-10-06 03:47:49 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
})
|
2021-04-30 00:15:15 +08:00
|
|
|
}
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
2024-05-28 23:32:23 +08:00
|
|
|
permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures())
|
2024-01-17 17:07:39 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2024-01-17 17:07:39 +08:00
|
|
|
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))
|
|
|
|
|
|
|
|
expected := apimodels.NamespaceConfigResponse{
|
|
|
|
"folder1": []apimodels.GettableRuleGroupConfig{
|
|
|
|
group1,
|
|
|
|
},
|
|
|
|
"folder2": []apimodels.GettableRuleGroupConfig{
|
|
|
|
group2,
|
|
|
|
},
|
2024-02-07 06:12:13 +08:00
|
|
|
"folder1/subfolder": []apimodels.GettableRuleGroupConfig{
|
2024-01-17 17:07:39 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2024-02-07 06:12:13 +08:00
|
|
|
for _, rule := range allRules["folder1/subfolder"][0].Rules {
|
2024-01-17 17:07:39 +08:00
|
|
|
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)
|
|
|
|
|
2024-02-07 06:12:13 +08:00
|
|
|
nestedKey := "folder1/subfolder"
|
2024-01-17 17:07:39 +08:00
|
|
|
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)
|
2024-05-31 16:09:20 +08:00
|
|
|
require.Equal(t, "folder1/subfolder", allExport.Groups[2].Folder)
|
2024-01-17 17:07:39 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
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")
|
2024-02-07 06:12:13 +08:00
|
|
|
require.Contains(t, newAll, "folder1/subfolder")
|
2024-01-17 17:07:39 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
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)
|
2024-05-31 16:09:20 +08:00
|
|
|
require.Equal(t, "folder1/subfolder", export.Groups[1].Folder)
|
2024-01-17 17:07:39 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
func createRule(t *testing.T, client apiClient, folder string) (apimodels.PostableRuleGroupConfig, string) {
|
2021-04-30 00:15:15 +08:00
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
interval, err := model.ParseDuration("1m")
|
|
|
|
require.NoError(t, err)
|
2022-06-30 23:46:26 +08:00
|
|
|
doubleInterval := 2 * interval
|
2021-04-30 00:15:15 +08:00
|
|
|
rules := apimodels.PostableRuleGroupConfig{
|
|
|
|
Name: "arulegroup",
|
|
|
|
Interval: interval,
|
|
|
|
Rules: []apimodels.PostableExtendedRuleNode{
|
|
|
|
{
|
|
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
2022-06-30 23:46:26 +08:00
|
|
|
For: &doubleInterval,
|
2021-04-30 00:15:15 +08:00
|
|
|
Labels: map[string]string{"label1": "val1"},
|
|
|
|
Annotations: map[string]string{"annotation1": "val1"},
|
|
|
|
},
|
|
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
|
|
Title: fmt.Sprintf("rule under folder %s", folder),
|
|
|
|
Condition: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
Data: []apimodels.AlertQuery{
|
2021-04-30 00:15:15 +08:00
|
|
|
{
|
|
|
|
RefID: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
2021-04-30 00:15:15 +08:00
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
DatasourceUID: expr.DatasourceUID,
|
2021-04-30 00:15:15 +08:00
|
|
|
Model: json.RawMessage(`{
|
|
|
|
"type": "math",
|
|
|
|
"expression": "2 + 3 > 1"
|
|
|
|
}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2023-10-07 06:11:24 +08:00
|
|
|
resp, status, _ := client.PostRulesGroupWithStatus(t, folder, &rules)
|
2022-06-21 23:39:22 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, 1)
|
|
|
|
return rules, resp.Created[0]
|
2023-10-06 03:47:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestAlertRulePostExport(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
2024-05-28 23:32:23 +08:00
|
|
|
permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures())
|
2023-10-06 03:47:49 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2023-10-06 03:47:49 +08:00
|
|
|
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")
|
|
|
|
|
|
|
|
var group1 apimodels.PostableRuleGroupConfig
|
|
|
|
|
|
|
|
group1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, json.Unmarshal(group1Raw, &group1))
|
|
|
|
|
|
|
|
t.Run("should return in export format", func(t *testing.T) {
|
|
|
|
var expected, actual apimodels.AlertingFileExport
|
|
|
|
getGroup1Raw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-export.json"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, json.Unmarshal(getGroup1Raw, &expected))
|
|
|
|
|
|
|
|
status, actualRaw := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{
|
|
|
|
Download: false,
|
|
|
|
Format: "json",
|
|
|
|
})
|
|
|
|
require.Equal(t, http.StatusOK, status)
|
|
|
|
require.NoError(t, json.Unmarshal([]byte(actualRaw), &actual))
|
|
|
|
|
|
|
|
pathsToIgnore := []string{
|
|
|
|
"Groups.Rules.UID",
|
|
|
|
"Groups.Folder",
|
|
|
|
"Data.Model", // Model is not amended with default values
|
|
|
|
}
|
|
|
|
|
|
|
|
// compare expected and actual and ignore the dynamic fields
|
|
|
|
diff := cmp.Diff(expected, actual, 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, actual.Groups[0].Folder, "folder1")
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should return 403 when no access to folder", func(t *testing.T) {
|
|
|
|
removeFolderPermission(t, permissionsStore, 1, userID, org.RoleEditor, "folder1")
|
|
|
|
apiClient.ReloadCachedPermissions(t)
|
|
|
|
|
|
|
|
status, _ := apiClient.PostRulesExportWithStatus(t, "folder1", &group1, &apimodels.ExportQueryParams{
|
|
|
|
Download: false,
|
|
|
|
Format: "json",
|
|
|
|
})
|
|
|
|
require.Equal(t, http.StatusForbidden, status)
|
|
|
|
})
|
2021-04-30 00:15:15 +08:00
|
|
|
}
|
2021-06-05 01:45:26 +08:00
|
|
|
|
2022-12-09 15:11:56 +08:00
|
|
|
func TestIntegrationAlertRuleConflictingTitle(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
2021-06-05 01:45:26 +08:00
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
2021-09-29 22:16:40 +08:00
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
EnableQuota: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
ViewersCanEdit: true,
|
2022-02-09 17:26:06 +08:00
|
|
|
AppModeProduction: true,
|
2021-06-05 01:45:26 +08:00
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
2021-06-05 01:45:26 +08:00
|
|
|
|
|
|
|
// Create user
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
2021-08-12 21:04:09 +08:00
|
|
|
Password: "admin",
|
|
|
|
Login: "admin",
|
|
|
|
})
|
2021-06-05 01:45:26 +08:00
|
|
|
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
|
|
|
|
2022-05-16 18:45:41 +08:00
|
|
|
// Create the namespace we'll save our alerts to.
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient.CreateFolder(t, "folder1", "folder1")
|
2022-05-16 18:45:41 +08:00
|
|
|
// Create the namespace we'll save our alerts to.
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient.CreateFolder(t, "folder2", "folder2")
|
2022-05-16 18:45:41 +08:00
|
|
|
|
2022-04-14 20:21:36 +08:00
|
|
|
rules := newTestingRuleConfig(t)
|
2021-06-05 01:45:26 +08:00
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
respModel, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rules)
|
2022-06-21 23:39:22 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, respModel.Created, len(rules.Rules))
|
2021-06-05 01:45:26 +08:00
|
|
|
|
2022-04-14 20:21:36 +08:00
|
|
|
// fetch the created rules, so we can get the uid's and trigger
|
|
|
|
// and update by reusing the uid's
|
2022-06-21 23:39:22 +08:00
|
|
|
createdRuleGroup := apiClient.GetRulesGroup(t, "folder1", rules.Name).GettableRuleGroupConfig
|
2022-04-14 20:21:36 +08:00
|
|
|
require.Len(t, createdRuleGroup.Rules, 2)
|
|
|
|
|
2021-06-05 01:45:26 +08:00
|
|
|
t.Run("trying to create alert with same title under same folder should fail", func(t *testing.T) {
|
2023-06-09 06:51:50 +08:00
|
|
|
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
|
|
|
|
rulesWithUID.Rules = append(rulesWithUID.Rules, rules.Rules[0]) // Create new copy of first rule.
|
2022-04-14 20:21:36 +08:00
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID)
|
2024-02-08 01:55:48 +08:00
|
|
|
assert.Equal(t, http.StatusConflict, status)
|
2022-04-14 23:54:49 +08:00
|
|
|
|
2023-08-30 23:46:47 +08:00
|
|
|
var res map[string]any
|
2022-06-21 23:39:22 +08:00
|
|
|
require.NoError(t, json.Unmarshal([]byte(body), &res))
|
2024-02-08 01:55:48 +08:00
|
|
|
require.Contains(t, res["message"], ngmodels.ErrAlertRuleUniqueConstraintViolation.Error())
|
2022-04-14 20:21:36 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("trying to update an alert to the title of an existing alert in the same folder should fail", func(t *testing.T) {
|
2023-06-09 06:51:50 +08:00
|
|
|
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
|
|
|
|
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "AlwaysFiring"
|
2021-06-05 01:45:26 +08:00
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID)
|
2024-02-08 01:55:48 +08:00
|
|
|
assert.Equal(t, http.StatusConflict, status)
|
2022-04-14 23:54:49 +08:00
|
|
|
|
2023-08-30 23:46:47 +08:00
|
|
|
var res map[string]any
|
2022-06-21 23:39:22 +08:00
|
|
|
require.NoError(t, json.Unmarshal([]byte(body), &res))
|
2024-02-08 01:55:48 +08:00
|
|
|
require.Contains(t, res["message"], ngmodels.ErrAlertRuleUniqueConstraintViolation.Error())
|
2021-06-05 01:45:26 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("trying to create alert with same title under another folder should succeed", func(t *testing.T) {
|
2022-04-14 20:21:36 +08:00
|
|
|
rules := newTestingRuleConfig(t)
|
2023-10-07 06:11:24 +08:00
|
|
|
resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder2", &rules)
|
2022-06-21 23:39:22 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, len(rules.Rules))
|
2023-06-09 06:51:50 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("trying to swap titles of existing alerts in the same folder should work", func(t *testing.T) {
|
|
|
|
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
|
|
|
|
title0 := rulesWithUID.Rules[0].GrafanaManagedAlert.Title
|
|
|
|
title1 := rulesWithUID.Rules[1].GrafanaManagedAlert.Title
|
|
|
|
rulesWithUID.Rules[0].GrafanaManagedAlert.Title = title1
|
|
|
|
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = title0
|
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID)
|
2023-06-09 06:51:50 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Updated, 2)
|
2023-06-09 06:51:50 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("trying to update titles of existing alerts in a chain in the same folder should work", func(t *testing.T) {
|
|
|
|
rulesWithUID := convertGettableRuleGroupToPostable(createdRuleGroup)
|
|
|
|
rulesWithUID.Rules[0].GrafanaManagedAlert.Title = rulesWithUID.Rules[1].GrafanaManagedAlert.Title
|
|
|
|
rulesWithUID.Rules[1].GrafanaManagedAlert.Title = "something new"
|
|
|
|
|
2023-10-07 06:11:24 +08:00
|
|
|
resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "folder1", &rulesWithUID)
|
2023-06-09 06:51:50 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Updated, len(rulesWithUID.Rules))
|
2021-06-05 01:45:26 +08:00
|
|
|
})
|
|
|
|
}
|
2021-10-04 23:33:55 +08:00
|
|
|
|
2022-12-09 15:11:56 +08:00
|
|
|
func TestIntegrationRulerRulesFilterByDashboard(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
2021-10-04 23:33:55 +08:00
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
EnableFeatureToggles: []string{"ngalert"},
|
|
|
|
DisableAnonymous: true,
|
2022-02-09 17:26:06 +08:00
|
|
|
AppModeProduction: true,
|
2021-10-04 23:33:55 +08:00
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
2021-10-04 23:33:55 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
DefaultOrgRole: string(org.RoleEditor),
|
2021-10-04 23:33:55 +08:00
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
|
|
|
|
2022-05-16 18:45:41 +08:00
|
|
|
dashboardUID := "default"
|
|
|
|
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
|
2022-06-21 23:39:22 +08:00
|
|
|
apiClient.CreateFolder(t, "default", "default")
|
|
|
|
|
2021-10-04 23:33:55 +08:00
|
|
|
interval, err := model.ParseDuration("10s")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Now, let's create some rules
|
|
|
|
{
|
|
|
|
rules := apimodels.PostableRuleGroupConfig{
|
|
|
|
Name: "anotherrulegroup",
|
|
|
|
Rules: []apimodels.PostableExtendedRuleNode{
|
|
|
|
{
|
|
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
2022-06-30 23:46:26 +08:00
|
|
|
For: &interval,
|
2021-10-04 23:33:55 +08:00
|
|
|
Labels: map[string]string{},
|
|
|
|
Annotations: map[string]string{
|
|
|
|
"__dashboardUid__": dashboardUID,
|
|
|
|
"__panelId__": "1",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
|
|
Title: "AlwaysFiring",
|
|
|
|
Condition: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
Data: []apimodels.AlertQuery{
|
2021-10-04 23:33:55 +08:00
|
|
|
{
|
|
|
|
RefID: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
2021-10-04 23:33:55 +08:00
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
DatasourceUID: expr.DatasourceUID,
|
2021-10-04 23:33:55 +08:00
|
|
|
Model: json.RawMessage(`{
|
|
|
|
"type": "math",
|
|
|
|
"expression": "2 + 3 > 1"
|
|
|
|
}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
|
|
Title: "AlwaysFiringButSilenced",
|
|
|
|
Condition: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
Data: []apimodels.AlertQuery{
|
2021-10-04 23:33:55 +08:00
|
|
|
{
|
|
|
|
RefID: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
2021-10-04 23:33:55 +08:00
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
DatasourceUID: expr.DatasourceUID,
|
2021-10-04 23:33:55 +08:00
|
|
|
Model: json.RawMessage(`{
|
|
|
|
"type": "math",
|
|
|
|
"expression": "2 + 3 > 1"
|
|
|
|
}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
NoDataState: apimodels.NoDataState(ngmodels.Alerting),
|
|
|
|
ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2023-10-07 06:11:24 +08:00
|
|
|
resp, status, _ := apiClient.PostRulesGroupWithStatus(t, "default", &rules)
|
2022-06-21 23:39:22 +08:00
|
|
|
assert.Equal(t, http.StatusAccepted, status)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, len(rules.Rules))
|
2021-10-04 23:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
expectedAllJSON := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
"default": [{
|
|
|
|
"name": "anotherrulegroup",
|
|
|
|
"interval": "1m",
|
|
|
|
"rules": [{
|
|
|
|
"expr": "",
|
|
|
|
"for": "10s",
|
|
|
|
"annotations": {
|
|
|
|
"__dashboardUid__": "%s",
|
|
|
|
"__panelId__": "1"
|
|
|
|
},
|
|
|
|
"grafana_alert": {
|
|
|
|
"id": 1,
|
|
|
|
"orgId": 1,
|
|
|
|
"title": "AlwaysFiring",
|
|
|
|
"condition": "A",
|
|
|
|
"data": [{
|
|
|
|
"refId": "A",
|
|
|
|
"queryType": "",
|
|
|
|
"relativeTimeRange": {
|
|
|
|
"from": 18000,
|
|
|
|
"to": 10800
|
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
"datasourceUid": "__expr__",
|
2021-10-04 23:33:55 +08:00
|
|
|
"model": {
|
|
|
|
"expression": "2 + 3 \u003e 1",
|
|
|
|
"intervalMs": 1000,
|
|
|
|
"maxDataPoints": 43200,
|
|
|
|
"type": "math"
|
|
|
|
}
|
|
|
|
}],
|
|
|
|
"updated": "2021-02-21T01:10:30Z",
|
|
|
|
"intervalSeconds": 60,
|
2023-02-01 20:15:03 +08:00
|
|
|
"is_paused": false,
|
2021-10-04 23:33:55 +08:00
|
|
|
"version": 1,
|
|
|
|
"uid": "uid",
|
|
|
|
"namespace_uid": "nsuid",
|
|
|
|
"rule_group": "anotherrulegroup",
|
|
|
|
"no_data_state": "NoData",
|
|
|
|
"exec_err_state": "Alerting"
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
"expr": "",
|
2022-06-30 23:46:26 +08:00
|
|
|
"for":"0s",
|
2021-10-04 23:33:55 +08:00
|
|
|
"grafana_alert": {
|
|
|
|
"id": 2,
|
|
|
|
"orgId": 1,
|
|
|
|
"title": "AlwaysFiringButSilenced",
|
|
|
|
"condition": "A",
|
|
|
|
"data": [{
|
|
|
|
"refId": "A",
|
|
|
|
"queryType": "",
|
|
|
|
"relativeTimeRange": {
|
|
|
|
"from": 18000,
|
|
|
|
"to": 10800
|
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
"datasourceUid": "__expr__",
|
2021-10-04 23:33:55 +08:00
|
|
|
"model": {
|
|
|
|
"expression": "2 + 3 \u003e 1",
|
|
|
|
"intervalMs": 1000,
|
|
|
|
"maxDataPoints": 43200,
|
|
|
|
"type": "math"
|
|
|
|
}
|
|
|
|
}],
|
|
|
|
"updated": "2021-02-21T01:10:30Z",
|
|
|
|
"intervalSeconds": 60,
|
2023-02-01 20:15:03 +08:00
|
|
|
"is_paused": false,
|
2021-10-04 23:33:55 +08:00
|
|
|
"version": 1,
|
|
|
|
"uid": "uid",
|
|
|
|
"namespace_uid": "nsuid",
|
|
|
|
"rule_group": "anotherrulegroup",
|
|
|
|
"no_data_state": "Alerting",
|
|
|
|
"exec_err_state": "Alerting"
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
}`, dashboardUID)
|
|
|
|
expectedFilteredByJSON := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
"default": [{
|
|
|
|
"name": "anotherrulegroup",
|
|
|
|
"interval": "1m",
|
|
|
|
"rules": [{
|
|
|
|
"expr": "",
|
|
|
|
"for": "10s",
|
|
|
|
"annotations": {
|
|
|
|
"__dashboardUid__": "%s",
|
|
|
|
"__panelId__": "1"
|
|
|
|
},
|
|
|
|
"grafana_alert": {
|
|
|
|
"id": 1,
|
|
|
|
"orgId": 1,
|
|
|
|
"title": "AlwaysFiring",
|
|
|
|
"condition": "A",
|
|
|
|
"data": [{
|
|
|
|
"refId": "A",
|
|
|
|
"queryType": "",
|
|
|
|
"relativeTimeRange": {
|
|
|
|
"from": 18000,
|
|
|
|
"to": 10800
|
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
"datasourceUid": "__expr__",
|
2021-10-04 23:33:55 +08:00
|
|
|
"model": {
|
|
|
|
"expression": "2 + 3 \u003e 1",
|
|
|
|
"intervalMs": 1000,
|
|
|
|
"maxDataPoints": 43200,
|
|
|
|
"type": "math"
|
|
|
|
}
|
|
|
|
}],
|
|
|
|
"updated": "2021-02-21T01:10:30Z",
|
|
|
|
"intervalSeconds": 60,
|
2023-02-01 20:15:03 +08:00
|
|
|
"is_paused": false,
|
2021-10-04 23:33:55 +08:00
|
|
|
"version": 1,
|
|
|
|
"uid": "uid",
|
|
|
|
"namespace_uid": "nsuid",
|
|
|
|
"rule_group": "anotherrulegroup",
|
|
|
|
"no_data_state": "NoData",
|
|
|
|
"exec_err_state": "Alerting"
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
}]
|
|
|
|
}`, dashboardUID)
|
|
|
|
expectedNoneJSON := `{}`
|
|
|
|
|
|
|
|
// Now, let's see how this looks like.
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules", grafanaListedAddr)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
|
|
|
|
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
|
|
|
require.JSONEq(t, expectedAllJSON, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check we get the same rule when filtering by dashboard_uid
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, dashboardUID)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
|
|
|
|
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
|
|
|
require.JSONEq(t, expectedFilteredByJSON, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check we get no rules when filtering by an unknown dashboard_uid
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s", grafanaListedAddr, "abc")
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
|
|
|
|
require.JSONEq(t, expectedNoneJSON, string(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check we get the same rule when filtering by dashboard_uid and panel_id
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=1", grafanaListedAddr, dashboardUID)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
|
|
|
|
body, _ := rulesNamespaceWithoutVariableValues(t, b)
|
|
|
|
require.JSONEq(t, expectedFilteredByJSON, body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check we get no rules when filtering by dashboard_uid and unknown panel_id
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=2", grafanaListedAddr, dashboardUID)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
|
|
|
|
|
|
require.JSONEq(t, expectedNoneJSON, string(b))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check an invalid panel_id returns a 400 Bad Request response
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=invalid", grafanaListedAddr, dashboardUID)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
2023-08-30 23:46:47 +08:00
|
|
|
var res map[string]any
|
2022-04-14 23:54:49 +08:00
|
|
|
require.NoError(t, json.Unmarshal(b, &res))
|
|
|
|
require.Equal(t, `invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax`, res["message"])
|
2021-10-04 23:33:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response
|
|
|
|
{
|
|
|
|
promRulesURL := fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules?panel_id=1", grafanaListedAddr)
|
|
|
|
// nolint:gosec
|
|
|
|
resp, err := http.Get(promRulesURL)
|
|
|
|
require.NoError(t, err)
|
|
|
|
t.Cleanup(func() {
|
|
|
|
err := resp.Body.Close()
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
2022-08-10 21:37:51 +08:00
|
|
|
b, err := io.ReadAll(resp.Body)
|
2021-10-04 23:33:55 +08:00
|
|
|
require.NoError(t, err)
|
2023-08-30 23:46:47 +08:00
|
|
|
var res map[string]any
|
2022-04-14 23:54:49 +08:00
|
|
|
require.NoError(t, json.Unmarshal(b, &res))
|
|
|
|
require.Equal(t, "panel_id must be set with dashboard_uid", res["message"])
|
2021-10-04 23:33:55 +08:00
|
|
|
}
|
|
|
|
}
|
2022-04-14 20:21:36 +08:00
|
|
|
|
2022-12-09 15:11:56 +08:00
|
|
|
func TestIntegrationRuleGroupSequence(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
2022-06-22 22:52:46 +08:00
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
2022-06-22 22:52:46 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
DefaultOrgRole: string(org.RoleEditor),
|
2022-06-22 22:52:46 +08:00
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
|
|
|
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
2024-01-17 17:07:39 +08:00
|
|
|
parentFolderUID := util.GenerateShortUID()
|
|
|
|
client.CreateFolder(t, parentFolderUID, "parent")
|
|
|
|
folderUID := util.GenerateShortUID()
|
|
|
|
client.CreateFolder(t, folderUID, "folder1", parentFolderUID)
|
2022-06-22 22:52:46 +08:00
|
|
|
|
|
|
|
group1 := generateAlertRuleGroup(5, alertRuleGen())
|
|
|
|
group2 := generateAlertRuleGroup(5, alertRuleGen())
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &group1)
|
2022-06-22 22:52:46 +08:00
|
|
|
require.Equal(t, http.StatusAccepted, status)
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, _ = client.PostRulesGroupWithStatus(t, folderUID, &group2)
|
2022-06-22 22:52:46 +08:00
|
|
|
require.Equal(t, http.StatusAccepted, status)
|
|
|
|
|
|
|
|
t.Run("should persist order of the rules in a group", func(t *testing.T) {
|
2024-01-17 17:07:39 +08:00
|
|
|
group1Get := client.GetRulesGroup(t, folderUID, group1.Name)
|
2022-06-22 22:52:46 +08:00
|
|
|
assert.Equal(t, group1.Name, group1Get.Name)
|
|
|
|
assert.Equal(t, group1.Interval, group1Get.Interval)
|
|
|
|
assert.Len(t, group1Get.Rules, len(group1.Rules))
|
|
|
|
for i, getRule := range group1Get.Rules {
|
|
|
|
rule := group1.Rules[i]
|
|
|
|
assert.Equal(t, getRule.GrafanaManagedAlert.Title, rule.GrafanaManagedAlert.Title)
|
|
|
|
assert.NotEmpty(t, getRule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// now shuffle the rules
|
|
|
|
postableGroup1 := convertGettableRuleGroupToPostable(group1Get.GettableRuleGroupConfig)
|
|
|
|
rand.Shuffle(len(postableGroup1.Rules), func(i, j int) {
|
|
|
|
postableGroup1.Rules[i], postableGroup1.Rules[j] = postableGroup1.Rules[j], postableGroup1.Rules[i]
|
|
|
|
})
|
|
|
|
expectedUids := make([]string, 0, len(postableGroup1.Rules))
|
|
|
|
for _, rule := range postableGroup1.Rules {
|
|
|
|
expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1)
|
2022-06-22 22:52:46 +08:00
|
|
|
require.Equal(t, http.StatusAccepted, status)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
group1Get = client.GetRulesGroup(t, folderUID, group1.Name)
|
2022-06-22 22:52:46 +08:00
|
|
|
|
|
|
|
require.Len(t, group1Get.Rules, len(postableGroup1.Rules))
|
|
|
|
|
|
|
|
actualUids := make([]string, 0, len(group1Get.Rules))
|
|
|
|
for _, getRule := range group1Get.Rules {
|
|
|
|
actualUids = append(actualUids, getRule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
|
|
|
assert.Equal(t, expectedUids, actualUids)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should be able to move a rule from another group in a specific position", func(t *testing.T) {
|
2024-01-17 17:07:39 +08:00
|
|
|
group1Get := client.GetRulesGroup(t, folderUID, group1.Name)
|
|
|
|
group2Get := client.GetRulesGroup(t, folderUID, group2.Name)
|
2022-06-22 22:52:46 +08:00
|
|
|
|
|
|
|
movedRule := convertGettableRuleToPostable(group2Get.Rules[3])
|
|
|
|
// now shuffle the rules
|
|
|
|
postableGroup1 := convertGettableRuleGroupToPostable(group1Get.GettableRuleGroupConfig)
|
|
|
|
postableGroup1.Rules = append(append(append([]apimodels.PostableExtendedRuleNode{}, postableGroup1.Rules[0:1]...), movedRule), postableGroup1.Rules[2:]...)
|
|
|
|
expectedUids := make([]string, 0, len(postableGroup1.Rules))
|
|
|
|
for _, rule := range postableGroup1.Rules {
|
|
|
|
expectedUids = append(expectedUids, rule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, _ := client.PostRulesGroupWithStatus(t, folderUID, &postableGroup1)
|
2022-06-22 22:52:46 +08:00
|
|
|
require.Equal(t, http.StatusAccepted, status)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
group1Get = client.GetRulesGroup(t, folderUID, group1.Name)
|
2022-06-22 22:52:46 +08:00
|
|
|
|
|
|
|
require.Len(t, group1Get.Rules, len(postableGroup1.Rules))
|
|
|
|
|
|
|
|
actualUids := make([]string, 0, len(group1Get.Rules))
|
|
|
|
for _, getRule := range group1Get.Rules {
|
|
|
|
actualUids = append(actualUids, getRule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
|
|
|
assert.Equal(t, expectedUids, actualUids)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
group2Get = client.GetRulesGroup(t, folderUID, group2.Name)
|
2022-06-22 22:52:46 +08:00
|
|
|
assert.Len(t, group2Get.Rules, len(group2.Rules)-1)
|
|
|
|
for _, rule := range group2Get.Rules {
|
|
|
|
require.NotEqual(t, movedRule.GrafanaManagedAlert.UID, rule.GrafanaManagedAlert.UID)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
Alerting: Support UTF-8 (#81512)
This pull request updates our fork of Alertmanager to commit 65bdab0, which is based on commit 5658f8c in Prometheus Alertmanager.
It applies the changes from grafana/alerting#155 which removes the overrides for validation of alerts, labels and silences that we had put in place to allow alerts and silences to work for non-Prometheus datasources. However, as this is now supported in Alertmanager with the UTF-8 work, we can use the new upstream functions and remove these overrides.
The compat package is a package in Alertmanager that takes care of backwards compatibility when parsing matchers, validating alerts, labels and silences. It has three modes: classic mode, UTF-8 strict mode, fallback mode. These modes are controlled via compat.InitFromFlags. Grafana initializes the compat package without any feature flags, which is the equivalent of fallback mode. Classic and UTF-8 strict mode are used in Mimir.
While Grafana Managed Alerts have no need for fallback mode, Grafana can still be used as an interface to manage the configurations of Mimir Alertmanagers and view configurations of Prometheus Alertmanager, and those installations might not have migrated or being running on older versions. Such installations behave as if in classic mode, and Grafana must be able to parse their configurations to interact with them for some period of time. As such, Grafana uses fallback mode until we are ready to drop support for outdated installations of Mimir and the Prometheus Alertmanager.
2024-02-06 16:33:47 +08:00
|
|
|
func TestIntegrationRuleCreate(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
Alerting: Support UTF-8 (#81512)
This pull request updates our fork of Alertmanager to commit 65bdab0, which is based on commit 5658f8c in Prometheus Alertmanager.
It applies the changes from grafana/alerting#155 which removes the overrides for validation of alerts, labels and silences that we had put in place to allow alerts and silences to work for non-Prometheus datasources. However, as this is now supported in Alertmanager with the UTF-8 work, we can use the new upstream functions and remove these overrides.
The compat package is a package in Alertmanager that takes care of backwards compatibility when parsing matchers, validating alerts, labels and silences. It has three modes: classic mode, UTF-8 strict mode, fallback mode. These modes are controlled via compat.InitFromFlags. Grafana initializes the compat package without any feature flags, which is the equivalent of fallback mode. Classic and UTF-8 strict mode are used in Mimir.
While Grafana Managed Alerts have no need for fallback mode, Grafana can still be used as an interface to manage the configurations of Mimir Alertmanagers and view configurations of Prometheus Alertmanager, and those installations might not have migrated or being running on older versions. Such installations behave as if in classic mode, and Grafana must be able to parse their configurations to interact with them for some period of time. As such, Grafana uses fallback mode until we are ready to drop support for outdated installations of Mimir and the Prometheus Alertmanager.
2024-02-06 16:33:47 +08:00
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
|
|
Password: "admin",
|
|
|
|
Login: "admin",
|
|
|
|
})
|
|
|
|
client := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
|
|
|
|
|
|
|
namespaceUID := "default"
|
|
|
|
client.CreateFolder(t, namespaceUID, namespaceUID)
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
name string
|
|
|
|
config apimodels.PostableRuleGroupConfig
|
|
|
|
}{{
|
|
|
|
name: "can create a rule with UTF-8",
|
|
|
|
config: apimodels.PostableRuleGroupConfig{
|
|
|
|
Name: "test1",
|
|
|
|
Interval: model.Duration(time.Minute),
|
|
|
|
Rules: []apimodels.PostableExtendedRuleNode{
|
|
|
|
{
|
|
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
|
|
|
For: util.Pointer(model.Duration(2 * time.Minute)),
|
|
|
|
Labels: map[string]string{
|
|
|
|
"foo🙂": "bar",
|
|
|
|
"_bar1": "baz🙂",
|
|
|
|
},
|
|
|
|
Annotations: map[string]string{
|
|
|
|
"Προμηθέας": "prom", // Prometheus in Greek
|
|
|
|
"犬": "Shiba Inu", // Dog in Japanese
|
|
|
|
},
|
|
|
|
},
|
|
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
|
|
Title: "test1 rule1",
|
|
|
|
Condition: "A",
|
|
|
|
Data: []apimodels.AlertQuery{
|
|
|
|
{
|
|
|
|
RefID: "A",
|
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(0),
|
|
|
|
To: apimodels.Duration(15 * time.Minute),
|
|
|
|
},
|
|
|
|
DatasourceUID: expr.DatasourceUID,
|
|
|
|
Model: json.RawMessage(`{"type": "math","expression": "1"}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
resp, status, _ := client.PostRulesGroupWithStatus(t, namespaceUID, &tc.config)
|
|
|
|
require.Equal(t, http.StatusAccepted, status)
|
|
|
|
require.Len(t, resp.Created, 1)
|
|
|
|
require.Len(t, resp.Updated, 0)
|
|
|
|
require.Len(t, resp.Deleted, 0)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-09 15:11:56 +08:00
|
|
|
func TestIntegrationRuleUpdate(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
2022-06-30 23:46:26 +08:00
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
2024-05-28 23:32:23 +08:00
|
|
|
permissionsStore := resourcepermissions.NewStore(env.Cfg, env.SQLStore, featuremgmt.WithFeatures())
|
2022-06-30 23:46:26 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
userID := createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2022-08-10 17:56:48 +08:00
|
|
|
DefaultOrgRole: string(org.RoleEditor),
|
2022-06-30 23:46:26 +08:00
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
2023-06-16 01:33:42 +08:00
|
|
|
if setting.IsEnterprise {
|
|
|
|
// add blanket access to data sources.
|
|
|
|
_, err := permissionsStore.SetUserResourcePermission(context.Background(),
|
|
|
|
1,
|
|
|
|
accesscontrol.User{ID: userID},
|
|
|
|
resourcepermissions.SetResourcePermissionCommand{
|
|
|
|
Actions: []string{
|
|
|
|
datasources.ActionQuery,
|
|
|
|
},
|
|
|
|
Resource: datasources.ScopeRoot,
|
|
|
|
ResourceID: "*",
|
|
|
|
ResourceAttribute: "uid",
|
|
|
|
}, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2023-06-16 01:33:42 +08:00
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
|
|
Password: "admin",
|
|
|
|
Login: "admin",
|
|
|
|
})
|
|
|
|
|
|
|
|
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
|
|
|
|
2022-06-30 23:46:26 +08:00
|
|
|
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
2024-01-17 17:07:39 +08:00
|
|
|
folderUID := util.GenerateShortUID()
|
|
|
|
client.CreateFolder(t, folderUID, "folder1")
|
2022-06-30 23:46:26 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2022-06-30 23:46:26 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2022-06-30 23:46:26 +08:00
|
|
|
require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For)
|
|
|
|
|
|
|
|
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
expected = 0
|
|
|
|
group.Rules[0].ApiRuleNode.For = &expected
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2022-06-30 23:46:26 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
2022-06-30 23:46:26 +08:00
|
|
|
require.Equal(t, expected, *getGroup.Rules[0].ApiRuleNode.For)
|
|
|
|
})
|
2023-06-16 01:33:42 +08:00
|
|
|
t.Run("when data source missing", func(t *testing.T) {
|
|
|
|
var groupName string
|
|
|
|
{
|
|
|
|
ds1 := adminClient.CreateTestDatasource(t)
|
|
|
|
group := generateAlertRuleGroup(3, alertRuleGen(withDatasourceQuery(ds1.Body.Datasource.UID)))
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-06-16 01:33:42 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2023-06-16 01:33:42 +08:00
|
|
|
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
|
|
|
|
require.Len(t, group.Rules, 3)
|
|
|
|
|
|
|
|
adminClient.DeleteDatasource(t, ds1.Body.Datasource.UID)
|
|
|
|
|
|
|
|
// expire datasource caching
|
|
|
|
<-time.After(datasourceService.DefaultCacheTTL + 1*time.Second) // TODO delete when TTL could be configured
|
|
|
|
|
|
|
|
groupName = group.Name
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("noop should not fail", func(t *testing.T) {
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
2023-06-16 01:33:42 +08:00
|
|
|
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-06-16 01:33:42 +08:00
|
|
|
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) {
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
2023-06-16 01:33:42 +08:00
|
|
|
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
|
|
|
|
group.Rules[0].GrafanaManagedAlert.Title = uuid.NewString()
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-06-16 01:33:42 +08:00
|
|
|
|
|
|
|
if status == http.StatusAccepted {
|
2023-10-07 06:11:24 +08:00
|
|
|
assert.Len(t, resp.Deleted, 1)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
2023-06-16 01:33:42 +08:00
|
|
|
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) {
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
2023-06-16 01:33:42 +08:00
|
|
|
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
|
|
|
|
// remove the last rule.
|
|
|
|
group.Rules = group.Rules[0 : len(group.Rules)-1]
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-06-16 01:33:42 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to delete last rule from group. Response: %s", body)
|
2023-10-07 06:11:24 +08:00
|
|
|
assert.Len(t, resp.Deleted, 1)
|
2023-06-16 01:33:42 +08:00
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
2023-06-16 01:33:42 +08:00
|
|
|
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
require.Len(t, group.Rules, 2)
|
|
|
|
})
|
|
|
|
t.Run("should let fix single rule", func(t *testing.T) {
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, groupName)
|
2023-06-16 01:33:42 +08:00
|
|
|
group := convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
|
|
|
|
ds2 := adminClient.CreateTestDatasource(t)
|
|
|
|
withDatasourceQuery(ds2.Body.Datasource.UID)(&group.Rules[0])
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-06-16 01:33:42 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
2023-10-07 06:11:24 +08:00
|
|
|
assert.Len(t, resp.Deleted, 0)
|
|
|
|
assert.Len(t, resp.Updated, 2)
|
|
|
|
assert.Len(t, resp.Created, 0)
|
2023-06-16 01:33:42 +08:00
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
2023-06-16 01:33:42 +08:00
|
|
|
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) {
|
2024-01-17 17:07:39 +08:00
|
|
|
status, body := client.DeleteRulesGroup(t, folderUID, groupName)
|
2023-06-16 01:33:42 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
|
|
|
})
|
|
|
|
})
|
2022-06-30 23:46:26 +08:00
|
|
|
}
|
|
|
|
|
2022-04-14 20:21:36 +08:00
|
|
|
func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig {
|
|
|
|
interval, err := model.ParseDuration("1m")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
firstRule := apimodels.PostableExtendedRuleNode{
|
|
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
2022-06-30 23:46:26 +08:00
|
|
|
For: &interval,
|
2022-04-14 20:21:36 +08:00
|
|
|
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",
|
|
|
|
Condition: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
Data: []apimodels.AlertQuery{
|
2022-04-14 20:21:36 +08:00
|
|
|
{
|
|
|
|
RefID: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
2022-04-14 20:21:36 +08:00
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
DatasourceUID: expr.DatasourceUID,
|
2022-04-14 20:21:36 +08:00
|
|
|
Model: json.RawMessage(`{
|
|
|
|
"type": "math",
|
|
|
|
"expression": "2 + 3 > 1"
|
|
|
|
}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
secondRule := apimodels.PostableExtendedRuleNode{
|
|
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
2022-06-30 23:46:26 +08:00
|
|
|
For: &interval,
|
2022-04-14 20:21:36 +08:00
|
|
|
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: "AlwaysFiring2",
|
|
|
|
Condition: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
Data: []apimodels.AlertQuery{
|
2022-04-14 20:21:36 +08:00
|
|
|
{
|
|
|
|
RefID: "A",
|
2023-03-27 23:55:13 +08:00
|
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
2022-04-14 20:21:36 +08:00
|
|
|
},
|
2023-02-01 01:50:10 +08:00
|
|
|
DatasourceUID: expr.DatasourceUID,
|
2022-04-14 20:21:36 +08:00
|
|
|
Model: json.RawMessage(`{
|
|
|
|
"type": "math",
|
|
|
|
"expression": "2 + 3 > 1"
|
|
|
|
}`),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
return apimodels.PostableRuleGroupConfig{
|
|
|
|
Name: "arulegroup",
|
|
|
|
Rules: []apimodels.PostableExtendedRuleNode{
|
|
|
|
firstRule,
|
|
|
|
secondRule,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-02-01 20:15:03 +08:00
|
|
|
|
|
|
|
func TestIntegrationRulePause(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
2023-02-01 20:15:03 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2023-02-01 20:15:03 +08:00
|
|
|
DefaultOrgRole: string(org.RoleEditor),
|
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
|
|
|
client := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
2024-01-17 17:07:39 +08:00
|
|
|
folderUID := util.GenerateShortUID()
|
|
|
|
client.CreateFolder(t, folderUID, "folder1")
|
2023-02-01 20:15:03 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, 1)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
|
|
|
require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should create a unpaused rule if isPaused is false", func(t *testing.T) {
|
|
|
|
group := generateAlertRuleGroup(1, alertRuleGen())
|
|
|
|
expectedIsPaused := false
|
|
|
|
group.Rules[0].GrafanaManagedAlert.IsPaused = &expectedIsPaused
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, 1)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
|
|
|
require.Equal(t, expectedIsPaused, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should create a unpaused rule if isPaused is not present", func(t *testing.T) {
|
|
|
|
group := generateAlertRuleGroup(1, alertRuleGen())
|
|
|
|
group.Rules[0].GrafanaManagedAlert.IsPaused = nil
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
resp, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
2023-10-07 06:11:24 +08:00
|
|
|
require.Len(t, resp.Created, 1)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to get rule group. Response: %s", body)
|
|
|
|
require.False(t, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
|
|
|
})
|
|
|
|
|
|
|
|
getBooleanPointer := func(b bool) *bool { return &b }
|
|
|
|
testCases := []struct {
|
|
|
|
description string
|
|
|
|
isPausedInDb bool
|
|
|
|
isPausedInBody *bool
|
|
|
|
expectedIsPausedInDb bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
description: "should pause rule if there is a paused rule in DB and isPaused is true",
|
|
|
|
isPausedInDb: true,
|
|
|
|
isPausedInBody: getBooleanPointer(true),
|
|
|
|
expectedIsPausedInDb: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: "should unpause rule if there is a paused rule in DB and isPaused is false",
|
|
|
|
isPausedInDb: true,
|
|
|
|
isPausedInBody: getBooleanPointer(false),
|
|
|
|
expectedIsPausedInDb: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: "should keep rule paused if there is a paused rule in DB and isPaused is not present",
|
|
|
|
isPausedInDb: true,
|
|
|
|
isPausedInBody: nil,
|
|
|
|
expectedIsPausedInDb: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: "should pause rule if there is an unpaused rule in DB and isPaused is true",
|
|
|
|
isPausedInDb: false,
|
|
|
|
isPausedInBody: getBooleanPointer(true),
|
|
|
|
expectedIsPausedInDb: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: "should unpause rule if there is an unpaused rule in DB and isPaused is false",
|
|
|
|
isPausedInDb: false,
|
|
|
|
isPausedInBody: getBooleanPointer(false),
|
|
|
|
expectedIsPausedInDb: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
description: "should keep rule unpaused if there is an unpaused rule in DB and isPaused is not present",
|
|
|
|
isPausedInDb: false,
|
|
|
|
isPausedInBody: nil,
|
|
|
|
expectedIsPausedInDb: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range testCases {
|
|
|
|
t.Run(tc.description, func(t *testing.T) {
|
|
|
|
group := generateAlertRuleGroup(1, alertRuleGen())
|
|
|
|
group.Rules[0].GrafanaManagedAlert.IsPaused = &tc.isPausedInDb
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body := client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup := client.GetRulesGroup(t, folderUID, group.Name)
|
2023-02-01 20:15:03 +08:00
|
|
|
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
|
2024-01-17 17:07:39 +08:00
|
|
|
_, status, body = client.PostRulesGroupWithStatus(t, folderUID, &group)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post rule group. Response: %s", body)
|
|
|
|
|
2024-01-17 17:07:39 +08:00
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, group.Name)
|
2023-02-01 20:15:03 +08:00
|
|
|
require.Equal(t, tc.expectedIsPausedInDb, getGroup.Rules[0].GrafanaManagedAlert.IsPaused)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-01-05 00:47:13 +08:00
|
|
|
|
|
|
|
func TestIntegrationHysteresisRule(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
|
|
|
// Setup Grafana and its Database. Scheduler is set to evaluate every 1 second
|
|
|
|
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
NGAlertSchedulerBaseInterval: 1 * time.Second,
|
|
|
|
EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagRecoveryThreshold},
|
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
2024-01-05 00:47:13 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2024-01-05 00:47:13 +08:00
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
|
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
|
|
|
|
|
|
|
folder := "hysteresis"
|
|
|
|
testDs := apiClient.CreateTestDatasource(t)
|
|
|
|
apiClient.CreateFolder(t, folder, folder)
|
|
|
|
|
|
|
|
bodyRaw, err := testData.ReadFile("test-data/hysteresis_rule.json")
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
var postData apimodels.PostableRuleGroupConfig
|
|
|
|
require.NoError(t, json.Unmarshal(bodyRaw, &postData))
|
|
|
|
for _, rule := range postData.Rules {
|
|
|
|
for i := range rule.GrafanaManagedAlert.Data {
|
|
|
|
rule.GrafanaManagedAlert.Data[i].DatasourceUID = strings.ReplaceAll(rule.GrafanaManagedAlert.Data[i].DatasourceUID, "REPLACE_ME", testDs.Body.Datasource.UID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
changes, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &postData)
|
|
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
|
|
require.Len(t, changes.Created, 1)
|
|
|
|
ruleUid := changes.Created[0]
|
|
|
|
|
|
|
|
var frame data.Frame
|
|
|
|
require.Eventuallyf(t, func() bool {
|
|
|
|
frame, status, body = apiClient.GetRuleHistoryWithStatus(t, ruleUid)
|
|
|
|
require.Equalf(t, http.StatusOK, status, body)
|
|
|
|
return frame.Rows() > 1
|
|
|
|
}, 15*time.Second, 1*time.Second, "Alert state history expected to have more than one record but got %d. Body: %s", frame.Rows(), body)
|
|
|
|
|
|
|
|
f, _ := frame.FieldByName("next")
|
|
|
|
|
|
|
|
alertingIdx := 0
|
|
|
|
normalIdx := 1
|
|
|
|
if f.At(alertingIdx).(string) != "Alerting" {
|
|
|
|
alertingIdx = 1
|
|
|
|
normalIdx = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
assert.Equalf(t, "Alerting", f.At(alertingIdx).(string), body)
|
|
|
|
assert.Equalf(t, "Normal", f.At(normalIdx).(string), body)
|
|
|
|
|
|
|
|
type HistoryData struct {
|
|
|
|
Values map[string]int64
|
|
|
|
}
|
|
|
|
|
|
|
|
f, _ = frame.FieldByName("data")
|
|
|
|
var d HistoryData
|
|
|
|
require.NoErrorf(t, json.Unmarshal([]byte(f.At(alertingIdx).(string)), &d), body)
|
|
|
|
assert.EqualValuesf(t, 5, d.Values["B"], body)
|
|
|
|
require.NoErrorf(t, json.Unmarshal([]byte(f.At(normalIdx).(string)), &d), body)
|
|
|
|
assert.EqualValuesf(t, 1, d.Values["B"], body)
|
|
|
|
}
|
2024-02-15 22:45:10 +08:00
|
|
|
|
|
|
|
func TestIntegrationRuleNotificationSettings(t *testing.T) {
|
|
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
|
|
|
|
// Setup Grafana and its Database. Scheduler is set to evaluate every 1 second
|
|
|
|
dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
NGAlertSchedulerBaseInterval: 1 * time.Second,
|
|
|
|
EnableFeatureToggles: []string{featuremgmt.FlagConfigurableSchedulerTick, featuremgmt.FlagAlertingSimplifiedRouting},
|
|
|
|
})
|
|
|
|
|
2024-04-04 21:04:47 +08:00
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p)
|
2024-02-15 22:45:10 +08:00
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
2024-04-04 21:04:47 +08:00
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
2024-02-15 22:45:10 +08:00
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
|
|
Password: "password",
|
|
|
|
Login: "grafana",
|
|
|
|
})
|
|
|
|
|
|
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "grafana", "password")
|
|
|
|
|
|
|
|
folder := "Test-Alerting"
|
|
|
|
apiClient.CreateFolder(t, folder, folder)
|
|
|
|
|
|
|
|
testDataRaw, err := testData.ReadFile(path.Join("test-data", "rule-notification-settings-1-post.json"))
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
type testData struct {
|
|
|
|
RuleGroup apimodels.PostableRuleGroupConfig
|
|
|
|
Receiver apimodels.EmbeddedContactPoint
|
|
|
|
TimeInterval apimodels.MuteTimeInterval
|
|
|
|
}
|
|
|
|
var d testData
|
|
|
|
err = json.Unmarshal(testDataRaw, &d)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
apiClient.EnsureReceiver(t, d.Receiver)
|
|
|
|
apiClient.EnsureMuteTiming(t, d.TimeInterval)
|
|
|
|
|
|
|
|
t.Run("create should fail if receiver does not exist", func(t *testing.T) {
|
|
|
|
var copyD testData
|
|
|
|
err = json.Unmarshal(testDataRaw, ©D)
|
|
|
|
group := copyD.RuleGroup
|
|
|
|
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
ns.Receiver = "random-receiver"
|
|
|
|
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group)
|
|
|
|
require.Equalf(t, http.StatusBadRequest, status, body)
|
|
|
|
t.Log(body)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("create should fail if mute timing does not exist", func(t *testing.T) {
|
|
|
|
var copyD testData
|
|
|
|
err = json.Unmarshal(testDataRaw, ©D)
|
|
|
|
group := copyD.RuleGroup
|
|
|
|
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
ns.MuteTimeIntervals = []string{"random-time-interval"}
|
|
|
|
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group)
|
|
|
|
require.Equalf(t, http.StatusBadRequest, status, body)
|
|
|
|
t.Log(body)
|
|
|
|
})
|
|
|
|
|
2024-04-17 00:14:39 +08:00
|
|
|
t.Run("create should not fail if group_by is missing required labels but they should still be used", func(t *testing.T) {
|
2024-02-15 22:45:10 +08:00
|
|
|
var copyD testData
|
|
|
|
err = json.Unmarshal(testDataRaw, ©D)
|
|
|
|
group := copyD.RuleGroup
|
|
|
|
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
ns.GroupBy = []string{"label1"}
|
|
|
|
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group)
|
2024-04-17 00:14:39 +08:00
|
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
|
|
|
|
|
|
cfg, status, body := apiClient.GetAlertmanagerConfigWithStatus(t)
|
|
|
|
if !assert.Equalf(t, http.StatusOK, status, body) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that the group by contains the default required labels.
|
|
|
|
autogenRoute := cfg.AlertmanagerConfig.Route.Routes[0]
|
|
|
|
receiverRoute := autogenRoute.Routes[0]
|
|
|
|
ruleRoute := receiverRoute.Routes[0]
|
|
|
|
assert.Equal(t, []model.LabelName{ngmodels.FolderTitleLabel, model.AlertNameLabel, "label1"}, ruleRoute.GroupBy)
|
|
|
|
|
2024-02-15 22:45:10 +08:00
|
|
|
t.Log(body)
|
|
|
|
})
|
|
|
|
|
2024-04-17 00:14:39 +08:00
|
|
|
t.Run("create with '...' groupBy followed by config post should succeed", func(t *testing.T) {
|
|
|
|
var copyD testData
|
|
|
|
err = json.Unmarshal(testDataRaw, ©D)
|
|
|
|
group := copyD.RuleGroup
|
|
|
|
ns := group.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
ns.GroupBy = []string{ngmodels.FolderTitleLabel, model.AlertNameLabel, ngmodels.GroupByAll}
|
|
|
|
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group)
|
|
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
|
|
|
|
|
|
// Now update the config with no changes.
|
|
|
|
_, status, body = apiClient.GetAlertmanagerConfigWithStatus(t)
|
|
|
|
if !assert.Equalf(t, http.StatusOK, status, body) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg := apimodels.PostableUserConfig{}
|
|
|
|
|
|
|
|
err = json.Unmarshal([]byte(body), &cfg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
ok, err := apiClient.PostConfiguration(t, cfg)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, ok)
|
|
|
|
})
|
|
|
|
|
2024-02-15 22:45:10 +08:00
|
|
|
t.Run("should create rule and generate route", func(t *testing.T) {
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &d.RuleGroup)
|
|
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
|
|
notificationSettings := d.RuleGroup.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
|
|
|
|
var routeBody string
|
|
|
|
if !assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
|
|
amConfig, status, body := apiClient.GetAlertmanagerConfigWithStatus(t)
|
|
|
|
routeBody = body
|
|
|
|
if !assert.Equalf(t, http.StatusOK, status, body) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
route := amConfig.AlertmanagerConfig.Route
|
|
|
|
|
|
|
|
if !assert.Len(c, route.Routes, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that we are in the auto-generated root
|
|
|
|
autogenRoute := route.Routes[0]
|
|
|
|
if !assert.Len(c, autogenRoute.ObjectMatchers, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
canContinue := assert.Equal(c, ngmodels.AutogeneratedRouteLabel, autogenRoute.ObjectMatchers[0].Name)
|
|
|
|
assert.Equal(c, labels.MatchEqual, autogenRoute.ObjectMatchers[0].Type)
|
|
|
|
assert.Equal(c, "true", autogenRoute.ObjectMatchers[0].Value)
|
|
|
|
|
|
|
|
assert.Equalf(c, route.Receiver, autogenRoute.Receiver, "Autogenerated root receiver must be the default one")
|
|
|
|
assert.Nil(c, autogenRoute.GroupWait)
|
|
|
|
assert.Nil(c, autogenRoute.GroupInterval)
|
|
|
|
assert.Nil(c, autogenRoute.RepeatInterval)
|
|
|
|
assert.Empty(c, autogenRoute.MuteTimeIntervals)
|
|
|
|
assert.Empty(c, autogenRoute.GroupBy)
|
|
|
|
if !canContinue {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Now check that the second level is route for receivers
|
|
|
|
if !assert.NotEmpty(c, autogenRoute.Routes) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// There can be many routes, for all receivers
|
|
|
|
idx := slices.IndexFunc(autogenRoute.Routes, func(route *apimodels.Route) bool {
|
|
|
|
return route.Receiver == notificationSettings.Receiver
|
|
|
|
})
|
|
|
|
if !assert.GreaterOrEqual(t, idx, 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
receiverRoute := autogenRoute.Routes[idx]
|
|
|
|
if !assert.Len(c, receiverRoute.ObjectMatchers, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
canContinue = assert.Equal(c, ngmodels.AutogeneratedRouteReceiverNameLabel, receiverRoute.ObjectMatchers[0].Name)
|
|
|
|
assert.Equal(c, labels.MatchEqual, receiverRoute.ObjectMatchers[0].Type)
|
|
|
|
assert.Equal(c, notificationSettings.Receiver, receiverRoute.ObjectMatchers[0].Value)
|
|
|
|
|
|
|
|
assert.Equal(c, notificationSettings.Receiver, receiverRoute.Receiver)
|
|
|
|
assert.Nil(c, receiverRoute.GroupWait)
|
|
|
|
assert.Nil(c, receiverRoute.GroupInterval)
|
|
|
|
assert.Nil(c, receiverRoute.RepeatInterval)
|
|
|
|
assert.Empty(c, receiverRoute.MuteTimeIntervals)
|
|
|
|
var groupBy []string
|
|
|
|
for _, name := range receiverRoute.GroupBy {
|
|
|
|
groupBy = append(groupBy, string(name))
|
|
|
|
}
|
|
|
|
slices.Sort(groupBy)
|
|
|
|
assert.EqualValues(c, []string{"alertname", "grafana_folder"}, groupBy)
|
|
|
|
if !canContinue {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Now check that we created the 3rd level for specific combination of settings
|
|
|
|
if !assert.Lenf(c, receiverRoute.Routes, 1, "Receiver route should contain one options route") {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
optionsRoute := receiverRoute.Routes[0]
|
|
|
|
if !assert.Len(c, optionsRoute.ObjectMatchers, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
assert.Equal(c, ngmodels.AutogeneratedRouteSettingsHashLabel, optionsRoute.ObjectMatchers[0].Name)
|
|
|
|
assert.Equal(c, labels.MatchEqual, optionsRoute.ObjectMatchers[0].Type)
|
|
|
|
assert.EqualValues(c, notificationSettings.GroupWait, optionsRoute.GroupWait)
|
|
|
|
assert.EqualValues(c, notificationSettings.GroupInterval, optionsRoute.GroupInterval)
|
|
|
|
assert.EqualValues(c, notificationSettings.RepeatInterval, optionsRoute.RepeatInterval)
|
|
|
|
assert.EqualValues(c, notificationSettings.MuteTimeIntervals, optionsRoute.MuteTimeIntervals)
|
|
|
|
groupBy = nil
|
|
|
|
for _, name := range optionsRoute.GroupBy {
|
|
|
|
groupBy = append(groupBy, string(name))
|
|
|
|
}
|
|
|
|
assert.EqualValues(c, notificationSettings.GroupBy, groupBy)
|
|
|
|
}, 10*time.Second, 1*time.Second) {
|
|
|
|
t.Logf("config: %s", routeBody)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should correctly create alerts", func(t *testing.T) {
|
|
|
|
var response string
|
|
|
|
if !assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
|
|
groups, status, body := apiClient.GetActiveAlertsWithStatus(t)
|
|
|
|
require.Equalf(t, http.StatusOK, status, body)
|
|
|
|
response = body
|
|
|
|
if len(groups) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
g := groups[0]
|
|
|
|
alert := g.Alerts[0]
|
|
|
|
assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteLabel)
|
|
|
|
assert.Equal(c, "true", alert.Labels[ngmodels.AutogeneratedRouteLabel])
|
|
|
|
assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteReceiverNameLabel)
|
|
|
|
assert.Equal(c, d.Receiver.Name, alert.Labels[ngmodels.AutogeneratedRouteReceiverNameLabel])
|
|
|
|
assert.Contains(c, alert.Labels, ngmodels.AutogeneratedRouteSettingsHashLabel)
|
|
|
|
assert.NotEmpty(c, alert.Labels[ngmodels.AutogeneratedRouteSettingsHashLabel])
|
|
|
|
}, 10*time.Second, 1*time.Second) {
|
|
|
|
t.Logf("response: %s", response)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("should update rule with empty settings and delete route", func(t *testing.T) {
|
|
|
|
var copyD testData
|
|
|
|
err = json.Unmarshal(testDataRaw, ©D)
|
|
|
|
group := copyD.RuleGroup
|
|
|
|
notificationSettings := group.Rules[0].GrafanaManagedAlert.NotificationSettings
|
|
|
|
group.Rules[0].GrafanaManagedAlert.NotificationSettings = nil
|
|
|
|
|
|
|
|
_, status, body := apiClient.PostRulesGroupWithStatus(t, folder, &group)
|
|
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
|
|
|
|
|
|
var routeBody string
|
|
|
|
if !assert.EventuallyWithT(t, func(c *assert.CollectT) {
|
|
|
|
amConfig, status, body := apiClient.GetAlertmanagerConfigWithStatus(t)
|
|
|
|
routeBody = body
|
|
|
|
if !assert.Equalf(t, http.StatusOK, status, body) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
route := amConfig.AlertmanagerConfig.Route
|
|
|
|
|
|
|
|
if !assert.Len(c, route.Routes, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Check that we are in the auto-generated root
|
|
|
|
autogenRoute := route.Routes[0]
|
|
|
|
if !assert.Len(c, autogenRoute.ObjectMatchers, 1) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if !assert.Equal(c, ngmodels.AutogeneratedRouteLabel, autogenRoute.ObjectMatchers[0].Name) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Now check that the second level is route for receivers
|
|
|
|
if !assert.NotEmpty(c, autogenRoute.Routes) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// There can be many routes, for all receivers
|
|
|
|
idx := slices.IndexFunc(autogenRoute.Routes, func(route *apimodels.Route) bool {
|
|
|
|
return route.Receiver == notificationSettings.Receiver
|
|
|
|
})
|
|
|
|
if !assert.GreaterOrEqual(t, idx, 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
receiverRoute := autogenRoute.Routes[idx]
|
|
|
|
if !assert.Empty(c, receiverRoute.Routes) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}, 10*time.Second, 1*time.Second) {
|
|
|
|
t.Logf("config: %s", routeBody)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2024-06-11 07:05:47 +08:00
|
|
|
|
|
|
|
func TestIntegrationRuleUpdateAllDatabases(t *testing.T) {
|
|
|
|
// Setup Grafana and its Database
|
|
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
|
|
DisableLegacyAlerting: true,
|
|
|
|
EnableUnifiedAlerting: true,
|
|
|
|
DisableAnonymous: true,
|
|
|
|
AppModeProduction: true,
|
|
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
|
|
|
|
|
|
// Create a user to make authenticated requests
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
|
|
Password: "admin",
|
|
|
|
Login: "admin",
|
|
|
|
})
|
|
|
|
|
|
|
|
client := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
|
|
|
|
|
|
|
folderUID := util.GenerateShortUID()
|
|
|
|
client.CreateFolder(t, folderUID, "folder1")
|
|
|
|
|
|
|
|
t.Run("group renamed followed by delete for case-only changes should not delete both groups", func(t *testing.T) { // Regression test.
|
|
|
|
group := generateAlertRuleGroup(3, alertRuleGen())
|
|
|
|
groupName := group.Name
|
|
|
|
|
|
|
|
_, 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, folderUID, group.Name)
|
|
|
|
require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group")
|
|
|
|
require.Equal(t, groupName, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup)
|
|
|
|
|
|
|
|
group = convertGettableRuleGroupToPostable(getGroup.GettableRuleGroupConfig)
|
|
|
|
newGroup := strings.ToUpper(group.Name)
|
|
|
|
group.Name = newGroup
|
|
|
|
_, 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, folderUID, group.Name)
|
|
|
|
require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group")
|
|
|
|
require.Equal(t, newGroup, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup)
|
|
|
|
|
|
|
|
status, body = client.DeleteRulesGroup(t, folderUID, groupName)
|
|
|
|
require.Equalf(t, http.StatusAccepted, status, "failed to post noop rule group. Response: %s", body)
|
|
|
|
|
|
|
|
// Old group is gone.
|
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, groupName)
|
|
|
|
require.Lenf(t, getGroup.Rules, 0, "expected no rules")
|
|
|
|
|
|
|
|
// New group still exists.
|
|
|
|
getGroup = client.GetRulesGroup(t, folderUID, newGroup)
|
|
|
|
require.Lenf(t, getGroup.Rules, 3, "expected 3 rules in group")
|
|
|
|
require.Equal(t, newGroup, getGroup.Rules[0].GrafanaManagedAlert.RuleGroup)
|
|
|
|
})
|
|
|
|
}
|