Skip to content

Commit

Permalink
Early Request Validation in Query Frontend (#10093)
Browse files Browse the repository at this point in the history
* WIP sketch out validation roundtripper with existing metrics and labels query codecs

* lint: license header

* separate series request roundtrippers

* introduce request validation roundtripper tests

* lint import order

* introduce & test cardinality query validation in frontend roundtripper; expand metrics query tests to cover instant queries

* CHANGELOG
  • Loading branch information
francoposa authored Dec 6, 2024
1 parent a26eee3 commit 3251e86
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
* [ENHANCEMENT] Distributor: allow a different limit for info series (series ending in `_info`) label count, via `-validation.max-label-names-per-info-series`. #10028
* [ENHANCEMENT] Ingester: do not reuse labels, samples and histograms slices in the write request if there are more entries than 10x the pre-allocated size. This should help to reduce the in-use memory in case of few requests with a very large number of labels, samples or histograms. #10040
* [ENHANCEMENT] Query-Frontend: prune `<subquery> and on() (vector(x)==y)` style queries and stop pruning `<subquery> < -Inf`. Triggered by https://github.com/prometheus/prometheus/pull/15245. #10026
* [ENHANCEMENT] Query-Frontend: perform request format validation before processing the request. #10093
* [BUGFIX] Fix issue where functions such as `rate()` over native histograms could return incorrect values if a float stale marker was present in the selected range. #9508
* [BUGFIX] Fix issue where negation of native histograms (eg. `-some_native_histogram_series`) did nothing. #9508
* [BUGFIX] Fix issue where `metric might not be a counter, name does not end in _total/_sum/_count/_bucket` annotation would be emitted even if `rate` or `increase` did not have enough samples to compute a result. #9508
Expand Down
35 changes: 35 additions & 0 deletions pkg/frontend/querymiddleware/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package querymiddleware
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -32,6 +33,7 @@ import (
"golang.org/x/exp/slices"

apierror "github.com/grafana/mimir/pkg/api/error"
"github.com/grafana/mimir/pkg/cardinality"
"github.com/grafana/mimir/pkg/mimirpb"
"github.com/grafana/mimir/pkg/querier/api"
"github.com/grafana/mimir/pkg/querier/stats"
Expand Down Expand Up @@ -582,6 +584,39 @@ func DecodeLabelsSeriesQueryTimeParams(reqValues *url.Values) (start, end int64,
return start, end, err
}

// DecodeCardinalityQueryParams strictly handles validation for cardinality API endpoint parameters.
// The current decoding of the cardinality requests is handled in the cardinality package
// which is not yet compatible with the codec's approach of using interfaces
// and multiple concrete proto implementations to represent different query types.
func DecodeCardinalityQueryParams(r *http.Request) (any, error) {
var err error

reqValues, err := util.ParseRequestFormWithoutConsumingBody(r)
if err != nil {
return nil, apierror.New(apierror.TypeBadData, err.Error())
}

var parsedReq any
switch {
case strings.HasSuffix(r.URL.Path, cardinalityLabelNamesPathSuffix):
parsedReq, err = cardinality.DecodeLabelNamesRequestFromValues(reqValues)

case strings.HasSuffix(r.URL.Path, cardinalityLabelValuesPathSuffix):
parsedReq, err = cardinality.DecodeLabelValuesRequestFromValues(reqValues)

case strings.HasSuffix(r.URL.Path, cardinalityActiveSeriesPathSuffix):
parsedReq, err = cardinality.DecodeActiveSeriesRequestFromValues(reqValues)

default:
return nil, errors.New("unknown cardinality API endpoint")
}

if err != nil {
return nil, apierror.New(apierror.TypeBadData, err.Error())
}
return parsedReq, nil
}

func decodeQueryMinMaxTime(queryExpr parser.Expr, start, end, step int64, lookbackDelta time.Duration) (minTime, maxTime int64) {
evalStmt := &parser.EvalStmt{
Expr: queryExpr,
Expand Down
92 changes: 92 additions & 0 deletions pkg/frontend/querymiddleware/request_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: AGPL-3.0-only

package querymiddleware

import (
"context"
"net/http"

"github.com/grafana/dskit/cancellation"
)

const requestValidationFailedFmt = "request validation failed for "

var errMetricsQueryRequestValidationFailed = cancellation.NewErrorf(
requestValidationFailedFmt + "metrics query",
)
var errLabelsQueryRequestValidationFailed = cancellation.NewErrorf(
requestValidationFailedFmt + "labels query",
)
var errCardinalityQueryRequestValidationFailed = cancellation.NewErrorf(
requestValidationFailedFmt + "cardinality query",
)

type MetricsQueryRequestValidationRoundTripper struct {
codec Codec
next http.RoundTripper
}

func NewMetricsQueryRequestValidationRoundTripper(codec Codec, next http.RoundTripper) http.RoundTripper {
return MetricsQueryRequestValidationRoundTripper{
codec: codec,
next: next,
}
}

func (rt MetricsQueryRequestValidationRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
ctx, cancel := context.WithCancelCause(r.Context())
defer cancel(errMetricsQueryRequestValidationFailed)
r = r.WithContext(ctx)

_, err := rt.codec.DecodeMetricsQueryRequest(ctx, r)
if err != nil {
return nil, err
}
return rt.next.RoundTrip(r)
}

type LabelsQueryRequestValidationRoundTripper struct {
codec Codec
next http.RoundTripper
}

func NewLabelsQueryRequestValidationRoundTripper(codec Codec, next http.RoundTripper) http.RoundTripper {
return LabelsQueryRequestValidationRoundTripper{
codec: codec,
next: next,
}
}

func (rt LabelsQueryRequestValidationRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
ctx, cancel := context.WithCancelCause(r.Context())
defer cancel(errLabelsQueryRequestValidationFailed)
r = r.WithContext(ctx)

_, err := rt.codec.DecodeLabelsQueryRequest(ctx, r)
if err != nil {
return nil, err
}
return rt.next.RoundTrip(r)
}

type CardinalityQueryRequestValidationRoundTripper struct {
next http.RoundTripper
}

func NewCardinalityQueryRequestValidationRoundTripper(next http.RoundTripper) http.RoundTripper {
return CardinalityQueryRequestValidationRoundTripper{
next: next,
}
}

func (rt CardinalityQueryRequestValidationRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
ctx, cancel := context.WithCancelCause(r.Context())
defer cancel(errCardinalityQueryRequestValidationFailed)
r = r.WithContext(ctx)

_, err := DecodeCardinalityQueryParams(r)
if err != nil {
return nil, err
}
return rt.next.RoundTrip(r)
}
Loading

0 comments on commit 3251e86

Please sign in to comment.