diff --git a/go.mod b/go.mod index 2701c7c699f..d26627f9b3b 100644 --- a/go.mod +++ b/go.mod @@ -345,7 +345,7 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) -replace github.com/prometheus/prometheus => github.com/grafana/mimir-prometheus v1.8.2-0.20250715103124-73cbc98c25ff +replace github.com/prometheus/prometheus => github.com/grafana/mimir-prometheus v1.8.2-0.20250716123832-f18c798bd04b // Replace memberlist with our fork which includes some fixes that haven't been // merged upstream yet: @@ -372,7 +372,7 @@ replace github.com/opentracing-contrib/go-stdlib => github.com/grafana/opentraci replace github.com/opentracing-contrib/go-grpc => github.com/charleskorn/go-grpc v0.0.0-20231024023642-e9298576254f // Replacing prometheus/alertmanager with our fork. -replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 +replace github.com/prometheus/alertmanager => github.com/juliusmh/alertmanager v0.26.1-0.20250716125725-19a9223bec8c // Use Mimir fork of prometheus/otlptranslator to allow for higher velocity of upstream development, // while allowing Mimir to move at a more conservative pace. diff --git a/go.sum b/go.sum index d77ea51809e..3ef0aa894e6 100644 --- a/go.sum +++ b/go.sum @@ -579,14 +579,12 @@ github.com/grafana/memberlist v0.3.1-0.20250428154222-f7d51a6f6700 h1:0t7iOQ5ZkB github.com/grafana/memberlist v0.3.1-0.20250428154222-f7d51a6f6700/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= github.com/grafana/mimir-otlptranslator v0.0.0-20250703083430-c31a9568ad96 h1:kq5zJVW9LyFOB5xCeQPTON2HNjwwEkefhegZXGIhQPk= github.com/grafana/mimir-otlptranslator v0.0.0-20250703083430-c31a9568ad96/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= -github.com/grafana/mimir-prometheus v1.8.2-0.20250715103124-73cbc98c25ff h1:u+lw876tigZBv4/jGIvDmOJ6uLmscQq8HURg38DaldM= -github.com/grafana/mimir-prometheus v1.8.2-0.20250715103124-73cbc98c25ff/go.mod h1:51sCkEHDpexFCGizYi1W4omiFaKThWThkquNPOxhwxQ= +github.com/grafana/mimir-prometheus v1.8.2-0.20250716123832-f18c798bd04b h1:qZM11g9GR8sibnedD0zt56uASxhxjnx/CMP/JAlCRZQ= +github.com/grafana/mimir-prometheus v1.8.2-0.20250716123832-f18c798bd04b/go.mod h1:51sCkEHDpexFCGizYi1W4omiFaKThWThkquNPOxhwxQ= github.com/grafana/opentracing-contrib-go-stdlib v0.0.0-20230509071955-f410e79da956 h1:em1oddjXL8c1tL0iFdtVtPloq2hRPen2MJQKoAWpxu0= github.com/grafana/opentracing-contrib-go-stdlib v0.0.0-20230509071955-f410e79da956/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg= github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grafana/regexp v0.0.0-20240531075221-3685f1377d7b h1:oMAq12GxTpwo9jxbnG/M4F/HdpwbibTaVoxNA0NZprY= @@ -721,6 +719,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/juliusmh/alertmanager v0.26.1-0.20250716125725-19a9223bec8c h1:VaNJy7gIh+FGGaDOmViA7bUsLeTVs10tQ5tCVXHbo6c= +github.com/juliusmh/alertmanager v0.26.1-0.20250716125725-19a9223bec8c/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= diff --git a/pkg/distributor/distributor.go b/pkg/distributor/distributor.go index 8da79f87512..69a686876c4 100644 --- a/pkg/distributor/distributor.go +++ b/pkg/distributor/distributor.go @@ -67,12 +67,6 @@ import ( var tracer = otel.Tracer("pkg/distributor") -func init() { - // Mimir doesn't support Prometheus' UTF-8 metric/label name scheme yet. - // nolint:staticcheck - model.NameValidationScheme = model.LegacyValidation -} - var ( // Validation errors. errInvalidTenantShardSize = errors.New("invalid tenant shard size, the value must be greater than or equal to zero") @@ -1080,6 +1074,8 @@ func (d *Distributor) prePushHaDedupeMiddleware(next PushFunc) PushFunc { } } +// prePushRelabelMiddleware relabels the metric. Validation of those labels is +// handled in prePushValidationMiddleware. func (d *Distributor) prePushRelabelMiddleware(next PushFunc) PushFunc { return func(ctx context.Context, pushReq *Request) error { next, maybeCleanup := NextOrCleanup(next, pushReq) diff --git a/pkg/distributor/validate.go b/pkg/distributor/validate.go index 54f2d205f6c..f729764cfbc 100644 --- a/pkg/distributor/validate.go +++ b/pkg/distributor/validate.go @@ -390,6 +390,7 @@ type labelValidationConfig interface { MaxLabelNamesPerInfoSeries(userID string) int MaxLabelNameLength(userID string) int MaxLabelValueLength(userID string) int + ValidationScheme(userID string) model.ValidationScheme } func removeNonASCIIChars(in string) (out string) { @@ -421,7 +422,9 @@ func validateLabels(m *sampleValidationMetrics, cfg labelValidationConfig, userI return errors.New(noMetricNameMsgFormat) } - if !model.IsValidMetricName(model.LabelValue(unsafeMetricName)) { + validationScheme := cfg.ValidationScheme(userID) + + if !isValidMetricName(unsafeMetricName, validationScheme) { cat.IncrementDiscardedSamples(ls, 1, reasonInvalidMetricName, ts) m.invalidMetricName.WithLabelValues(userID, group).Inc() return fmt.Errorf(invalidMetricNameMsgFormat, removeNonASCIIChars(unsafeMetricName)) @@ -447,7 +450,7 @@ func validateLabels(m *sampleValidationMetrics, cfg labelValidationConfig, userI maxLabelValueLength := cfg.MaxLabelValueLength(userID) lastLabelName := "" for _, l := range ls { - if !skipLabelValidation && !model.LabelName(l.Name).IsValid() { + if !skipLabelValidation && !isValidLabelName(l.Name, validationScheme) { m.invalidLabel.WithLabelValues(userID, group).Inc() cat.IncrementDiscardedSamples(ls, 1, reasonInvalidLabel, ts) return fmt.Errorf(invalidLabelMsgFormat, l.Name, mimirpb.FromLabelAdaptersToString(ls)) @@ -474,6 +477,28 @@ func validateLabels(m *sampleValidationMetrics, cfg labelValidationConfig, userI return nil } +func isValidMetricName(name string, validationScheme model.ValidationScheme) bool { + switch validationScheme { + case model.LegacyValidation: + return model.IsValidLegacyMetricName(name) + case model.UTF8Validation: + return model.IsValidMetricName(model.LabelValue(name)) + default: + return false + } +} + +func isValidLabelName(name string, validationScheme model.ValidationScheme) bool { + switch validationScheme { + case model.LegacyValidation: + return model.LabelName(name).IsValidLegacy() + case model.UTF8Validation: + return model.LabelName(name).IsValid() + default: + return false + } +} + // metadataValidationMetrics is a collection of metrics used by metadata validation. type metadataValidationMetrics struct { missingMetricName *prometheus.CounterVec diff --git a/pkg/distributor/validate_test.go b/pkg/distributor/validate_test.go index 9945cedf2c0..42a866f6690 100644 --- a/pkg/distributor/validate_test.go +++ b/pkg/distributor/validate_test.go @@ -37,6 +37,7 @@ type validateLabelsCfg struct { maxLabelNamesPerInfoSeries int maxLabelNameLength int maxLabelValueLength int + validationScheme model.ValidationScheme } func (v validateLabelsCfg) MaxLabelNamesPerSeries(_ string) int { @@ -55,6 +56,10 @@ func (v validateLabelsCfg) MaxLabelValueLength(_ string) int { return v.maxLabelValueLength } +func (v validateLabelsCfg) ValidationScheme(_ string) model.ValidationScheme { + return v.validationScheme +} + type validateMetadataCfg struct { enforceMetadataMetricName bool maxMetadataLength int @@ -73,13 +78,19 @@ func TestValidateLabels(t *testing.T) { reg := prometheus.NewPedanticRegistry() s := newSampleValidationMetrics(reg) - var cfg validateLabelsCfg userID := "testUser" + legacyConfig := validateLabelsCfg{ + maxLabelNamesPerSeries: 3, + maxLabelNamesPerInfoSeries: 4, + maxLabelNameLength: 25, + maxLabelValueLength: 25, + validationScheme: model.LegacyValidation, + } + utf8Config := legacyConfig + utf8Config.validationScheme = model.UTF8Validation + unsetConfig := legacyConfig + unsetConfig.validationScheme = model.UnsetValidation - cfg.maxLabelValueLength = 25 - cfg.maxLabelNameLength = 25 - cfg.maxLabelNamesPerSeries = 3 - cfg.maxLabelNamesPerInfoSeries = 4 limits := catestutils.NewMockCostAttributionLimits(0, userID, "team") careg := prometheus.NewRegistry() manager, err := costattribution.NewManager(5*time.Second, 10*time.Second, log.NewNopLogger(), limits, reg, careg) @@ -90,30 +101,35 @@ func TestValidateLabels(t *testing.T) { metric model.Metric skipLabelNameValidation bool skipLabelCountValidation bool + config validateLabelsCfg err error }{ { metric: map[model.LabelName]model.LabelValue{"team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: errors.New(noMetricNameMsgFormat), }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: " ", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf(invalidMetricNameMsgFormat, " "), }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "metric_name_with_\xb0_invalid_utf8_\xb0", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf(invalidMetricNameMsgFormat, "metric_name_with__invalid_utf8_ (non-ascii characters removed)"), }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "valid", "foo ": "bar", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf( invalidLabelMsgFormat, "foo ", @@ -130,12 +146,14 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "valid", "team": "c"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: nil, }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "badLabelName", "this_is_a_really_really_long_name_that_should_cause_an_error": "test_value_please_ignore", "team": "biz"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf( labelNameTooLongMsgFormat, "this_is_a_really_really_long_name_that_should_cause_an_error", @@ -152,6 +170,7 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "badLabelValue", "much_shorter_name": "test_value_please_ignore_no_really_nothing_to_see_here", "team": "biz"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: LabelValueTooLongError{ Label: mimirpb.LabelAdapter{Name: "much_shorter_name", Value: "test_value_please_ignore_no_really_nothing_to_see_here"}, Limit: 25, @@ -166,6 +185,7 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "bar": "baz", "blip": "blop", "team": "plof"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf( tooManyLabelsMsgFormat, tooManyLabelsArgs( @@ -184,6 +204,7 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo_info", "bar": "baz", "blip": "blop", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: nil, }, { @@ -191,6 +212,7 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo_info", "bar": "baz", "blip": "blop", "blap": "blup", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf( tooManyInfoLabelsMsgFormat, tooManyLabelsArgs( @@ -209,24 +231,28 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "bar": "baz", "blip": "blop", "team": "a"}, skipLabelNameValidation: false, skipLabelCountValidation: true, + config: legacyConfig, err: nil, }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "invalid%label&name": "bar", "team": "biz"}, skipLabelNameValidation: true, skipLabelCountValidation: false, + config: legacyConfig, err: nil, }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "label1": "你好", "team": "plof"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: nil, }, { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "label1": "abc\xfe\xfddef", "team": "plof"}, skipLabelNameValidation: false, skipLabelCountValidation: false, + config: legacyConfig, err: fmt.Errorf( invalidLabelValueMsgFormat, "label1", "abc\ufffddef", "foo", @@ -236,10 +262,46 @@ func TestValidateLabels(t *testing.T) { metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "label1": "abc\xfe\xfddef"}, skipLabelNameValidation: true, skipLabelCountValidation: false, + config: legacyConfig, + err: nil, + }, + { + metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "name😀": "value", "team": "b"}, + skipLabelNameValidation: false, + skipLabelCountValidation: false, + config: legacyConfig, + err: fmt.Errorf( + invalidLabelMsgFormat, + "name😀", + mimirpb.FromLabelAdaptersToString( + []mimirpb.LabelAdapter{ + {Name: model.MetricNameLabel, Value: "foo"}, + {Name: "name😀", Value: "value"}, + {Name: "team", Value: "b"}, + }, + ), + ), + }, + // Unset validation scheme results in invalid metric name error + { + metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "name😀": "value", "team": "b"}, + skipLabelNameValidation: false, + skipLabelCountValidation: false, + config: unsetConfig, + err: fmt.Errorf( + invalidMetricNameMsgFormat, + "foo", + ), + }, + { + metric: map[model.LabelName]model.LabelValue{model.MetricNameLabel: "foo", "name😀": "value", "team": "b"}, + skipLabelNameValidation: false, + skipLabelCountValidation: false, + config: utf8Config, err: nil, }, } { - err := validateLabels(s, cfg, userID, "custom label", mimirpb.FromMetricsToLabelAdapters(c.metric), c.skipLabelNameValidation, c.skipLabelCountValidation, cast, ts) + err := validateLabels(s, c.config, userID, "custom label", mimirpb.FromMetricsToLabelAdapters(c.metric), c.skipLabelNameValidation, c.skipLabelCountValidation, cast, ts) assert.Equal(t, c.err, err, "wrong error") } @@ -249,13 +311,13 @@ func TestValidateLabels(t *testing.T) { require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` # HELP cortex_discarded_samples_total The total number of samples that were discarded. # TYPE cortex_discarded_samples_total counter - cortex_discarded_samples_total{group="custom label",reason="label_invalid",user="testUser"} 1 + cortex_discarded_samples_total{group="custom label",reason="label_invalid",user="testUser"} 2 cortex_discarded_samples_total{group="custom label",reason="label_name_too_long",user="testUser"} 1 cortex_discarded_samples_total{group="custom label",reason="label_value_invalid",user="testUser"} 1 cortex_discarded_samples_total{group="custom label",reason="label_value_too_long",user="testUser"} 1 cortex_discarded_samples_total{group="custom label",reason="max_label_names_per_series",user="testUser"} 1 cortex_discarded_samples_total{group="custom label",reason="max_label_names_per_info_series",user="testUser"} 1 - cortex_discarded_samples_total{group="custom label",reason="metric_name_invalid",user="testUser"} 2 + cortex_discarded_samples_total{group="custom label",reason="metric_name_invalid",user="testUser"} 3 cortex_discarded_samples_total{group="custom label",reason="missing_metric_name",user="testUser"} 1 cortex_discarded_samples_total{group="custom label",reason="random reason",user="different user"} 1 `), "cortex_discarded_samples_total")) @@ -264,12 +326,14 @@ func TestValidateLabels(t *testing.T) { # HELP cortex_discarded_attributed_samples_total The total number of samples that were discarded per attribution. # TYPE cortex_discarded_attributed_samples_total counter cortex_discarded_attributed_samples_total{reason="label_invalid",team="a",tenant="testUser",tracker="cost-attribution"} 1 + cortex_discarded_attributed_samples_total{reason="label_invalid",team="b",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="label_name_too_long",team="biz",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="label_value_invalid",team="plof",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="label_value_too_long",team="biz",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="max_label_names_per_info_series",team="a",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="max_label_names_per_series",team="plof",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="metric_name_invalid",team="a",tenant="testUser",tracker="cost-attribution"} 2 + cortex_discarded_attributed_samples_total{reason="metric_name_invalid",team="b",tenant="testUser",tracker="cost-attribution"} 1 cortex_discarded_attributed_samples_total{reason="missing_metric_name",team="a",tenant="testUser",tracker="cost-attribution"} 1 `), "cortex_discarded_attributed_samples_total")) @@ -451,6 +515,7 @@ func TestValidateLabelDuplication(t *testing.T) { cfg.maxLabelNameLength = 10 cfg.maxLabelNamesPerSeries = 10 cfg.maxLabelValueLength = 10 + cfg.validationScheme = model.UTF8Validation userID := "testUser" actual := validateLabels(newSampleValidationMetrics(nil), cfg, userID, "", []mimirpb.LabelAdapter{ diff --git a/pkg/frontend/querymiddleware/request_validation.go b/pkg/frontend/querymiddleware/request_validation.go index 6db11683dc0..3d05c5f2f74 100644 --- a/pkg/frontend/querymiddleware/request_validation.go +++ b/pkg/frontend/querymiddleware/request_validation.go @@ -7,15 +7,8 @@ import ( "net/http" "github.com/grafana/dskit/cancellation" - "github.com/prometheus/common/model" ) -func init() { - // Mimir doesn't support Prometheus' UTF-8 metric/label name scheme yet. - // nolint:staticcheck - model.NameValidationScheme = model.LegacyValidation -} - const requestValidationFailedFmt = "request validation failed for " var errMetricsQueryRequestValidationFailed = cancellation.NewErrorf( diff --git a/pkg/frontend/querymiddleware/request_validation_test.go b/pkg/frontend/querymiddleware/request_validation_test.go index 7d32570f0d5..54e0d9234af 100644 --- a/pkg/frontend/querymiddleware/request_validation_test.go +++ b/pkg/frontend/querymiddleware/request_validation_test.go @@ -208,9 +208,9 @@ func TestCardinalityQueryRequestValidationRoundTripper(t *testing.T) { expectedErrType: apierror.TypeBadData, }, { - // non-utf8 label name will be rejected even when we transition to UTF-8 label names + // non-legacy label name will be accepted url: cardinalityLabelValuesPathSuffix + "?label_names[]=\\xbd\\xb2\\x3d\\xbc\\x20\\xe2\\x8c\\x98", - expectedErrType: apierror.TypeBadData, + expectedErrType: "", }, { url: cardinalityLabelValuesPathSuffix + "?label_names[]=foo", diff --git a/pkg/mimir/modules.go b/pkg/mimir/modules.go index f8b6d25dff8..5b79e3d7a11 100644 --- a/pkg/mimir/modules.go +++ b/pkg/mimir/modules.go @@ -828,6 +828,9 @@ func (t *Mimir) initQueryFrontendTripperware() (serv services.Service, err error panic(fmt.Sprintf("invalid config not caught by validation: unknown PromQL engine '%s'", t.Cfg.Querier.QueryEngine)) } + // Wrap the query engine so that we can override ValidationScheme in promql.QueryOpts. + eng = streamingpromqlcompat.NameValidationEngine(eng, t.Overrides) + tripperware, err := querymiddleware.NewTripperware( t.Cfg.Frontend.QueryMiddleware, util_log.Logger, diff --git a/pkg/mimir/promexts.go b/pkg/mimir/promexts.go index cabc1122543..79356aaf6d9 100644 --- a/pkg/mimir/promexts.go +++ b/pkg/mimir/promexts.go @@ -3,14 +3,9 @@ package mimir import ( - "github.com/prometheus/common/model" - "github.com/grafana/mimir/pkg/util/promqlext" ) func init() { promqlext.ExtendPromQL() - // Mimir doesn't support Prometheus' UTF-8 metric/label name scheme yet. - // nolint:staticcheck - model.NameValidationScheme = model.LegacyValidation } diff --git a/pkg/querier/cardinality_analysis_handler_test.go b/pkg/querier/cardinality_analysis_handler_test.go index 433cad44f03..9f41af82ca7 100644 --- a/pkg/querier/cardinality_analysis_handler_test.go +++ b/pkg/querier/cardinality_analysis_handler_test.go @@ -706,8 +706,8 @@ func TestLabelValuesCardinalityHandler_ParseError(t *testing.T) { expectedErrorMessage: "'label_names[]' param is required", }, "label_names param is invalid": { - url: "/label_values?label_names[]=olá", - expectedErrorMessage: "invalid 'label_names' param 'olá'", + url: "/label_values?label_names[]=\xff\xfe", + expectedErrorMessage: "invalid 'label_names' param '\xff\xfe'", }, "multiple selector params are provided": { url: "/label_values?label_names[]=hello&selector=foo&selector=bar", diff --git a/pkg/querier/querier.go b/pkg/querier/querier.go index b4d46f8b634..2913f018d14 100644 --- a/pkg/querier/querier.go +++ b/pkg/querier/querier.go @@ -203,6 +203,7 @@ func New(cfg Config, limits *validation.Overrides, distributor Distributor, quer panic(fmt.Sprintf("invalid config not caught by validation: unknown PromQL engine '%s'", cfg.QueryEngine)) } + eng = compat.NameValidationEngine(eng, limits) return NewSampleAndChunkQueryable(lazyQueryable), exemplarQueryable, eng, nil } diff --git a/pkg/querier/stats_renderer_test.go b/pkg/querier/stats_renderer_test.go index bc9b12f3716..761f13dda06 100644 --- a/pkg/querier/stats_renderer_test.go +++ b/pkg/querier/stats_renderer_test.go @@ -13,7 +13,6 @@ import ( "github.com/grafana/dskit/user" "github.com/grafana/regexp" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" "github.com/prometheus/common/promslog" "github.com/prometheus/common/route" "github.com/prometheus/prometheus/config" @@ -26,12 +25,6 @@ import ( mimir_stats "github.com/grafana/mimir/pkg/querier/stats" ) -func init() { - // Mimir doesn't support Prometheus' UTF-8 metric/label name scheme yet. - // nolint:staticcheck - model.NameValidationScheme = model.LegacyValidation -} - func TestStatsRenderer(t *testing.T) { testCases := map[string]struct { diff --git a/pkg/ruler/api.go b/pkg/ruler/api.go index d61d75eb261..7acf33e4f7d 100644 --- a/pkg/ruler/api.go +++ b/pkg/ruler/api.go @@ -667,7 +667,7 @@ func (a *API) CreateRuleGroup(w http.ResponseWriter, req *http.Request) { return } - errs := a.ruler.manager.ValidateRuleGroup(rg, node) + errs := a.ruler.manager.ValidateRuleGroup(userID, rg, node) if len(errs) > 0 { e := []string{} for _, err := range errs { diff --git a/pkg/ruler/compat.go b/pkg/ruler/compat.go index 3a001060e33..a18749f9369 100644 --- a/pkg/ruler/compat.go +++ b/pkg/ruler/compat.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/dskit/user" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/exemplar" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" @@ -216,6 +217,7 @@ type RulesLimits interface { RulerMaxIndependentRuleEvaluationConcurrencyPerTenant(userID string) int64 RulerAlertmanagerClientConfig(userID string) notifierCfg.AlertmanagerClientConfig RulerMinRuleEvaluationInterval(userID string) time.Duration + ValidationScheme(userID string) model.ValidationScheme } func MetricsQueryFunc(qf rules.QueryFunc, userID string, queries, failedQueries *prometheus.CounterVec, remoteQuerier bool) rules.QueryFunc { diff --git a/pkg/ruler/manager.go b/pkg/ruler/manager.go index d78a0a77b48..704ef702efa 100644 --- a/pkg/ruler/manager.go +++ b/pkg/ruler/manager.go @@ -18,6 +18,7 @@ import ( "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/discovery" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/rulefmt" @@ -416,7 +417,7 @@ func (r *DefaultMultiTenantManager) Stop() { r.mapper.cleanup() } -func (r *DefaultMultiTenantManager) ValidateRuleGroup(g rulefmt.RuleGroup, node rulefmt.RuleGroupNode) []error { +func (r *DefaultMultiTenantManager) ValidateRuleGroup(userID string, g rulefmt.RuleGroup, node rulefmt.RuleGroupNode) []error { var errs []error if g.Name == "" { @@ -439,6 +440,8 @@ func (r *DefaultMultiTenantManager) ValidateRuleGroup(g rulefmt.RuleGroup, node errs = append(errs, fmt.Errorf("invalid rules configuration: rule group '%s' has both query_offset and (deprecated) evaluation_delay set, but to different values; please remove the deprecated evaluation_delay and use query_offset instead", g.Name)) } + validationScheme := r.limits.ValidationScheme(userID) + for i, r := range g.Rules { for _, err := range r.Validate(node.Rules[i]) { var ruleName string @@ -454,11 +457,56 @@ func (r *DefaultMultiTenantManager) ValidateRuleGroup(g rulefmt.RuleGroup, node Err: err, }) } + // Perform our (stricter) validation. + errs = append(errs, validateRule(r, validationScheme)...) } return errs } +// validateRule extends rulefmt.Rule.Validate but respects the given validationScheme. +func validateRule(r rulefmt.Rule, validationScheme model.ValidationScheme) []error { + var errs []error + if r.Record != "" { + if !isValidMetricName(r.Record, validationScheme) { + errs = append(errs, fmt.Errorf("invalid recording rule name: %s", r.Record)) + } + } + for k := range r.Labels { + if !isValidLabelName(k, validationScheme) || k == model.MetricNameLabel { + errs = append(errs, fmt.Errorf("invalid label name: %s", k)) + } + } + for k := range r.Annotations { + if !isValidLabelName(k, validationScheme) { + errs = append(errs, fmt.Errorf("invalid annotation name: %s", k)) + } + } + return errs +} + +func isValidMetricName(name string, validationScheme model.ValidationScheme) bool { + switch validationScheme { + case model.LegacyValidation: + return model.IsValidLegacyMetricName(name) + case model.UTF8Validation: + return model.IsValidMetricName(model.LabelValue(name)) + default: + return false + } +} + +func isValidLabelName(name string, validationScheme model.ValidationScheme) bool { + switch validationScheme { + case model.LegacyValidation: + return model.LabelName(name).IsValidLegacy() + case model.UTF8Validation: + return model.LabelName(name).IsValid() + default: + return false + } +} + // filterRuleGroupsByNotEmptyUsers filters out all the tenants that have no rule groups. // The returned removed map may be nil if no user was removed from the input configs. // diff --git a/pkg/ruler/ruler.go b/pkg/ruler/ruler.go index 13e7346659e..01e24c99e0e 100644 --- a/pkg/ruler/ruler.go +++ b/pkg/ruler/ruler.go @@ -291,7 +291,7 @@ type MultiTenantManager interface { Stop() // ValidateRuleGroup validates a rulegroup - ValidateRuleGroup(rulefmt.RuleGroup, rulefmt.RuleGroupNode) []error + ValidateRuleGroup(userID string, ruleGroup rulefmt.RuleGroup, ruleGroupNode rulefmt.RuleGroupNode) []error // Start evaluating rules. Start() diff --git a/pkg/streamingpromql/compat/name_validation_engine.go b/pkg/streamingpromql/compat/name_validation_engine.go new file mode 100644 index 00000000000..236cce4cde1 --- /dev/null +++ b/pkg/streamingpromql/compat/name_validation_engine.go @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package compat + +import ( + "context" + "time" + + "github.com/grafana/dskit/tenant" + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/storage" + + "github.com/grafana/mimir/pkg/util/validation" +) + +type nameValidationEngine struct { + engine promql.QueryEngine + limits *validation.Overrides +} + +// NameValidationEngine creates a new promql.QueryEngine that wraps engine and overrides query options +// with the name validation scheme from limits. +func NameValidationEngine(engine promql.QueryEngine, limits *validation.Overrides) promql.QueryEngine { + return &nameValidationEngine{engine: engine, limits: limits} +} + +type optsWithValidationScheme struct { + promql.QueryOpts + validationScheme model.ValidationScheme +} + +func (o optsWithValidationScheme) EnablePerStepStats() bool { + return o.QueryOpts.EnablePerStepStats() +} + +func (o optsWithValidationScheme) LookbackDelta() time.Duration { + return o.QueryOpts.LookbackDelta() +} + +func (o optsWithValidationScheme) ValidationScheme() model.ValidationScheme { + return o.validationScheme +} + +func (e nameValidationEngine) NewInstantQuery(ctx context.Context, q storage.Queryable, opts promql.QueryOpts, qs string, ts time.Time) (promql.Query, error) { + validationScheme, err := e.getValidationScheme(ctx) + if err != nil { + return nil, err + } + if opts == nil { + opts = promql.NewPrometheusQueryOpts(false, 0, model.UTF8Validation) + } + opts = &optsWithValidationScheme{ + QueryOpts: opts, + validationScheme: validationScheme, + } + return e.engine.NewInstantQuery(ctx, q, opts, qs, ts) +} + +func (e nameValidationEngine) NewRangeQuery(ctx context.Context, q storage.Queryable, opts promql.QueryOpts, qs string, start, end time.Time, interval time.Duration) (promql.Query, error) { + validationScheme, err := e.getValidationScheme(ctx) + if err != nil { + return nil, err + } + if opts == nil { + opts = promql.NewPrometheusQueryOpts(false, 0, model.UTF8Validation) + } + opts = &optsWithValidationScheme{ + QueryOpts: opts, + validationScheme: validationScheme, + } + return e.engine.NewRangeQuery(ctx, q, opts, qs, start, end, interval) +} + +// getValidationScheme retrieves the name validation scheme to use from a context containing tenant IDs. +// Returns legacy validation scheme if at least one tenant uses legacy validation. +func (e nameValidationEngine) getValidationScheme(ctx context.Context) (model.ValidationScheme, error) { + tenantIDs, err := tenant.TenantIDs(ctx) + if err != nil { + return model.UnsetValidation, err + } + for _, tenantID := range tenantIDs { + if e.limits.ValidationScheme(tenantID) == model.LegacyValidation { + return model.LegacyValidation, nil + } + } + return model.UTF8Validation, nil +} diff --git a/pkg/streamingpromql/engine_test.go b/pkg/streamingpromql/engine_test.go index f12ad73e7e6..367f486e4dd 100644 --- a/pkg/streamingpromql/engine_test.go +++ b/pkg/streamingpromql/engine_test.go @@ -22,6 +22,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/timestamp" @@ -3213,7 +3214,7 @@ func TestQueryStats(t *testing.T) { runQueryAndGetSamplesStats := func(t *testing.T, engine promql.QueryEngine, expr string, isInstantQuery bool) *promstats.QuerySamples { var q promql.Query var err error - opts := promql.NewPrometheusQueryOpts(true, 0) + opts := promql.NewPrometheusQueryOpts(true, 0, model.UTF8Validation) if isInstantQuery { q, err = engine.NewInstantQuery(context.Background(), storage, opts, expr, end) } else { @@ -3507,7 +3508,7 @@ func TestQueryStatsUpstreamTestCases(t *testing.T) { runQueryAndGetSamplesStats := func(t *testing.T, engine promql.QueryEngine, expr string, start, end time.Time, interval time.Duration) *promstats.QuerySamples { var q promql.Query var err error - opts := promql.NewPrometheusQueryOpts(true, 0) + opts := promql.NewPrometheusQueryOpts(true, 0, model.UTF8Validation) if interval == 0 { // Instant query @@ -3888,7 +3889,7 @@ func TestQueryStatementLookbackDelta(t *testing.T) { require.NoError(t, err) t.Run("lookback delta not set in query options", func(t *testing.T) { - queryOpts := promql.NewPrometheusQueryOpts(false, 0) + queryOpts := promql.NewPrometheusQueryOpts(false, 0, model.UTF8Validation) runTest(t, engine, queryOpts, defaultLookbackDelta) }) @@ -3897,7 +3898,7 @@ func TestQueryStatementLookbackDelta(t *testing.T) { }) t.Run("lookback delta set in query options", func(t *testing.T) { - queryOpts := promql.NewPrometheusQueryOpts(false, 14*time.Minute) + queryOpts := promql.NewPrometheusQueryOpts(false, 14*time.Minute, model.UTF8Validation) runTest(t, engine, queryOpts, 14*time.Minute) }) }) @@ -3909,7 +3910,7 @@ func TestQueryStatementLookbackDelta(t *testing.T) { require.NoError(t, err) t.Run("lookback delta not set in query options", func(t *testing.T) { - queryOpts := promql.NewPrometheusQueryOpts(false, 0) + queryOpts := promql.NewPrometheusQueryOpts(false, 0, model.UTF8Validation) runTest(t, engine, queryOpts, 12*time.Minute) }) @@ -3918,7 +3919,7 @@ func TestQueryStatementLookbackDelta(t *testing.T) { }) t.Run("lookback delta set in query options", func(t *testing.T) { - queryOpts := promql.NewPrometheusQueryOpts(false, 14*time.Minute) + queryOpts := promql.NewPrometheusQueryOpts(false, 14*time.Minute, model.UTF8Validation) runTest(t, engine, queryOpts, 14*time.Minute) }) }) diff --git a/pkg/streamingpromql/operators/aggregations/aggregation_test.go b/pkg/streamingpromql/operators/aggregations/aggregation_test.go index f26cb689b28..caf0a38c2ba 100644 --- a/pkg/streamingpromql/operators/aggregations/aggregation_test.go +++ b/pkg/streamingpromql/operators/aggregations/aggregation_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/histogram" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/timestamp" @@ -337,7 +338,16 @@ func TestAggregations_ReturnIncompleteGroupsOnEarlyClose(t *testing.T) { "count_values": { createOperator: func(inner types.InstantVectorOperator, queryTimeRange types.QueryTimeRange, memoryConsumptionTracker *limiter.MemoryConsumptionTracker) (types.InstantVectorOperator, error) { labelName := operators.NewStringLiteral("value", posrange.PositionRange{}) - return NewCountValues(inner, labelName, queryTimeRange, []string{"group"}, false, memoryConsumptionTracker, posrange.PositionRange{}), nil + return NewCountValues( + inner, + labelName, + queryTimeRange, + []string{"group"}, + false, + memoryConsumptionTracker, + posrange.PositionRange{}, + model.UTF8Validation, + ), nil }, instant: true, allowExpectedSeriesInAnyOrder: true, diff --git a/pkg/streamingpromql/operators/aggregations/count_values.go b/pkg/streamingpromql/operators/aggregations/count_values.go index 65e85bafb0f..a4aa6ef3343 100644 --- a/pkg/streamingpromql/operators/aggregations/count_values.go +++ b/pkg/streamingpromql/operators/aggregations/count_values.go @@ -32,6 +32,7 @@ type CountValues struct { expressionPosition posrange.PositionRange resolvedLabelName string + validationScheme model.ValidationScheme series [][]promql.FPoint @@ -51,6 +52,7 @@ func NewCountValues( without bool, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, expressionPosition posrange.PositionRange, + validationScheme model.ValidationScheme, ) *CountValues { if without { grouping = append(grouping, labels.MetricName) @@ -66,6 +68,7 @@ func NewCountValues( Without: without, MemoryConsumptionTracker: memoryConsumptionTracker, expressionPosition: expressionPosition, + validationScheme: validationScheme, } } @@ -154,10 +157,19 @@ func (c *CountValues) SeriesMetadata(ctx context.Context) ([]types.SeriesMetadat func (c *CountValues) loadLabelName() error { c.resolvedLabelName = c.LabelName.GetValue() - if !model.LabelName(c.resolvedLabelName).IsValid() { + labelName := model.LabelName(c.resolvedLabelName) + isValid := false + switch c.validationScheme { + case model.LegacyValidation: + isValid = labelName.IsValidLegacy() + case model.UTF8Validation: + isValid = labelName.IsValid() + default: + return fmt.Errorf("invalid validation scheme %q", c.validationScheme) + } + if !isValid { return fmt.Errorf("invalid label name %q", c.resolvedLabelName) } - return nil } diff --git a/pkg/streamingpromql/operators/aggregations/count_values_test.go b/pkg/streamingpromql/operators/aggregations/count_values_test.go index b6b13225e79..841abc1e3cc 100644 --- a/pkg/streamingpromql/operators/aggregations/count_values_test.go +++ b/pkg/streamingpromql/operators/aggregations/count_values_test.go @@ -6,6 +6,7 @@ import ( "context" "testing" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/promql" @@ -49,6 +50,11 @@ func TestCountValues_GroupLabelling(t *testing.T) { inputSeries: labels.FromStrings(labels.MetricName, "my_metric", "env", "prod", "foo", "bar"), expectedOutputSeries: labels.FromStrings("env", "prod", "value", "123"), }, + "grouping with 'by', single utf8 grouping label, input does have grouping label": { + grouping: []string{"env😀"}, + inputSeries: labels.FromStrings(labels.MetricName, "my_metric", "env😀", "prod", "foo", "bar"), + expectedOutputSeries: labels.FromStrings("env😀", "prod", "value", "123"), + }, "grouping with 'by', multiple grouping labels, input has only metric name": { grouping: []string{"cluster", "env"}, inputSeries: labels.FromStrings(labels.MetricName, "my_metric"), @@ -121,6 +127,12 @@ func TestCountValues_GroupLabelling(t *testing.T) { inputSeries: labels.FromStrings(labels.MetricName, "my_metric", "a-label", "a-value", "d-label", "d-value", "f-label", "f-value"), expectedOutputSeries: labels.FromStrings("a-label", "a-value", "d-label", "d-value", "f-label", "f-value", "value", "123"), }, + "grouping with 'without', single utf8 grouping label, input does have grouping label": { + grouping: []string{"env😀"}, + without: true, + inputSeries: labels.FromStrings(labels.MetricName, "my_metric", "env😀", "prod", "a-label", "a-value", "f-label", "f-value"), + expectedOutputSeries: labels.FromStrings("a-label", "a-value", "f-label", "f-value", "value", "123"), + }, "grouping with 'without', multiple grouping labels, input has some grouping labels": { grouping: []string{"cluster", "env"}, without: true, @@ -222,7 +234,16 @@ func TestCountValues_GroupLabelling(t *testing.T) { } labelName := operators.NewStringLiteral("value", posrange.PositionRange{}) - aggregator := NewCountValues(inner, labelName, types.NewInstantQueryTimeRange(timestamp.Time(0)), testCase.grouping, testCase.without, memoryConsumptionTracker, posrange.PositionRange{}) + aggregator := NewCountValues( + inner, + labelName, + types.NewInstantQueryTimeRange(timestamp.Time(0)), + testCase.grouping, + testCase.without, + memoryConsumptionTracker, + posrange.PositionRange{}, + model.UTF8Validation, + ) metadata, err := aggregator.SeriesMetadata(context.Background()) require.NoError(t, err) diff --git a/pkg/streamingpromql/operators/functions/factories.go b/pkg/streamingpromql/operators/functions/factories.go index f595ccdb112..ef342b1b7b2 100644 --- a/pkg/streamingpromql/operators/functions/factories.go +++ b/pkg/streamingpromql/operators/functions/factories.go @@ -6,6 +6,7 @@ import ( "fmt" "math" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/promql/parser/posrange" @@ -20,13 +21,18 @@ import ( type FunctionOperatorFactory func( args []types.Operator, - absentLabels labels.Labels, // Only used by absent and absent_over_time. - memoryConsumptionTracker *limiter.MemoryConsumptionTracker, - annotations *annotations.Annotations, - expressionPosition posrange.PositionRange, - timeRange types.QueryTimeRange, + params *InstantVectorFunctionOperatorParams, ) (types.Operator, error) +type InstantVectorFunctionOperatorParams struct { + AbsentLabels labels.Labels // Only used by absent and absent_over_time. + MemoryConsumptionTracker *limiter.MemoryConsumptionTracker + Annotations *annotations.Annotations + ExpressionPosition posrange.PositionRange + TimeRange types.QueryTimeRange + ValidationScheme model.ValidationScheme +} + // SingleInputVectorFunctionOperatorFactory creates an InstantVectorFunctionOperatorFactory for functions // that have exactly 1 argument (v instant-vector). // @@ -34,7 +40,7 @@ type FunctionOperatorFactory func( // - name: The name of the function // - f: The function implementation func SingleInputVectorFunctionOperatorFactory(name string, f FunctionOverInstantVectorDefinition) FunctionOperatorFactory { - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for %s, got %v", name, len(args)) @@ -46,10 +52,10 @@ func SingleInputVectorFunctionOperatorFactory(name string, f FunctionOverInstant return nil, fmt.Errorf("expected an instant vector argument for %s, got %T", name, args[0]) } - var o types.InstantVectorOperator = NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverInstantVector(inner, nil, params, f) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil @@ -77,11 +83,11 @@ func TimeTransformationFunctionOperatorFactory(name string, seriesDataFunc Insta SeriesMetadataFunction: DropSeriesName, } - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { var inner types.InstantVectorOperator if len(args) == 0 { // if the argument is not provided, it will default to vector(time()) - inner = scalars.NewScalarToInstantVector(operators.NewTime(timeRange, memoryConsumptionTracker, expressionPosition), expressionPosition, memoryConsumptionTracker) + inner = scalars.NewScalarToInstantVector(operators.NewTime(params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), params.ExpressionPosition, params.MemoryConsumptionTracker) } else if len(args) == 1 { // if one argument is provided, it must be an instant vector var ok bool @@ -95,9 +101,9 @@ func TimeTransformationFunctionOperatorFactory(name string, seriesDataFunc Insta return nil, fmt.Errorf("expected 0 or 1 argument for %s, got %v", name, len(args)) } - var o types.InstantVectorOperator = NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverInstantVector(inner, nil, params, f) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil @@ -131,7 +137,7 @@ func FunctionOverRangeVectorOperatorFactory( name string, f FunctionOverRangeVectorDefinition, ) FunctionOperatorFactory { - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for %s, got %v", name, len(args)) @@ -143,17 +149,17 @@ func FunctionOverRangeVectorOperatorFactory( return nil, fmt.Errorf("expected a range vector argument for %s, got %T", name, args[0]) } - var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, nil, memoryConsumptionTracker, f, annotations, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, nil, params.MemoryConsumptionTracker, f, params.Annotations, params.ExpressionPosition, params.TimeRange) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil } } -func PredictLinearFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func PredictLinearFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { f := PredictLinear if len(args) != 2 { @@ -173,16 +179,16 @@ func PredictLinearFactory(args []types.Operator, _ labels.Labels, memoryConsumpt return nil, fmt.Errorf("expected second argument for predict_linear to be a scalar, got %T", args[1]) } - var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{arg}, memoryConsumptionTracker, f, annotations, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{arg}, params.MemoryConsumptionTracker, f, params.Annotations, params.ExpressionPosition, params.TimeRange) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil } -func QuantileOverTimeFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func QuantileOverTimeFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { f := QuantileOverTime if len(args) != 2 { @@ -202,16 +208,16 @@ func QuantileOverTimeFactory(args []types.Operator, _ labels.Labels, memoryConsu return nil, fmt.Errorf("expected second argument for quantile_over_time to be a range vector, got %T", args[0]) } - var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{arg}, memoryConsumptionTracker, f, annotations, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{arg}, params.MemoryConsumptionTracker, f, params.Annotations, params.ExpressionPosition, params.TimeRange) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil } -func scalarToInstantVectorOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, _ types.QueryTimeRange) (types.Operator, error) { +func scalarToInstantVectorOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for vector, got %v", len(args)) @@ -223,10 +229,10 @@ func scalarToInstantVectorOperatorFactory(args []types.Operator, _ labels.Labels return nil, fmt.Errorf("expected a scalar argument for vector, got %T", args[0]) } - return scalars.NewScalarToInstantVector(inner, expressionPosition, memoryConsumptionTracker), nil + return scalars.NewScalarToInstantVector(inner, params.ExpressionPosition, params.MemoryConsumptionTracker), nil } -func LabelJoinFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func LabelJoinFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { // It is valid for label_join to have no source label names. ie, only 3 arguments are actually required. if len(args) < 3 { // Should be caught by the PromQL parser, but we check here for safety. @@ -264,17 +270,17 @@ func LabelJoinFunctionOperatorFactory(args []types.Operator, _ labels.Labels, me f := FunctionOverInstantVectorDefinition{ SeriesDataFunc: PassthroughData, SeriesMetadataFunction: SeriesMetadataFunctionDefinition{ - Func: LabelJoinFactory(dstLabel, separator, srcLabels), + Func: LabelJoinFactory(dstLabel, separator, srcLabels, params.ValidationScheme), NeedsSeriesDeduplication: true, }, } - o := NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) + o := NewFunctionOverInstantVector(inner, nil, params, f) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } -func LabelReplaceFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func LabelReplaceFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 5 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 5 arguments for label_replace, got %v", len(args)) @@ -313,17 +319,17 @@ func LabelReplaceFunctionOperatorFactory(args []types.Operator, _ labels.Labels, f := FunctionOverInstantVectorDefinition{ SeriesDataFunc: PassthroughData, SeriesMetadataFunction: SeriesMetadataFunctionDefinition{ - Func: LabelReplaceFactory(dstLabel, replacement, srcLabel, regex), + Func: LabelReplaceFactory(dstLabel, replacement, srcLabel, regex, params.ValidationScheme), NeedsSeriesDeduplication: true, }, } - o := NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) + o := NewFunctionOverInstantVector(inner, nil, params, f) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } -func AbsentOperatorFactory(args []types.Operator, labels labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func AbsentOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { return nil, fmt.Errorf("expected exactly 1 parameter for 'absent', got %v", len(args)) } @@ -333,10 +339,10 @@ func AbsentOperatorFactory(args []types.Operator, labels labels.Labels, memoryCo return nil, fmt.Errorf("expected InstantVectorOperator as parameter of 'absent' function call, got %T", args[0]) } - return NewAbsent(inner, labels, timeRange, memoryConsumptionTracker, expressionPosition), nil + return NewAbsent(inner, params.AbsentLabels, params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } -func AbsentOverTimeOperatorFactory(args []types.Operator, labels labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func AbsentOverTimeOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { return nil, fmt.Errorf("expected exactly 1 parameter for 'absent_over_time', got %v", len(args)) } @@ -346,10 +352,10 @@ func AbsentOverTimeOperatorFactory(args []types.Operator, labels labels.Labels, return nil, fmt.Errorf("expected RangeVectorOperator as parameter of 'absent_over_time' function call, got %T", args[0]) } - return NewAbsentOverTime(inner, labels, timeRange, memoryConsumptionTracker, expressionPosition), nil + return NewAbsentOverTime(inner, params.AbsentLabels, params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } -func ClampFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func ClampFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 3 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 3 arguments for clamp, got %v", len(args)) @@ -378,12 +384,12 @@ func ClampFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memory SeriesMetadataFunction: DropSeriesName, } - o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{min, max}, memoryConsumptionTracker, f, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{min, max}, params, f) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } func ClampMinMaxFunctionOperatorFactory(functionName string, isMin bool) FunctionOperatorFactory { - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 2 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 2 arguments for %s, got %v", functionName, len(args)) @@ -406,12 +412,12 @@ func ClampMinMaxFunctionOperatorFactory(functionName string, isMin bool) Functio SeriesMetadataFunction: DropSeriesName, } - o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{clampTo}, memoryConsumptionTracker, f, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{clampTo}, params, f) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } } -func RoundFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func RoundFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 && len(args) != 2 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected 1 or 2 arguments for round, got %v", len(args)) @@ -431,7 +437,7 @@ func RoundFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memory return nil, fmt.Errorf("expected a scalar for 2nd argument for round, got %T", args[1]) } } else { - toNearest = scalars.NewScalarConstant(float64(1), timeRange, memoryConsumptionTracker, expressionPosition) + toNearest = scalars.NewScalarConstant(float64(1), params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition) } f := FunctionOverInstantVectorDefinition{ @@ -439,11 +445,11 @@ func RoundFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memory SeriesMetadataFunction: DropSeriesName, } - o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{toNearest}, memoryConsumptionTracker, f, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewFunctionOverInstantVector(inner, []types.ScalarOperator{toNearest}, params, f) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } -func HistogramQuantileFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func HistogramQuantileFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 2 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 2 arguments for histogram_quantile, got %v", len(args)) @@ -461,11 +467,11 @@ func HistogramQuantileFunctionOperatorFactory(args []types.Operator, _ labels.La return nil, fmt.Errorf("expected an instant vector for 2nd argument for histogram_quantile, got %T", args[1]) } - o := NewHistogramQuantileFunction(ph, inner, memoryConsumptionTracker, annotations, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewHistogramQuantileFunction(ph, inner, params.MemoryConsumptionTracker, params.Annotations, params.ExpressionPosition, params.TimeRange) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } -func HistogramFractionFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func HistogramFractionFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 3 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 3 arguments for histogram_fraction, got %v", len(args)) @@ -489,11 +495,11 @@ func HistogramFractionFunctionOperatorFactory(args []types.Operator, _ labels.La return nil, fmt.Errorf("expected an instant vector for 3rd argument for histogram_fraction, got %T", args[2]) } - o := NewHistogramFractionFunction(lower, upper, inner, memoryConsumptionTracker, annotations, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewHistogramFractionFunction(lower, upper, inner, params.MemoryConsumptionTracker, params.Annotations, params.ExpressionPosition, params.TimeRange) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } -func TimestampFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func TimestampFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for timestamp, got %v", len(args)) @@ -513,8 +519,8 @@ func TimestampFunctionOperatorFactory(args []types.Operator, _ labels.Labels, me f.SeriesDataFunc = PassthroughData } - o := NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker), nil + o := NewFunctionOverInstantVector(inner, nil, params, f) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker), nil } func SortByLabelOperatorFactory(descending bool) FunctionOperatorFactory { @@ -523,7 +529,7 @@ func SortByLabelOperatorFactory(descending bool) FunctionOperatorFactory { functionName = "sort_by_label_desc" } - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) < 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected at least 1 argument for %s, got %v", functionName, len(args)) @@ -549,11 +555,11 @@ func SortByLabelOperatorFactory(descending bool) FunctionOperatorFactory { // sort_by_labels and sort_by_labels_desc only affect the results of instant queries // since range query results have a fixed output ordering. However, we still validate // all the arguments as if we were going to sort for consistency. - if !timeRange.IsInstant { + if !params.TimeRange.IsInstant { return inner, nil } - return NewSortByLabel(inner, descending, labels, memoryConsumptionTracker, expressionPosition), nil + return NewSortByLabel(inner, descending, labels, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } } @@ -564,7 +570,7 @@ func SortOperatorFactory(descending bool) FunctionOperatorFactory { functionName = "sort_desc" } - return func(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { + return func(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for %s, got %v", functionName, len(args)) @@ -576,13 +582,13 @@ func SortOperatorFactory(descending bool) FunctionOperatorFactory { return nil, fmt.Errorf("expected an instant vector for 1st argument for %s, got %T", functionName, args[0]) } - if timeRange.StepCount != 1 { + if params.TimeRange.StepCount != 1 { // If this is a range query, sort / sort_desc does not reorder series, but does drop all histograms like it would for an instant query. f := FunctionOverInstantVectorDefinition{SeriesDataFunc: DropHistograms} - return NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange), nil + return NewFunctionOverInstantVector(inner, nil, params, f), nil } - return NewSort(inner, descending, memoryConsumptionTracker, expressionPosition), nil + return NewSort(inner, descending, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } } @@ -617,25 +623,25 @@ func RegisterFunction(function Function, name string, returnType parser.ValueTyp return nil } -func piOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func piOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 0 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 0 arguments for pi, got %v", len(args)) } - return scalars.NewScalarConstant(math.Pi, timeRange, memoryConsumptionTracker, expressionPosition), nil + return scalars.NewScalarConstant(math.Pi, params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } -func timeOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func timeOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 0 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 0 arguments for time, got %v", len(args)) } - return operators.NewTime(timeRange, memoryConsumptionTracker, expressionPosition), nil + return operators.NewTime(params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } -func instantVectorToScalarOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, _ *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func instantVectorToScalarOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { if len(args) != 1 { // Should be caught by the PromQL parser, but we check here for safety. return nil, fmt.Errorf("expected exactly 1 argument for scalar, got %v", len(args)) @@ -647,20 +653,20 @@ func instantVectorToScalarOperatorFactory(args []types.Operator, _ labels.Labels return nil, fmt.Errorf("expected an instant vector argument for scalar, got %T", args[0]) } - return scalars.NewInstantVectorToScalar(inner, timeRange, memoryConsumptionTracker, expressionPosition), nil + return scalars.NewInstantVectorToScalar(inner, params.TimeRange, params.MemoryConsumptionTracker, params.ExpressionPosition), nil } -func UnaryNegationOfInstantVectorOperatorFactory(inner types.InstantVectorOperator, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) types.Operator { +func UnaryNegationOfInstantVectorOperatorFactory(inner types.InstantVectorOperator, params *InstantVectorFunctionOperatorParams) types.Operator { f := FunctionOverInstantVectorDefinition{ SeriesDataFunc: UnaryNegation, SeriesMetadataFunction: DropSeriesName, } - o := NewFunctionOverInstantVector(inner, nil, memoryConsumptionTracker, f, expressionPosition, timeRange) - return operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o := NewFunctionOverInstantVector(inner, nil, params, f) + return operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } -func DoubleExponentialSmoothingFunctionOperatorFactory(args []types.Operator, _ labels.Labels, memoryConsumptionTracker *limiter.MemoryConsumptionTracker, annotations *annotations.Annotations, expressionPosition posrange.PositionRange, timeRange types.QueryTimeRange) (types.Operator, error) { +func DoubleExponentialSmoothingFunctionOperatorFactory(args []types.Operator, params *InstantVectorFunctionOperatorParams) (types.Operator, error) { f := DoubleExponentialSmoothing functionName := "double_exponential_smoothing" @@ -686,10 +692,10 @@ func DoubleExponentialSmoothingFunctionOperatorFactory(args []types.Operator, _ return nil, fmt.Errorf("expected third argument for %s to be a scalar, got %T", functionName, args[2]) } - var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{smoothingFactor, trendFactor}, memoryConsumptionTracker, f, annotations, expressionPosition, timeRange) + var o types.InstantVectorOperator = NewFunctionOverRangeVector(inner, []types.ScalarOperator{smoothingFactor, trendFactor}, params.MemoryConsumptionTracker, f, params.Annotations, params.ExpressionPosition, params.TimeRange) if f.SeriesMetadataFunction.NeedsSeriesDeduplication { - o = operators.NewDeduplicateAndMerge(o, memoryConsumptionTracker) + o = operators.NewDeduplicateAndMerge(o, params.MemoryConsumptionTracker) } return o, nil diff --git a/pkg/streamingpromql/operators/functions/function_over_instant_vector.go b/pkg/streamingpromql/operators/functions/function_over_instant_vector.go index 203db5743b2..3188399afdc 100644 --- a/pkg/streamingpromql/operators/functions/function_over_instant_vector.go +++ b/pkg/streamingpromql/operators/functions/function_over_instant_vector.go @@ -40,19 +40,17 @@ var _ types.InstantVectorOperator = &FunctionOverInstantVector{} func NewFunctionOverInstantVector( inner types.InstantVectorOperator, scalarArgs []types.ScalarOperator, - memoryConsumptionTracker *limiter.MemoryConsumptionTracker, + params *InstantVectorFunctionOperatorParams, f FunctionOverInstantVectorDefinition, - expressionPosition posrange.PositionRange, - timeRange types.QueryTimeRange, ) *FunctionOverInstantVector { return &FunctionOverInstantVector{ Inner: inner, ScalarArgs: scalarArgs, - MemoryConsumptionTracker: memoryConsumptionTracker, + MemoryConsumptionTracker: params.MemoryConsumptionTracker, Func: f, - expressionPosition: expressionPosition, - timeRange: timeRange, + expressionPosition: params.ExpressionPosition, + timeRange: params.TimeRange, } } diff --git a/pkg/streamingpromql/operators/functions/label.go b/pkg/streamingpromql/operators/functions/label.go index 1ec655a1ab2..68e8445ab23 100644 --- a/pkg/streamingpromql/operators/functions/label.go +++ b/pkg/streamingpromql/operators/functions/label.go @@ -18,17 +18,21 @@ import ( "github.com/grafana/mimir/pkg/util/limiter" ) -func LabelJoinFactory(dstLabelOp, separatorOp types.StringOperator, srcLabelOps []types.StringOperator) SeriesMetadataFunction { +func LabelJoinFactory(dstLabelOp, separatorOp types.StringOperator, srcLabelOps []types.StringOperator, validationScheme model.ValidationScheme) SeriesMetadataFunction { return func(seriesMetadata []types.SeriesMetadata, tracker *limiter.MemoryConsumptionTracker) ([]types.SeriesMetadata, error) { dst := dstLabelOp.GetValue() - if !model.LabelName(dst).IsValid() { + if isValid, err := isValidLabelName(dst, validationScheme); err != nil { + return nil, err + } else if !isValid { return nil, fmt.Errorf("invalid destination label name in label_join(): %s", dst) } separator := separatorOp.GetValue() srcLabels := make([]string, len(srcLabelOps)) for i, op := range srcLabelOps { src := op.GetValue() - if !model.LabelName(src).IsValid() { + if isValid, err := isValidLabelName(src, validationScheme); err != nil { + return nil, err + } else if !isValid { return nil, fmt.Errorf("invalid source label name in label_join(): %s", dst) } srcLabels[i] = src @@ -63,7 +67,7 @@ func LabelJoinFactory(dstLabelOp, separatorOp types.StringOperator, srcLabelOps } } -func LabelReplaceFactory(dstLabelOp, replacementOp, srcLabelOp, regexOp types.StringOperator) SeriesMetadataFunction { +func LabelReplaceFactory(dstLabelOp, replacementOp, srcLabelOp, regexOp types.StringOperator, validationScheme model.ValidationScheme) SeriesMetadataFunction { return func(seriesMetadata []types.SeriesMetadata, tracker *limiter.MemoryConsumptionTracker) ([]types.SeriesMetadata, error) { regexStr := regexOp.GetValue() regex, err := regexp.Compile("^(?s:" + regexStr + ")$") @@ -71,7 +75,9 @@ func LabelReplaceFactory(dstLabelOp, replacementOp, srcLabelOp, regexOp types.St return nil, fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr) } dst := dstLabelOp.GetValue() - if !model.LabelName(dst).IsValid() { + if isValid, err := isValidLabelName(dst, validationScheme); err != nil { + return nil, err + } else if !isValid { return nil, fmt.Errorf("invalid destination label name in label_replace(): %s", dst) } repl := replacementOp.GetValue() @@ -98,3 +104,15 @@ func LabelReplaceFactory(dstLabelOp, replacementOp, srcLabelOp, regexOp types.St return seriesMetadata, nil } } + +func isValidLabelName(name string, validationScheme model.ValidationScheme) (bool, error) { + labelName := model.LabelName(name) + switch validationScheme { + case model.LegacyValidation: + return labelName.IsValidLegacy(), nil + case model.UTF8Validation: + return labelName.IsValid(), nil + default: + return false, fmt.Errorf("invalid validation scheme in label_replace(): %s", validationScheme) + } +} diff --git a/pkg/streamingpromql/planning.go b/pkg/streamingpromql/planning.go index 0300217a469..6c77c1cdcfa 100644 --- a/pkg/streamingpromql/planning.go +++ b/pkg/streamingpromql/planning.go @@ -441,7 +441,7 @@ func findFunction(name string) (functions.Function, bool) { // Materialize converts a query plan into an executable query. func (e *Engine) Materialize(ctx context.Context, plan *planning.QueryPlan, queryable storage.Queryable, opts promql.QueryOpts) (promql.Query, error) { if opts == nil { - opts = promql.NewPrometheusQueryOpts(false, 0) + opts = promql.DefaultQueryOpts() } queryID, err := e.activeQueryTracker.Insert(ctx, plan.OriginalExpression+" # (materialization)") @@ -467,6 +467,7 @@ func (e *Engine) Materialize(ctx context.Context, plan *planning.QueryPlan, quer Annotations: q.annotations, LookbackDelta: q.lookbackDelta, EagerLoadSelectors: q.engine.eagerLoadSelectors, + ValidationScheme: q.validationScheme, } q.statement = &parser.EvalStmt{ diff --git a/pkg/streamingpromql/planning/core/aggregate_expression.go b/pkg/streamingpromql/planning/core/aggregate_expression.go index d51cdc375d0..41b006f5f62 100644 --- a/pkg/streamingpromql/planning/core/aggregate_expression.go +++ b/pkg/streamingpromql/planning/core/aggregate_expression.go @@ -151,7 +151,7 @@ func (a *AggregateExpression) OperatorFactory(children []types.Operator, timeRan return nil, fmt.Errorf("expected StringOperator as parameter child of AggregateExpression with operation %s, got %T", a.Op.String(), children[0]) } - o = aggregations.NewCountValues(inner, param, timeRange, a.Grouping, a.Without, params.MemoryConsumptionTracker, a.ExpressionPosition.ToPrometheusType()) + o = aggregations.NewCountValues(inner, param, timeRange, a.Grouping, a.Without, params.MemoryConsumptionTracker, a.ExpressionPosition.ToPrometheusType(), params.ValidationScheme) default: if len(children) != 1 { diff --git a/pkg/streamingpromql/planning/core/function_call.go b/pkg/streamingpromql/planning/core/function_call.go index d94bfd6bb96..921e25e857d 100644 --- a/pkg/streamingpromql/planning/core/function_call.go +++ b/pkg/streamingpromql/planning/core/function_call.go @@ -94,7 +94,15 @@ func (f *FunctionCall) OperatorFactory(children []types.Operator, timeRange type absentLabels = mimirpb.FromLabelAdaptersToLabels(f.AbsentLabels) } - o, err := fnc.OperatorFactory(children, absentLabels, params.MemoryConsumptionTracker, params.Annotations, f.ExpressionPosition.ToPrometheusType(), timeRange) + functionParams := &functions.InstantVectorFunctionOperatorParams{ + AbsentLabels: absentLabels, + MemoryConsumptionTracker: params.MemoryConsumptionTracker, + Annotations: params.Annotations, + ExpressionPosition: f.ExpressionPosition.ToPrometheusType(), + TimeRange: timeRange, + ValidationScheme: params.ValidationScheme, + } + o, err := fnc.OperatorFactory(children, functionParams) if err != nil { return nil, err } diff --git a/pkg/streamingpromql/planning/core/unary_expression.go b/pkg/streamingpromql/planning/core/unary_expression.go index d9c4907a8f2..e8f5c151443 100644 --- a/pkg/streamingpromql/planning/core/unary_expression.go +++ b/pkg/streamingpromql/planning/core/unary_expression.go @@ -73,7 +73,14 @@ func (u *UnaryExpression) OperatorFactory(children []types.Operator, timeRange t switch child := children[0].(type) { case types.InstantVectorOperator: - o := functions.UnaryNegationOfInstantVectorOperatorFactory(child, params.MemoryConsumptionTracker, u.ExpressionPosition.ToPrometheusType(), timeRange) + functionParams := &functions.InstantVectorFunctionOperatorParams{ + MemoryConsumptionTracker: params.MemoryConsumptionTracker, + Annotations: params.Annotations, + ValidationScheme: params.ValidationScheme, + ExpressionPosition: u.ExpressionPosition.ToPrometheusType(), + TimeRange: timeRange, + } + o := functions.UnaryNegationOfInstantVectorOperatorFactory(child, functionParams) return planning.NewSingleUseOperatorFactory(o), nil case types.ScalarOperator: o := scalars.NewUnaryNegationOfScalar(child, u.ExpressionPosition.ToPrometheusType()) diff --git a/pkg/streamingpromql/planning/plan.go b/pkg/streamingpromql/planning/plan.go index 8589dfd0def..894cfe19e3a 100644 --- a/pkg/streamingpromql/planning/plan.go +++ b/pkg/streamingpromql/planning/plan.go @@ -9,6 +9,7 @@ import ( "time" "github.com/gogo/protobuf/proto" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/timestamp" "github.com/prometheus/prometheus/promql/parser" "github.com/prometheus/prometheus/storage" @@ -92,6 +93,7 @@ type OperatorParameters struct { Annotations *annotations.Annotations LookbackDelta time.Duration EagerLoadSelectors bool + ValidationScheme model.ValidationScheme } func (p *QueryPlan) ToEncodedPlan(includeDescriptions bool, includeDetails bool) (*EncodedQueryPlan, error) { diff --git a/pkg/streamingpromql/query.go b/pkg/streamingpromql/query.go index 39860573499..703516881d6 100644 --- a/pkg/streamingpromql/query.go +++ b/pkg/streamingpromql/query.go @@ -14,6 +14,7 @@ import ( "github.com/go-kit/log/level" "github.com/grafana/dskit/cancellation" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/promql/parser" @@ -46,6 +47,7 @@ type Query struct { annotations *annotations.Annotations stats *types.QueryStats lookbackDelta time.Duration + validationScheme model.ValidationScheme // Time range of the top-level query. // Subqueries may use a different range. @@ -59,7 +61,7 @@ type Query struct { func (e *Engine) newQuery(ctx context.Context, queryable storage.Queryable, opts promql.QueryOpts, timeRange types.QueryTimeRange, originalExpression string) (*Query, error) { if opts == nil { - opts = promql.NewPrometheusQueryOpts(false, 0) + opts = promql.DefaultQueryOpts() } lookbackDelta := opts.LookbackDelta() @@ -86,6 +88,7 @@ func (e *Engine) newQuery(ctx context.Context, queryable storage.Queryable, opts topLevelQueryTimeRange: timeRange, lookbackDelta: lookbackDelta, originalExpression: originalExpression, + validationScheme: opts.ValidationScheme(), } return q, nil diff --git a/pkg/util/validation/limits.go b/pkg/util/validation/limits.go index 8f14d9db937..25f26472519 100644 --- a/pkg/util/validation/limits.go +++ b/pkg/util/validation/limits.go @@ -1390,6 +1390,13 @@ func (o *Overrides) CardinalityAnalysisMaxResults(userID string) int { return o.getOverridesForUser(userID).CardinalityAnalysisMaxResults } +// ValidationScheme returns the validation scheme to use for a particular tenant. +// Defaults to LegacyValidation. +func (o *Overrides) ValidationScheme(userID string) model.ValidationScheme { + // TODO(juliusmh): make this configurable by tenant + return model.LegacyValidation +} + func (o *Overrides) getOverridesForUser(userID string) *Limits { if o.tenantLimits != nil { l := o.tenantLimits.ByUserID(userID) diff --git a/vendor/github.com/prometheus/alertmanager/config/notifiers.go b/vendor/github.com/prometheus/alertmanager/config/notifiers.go index 97ce8692da6..16b7a33f308 100644 --- a/vendor/github.com/prometheus/alertmanager/config/notifiers.go +++ b/vendor/github.com/prometheus/alertmanager/config/notifiers.go @@ -514,7 +514,7 @@ type WebhookConfig struct { // Timeout is the maximum time allowed to invoke the webhook. Setting this to 0 // does not impose a timeout. - Timeout time.Duration `yaml:"timeout" json:"timeout"` + Timeout model.Duration `yaml:"timeout" json:"timeout"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go b/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go index 0c0dfffb1fd..951310268c9 100644 --- a/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go +++ b/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go @@ -190,7 +190,7 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers { // isValidClassicLabelName returns true if the string is a valid classic label name. func isValidClassicLabelName(_ log.Logger) func(model.LabelName) bool { return func(name model.LabelName) bool { - return name.IsValid() + return name.IsValidLegacy() } } diff --git a/vendor/github.com/prometheus/alertmanager/notify/webhook/webhook.go b/vendor/github.com/prometheus/alertmanager/notify/webhook/webhook.go index eb4e01ba40d..153c17f565d 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/webhook/webhook.go +++ b/vendor/github.com/prometheus/alertmanager/notify/webhook/webhook.go @@ -21,6 +21,7 @@ import ( "net/http" "os" "strings" + "time" "github.com/go-kit/log" "github.com/go-kit/log/level" @@ -125,7 +126,7 @@ func (n *Notifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, er } if n.conf.Timeout > 0 { - postCtx, cancel := context.WithTimeoutCause(ctx, n.conf.Timeout, fmt.Errorf("configured webhook timeout reached (%s)", n.conf.Timeout)) + postCtx, cancel := context.WithTimeoutCause(ctx, time.Duration(n.conf.Timeout), fmt.Errorf("configured webhook timeout reached (%s)", n.conf.Timeout)) defer cancel() ctx = postCtx } diff --git a/vendor/github.com/prometheus/prometheus/promql/engine.go b/vendor/github.com/prometheus/prometheus/promql/engine.go index f5ee591d3b3..fbb11ef6903 100644 --- a/vendor/github.com/prometheus/prometheus/promql/engine.go +++ b/vendor/github.com/prometheus/prometheus/promql/engine.go @@ -151,17 +151,25 @@ type PrometheusQueryOpts struct { enablePerStepStats bool // Lookback delta duration for this query. lookbackDelta time.Duration + // validationScheme for metric/label names. + validationScheme model.ValidationScheme } var _ QueryOpts = &PrometheusQueryOpts{} -func NewPrometheusQueryOpts(enablePerStepStats bool, lookbackDelta time.Duration) QueryOpts { +func NewPrometheusQueryOpts(enablePerStepStats bool, lookbackDelta time.Duration, validationScheme model.ValidationScheme) QueryOpts { return &PrometheusQueryOpts{ enablePerStepStats: enablePerStepStats, lookbackDelta: lookbackDelta, + validationScheme: validationScheme, } } +// DefaultQueryOpts provides the default query options. +func DefaultQueryOpts() QueryOpts { + return NewPrometheusQueryOpts(false, 0, model.UTF8Validation) +} + func (p *PrometheusQueryOpts) EnablePerStepStats() bool { return p.enablePerStepStats } @@ -170,11 +178,17 @@ func (p *PrometheusQueryOpts) LookbackDelta() time.Duration { return p.lookbackDelta } +func (p *PrometheusQueryOpts) ValidationScheme() model.ValidationScheme { + return p.validationScheme +} + type QueryOpts interface { // Enables recording per-step statistics if the engine has it enabled as well. Disabled by default. EnablePerStepStats() bool // Lookback delta duration for this query. LookbackDelta() time.Duration + // ValidationScheme to use for metric and label names. + ValidationScheme() model.ValidationScheme } // query implements the Query interface. @@ -193,6 +207,8 @@ type query struct { matrix Matrix // Cancellation function for the query. cancel func() + // validationScheme for label/metric names for the query. + validationScheme model.ValidationScheme // The engine against which the query is executed. ng *Engine @@ -520,7 +536,7 @@ func (ng *Engine) NewRangeQuery(ctx context.Context, q storage.Queryable, opts Q func (ng *Engine) newQuery(q storage.Queryable, qs string, opts QueryOpts, start, end time.Time, interval time.Duration) (*parser.Expr, *query) { if opts == nil { - opts = NewPrometheusQueryOpts(false, 0) + opts = DefaultQueryOpts() } lookbackDelta := opts.LookbackDelta() @@ -535,12 +551,13 @@ func (ng *Engine) newQuery(q storage.Queryable, qs string, opts QueryOpts, start LookbackDelta: lookbackDelta, } qry := &query{ - q: qs, - stmt: es, - ng: ng, - stats: stats.NewQueryTimers(), - sampleStats: stats.NewQuerySamples(ng.enablePerStepStats && opts.EnablePerStepStats()), - queryable: q, + q: qs, + stmt: es, + ng: ng, + stats: stats.NewQueryTimers(), + sampleStats: stats.NewQuerySamples(ng.enablePerStepStats && opts.EnablePerStepStats()), + queryable: q, + validationScheme: opts.ValidationScheme(), } return &es.Expr, qry } @@ -745,6 +762,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval enableDelayedNameRemoval: ng.enableDelayedNameRemoval, enableTypeAndUnitLabels: ng.enableTypeAndUnitLabels, querier: querier, + validationScheme: query.validationScheme, } query.sampleStats.InitStepTracking(start, start, 1) @@ -805,6 +823,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval enableDelayedNameRemoval: ng.enableDelayedNameRemoval, enableTypeAndUnitLabels: ng.enableTypeAndUnitLabels, querier: querier, + validationScheme: query.validationScheme, } query.sampleStats.InitStepTracking(evaluator.startTimestamp, evaluator.endTimestamp, evaluator.interval) val, warnings, err := evaluator.Eval(ctxInnerEval, s.Expr) @@ -1079,6 +1098,7 @@ type evaluator struct { enableDelayedNameRemoval bool enableTypeAndUnitLabels bool querier storage.Querier + validationScheme model.ValidationScheme } // errorf causes a panic with the input formatted into an error. @@ -1126,6 +1146,18 @@ func (ev *evaluator) Eval(ctx context.Context, expr parser.Expr) (v parser.Value return v, ws, nil } +func (ev *evaluator) isValidLabelName(name string) (bool, error) { + labelName := model.LabelName(name) + switch ev.validationScheme { + case model.LegacyValidation: + return labelName.IsValidLegacy(), nil + case model.UTF8Validation: + return labelName.IsValid(), nil + default: + return false, fmt.Errorf("unknown validation scheme %q", ev.validationScheme) + } +} + // EvalSeriesHelper stores extra information about a series. type EvalSeriesHelper struct { // Used to map left-hand to right-hand in binary operations. @@ -1678,7 +1710,10 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, if e.Op == parser.COUNT_VALUES { valueLabel := param.(*parser.StringLiteral) - if !model.LabelName(valueLabel.Val).IsValid() { + isValid, err := ev.isValidLabelName(valueLabel.Val) + if err != nil { + ev.error(err) + } else if !isValid { ev.errorf("invalid label name %s", valueLabel) } if !e.Without { @@ -2084,6 +2119,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, enableDelayedNameRemoval: ev.enableDelayedNameRemoval, enableTypeAndUnitLabels: ev.enableTypeAndUnitLabels, querier: ev.querier, + validationScheme: ev.validationScheme, } if e.Step != 0 { @@ -2130,6 +2166,7 @@ func (ev *evaluator) eval(ctx context.Context, expr parser.Expr) (parser.Value, enableDelayedNameRemoval: ev.enableDelayedNameRemoval, enableTypeAndUnitLabels: ev.enableTypeAndUnitLabels, querier: ev.querier, + validationScheme: ev.validationScheme, } res, ws := newEv.eval(ctx, e.Expr) ev.currentSamples = newEv.currentSamples diff --git a/vendor/github.com/prometheus/prometheus/promql/functions.go b/vendor/github.com/prometheus/prometheus/promql/functions.go index 9af904c9e64..5b59bfd63f2 100644 --- a/vendor/github.com/prometheus/prometheus/promql/functions.go +++ b/vendor/github.com/prometheus/prometheus/promql/functions.go @@ -1576,7 +1576,10 @@ func (ev *evaluator) evalLabelReplace(ctx context.Context, args parser.Expressio if err != nil { panic(fmt.Errorf("invalid regular expression in label_replace(): %s", regexStr)) } - if !model.LabelName(dst).IsValid() { + isValid, err := ev.isValidLabelName(dst) + if err != nil { + panic(err) + } else if !isValid { panic(fmt.Errorf("invalid destination label name in label_replace(): %s", dst)) } @@ -1624,12 +1627,18 @@ func (ev *evaluator) evalLabelJoin(ctx context.Context, args parser.Expressions) ) for i := 3; i < len(args); i++ { src := stringFromArg(args[i]) - if !model.LabelName(src).IsValid() { + isValid, err := ev.isValidLabelName(src) + if err != nil { + panic(err) + } else if !isValid { panic(fmt.Errorf("invalid source label name in label_join(): %s", src)) } srcLabels[i-3] = src } - if !model.LabelName(dst).IsValid() { + isValid, err := ev.isValidLabelName(dst) + if err != nil { + panic(err) + } else if !isValid { panic(fmt.Errorf("invalid destination label name in label_join(): %s", dst)) } diff --git a/vendor/github.com/prometheus/prometheus/web/api/v1/api.go b/vendor/github.com/prometheus/prometheus/web/api/v1/api.go index 4f3926a2ea8..faa10e6c219 100644 --- a/vendor/github.com/prometheus/prometheus/web/api/v1/api.go +++ b/vendor/github.com/prometheus/prometheus/web/api/v1/api.go @@ -544,7 +544,11 @@ func extractQueryOpts(r *http.Request) (promql.QueryOpts, error) { duration = parsedDuration } - return promql.NewPrometheusQueryOpts(r.FormValue("stats") == "all", duration), nil + return promql.NewPrometheusQueryOpts( + r.FormValue("stats") == "all", + duration, + model.UTF8Validation, + ), nil } func (api *API) queryRange(r *http.Request) (result apiFuncResult) { diff --git a/vendor/modules.txt b/vendor/modules.txt index c67cddf9ece..fd1da408085 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1113,7 +1113,7 @@ github.com/pmezard/go-difflib/difflib # github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c ## explicit; go 1.14 github.com/power-devops/perfstat -# github.com/prometheus/alertmanager v0.28.1 => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 +# github.com/prometheus/alertmanager v0.28.1 => github.com/juliusmh/alertmanager v0.26.1-0.20250716125725-19a9223bec8c ## explicit; go 1.23.0 github.com/prometheus/alertmanager/api github.com/prometheus/alertmanager/api/metrics @@ -1206,7 +1206,7 @@ github.com/prometheus/otlptranslator github.com/prometheus/procfs github.com/prometheus/procfs/internal/fs github.com/prometheus/procfs/internal/util -# github.com/prometheus/prometheus v1.99.0 => github.com/grafana/mimir-prometheus v1.8.2-0.20250715103124-73cbc98c25ff +# github.com/prometheus/prometheus v1.99.0 => github.com/grafana/mimir-prometheus v1.8.2-0.20250716123832-f18c798bd04b ## explicit; go 1.23.0 github.com/prometheus/prometheus/config github.com/prometheus/prometheus/discovery @@ -2126,13 +2126,13 @@ sigs.k8s.io/kustomize/kyaml/yaml/walk sigs.k8s.io/yaml sigs.k8s.io/yaml/goyaml.v2 sigs.k8s.io/yaml/goyaml.v3 -# github.com/prometheus/prometheus => github.com/grafana/mimir-prometheus v1.8.2-0.20250715103124-73cbc98c25ff +# github.com/prometheus/prometheus => github.com/grafana/mimir-prometheus v1.8.2-0.20250716123832-f18c798bd04b # github.com/hashicorp/memberlist => github.com/grafana/memberlist v0.3.1-0.20250428154222-f7d51a6f6700 # gopkg.in/yaml.v3 => github.com/colega/go-yaml-yaml v0.0.0-20220720105220-255a8d16d094 # github.com/grafana/regexp => github.com/grafana/regexp v0.0.0-20240531075221-3685f1377d7b # github.com/munnerz/goautoneg => github.com/grafana/goautoneg v0.0.0-20240607115440-f335c04c58ce # github.com/opentracing-contrib/go-stdlib => github.com/grafana/opentracing-contrib-go-stdlib v0.0.0-20230509071955-f410e79da956 # github.com/opentracing-contrib/go-grpc => github.com/charleskorn/go-grpc v0.0.0-20231024023642-e9298576254f -# github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 +# github.com/prometheus/alertmanager => github.com/juliusmh/alertmanager v0.26.1-0.20250716125725-19a9223bec8c # github.com/prometheus/otlptranslator => github.com/grafana/mimir-otlptranslator v0.0.0-20250703083430-c31a9568ad96 # github.com/thanos-io/objstore => github.com/charleskorn/objstore v0.0.0-20250527065533-21d4c0c463eb