Prometheus: Add support for cloud partners Prometheus data sources (#103482)

* wip

* Add prom flavor support for data source variables and export/import dashboards (#103321)

* add dashboard and data source var selection

* use match plugin id instead

* use updated matchpluginid

* formatting

* cleanup

* regex anchor

* update error msg

* Alerting: Clean up prometheus-flavored types and functions (#103703)

* clean up types and utility functions for dealing with
prometheus-flavored data sources

* Refactor alerting datasource types to use constants as source of truth

* Alerting: Clean up prometheus-flavored types and functions on the bac… (#103716)

Alerting: Clean up prometheus-flavored types and functions on the backend

* add matchPluginId tests

* Update matchPluginId func to bidirectional (#103746)

* update matchpluginid func to bidirectional

* lint

* formatting

* use actual isSupportedExternalRulesSourceType in test

* add tests in datasource_srv

* betterer

* remove type assertion

* remove unnecessary case

* use satisifies to not have to convert tuple to an array of string

* add prometheus_flavor test

---------

Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com>
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
Co-authored-by: Alexander Akhmetov <me@alx.cx>
This commit is contained in:
Kevin Yu
2025-04-10 12:49:11 -07:00
committed by GitHub
co-authored by Andrew Hackmann Gilles De Mey Alexander Akhmetov
parent 22a9e1b5ff
commit fd6fd91115
32 changed files with 654 additions and 155 deletions
+16 -14
View File
@@ -16,20 +16,22 @@ var logger = log.New("datasource")
// requiredURL contains the set of data sources that require a URL.
var requiredURL = map[string]bool{
datasources.DS_GRAPHITE: true,
datasources.DS_INFLUXDB: true,
datasources.DS_INFLUXDB_08: true,
datasources.DS_ES: true,
datasources.DS_PROMETHEUS: true,
datasources.DS_ALERTMANAGER: true,
datasources.DS_JAEGER: true,
datasources.DS_LOKI: true,
datasources.DS_OPENTSDB: true,
datasources.DS_TEMPO: true,
datasources.DS_ZIPKIN: true,
datasources.DS_MYSQL: true,
datasources.DS_POSTGRES: true,
datasources.DS_MSSQL: true,
datasources.DS_GRAPHITE: true,
datasources.DS_INFLUXDB: true,
datasources.DS_INFLUXDB_08: true,
datasources.DS_ES: true,
datasources.DS_PROMETHEUS: true,
datasources.DS_AMAZON_PROMETHEUS: true,
datasources.DS_AZURE_PROMETHEUS: true,
datasources.DS_ALERTMANAGER: true,
datasources.DS_JAEGER: true,
datasources.DS_LOKI: true,
datasources.DS_OPENTSDB: true,
datasources.DS_TEMPO: true,
datasources.DS_ZIPKIN: true,
datasources.DS_MYSQL: true,
datasources.DS_POSTGRES: true,
datasources.DS_MSSQL: true,
}
// URLValidationError represents an error from validating a data source URL.
+1 -1
View File
@@ -551,7 +551,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug
dsDTO.Database = ds.Database
}
if ds.Type == datasources.DS_PROMETHEUS {
if ds.Type == datasources.DS_PROMETHEUS || ds.Type == datasources.DS_AMAZON_PROMETHEUS || ds.Type == datasources.DS_AZURE_PROMETHEUS {
// add unproxied server URL for link to Prometheus web UI
ds.JsonData.Set("directUrl", ds.URL)
}
+1 -1
View File
@@ -313,7 +313,7 @@ func (proxy *DataSourceProxy) validateRequest() error {
}
// Trailing validation below this point for routes that were not matched
if proxy.ds.Type == datasources.DS_PROMETHEUS {
if proxy.ds.Type == datasources.DS_PROMETHEUS || proxy.ds.Type == datasources.DS_AMAZON_PROMETHEUS || proxy.ds.Type == datasources.DS_AZURE_PROMETHEUS {
if proxy.ctx.Req.Method == "DELETE" {
return errors.New("non allow-listed DELETEs not allowed on proxied Prometheus datasource")
}
+1 -1
View File
@@ -182,7 +182,7 @@ func getResponseFrame(logger *log.ConcreteLogger, resp *backend.QueryDataRespons
}
func isAllFrameVectors(datasourceType string, frames data.Frames) bool {
if datasourceType != datasources.DS_PROMETHEUS {
if datasourceType != datasources.DS_PROMETHEUS && datasourceType != datasources.DS_AMAZON_PROMETHEUS && datasourceType != datasources.DS_AZURE_PROMETHEUS {
return false
}
allVector := false
@@ -46,12 +46,26 @@ func (s *Service) detectPrometheusVariants(ctx context.Context) (map[string]int6
s.log.Error("Failed to read all Prometheus data sources", "error", err)
return nil, err
}
dsAmazonProm := &datasources.GetDataSourcesByTypeQuery{Type: "grafana-amazonprometheus-datasource"}
dataSourcesAmazonProm, err := s.datasources.GetDataSourcesByType(ctx, dsAmazonProm)
if err != nil {
s.log.Error("Failed to read all Amazon Prometheus data sources", "error", err)
return nil, err
}
dsAzureProm := &datasources.GetDataSourcesByTypeQuery{Type: "grafana-azureprometheus-datasource"}
dataSourcesAzureProm, err := s.datasources.GetDataSourcesByType(ctx, dsAzureProm)
if err != nil {
s.log.Error("Failed to read all Azure Prometheus data sources", "error", err)
return nil, err
}
allPromDataSources := append(append(dataSources, dataSourcesAmazonProm...), dataSourcesAzureProm...)
g, ctx := errgroup.WithContext(ctx)
g.SetLimit(10)
flavors := sync.Map{}
for _, ds := range dataSources {
for _, ds := range allPromDataSources {
ds := ds
g.Go(func() error {
variant, err := s.detectPrometheusVariant(ctx, ds)
@@ -32,6 +32,18 @@ func TestDetectPrometheusVariant(t *testing.T) {
}))
t.Cleanup(cortex.Close)
// Amazon Prometheus is Cortex-like
amazonPrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
t.Cleanup(amazonPrometheus.Close)
// Azure Prometheus is Cortex-like
azurePrometheus := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
t.Cleanup(azurePrometheus.Close)
sqlStore := dbtest.NewFakeDB()
statsService := statstest.NewFakeService()
s := createService(
@@ -80,6 +92,26 @@ func TestDetectPrometheusVariant(t *testing.T) {
Access: "proxy",
URL: cortex.URL,
},
{
ID: 5,
UID: "amazon-prometheus",
OrgID: 1,
Version: 1,
Name: "Amazon Prometheus",
Type: "prometheus",
Access: "proxy",
URL: amazonPrometheus.URL,
},
{
ID: 6,
UID: "azure-prometheus",
OrgID: 1,
Version: 1,
Name: "Azure Prometheus",
Type: "prometheus",
Access: "proxy",
URL: azurePrometheus.URL,
},
}}),
)
@@ -88,5 +120,5 @@ func TestDetectPrometheusVariant(t *testing.T) {
assert.Equal(t, int64(2), flavors["mimir"])
assert.Equal(t, int64(1), flavors["vanilla"])
assert.Equal(t, int64(1), flavors["cortex-like"])
assert.Equal(t, int64(3), flavors["cortex-like"])
}
+23 -21
View File
@@ -11,27 +11,29 @@ import (
)
const (
DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy"
DS_ALERTMANAGER = "alertmanager"
DS_AZURE_MONITOR = "grafana-azure-monitor-datasource"
DS_DYNATRACE = "grafana-dynatrace-datasource"
DS_ES = "elasticsearch"
DS_ES_OPEN_DISTRO = "grafana-es-open-distro-datasource"
DS_ES_OPENSEARCH = "grafana-opensearch-datasource"
DS_GRAPHITE = "graphite"
DS_INFLUXDB = "influxdb"
DS_INFLUXDB_08 = "influxdb_08"
DS_JAEGER = "jaeger"
DS_LOKI = "loki"
DS_MSSQL = "mssql"
DS_MYSQL = "mysql"
DS_OPENTSDB = "opentsdb"
DS_POSTGRES = "grafana-postgresql-datasource"
DS_PROMETHEUS = "prometheus"
DS_TEMPO = "tempo"
DS_TESTDATA = "grafana-testdata-datasource"
DS_ZIPKIN = "zipkin"
DS_ACCESS_DIRECT = "direct"
DS_ACCESS_PROXY = "proxy"
DS_ALERTMANAGER = "alertmanager"
DS_AZURE_MONITOR = "grafana-azure-monitor-datasource"
DS_DYNATRACE = "grafana-dynatrace-datasource"
DS_ES = "elasticsearch"
DS_ES_OPEN_DISTRO = "grafana-es-open-distro-datasource"
DS_ES_OPENSEARCH = "grafana-opensearch-datasource"
DS_GRAPHITE = "graphite"
DS_INFLUXDB = "influxdb"
DS_INFLUXDB_08 = "influxdb_08"
DS_JAEGER = "jaeger"
DS_LOKI = "loki"
DS_MSSQL = "mssql"
DS_MYSQL = "mysql"
DS_OPENTSDB = "opentsdb"
DS_POSTGRES = "grafana-postgresql-datasource"
DS_PROMETHEUS = "prometheus"
DS_AMAZON_PROMETHEUS = "grafana-amazonprometheus-datasource"
DS_AZURE_PROMETHEUS = "grafana-azureprometheus-datasource"
DS_TEMPO = "tempo"
DS_TESTDATA = "grafana-testdata-datasource"
DS_ZIPKIN = "zipkin"
// CustomHeaderName is the prefix that is used to store the name of a custom header.
CustomHeaderName = "httpHeaderName"
// CustomHeaderValue is the prefix that is used to store the value of a custom header.
@@ -922,7 +922,7 @@ func awsServiceNamespace(dsType string, jsonData *simplejson.Json) string {
} else {
return "es"
}
case datasources.DS_PROMETHEUS, datasources.DS_ALERTMANAGER:
case datasources.DS_PROMETHEUS, datasources.DS_AMAZON_PROMETHEUS, datasources.DS_ALERTMANAGER:
return "aps"
default:
panic(fmt.Sprintf("Unsupported datasource %q", dsType))
@@ -1000,6 +1000,11 @@ func TestService_awsServiceNamespace(t *testing.T) {
givenDs: datasources.DS_PROMETHEUS,
givenJson: `{ "sigV4Auth": true, "serverless": true }`,
want: "aps",
}, {
desc: "amazon prometheus",
givenDs: datasources.DS_AMAZON_PROMETHEUS,
givenJson: `{ "sigV4Auth": true }`,
want: "aps",
}, {
desc: "alertmanager",
givenDs: datasources.DS_ALERTMANAGER,
@@ -1011,6 +1016,12 @@ func TestService_awsServiceNamespace(t *testing.T) {
givenJson: `{ "sigV4Auth": true, "serverless": true }`,
want: "aps",
panic: true,
}, {
desc: "azure prometheus",
givenDs: datasources.DS_AZURE_PROMETHEUS,
givenJson: `{ "sigV4Auth": true }`,
want: "aps",
panic: true,
},
}
for _, tc := range testCases {
+16 -9
View File
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/web"
)
@@ -15,16 +16,16 @@ type promEndpoints struct {
rules, alerts string
}
var dsTypeToLotexRoutes = map[string]promEndpoints{
"prometheus": {
var (
prometheusEndpoints = promEndpoints{
rules: "/api/v1/rules",
alerts: "/api/v1/alerts",
},
"loki": {
}
lokiEndpoints = promEndpoints{
rules: "/prometheus/api/v1/rules",
alerts: "/prometheus/api/v1/alerts",
},
}
}
)
type LotexProm struct {
log log.Logger
@@ -91,9 +92,15 @@ func (p *LotexProm) getEndpoints(ctx *contextmodel.ReqContext) (*promEndpoints,
return nil, fmt.Errorf("URL for this data source is empty")
}
routes, ok := dsTypeToLotexRoutes[ds.Type]
if !ok {
return nil, fmt.Errorf("unexpected datasource type. expecting loki or prometheus")
var routes promEndpoints
switch {
case isPrometheusCompatible(ds.Type):
routes = prometheusEndpoints
case ds.Type == datasources.DS_LOKI:
routes = lokiEndpoints
default:
return nil, unexpectedDatasourceTypeError(ds.Type, "loki, prometheus, amazon prometheus, azure prometheus")
}
return &routes, nil
}
@@ -0,0 +1,98 @@
package api
import (
"errors"
"net/http"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/web"
)
func TestLotexProm_GetEndpoints(t *testing.T) {
tc := []struct {
name string
namedParams map[string]string
datasourceCache datasources.CacheService
expectedRoutes *promEndpoints
err error
}{
{
name: "with an empty datasource UID",
namedParams: map[string]string{":DatasourceUID": ""},
err: errors.New("datasource UID is invalid"),
},
{
name: "with an error while trying to fetch the datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{err: datasources.ErrDataSourceNotFound},
err: errors.New("data source not found"),
},
{
name: "with an empty datasource URL",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{}},
err: errors.New("URL for this data source is empty"),
},
{
name: "with an unsupported datasource type",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: "unsupported-type"}},
err: errors.New("unexpected datasource type 'unsupported-type', expected loki, prometheus, amazon prometheus, azure prometheus"),
},
{
name: "with a Loki datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_LOKI}},
expectedRoutes: &lokiEndpoints,
err: nil,
},
{
name: "with a Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://prom.com", Type: datasources.DS_PROMETHEUS}},
expectedRoutes: &prometheusEndpoints,
err: nil,
},
{
name: "with an Amazon Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://amp.com", Type: datasources.DS_AMAZON_PROMETHEUS}},
expectedRoutes: &prometheusEndpoints,
err: nil,
},
{
name: "with an Azure Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://azp.com", Type: datasources.DS_AZURE_PROMETHEUS}},
expectedRoutes: &prometheusEndpoints,
err: nil,
},
}
for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
proxy := &AlertingProxy{DataProxy: &datasourceproxy.DataSourceProxyService{DataSourceCache: tt.datasourceCache}}
prom := &LotexProm{AlertingProxy: proxy, log: log.NewNopLogger()}
// Setup request context.
httpReq, err := http.NewRequest(http.MethodGet, "http://grafanacloud.com", nil)
require.NoError(t, err)
ctx := &contextmodel.ReqContext{Context: &web.Context{Req: web.SetURLParams(httpReq, tt.namedParams)}}
endpoints, err := prom.getEndpoints(ctx)
if tt.err != nil {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expectedRoutes, endpoints)
}
})
}
}
+10 -12
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/web"
)
@@ -23,9 +24,6 @@ const (
)
const (
PrometheusDatasourceType = "prometheus"
LokiDatasourceType = "loki"
mimirPrefix = "/config/v1/rules"
prometheusPrefix = "/rules"
lokiPrefix = "/api/prom/rules"
@@ -33,11 +31,6 @@ const (
subtypeQuery = "subtype"
)
var dsTypeToRulerPrefix = map[string]string{
PrometheusDatasourceType: prometheusPrefix,
LokiDatasourceType: lokiPrefix,
}
var subtypeToPrefix = map[string]string{
Prometheus: prometheusPrefix,
Cortex: prometheusPrefix,
@@ -237,13 +230,18 @@ func (r *LotexRuler) validateAndGetPrefix(ctx *contextmodel.ReqContext) (string,
return "", fmt.Errorf("URL for this data source is empty")
}
prefix, ok := dsTypeToRulerPrefix[ds.Type]
if !ok {
return "", fmt.Errorf("unexpected datasource type. expecting loki or prometheus")
var prefix string
switch {
case isPrometheusCompatible(ds.Type):
prefix = prometheusPrefix
case ds.Type == datasources.DS_LOKI:
prefix = lokiPrefix
default:
return "", unexpectedDatasourceTypeError(ds.Type, "loki, prometheus, amazon prometheus, azure prometheus")
}
// If the datasource is Loki, there's nothing else for us to do - it doesn't have subtypes.
if ds.Type == LokiDatasourceType {
if ds.Type == datasources.DS_LOKI {
return prefix, nil
}
+42 -16
View File
@@ -49,48 +49,74 @@ func TestLotexRuler_ValidateAndGetPrefix(t *testing.T) {
{
name: "with an unsupported datasource type",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com"}},
err: errors.New("unexpected datasource type. expecting loki or prometheus"),
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: "unsupported-type"}},
err: errors.New("unexpected datasource type 'unsupported-type', expected loki, prometheus, amazon prometheus, azure prometheus"),
},
{
name: "with a Loki datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: LokiDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_LOKI}},
expected: "/api/prom/rules",
},
{
name: "with a Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: PrometheusDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_PROMETHEUS}},
expected: "/rules",
},
{
name: "with an Amazon Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://amp.com", Type: datasources.DS_AMAZON_PROMETHEUS}},
expected: "/rules",
},
{
name: "with an Azure Prometheus datasource",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://azp.com", Type: datasources.DS_AZURE_PROMETHEUS}},
expected: "/rules",
},
{
name: "with a Prometheus datasource and subtype of Cortex",
namedParams: map[string]string{":DatasourceUID": "d164"},
urlParams: "?subtype=cortex",
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: PrometheusDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_PROMETHEUS}},
expected: "/rules",
},
{
name: "with a Prometheus datasource and subtype of Mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
urlParams: "?subtype=mimir",
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: PrometheusDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_PROMETHEUS}},
expected: "/config/v1/rules",
},
{
name: "with a Prometheus datasource and subtype of Prometheus",
namedParams: map[string]string{":DatasourceUID": "d164"},
urlParams: "?subtype=prometheus",
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: PrometheusDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_PROMETHEUS}},
expected: "/rules",
},
{
name: "with a Prometheus datasource and no subtype",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: PrometheusDatasourceType}},
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://loki.com", Type: datasources.DS_PROMETHEUS}},
expected: "/rules",
},
{
name: "with an Amazon Prometheus datasource and subtype of Mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
urlParams: "?subtype=mimir",
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://amp.com", Type: datasources.DS_AMAZON_PROMETHEUS}},
expected: "/config/v1/rules",
},
{
name: "with an Azure Prometheus datasource and subtype of Mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
urlParams: "?subtype=mimir",
datasourceCache: fakeCacheService{datasource: &datasources.DataSource{URL: "http://azp.com", Type: datasources.DS_AZURE_PROMETHEUS}},
expected: "/config/v1/rules",
},
}
for _, tt := range tc {
@@ -149,7 +175,7 @@ func TestLotexRuler_RouteDeleteNamespaceRulesConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace%2Fwith%2Fslashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
{
name: "with a namespace that does not need to be escaped",
@@ -157,7 +183,7 @@ func TestLotexRuler_RouteDeleteNamespaceRulesConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace_without_slashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
}
@@ -210,7 +236,7 @@ func TestLotexRuler_RouteDeleteRuleGroupConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace%2Fwith%2Fslashes/group%2Fwith%2Fslashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
{
name: "with a namespace that does not need to be escaped",
@@ -219,7 +245,7 @@ func TestLotexRuler_RouteDeleteRuleGroupConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace_without_slashes/group_without_slashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
}
@@ -271,7 +297,7 @@ func TestLotexRuler_RouteGetNamespaceRulesConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace%2Fwith%2Fslashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
{
name: "with a namespace that does not need to be escaped",
@@ -279,7 +305,7 @@ func TestLotexRuler_RouteGetNamespaceRulesConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace_without_slashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
}
@@ -332,7 +358,7 @@ func TestLotexRuler_RouteGetRulegGroupConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace%2Fwith%2Fslashes/group%2Fwith%2Fslashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
{
name: "with a namespace that does not need to be escaped",
@@ -341,7 +367,7 @@ func TestLotexRuler_RouteGetRulegGroupConfig(t *testing.T) {
expected: "http://mimir.com/config/v1/rules/namespace_without_slashes/group_without_slashes?subtype=mimir",
urlParams: "?subtype=mimir",
namedParams: map[string]string{":DatasourceUID": "d164"},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: PrometheusDatasourceType},
datasource: &datasources.DataSource{URL: "http://mimir.com", Type: datasources.DS_PROMETHEUS},
},
}
+24 -3
View File
@@ -31,7 +31,28 @@ const (
groupQueryTag = "QUERY_GROUP"
)
var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
var (
searchRegex = regexp.MustCompile(`\{(\w+)\}`)
prometheusCompatibleDsTypes = []string{
datasources.DS_PROMETHEUS,
datasources.DS_AMAZON_PROMETHEUS,
datasources.DS_AZURE_PROMETHEUS,
}
)
func isPrometheusCompatible(dsType string) bool {
for _, t := range prometheusCompatibleDsTypes {
if dsType == t {
return true
}
}
return false
}
func isLotexRulerCompatible(dsType string) bool {
return dsType == datasources.DS_LOKI || isPrometheusCompatible(dsType)
}
func toMacaronPath(path string) string {
return string(searchRegex.ReplaceAllFunc([]byte(path), func(s []byte) []byte {
@@ -52,8 +73,8 @@ func getDatasourceByUID(ctx *contextmodel.ReqContext, cache datasources.CacheSer
return nil, unexpectedDatasourceTypeError(ds.Type, "alertmanager")
}
case apimodels.LoTexRulerBackend:
if ds.Type != "loki" && ds.Type != "prometheus" {
return nil, unexpectedDatasourceTypeError(ds.Type, "loki, prometheus")
if !isLotexRulerCompatible(ds.Type) {
return nil, unexpectedDatasourceTypeError(ds.Type, "loki, prometheus, amazon prometheus, azure prometheus")
}
default:
return nil, unexpectedDatasourceTypeError(ds.Type, expectedType.String())
+83
View File
@@ -13,6 +13,7 @@ import (
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/auth"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
models2 "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/org"
@@ -176,3 +177,85 @@ func (r *recordingConditionValidator) Validate(_ eval.EvaluationContext, conditi
}
var _ ConditionValidator = &recordingConditionValidator{}
func TestIsPrometheusCompatible(t *testing.T) {
testCases := []struct {
name string
dsType string
expected bool
}{
{
name: "prometheus datasource should be compatible",
dsType: datasources.DS_PROMETHEUS,
expected: true,
},
{
name: "amazon prometheus datasource should be compatible",
dsType: datasources.DS_AMAZON_PROMETHEUS,
expected: true,
},
{
name: "azure prometheus datasource should be compatible",
dsType: datasources.DS_AZURE_PROMETHEUS,
expected: true,
},
{
name: "loki datasource should not be prometheus compatible",
dsType: datasources.DS_LOKI,
expected: false,
},
{
name: "other datasource types should not be compatible",
dsType: "some-other-datasource",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := isPrometheusCompatible(tc.dsType)
assert.Equal(t, tc.expected, result)
})
}
}
func TestIsLotexRulerCompatible(t *testing.T) {
testCases := []struct {
name string
dsType string
expected bool
}{
{
name: "prometheus datasource should be compatible",
dsType: datasources.DS_PROMETHEUS,
expected: true,
},
{
name: "amazon prometheus datasource should be compatible",
dsType: datasources.DS_AMAZON_PROMETHEUS,
expected: true,
},
{
name: "azure prometheus datasource should be compatible",
dsType: datasources.DS_AZURE_PROMETHEUS,
expected: true,
},
{
name: "loki datasource should be compatible",
dsType: datasources.DS_LOKI,
expected: true,
},
{
name: "other datasource types should not be compatible",
dsType: "some-other-datasource",
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := isLotexRulerCompatible(tc.dsType)
assert.Equal(t, tc.expected, result)
})
}
}