Alerting: Fix path for cortex-style Prometheus namespaces conversion endpoint (#103655)

This commit is contained in:
Alexander Akhmetov
2025-04-09 10:40:22 +02:00
committed by GitHub
parent 39dcff23f9
commit babe188e31
12 changed files with 676 additions and 77 deletions
@@ -19,6 +19,11 @@ import (
"github.com/grafana/grafana/pkg/util"
)
const (
folderUIDHeader = "X-Grafana-Alerting-Folder-UID"
targetDatasourceUIDHeader = "X-Grafana-Alerting-Target-Datasource-UID"
)
var (
promGroup1 = apimodels.PrometheusRuleGroup{
Name: "test-group-1",
@@ -99,6 +104,78 @@ var (
}
)
func TestIntegrationConvertPrometheusEndpoints_RecordingRuleTargetDatasource(t *testing.T) {
runTest := func(t *testing.T, enableLokiPaths bool) {
testinfra.SQLiteIntegrationTest(t)
dir, gpath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
EnableFeatureToggles: []string{"grafanaManagedRecordingRulesDatasources", "grafanaManagedRecordingRules"},
EnableRecordingRules: true,
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, gpath)
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "password",
Login: "admin",
})
apiClient := newAlertingApiClient(grafanaListedAddr, "admin", "password")
apiClient.prometheusConversionUseLokiPaths = enableLokiPaths
sourceDS := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
targetDS := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
recordingGroup := apimodels.PrometheusRuleGroup{
Name: "test-recording-group",
Interval: prommodel.Duration(60 * time.Second),
Rules: []apimodels.PrometheusRule{
{
Record: "test_recording_metric",
Expr: "sum(up) by (job)",
Labels: map[string]string{
"env": "test",
},
},
},
}
namespace := "test-recording-namespace"
namespaceUID := util.GenerateShortUID()
apiClient.CreateFolder(t, namespaceUID, namespace)
t.Run("recording rules should use specified target datasource", func(t *testing.T) {
headers := map[string]string{
"Content-Type": "application/json",
targetDatasourceUIDHeader: targetDS.Body.Datasource.UID,
}
apiClient.ConvertPrometheusPostRuleGroup(t, namespace, sourceDS.Body.Datasource.UID, recordingGroup, headers)
ruleGroup, _, _ := apiClient.GetRulesGroupWithStatus(t, namespaceUID, recordingGroup.Name)
require.Len(t, ruleGroup.Rules, 1)
rule := ruleGroup.Rules[0]
require.NotNil(t, rule.GrafanaManagedAlert.Record)
require.Equal(t, targetDS.Body.Datasource.UID, rule.GrafanaManagedAlert.Record.TargetDatasourceUID)
require.NotEmpty(t, rule.GrafanaManagedAlert.Data)
require.Equal(t, sourceDS.Body.Datasource.UID, rule.GrafanaManagedAlert.Data[0].DatasourceUID)
})
}
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(t *testing.T) {
runTest := func(t *testing.T, enableLokiPaths bool, postContentType string) {
testinfra.SQLiteIntegrationTest(t)
@@ -133,6 +210,8 @@ func TestIntegrationConvertPrometheusEndpoints(t *testing.T) {
namespace1 := "test-namespace-1"
namespace2 := "test-namespace-2"
namespace3 := "test-namespace-3"
namespace4 := "test-namespace-4"
ds := apiClient.CreateDatasource(t, datasources.DS_PROMETHEUS)
@@ -140,6 +219,57 @@ func TestIntegrationConvertPrometheusEndpoints(t *testing.T) {
"Content-Type": postContentType,
}
t.Run("create multiple namespaces at once", func(t *testing.T) {
nsUID := util.GenerateShortUID()
nsTitle := "multiple-namespaces-folder"
apiClient.CreateFolder(t, nsUID, nsTitle)
ns := map[string][]apimodels.PrometheusRuleGroup{
namespace1: {promGroup1},
namespace2: {promGroup2},
namespace3: {promGroup3},
namespace4: {promGroup1, promGroup2, promGroup3},
}
// We will create the namespaces in a separate folder, so we need to
// pass the folder UID in the header.
headers := map[string]string{
"Content-Type": postContentType,
folderUIDHeader: nsUID,
}
apiClient.ConvertPrometheusPostRuleGroups(t, ds.Body.Datasource.UID, ns, headers)
// Check namespaces
ns1 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace1, headers)
expectedNs1 := map[string][]apimodels.PrometheusRuleGroup{
namespace1: {promGroup1},
}
require.Equal(t, expectedNs1, ns1)
ns2 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace2, headers)
expectedNs2 := map[string][]apimodels.PrometheusRuleGroup{
namespace2: {promGroup2},
}
require.Equal(t, expectedNs2, ns2)
ns3 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace3, headers)
expectedNs3 := map[string][]apimodels.PrometheusRuleGroup{
namespace3: {promGroup3},
}
require.Equal(t, expectedNs3, ns3)
ns4 := apiClient.ConvertPrometheusGetNamespaceRules(t, namespace4, headers)
expectedNs4 := map[string][]apimodels.PrometheusRuleGroup{
namespace4: {promGroup1, promGroup2, promGroup3},
}
require.Equal(t, expectedNs4, ns4)
// Check all namespaces at once
namespaces := apiClient.ConvertPrometheusGetAllRules(t, headers)
require.Equal(t, ns, namespaces)
})
t.Run("create rule groups and get them back", func(t *testing.T) {
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup1, postContentTypeHeader)
apiClient.ConvertPrometheusPostRuleGroup(t, namespace1, ds.Body.Datasource.UID, promGroup2, postContentTypeHeader)
@@ -551,8 +681,6 @@ 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,
+47 -5
View File
@@ -1110,6 +1110,48 @@ func (a apiClient) GetRuleByUID(t *testing.T, ruleUID string) apimodels.Gettable
return rule
}
func (a apiClient) ConvertPrometheusPostRuleGroups(t *testing.T, datasourceUID string, promNamespaces map[string][]apimodels.PrometheusRuleGroup, headers map[string]string) apimodels.ConvertPrometheusResponse {
t.Helper()
resp, status, body := a.RawConvertPrometheusPostRuleGroups(t, datasourceUID, promNamespaces, headers)
requireStatusCode(t, http.StatusAccepted, status, body)
return resp
}
func (a apiClient) RawConvertPrometheusPostRuleGroups(t *testing.T, datasourceUID string, promNamespaces map[string][]apimodels.PrometheusRuleGroup, headers map[string]string) (apimodels.ConvertPrometheusResponse, int, string) {
t.Helper()
path := "%s/api/convert/prometheus/config/v1/rules"
if a.prometheusConversionUseLokiPaths {
path = "%s/api/convert/api/prom/rules"
}
// Based on the content-type header, marshal the data to JSON or YAML
contentType := headers["Content-Type"]
var data []byte
var err error
if contentType == "application/json" {
data, err = json.Marshal(promNamespaces)
require.NoError(t, err)
} else {
data, err = yaml.Marshal(promNamespaces)
require.NoError(t, err)
}
buf := bytes.NewReader(data)
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf(path, a.url), buf)
require.NoError(t, err)
req.Header.Set("X-Grafana-Alerting-Datasource-UID", datasourceUID)
for key, value := range headers {
req.Header.Set(key, value)
}
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
}
func (a apiClient) ConvertPrometheusPostRuleGroup(t *testing.T, namespaceTitle, datasourceUID string, promGroup apimodels.PrometheusRuleGroup, headers map[string]string) apimodels.ConvertPrometheusResponse {
t.Helper()
@@ -1173,7 +1215,7 @@ func (a apiClient) RawConvertPrometheusGetRuleGroupRules(t *testing.T, namespace
require.NoError(t, err)
for key, value := range headers {
req.Header.Add(key, value)
req.Header.Set(key, value)
}
rule, status, raw := sendRequestYAML[apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
@@ -1193,7 +1235,7 @@ func (a apiClient) ConvertPrometheusGetNamespaceRules(t *testing.T, namespaceTit
require.NoError(t, err)
for key, value := range headers {
req.Header.Add(key, value)
req.Header.Set(key, value)
}
ns, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
@@ -1214,7 +1256,7 @@ func (a apiClient) ConvertPrometheusGetAllRules(t *testing.T, headers map[string
require.NoError(t, err)
for key, value := range headers {
req.Header.Add(key, value)
req.Header.Set(key, value)
}
result, status, raw := sendRequestYAML[map[string][]apimodels.PrometheusRuleGroup](t, req, http.StatusOK)
@@ -1242,7 +1284,7 @@ func (a apiClient) RawConvertPrometheusDeleteRuleGroup(t *testing.T, namespaceTi
require.NoError(t, err)
for key, value := range headers {
req.Header.Add(key, value)
req.Header.Set(key, value)
}
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)
@@ -1267,7 +1309,7 @@ func (a apiClient) RawConvertPrometheusDeleteNamespace(t *testing.T, namespaceTi
require.NoError(t, err)
for key, value := range headers {
req.Header.Add(key, value)
req.Header.Set(key, value)
}
return sendRequestJSON[apimodels.ConvertPrometheusResponse](t, req, http.StatusAccepted)