mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
905 lines
33 KiB
905 lines
33 KiB
package alerting
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
prommodel "github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
var (
|
|
promGroup1 = apimodels.PrometheusRuleGroup{
|
|
Name: "test-group-1",
|
|
Interval: prommodel.Duration(60 * time.Second),
|
|
Rules: []apimodels.PrometheusRule{
|
|
// Recording rule
|
|
{
|
|
Record: "test:requests:rate5m",
|
|
Expr: "sum(rate(test_requests_total[5m])) by (job)",
|
|
Labels: map[string]string{
|
|
"env": "prod",
|
|
"team": "infra",
|
|
},
|
|
},
|
|
// Two alerting rules
|
|
{
|
|
Alert: "HighMemoryUsage",
|
|
Expr: "process_memory_usage > 80",
|
|
For: util.Pointer(prommodel.Duration(5 * time.Minute)),
|
|
Labels: map[string]string{
|
|
"severity": "warning",
|
|
"team": "alerting",
|
|
},
|
|
Annotations: map[string]string{
|
|
"annotation-1": "value-1",
|
|
"annotation-2": "value-2",
|
|
},
|
|
},
|
|
{
|
|
Alert: "ServiceDown",
|
|
Expr: "up == 0",
|
|
For: util.Pointer(prommodel.Duration(2 * time.Minute)),
|
|
Labels: map[string]string{
|
|
"severity": "critical",
|
|
},
|
|
Annotations: map[string]string{
|
|
"annotation-1": "value-1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
promGroup2 = apimodels.PrometheusRuleGroup{
|
|
Name: "test-group-2",
|
|
Interval: prommodel.Duration(60 * time.Second),
|
|
Rules: []apimodels.PrometheusRule{
|
|
{
|
|
Alert: "HighDiskUsage",
|
|
Expr: "disk_usage > 80",
|
|
For: util.Pointer(prommodel.Duration(1 * time.Minute)),
|
|
Labels: map[string]string{
|
|
"severity": "low",
|
|
"team": "alerting",
|
|
},
|
|
Annotations: map[string]string{
|
|
"annotation-5": "value-5",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
promGroup3 = apimodels.PrometheusRuleGroup{
|
|
Name: "test-group-3",
|
|
Interval: prommodel.Duration(60 * time.Second),
|
|
Rules: []apimodels.PrometheusRule{
|
|
{
|
|
Alert: "ServiceDown",
|
|
Expr: "up == 0",
|
|
For: util.Pointer(prommodel.Duration(2 * time.Minute)),
|
|
Labels: map[string]string{
|
|
"severity": "critical",
|
|
},
|
|
Annotations: map[string]string{
|
|
"annotation-1": "value-1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
// Setup Grafana and its Database
|
|
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
|
|
|
// Create users to make authenticated requests
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "password",
|
|
Login: "viewer",
|
|
})
|
|
viewerClient := newAlertingApiClient(grafanaListedAddr, "viewer", "password")
|
|
|
|
namespace1 := "test-namespace-1"
|
|
namespace2 := "test-namespace-2"
|
|
|
|
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
t.Run("create rule groups and get them back", func(t *testing.T) {
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, nil)
|
|
|
|
// create a third group in a different namespace
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace2, ds.Body.Datasource.UID, promGroup3, nil)
|
|
|
|
// And a non-provisioned rule in another namespace
|
|
namespace3UID := util.GenerateShortUID()
|
|
apiClient.CreateFolder(t, namespace3UID, "folder")
|
|
createRule(t, apiClient, namespace3UID)
|
|
|
|
// Now get the first group
|
|
group1 := apiClient.ConvertPrometheusGetRuleGroupRules(t, namespace1, promGroup1.Name, nil)
|
|
require.Equal(t, promGroup1, group1)
|
|
|
|
// Get namespace1
|
|
ns1 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace1, nil)
|
|
expectedNs1 := map[string][]apimodels.PrometheusRuleGroup{
|
|
namespace1: {promGroup1, promGroup2},
|
|
}
|
|
require.Equal(t, expectedNs1, ns1)
|
|
|
|
// Get all namespaces
|
|
namespaces := apiClient.ConvertPrometheusGetAllRules(t, nil)
|
|
expectedNamespaces := map[string][]apimodels.PrometheusRuleGroup{
|
|
namespace1: {promGroup1, promGroup2},
|
|
namespace2: {promGroup3},
|
|
}
|
|
require.Equal(t, expectedNamespaces, namespaces)
|
|
})
|
|
|
|
t.Run("without permissions to create folders cannot create rule groups either", func(t *testing.T) {
|
|
_, status, raw := viewerClient.RawConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
|
requireStatusCode(t, http.StatusForbidden, status, raw)
|
|
})
|
|
|
|
t.Run("delete one rule group", func(t *testing.T) {
|
|
// Create three groups
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, nil)
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace2, ds.Body.Datasource.UID, promGroup3, nil)
|
|
|
|
// delete the first one
|
|
apiClient.ConvertPrometheusDeleteRuleGroup(t, namespace1, promGroup1.Name, nil)
|
|
|
|
// Check that the promGroup2 and promGroup3 are still there
|
|
namespaces := apiClient.ConvertPrometheusGetAllRules(t, nil)
|
|
expectedNamespaces := map[string][]apimodels.PrometheusRuleGroup{
|
|
namespace1: {promGroup2},
|
|
namespace2: {promGroup3},
|
|
}
|
|
require.Equal(t, expectedNamespaces, namespaces)
|
|
|
|
// Delete the second namespace
|
|
apiClient.ConvertPrometheusDeleteNamespace(t, namespace2, nil)
|
|
|
|
// Check that only the first namespace is left
|
|
namespaces = apiClient.ConvertPrometheusGetAllRules(t, nil)
|
|
expectedNamespaces = map[string][]apimodels.PrometheusRuleGroup{
|
|
namespace1: {promGroup2},
|
|
}
|
|
require.Equal(t, expectedNamespaces, namespaces)
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints_UpdateRule(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
// Setup Grafana and its Database
|
|
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
|
|
|
// Create a user to make authenticated requests
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "password",
|
|
Login: "viewer",
|
|
})
|
|
|
|
namespace1 := "test-namespace-1"
|
|
|
|
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
promGroup := apimodels.PrometheusRuleGroup{
|
|
Name: "test-group-for-an-update",
|
|
Interval: prommodel.Duration(60 * time.Second),
|
|
Rules: []apimodels.PrometheusRule{
|
|
{
|
|
Alert: "HighDiskUsage",
|
|
Expr: "disk_usage > 80",
|
|
For: util.Pointer(prommodel.Duration(1 * time.Minute)),
|
|
Labels: map[string]string{
|
|
"severity": "low",
|
|
"team": "alerting",
|
|
},
|
|
Annotations: map[string]string{
|
|
"annotation-5": "value-5",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("update a rule", func(t *testing.T) {
|
|
// Create the rule group
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup, nil)
|
|
|
|
// Now get the group
|
|
group1 := apiClient.ConvertPrometheusGetRuleGroupRules(t, namespace1, promGroup.Name, nil)
|
|
require.Equal(t, promGroup, group1)
|
|
|
|
// Update the rule group interval
|
|
promGroup.Interval = prommodel.Duration(30 * time.Second)
|
|
// Update the query
|
|
promGroup.Rules[0].Expr = "disk_usage > 90"
|
|
// Labels, and annotations too
|
|
promGroup.Rules[0].Labels["another-label"] = "something"
|
|
promGroup.Rules[0].Annotations["another-annotation"] = "also-something"
|
|
// Update the group
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup, nil)
|
|
|
|
// Now get the group again and check that the rule group has been updated
|
|
group1 = apiClient.ConvertPrometheusGetRuleGroupRules(t, namespace1, promGroup.Name, nil)
|
|
require.Equal(t, promGroup, group1)
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints_Conflict(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
// Setup Grafana and its Database
|
|
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
|
|
|
// Create users to make authenticated requests
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "password",
|
|
Login: "viewer",
|
|
})
|
|
|
|
namespace1 := "test-namespace-1"
|
|
|
|
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
t.Run("cannot overwrite a rule group with different provenance", func(t *testing.T) {
|
|
// Create a rule group using the provisioning API and then try to overwrite it
|
|
// using the Prometheus Conversion API. It should fail because the provenance
|
|
// we set for rules in these two APIs is different and we check that when updating.
|
|
provisionedRuleGroup := apimodels.AlertRuleGroup{
|
|
Title: promGroup1.Name,
|
|
Interval: 60,
|
|
FolderUID: namespace1,
|
|
Rules: []apimodels.ProvisionedAlertRule{
|
|
{
|
|
Title: "Rule1",
|
|
OrgID: 1,
|
|
RuleGroup: promGroup1.Name,
|
|
Condition: "A",
|
|
NoDataState: apimodels.Alerting,
|
|
ExecErrState: apimodels.AlertingErrState,
|
|
For: prommodel.Duration(time.Duration(60) * time.Second),
|
|
Data: []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
|
},
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create the folder
|
|
apiClient.CreateFolder(t, namespace1, namespace1)
|
|
// Create rule in the root folder using another API
|
|
_, status, response := apiClient.CreateOrUpdateRuleGroupProvisioning(t, provisionedRuleGroup)
|
|
require.Equalf(t, http.StatusOK, status, response)
|
|
|
|
// Should fail to post the group
|
|
_, status, body := apiClient.RawConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, nil)
|
|
requireStatusCode(t, http.StatusConflict, status, body)
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints_CreatePausedRules(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
// Setup Grafana and its Database
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
|
|
// Create users to make authenticated requests
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
namespace1 := "test-namespace-1"
|
|
|
|
namespace1UID := util.GenerateShortUID()
|
|
apiClient.CreateFolder(t, namespace1UID, namespace1)
|
|
|
|
t.Run("when pausing header is set, rules should be paused", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
recordingPaused bool
|
|
alertPaused bool
|
|
}{
|
|
{
|
|
name: "do not pause rules",
|
|
recordingPaused: false,
|
|
alertPaused: false,
|
|
},
|
|
{
|
|
name: "pause recording rules",
|
|
recordingPaused: true,
|
|
alertPaused: false,
|
|
},
|
|
{
|
|
name: "pause alert rules",
|
|
recordingPaused: false,
|
|
alertPaused: true,
|
|
},
|
|
{
|
|
name: "pause both recording and alert rules",
|
|
recordingPaused: true,
|
|
alertPaused: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
headers := map[string]string{}
|
|
if tc.recordingPaused {
|
|
headers["X-Grafana-Alerting-Recording-Rules-Paused"] = "true"
|
|
}
|
|
if tc.alertPaused {
|
|
headers["X-Grafana-Alerting-Alert-Rules-Paused"] = "true"
|
|
}
|
|
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, headers)
|
|
|
|
gr, _, _ := apiClient.GetRulesGroupWithStatus(t, namespace1UID, promGroup1.Name)
|
|
|
|
require.Len(t, gr.Rules, 3)
|
|
|
|
pausedRecordingRules := 0
|
|
pausedAlertRules := 0
|
|
|
|
for _, rule := range gr.Rules {
|
|
if rule.GrafanaManagedAlert.IsPaused {
|
|
if rule.GrafanaManagedAlert.Record != nil {
|
|
pausedRecordingRules++
|
|
} else {
|
|
pausedAlertRules++
|
|
}
|
|
}
|
|
}
|
|
|
|
if tc.recordingPaused {
|
|
require.Equal(t, 1, pausedRecordingRules)
|
|
} else {
|
|
require.Equal(t, 0, pausedRecordingRules)
|
|
}
|
|
|
|
if tc.alertPaused {
|
|
require.Equal(t, 2, pausedAlertRules)
|
|
} else {
|
|
require.Equal(t, 0, pausedAlertRules)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints_FolderUIDHeader(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
folderUIDHeader := "X-Grafana-Alerting-Folder-UID"
|
|
|
|
// Setup Grafana and its Database
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
// Create a parent folder
|
|
parentFolderUID := util.GenerateShortUID()
|
|
parentFolderTitle := "parent-folder"
|
|
apiClient.CreateFolder(t, parentFolderUID, parentFolderTitle)
|
|
|
|
// Create a child folder inside the parent folder
|
|
childFolderUID := util.GenerateShortUID()
|
|
childFolderTitle := "child-folder"
|
|
apiClient.CreateFolder(t, childFolderUID, childFolderTitle, parentFolderUID)
|
|
|
|
// Create another folder in root
|
|
otherFolderUID := util.GenerateShortUID()
|
|
otherFolderTitle := "other-folder"
|
|
apiClient.CreateFolder(t, otherFolderUID, otherFolderTitle)
|
|
|
|
t.Run("create and delete rule groups with folder UID header", func(t *testing.T) {
|
|
// Post the namespace to parentFolderUID, it should create a new folder with the namespace name,
|
|
// and put the rule group in it.
|
|
headers := map[string]string{
|
|
folderUIDHeader: parentFolderUID,
|
|
}
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, childFolderTitle, ds.Body.Datasource.UID, promGroup1, headers)
|
|
|
|
// Check that it's not visible when we get all namespaces from the root.
|
|
namespaces := apiClient.ConvertPrometheusGetAllRules(t, nil)
|
|
require.Empty(t, namespaces)
|
|
|
|
// Post the group2 to the root, it should create a new folder with the namespace name.
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, otherFolderTitle, ds.Body.Datasource.UID, promGroup2, nil)
|
|
|
|
// Now we should have:
|
|
// - parentFolderUID/child-folder/test-group-1
|
|
// - other-folder/test-group-2
|
|
|
|
// Verify the rule group was created in the child folder
|
|
//
|
|
// First try to get the group in the root folder, it should not be found
|
|
_, status, resp := apiClient.RawConvertPrometheusGetRuleGroupRules(t, childFolderTitle, promGroup1.Name, nil)
|
|
require.Equal(t, http.StatusNotFound, status, resp)
|
|
// Now try to get the group in the child folder
|
|
group1 := apiClient.ConvertPrometheusGetRuleGroupRules(t, childFolderTitle, promGroup1.Name, headers)
|
|
require.Equal(t, promGroup1, group1)
|
|
|
|
// Verify the rule group was created in the other folder
|
|
group2 := apiClient.ConvertPrometheusGetRuleGroupRules(t, otherFolderTitle, promGroup2.Name, nil)
|
|
require.Equal(t, promGroup2, group2)
|
|
})
|
|
|
|
t.Run("empty folder UID header defaults to root", func(t *testing.T) {
|
|
// Create a folder at root level
|
|
rootFolderUID := util.GenerateShortUID()
|
|
rootFolderTitle := "root-folder"
|
|
apiClient.CreateFolder(t, rootFolderUID, rootFolderTitle)
|
|
|
|
// Use empty folder UID header which should default to root
|
|
headers := map[string]string{
|
|
folderUIDHeader: "",
|
|
}
|
|
|
|
apiClient.ConvertPrometheusPostRuleGroup(t, rootFolderTitle, ds.Body.Datasource.UID, promGroup3, headers)
|
|
|
|
// Verify the rule group was created
|
|
group := apiClient.ConvertPrometheusGetRuleGroupRules(t, rootFolderTitle, promGroup3.Name, headers)
|
|
require.Equal(t, promGroup3, group)
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationConvertPrometheusEndpoints_Delete(t *testing.T) {
|
|
runTest := func(t *testing.T, enableLokiPaths bool) {
|
|
testinfra.SQLiteIntegrationTest(t)
|
|
|
|
// Setup Grafana and its Database
|
|
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
|
DisableLegacyAlerting: true,
|
|
EnableUnifiedAlerting: true,
|
|
DisableAnonymous: true,
|
|
AppModeProduction: true,
|
|
EnableFeatureToggles: []string{"alertingConversionAPI", "grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
|
|
EnableRecordingRules: true,
|
|
})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
|
|
|
|
// Create users with different permissions
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "password",
|
|
Login: "admin",
|
|
})
|
|
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
|
|
adminClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleEditor),
|
|
Password: "password",
|
|
Login: "editor",
|
|
})
|
|
editorClient := newAlertingApiClient(grafanaListedAddr, "editor", "password")
|
|
editorClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "password",
|
|
Login: "viewer",
|
|
})
|
|
viewerClient := newAlertingApiClient(grafanaListedAddr, "viewer", "password")
|
|
viewerClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
// Create a user with no access
|
|
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleNone),
|
|
Password: "password",
|
|
Login: "no-role-user",
|
|
})
|
|
noRoleClient := newAlertingApiClient(grafanaListedAddr, "no-role-user", "password")
|
|
noRoleClient.prometheusConversionUseLokiPaths = enableLokiPaths
|
|
|
|
ds := adminClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
|
|
|
|
t.Run("delete non-existent namespace returns 404", func(t *testing.T) {
|
|
nonExistentNamespace := "non-existent-namespace-" + util.GenerateShortUID()
|
|
_, status, raw := adminClient.RawConvertPrometheusDeleteNamespace(t, nonExistentNamespace, nil)
|
|
requireStatusCode(t, http.StatusNotFound, status, raw)
|
|
})
|
|
|
|
t.Run("delete non-existent rule group returns not found", func(t *testing.T) {
|
|
nonExistentNamespace := "non-existent-namespace-" + util.GenerateShortUID()
|
|
nonExistentGroup := "non-existent-group-" + util.GenerateShortUID()
|
|
_, status, raw := adminClient.RawConvertPrometheusDeleteRuleGroup(t, nonExistentNamespace, nonExistentGroup, nil)
|
|
requireStatusCode(t, http.StatusNotFound, status, raw)
|
|
})
|
|
|
|
t.Run("delete rule group from existing namespace that has no rule groups", func(t *testing.T) {
|
|
// Create a namespace but don't add any rule groups to it
|
|
emptyNamespace := "empty-namespace-" + util.GenerateShortUID()
|
|
emptyNamespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, emptyNamespaceUID, emptyNamespace)
|
|
|
|
// Try to delete a non-existent rule group from that namespace
|
|
nonExistentGroup := "non-existent-group-" + util.GenerateShortUID()
|
|
_, status, raw := adminClient.RawConvertPrometheusDeleteRuleGroup(t, emptyNamespace, nonExistentGroup, nil)
|
|
requireStatusCode(t, http.StatusNotFound, status, raw)
|
|
})
|
|
|
|
t.Run("delete rule group then verify it's gone", func(t *testing.T) {
|
|
// Create namespace and rule group
|
|
namespace := "test-namespace-delete-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
// Create rule group
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
// Verify the rule group exists
|
|
group := adminClient.ConvertPrometheusGetRuleGroupRules(t, namespace, promGroup1.Name, nil)
|
|
require.Equal(t, promGroup1.Name, group.Name)
|
|
|
|
// Delete the rule group
|
|
adminClient.ConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, nil)
|
|
|
|
// Verify the rule group is gone
|
|
_, status, _ := adminClient.RawConvertPrometheusGetRuleGroupRules(t, namespace, promGroup1.Name, nil)
|
|
require.Equal(t, http.StatusNotFound, status)
|
|
})
|
|
|
|
t.Run("delete namespace then verify it's empty", func(t *testing.T) {
|
|
// Create namespace with two rule groups
|
|
namespace := "test-namespace-delete-all-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
// Create rule groups
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup2, nil)
|
|
|
|
// Verify the namespace has rule groups
|
|
groups := adminClient.ConvertPrometheusGetNamespaceRules(t, namespace, nil)
|
|
require.ElementsMatch(t, groups[namespace], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
|
|
|
|
// Delete the namespace
|
|
adminClient.ConvertPrometheusDeleteNamespace(t, namespace, nil)
|
|
|
|
// Verify the namespace is empty
|
|
namespaces := adminClient.ConvertPrometheusGetAllRules(t, nil)
|
|
_, exists := namespaces[namespace]
|
|
require.False(t, exists)
|
|
})
|
|
|
|
t.Run("delete specific rule group leaves other groups intact", func(t *testing.T) {
|
|
// Create namespace with two rule groups
|
|
namespace := "test-namespace-delete-one-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
// Create rule groups
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup2, nil)
|
|
|
|
// Delete one rule group
|
|
adminClient.ConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, nil)
|
|
|
|
// Verify the other rule group still exists
|
|
groups := adminClient.ConvertPrometheusGetNamespaceRules(t, namespace, nil)
|
|
require.ElementsMatch(t, groups[namespace], []apimodels.PrometheusRuleGroup{promGroup2})
|
|
})
|
|
|
|
t.Run("viewer cannot delete rule groups", func(t *testing.T) {
|
|
// Create namespace and rule group as admin
|
|
namespace := "test-namespace-viewer-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
// Try to delete as viewer - this should return 403 Forbidden
|
|
_, status, body := viewerClient.RawConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, nil)
|
|
requireStatusCode(t, http.StatusForbidden, status, body)
|
|
|
|
// Verify the rule group still exists
|
|
group := adminClient.ConvertPrometheusGetRuleGroupRules(t, namespace, promGroup1.Name, nil)
|
|
require.Equal(t, promGroup1.Name, group.Name)
|
|
})
|
|
|
|
t.Run("viewer cannot delete namespaces", func(t *testing.T) {
|
|
// Create namespace and rule group as admin
|
|
namespace := "test-namespace-viewer-ns-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
// Try to delete as viewer - this should return 403 Forbidden
|
|
_, status, body := viewerClient.RawConvertPrometheusDeleteNamespace(t, namespace, nil)
|
|
requireStatusCode(t, http.StatusForbidden, status, body)
|
|
|
|
// Verify the namespace still exists
|
|
namespaces := adminClient.ConvertPrometheusGetAllRules(t, nil)
|
|
_, exists := namespaces[namespace]
|
|
require.True(t, exists)
|
|
})
|
|
|
|
t.Run("deleting rule group with nested folder structure using header", func(t *testing.T) {
|
|
// Create parent folder
|
|
parentFolder := "parent-folder-" + util.GenerateShortUID()
|
|
parentFolderUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, parentFolderUID, parentFolder)
|
|
|
|
// Create child folder inside parent
|
|
childFolder := "child-folder-" + util.GenerateShortUID()
|
|
childFolderUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, childFolderUID, childFolder, parentFolderUID)
|
|
|
|
// Create rule group in child folder
|
|
headers := map[string]string{
|
|
"X-Grafana-Alerting-Folder-UID": parentFolderUID,
|
|
}
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, childFolder, ds.Body.Datasource.UID, promGroup1, headers)
|
|
|
|
// Verify the rule group exists
|
|
group := adminClient.ConvertPrometheusGetRuleGroupRules(t, childFolder, promGroup1.Name, headers)
|
|
require.Equal(t, promGroup1.Name, group.Name)
|
|
|
|
// Delete the rule group
|
|
adminClient.ConvertPrometheusDeleteRuleGroup(t, childFolder, promGroup1.Name, headers)
|
|
|
|
// Verify the rule group is gone
|
|
_, status, _ := adminClient.RawConvertPrometheusGetRuleGroupRules(t, childFolder, promGroup1.Name, headers)
|
|
require.Equal(t, http.StatusNotFound, status)
|
|
})
|
|
|
|
t.Run("deleting namespace with nested folder structure using header", func(t *testing.T) {
|
|
// Create parent folder
|
|
parentFolder := "parent-folder-ns-" + util.GenerateShortUID()
|
|
parentFolderUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, parentFolderUID, parentFolder)
|
|
|
|
// Create child folder inside parent
|
|
childFolder := "child-folder-ns-" + util.GenerateShortUID()
|
|
childFolderUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, childFolderUID, childFolder, parentFolderUID)
|
|
|
|
// Create rule groups in child folder
|
|
headers := map[string]string{
|
|
"X-Grafana-Alerting-Folder-UID": parentFolderUID,
|
|
}
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, childFolder, ds.Body.Datasource.UID, promGroup1, headers)
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, childFolder, ds.Body.Datasource.UID, promGroup2, headers)
|
|
|
|
// And a rule group in the parent folder
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, parentFolder, ds.Body.Datasource.UID, promGroup3, nil)
|
|
|
|
// Verify both namespaces have rule groups
|
|
groups := adminClient.ConvertPrometheusGetNamespaceRules(t, childFolder, headers)
|
|
require.ElementsMatch(t, groups[childFolder], []apimodels.PrometheusRuleGroup{promGroup1, promGroup2})
|
|
require.Empty(t, groups[parentFolder])
|
|
|
|
parentGroups := adminClient.ConvertPrometheusGetNamespaceRules(t, parentFolder, nil)
|
|
require.Empty(t, parentGroups[childFolder])
|
|
require.ElementsMatch(t, parentGroups[parentFolder], []apimodels.PrometheusRuleGroup{promGroup3})
|
|
|
|
// Delete the child namespace
|
|
adminClient.ConvertPrometheusDeleteNamespace(t, childFolder, headers)
|
|
|
|
// Verify the namespace is empty
|
|
namespaces := adminClient.ConvertPrometheusGetAllRules(t, headers)
|
|
_, exists := namespaces[childFolder]
|
|
require.False(t, exists)
|
|
|
|
// But the parent folder still has its rule group
|
|
parentGroups = adminClient.ConvertPrometheusGetNamespaceRules(t, parentFolder, nil)
|
|
require.ElementsMatch(t, parentGroups[parentFolder], []apimodels.PrometheusRuleGroup{promGroup3})
|
|
})
|
|
|
|
t.Run("editor can delete rule group they created", func(t *testing.T) {
|
|
// Create namespace as admin
|
|
namespace := "test-namespace-editor-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
// Create rule group as editor
|
|
editorClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
// Verify the rule group exists
|
|
group := editorClient.ConvertPrometheusGetRuleGroupRules(t, namespace, promGroup1.Name, nil)
|
|
require.Equal(t, promGroup1.Name, group.Name)
|
|
|
|
// Delete as editor
|
|
editorClient.ConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, nil)
|
|
|
|
// Verify the rule group is gone
|
|
_, status, _ := editorClient.RawConvertPrometheusGetRuleGroupRules(t, namespace, promGroup1.Name, nil)
|
|
require.Equal(t, http.StatusNotFound, status)
|
|
})
|
|
|
|
t.Run("user with no role cannot delete rule groups", func(t *testing.T) {
|
|
// Create namespace and rule group as admin
|
|
namespace := "test-namespace-no-role-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
_, status, body := noRoleClient.RawConvertPrometheusDeleteRuleGroup(t, namespace, promGroup1.Name, nil)
|
|
requireStatusCode(t, http.StatusForbidden, status, body)
|
|
})
|
|
|
|
t.Run("user with no role cannot delete namespaces", func(t *testing.T) {
|
|
// Create namespace and rule group as admin
|
|
namespace := "test-namespace-no-role-ns-" + util.GenerateShortUID()
|
|
namespaceUID := util.GenerateShortUID()
|
|
adminClient.CreateFolder(t, namespaceUID, namespace)
|
|
|
|
adminClient.ConvertPrometheusPostRuleGroup(t, namespace, ds.Body.Datasource.UID, promGroup1, nil)
|
|
|
|
_, status, body := noRoleClient.RawConvertPrometheusDeleteNamespace(t, namespace, nil)
|
|
requireStatusCode(t, http.StatusForbidden, status, body)
|
|
})
|
|
}
|
|
|
|
t.Run("with the mimirtool paths", func(t *testing.T) {
|
|
runTest(t, false)
|
|
})
|
|
|
|
t.Run("with the cortextool Loki paths", func(t *testing.T) {
|
|
runTest(t, true)
|
|
})
|
|
}
|
|
|