github.com/thanos-io/thanos@v0.32.5/internal/cortex/querier/queryrange/roundtrip.go (about)

     1  // Copyright (c) The Cortex Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  // Copyright 2016 The Prometheus Authors
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  //
    17  // Mostly lifted from prometheus/web/api/v1/api.go.
    18  
    19  package queryrange
    20  
    21  import (
    22  	"context"
    23  	"io"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/go-kit/log"
    30  	"github.com/go-kit/log/level"
    31  	"github.com/opentracing/opentracing-go"
    32  	"github.com/pkg/errors"
    33  	"github.com/prometheus/client_golang/prometheus"
    34  	"github.com/prometheus/client_golang/prometheus/promauto"
    35  	"github.com/prometheus/prometheus/promql"
    36  	"github.com/weaveworks/common/httpgrpc"
    37  	"github.com/weaveworks/common/user"
    38  
    39  	"github.com/thanos-io/thanos/internal/cortex/chunk/cache"
    40  	"github.com/thanos-io/thanos/internal/cortex/querier"
    41  	"github.com/thanos-io/thanos/internal/cortex/tenant"
    42  	"github.com/thanos-io/thanos/internal/cortex/util"
    43  	"github.com/thanos-io/thanos/internal/cortex/util/flagext"
    44  )
    45  
    46  const day = 24 * time.Hour
    47  
    48  var (
    49  	// PassthroughMiddleware is a noop middleware
    50  	PassthroughMiddleware = MiddlewareFunc(func(next Handler) Handler {
    51  		return next
    52  	})
    53  
    54  	errInvalidMinShardingLookback = errors.New("a non-zero value is required for querier.query-ingesters-within when -querier.parallelise-shardable-queries is enabled")
    55  )
    56  
    57  // Config for query_range middleware chain.
    58  type Config struct {
    59  	SplitQueriesByInterval time.Duration `yaml:"split_queries_by_interval"`
    60  	AlignQueriesWithStep   bool          `yaml:"align_queries_with_step"`
    61  	ResultsCacheConfig     `yaml:"results_cache"`
    62  	CacheResults           bool `yaml:"cache_results"`
    63  	MaxRetries             int  `yaml:"max_retries"`
    64  	ShardedQueries         bool `yaml:"parallelise_shardable_queries"`
    65  	// List of headers which query_range middleware chain would forward to downstream querier.
    66  	ForwardHeaders flagext.StringSlice `yaml:"forward_headers_list"`
    67  }
    68  
    69  // Validate validates the config.
    70  func (cfg *Config) Validate(qCfg querier.Config) error {
    71  	if cfg.CacheResults {
    72  		if cfg.SplitQueriesByInterval <= 0 {
    73  			return errors.New("querier.cache-results may only be enabled in conjunction with querier.split-queries-by-interval. Please set the latter")
    74  		}
    75  		if err := cfg.ResultsCacheConfig.Validate(qCfg); err != nil {
    76  			return errors.Wrap(err, "invalid ResultsCache config")
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // HandlerFunc is like http.HandlerFunc, but for Handler.
    83  type HandlerFunc func(context.Context, Request) (Response, error)
    84  
    85  // Do implements Handler.
    86  func (q HandlerFunc) Do(ctx context.Context, req Request) (Response, error) {
    87  	return q(ctx, req)
    88  }
    89  
    90  // Handler is like http.Handle, but specifically for Prometheus query_range calls.
    91  type Handler interface {
    92  	Do(context.Context, Request) (Response, error)
    93  }
    94  
    95  // MiddlewareFunc is like http.HandlerFunc, but for Middleware.
    96  type MiddlewareFunc func(Handler) Handler
    97  
    98  // Wrap implements Middleware.
    99  func (q MiddlewareFunc) Wrap(h Handler) Handler {
   100  	return q(h)
   101  }
   102  
   103  // Middleware is a higher order Handler.
   104  type Middleware interface {
   105  	Wrap(Handler) Handler
   106  }
   107  
   108  // MergeMiddlewares produces a middleware that applies multiple middleware in turn;
   109  // ie Merge(f,g,h).Wrap(handler) == f.Wrap(g.Wrap(h.Wrap(handler)))
   110  func MergeMiddlewares(middleware ...Middleware) Middleware {
   111  	return MiddlewareFunc(func(next Handler) Handler {
   112  		for i := len(middleware) - 1; i >= 0; i-- {
   113  			next = middleware[i].Wrap(next)
   114  		}
   115  		return next
   116  	})
   117  }
   118  
   119  // Tripperware is a signature for all http client-side middleware.
   120  type Tripperware func(http.RoundTripper) http.RoundTripper
   121  
   122  // RoundTripFunc is to http.RoundTripper what http.HandlerFunc is to http.Handler.
   123  type RoundTripFunc func(*http.Request) (*http.Response, error)
   124  
   125  // RoundTrip implements http.RoundTripper.
   126  func (f RoundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
   127  	return f(r)
   128  }
   129  
   130  // NewTripperware returns a Tripperware configured with middlewares to limit, align, split, retry and cache requests.
   131  func NewTripperware(
   132  	cfg Config,
   133  	log log.Logger,
   134  	limits Limits,
   135  	codec Codec,
   136  	cacheExtractor Extractor,
   137  	engineOpts promql.EngineOpts,
   138  	minShardingLookback time.Duration,
   139  	registerer prometheus.Registerer,
   140  	cacheGenNumberLoader CacheGenNumberLoader,
   141  ) (Tripperware, cache.Cache, error) {
   142  	// Per tenant query metrics.
   143  	queriesPerTenant := promauto.With(registerer).NewCounterVec(prometheus.CounterOpts{
   144  		Name: "cortex_query_frontend_queries_total",
   145  		Help: "Total queries sent per tenant.",
   146  	}, []string{"op", "user"})
   147  
   148  	activeUsers := util.NewActiveUsersCleanupWithDefaultValues(func(user string) {
   149  		err := util.DeleteMatchingLabels(queriesPerTenant, map[string]string{"user": user})
   150  		if err != nil {
   151  			level.Warn(log).Log("msg", "failed to remove cortex_query_frontend_queries_total metric for user", "user", user)
   152  		}
   153  	})
   154  
   155  	// Metric used to keep track of each middleware execution duration.
   156  	metrics := NewInstrumentMiddlewareMetrics(registerer)
   157  
   158  	queryRangeMiddleware := []Middleware{NewLimitsMiddleware(limits)}
   159  	if cfg.AlignQueriesWithStep {
   160  		queryRangeMiddleware = append(queryRangeMiddleware, InstrumentMiddleware("step_align", metrics), StepAlignMiddleware)
   161  	}
   162  	if cfg.SplitQueriesByInterval != 0 {
   163  		staticIntervalFn := func(_ Request) time.Duration { return cfg.SplitQueriesByInterval }
   164  		queryRangeMiddleware = append(queryRangeMiddleware, InstrumentMiddleware("split_by_interval", metrics), SplitByIntervalMiddleware(staticIntervalFn, limits, codec, registerer))
   165  	}
   166  
   167  	var c cache.Cache
   168  	if cfg.CacheResults {
   169  		shouldCache := func(r Request) bool {
   170  			return !r.GetCachingOptions().Disabled
   171  		}
   172  		queryCacheMiddleware, cache, err := NewResultsCacheMiddleware(log, cfg.ResultsCacheConfig, constSplitter(cfg.SplitQueriesByInterval), limits, codec, cacheExtractor, cacheGenNumberLoader, shouldCache, registerer)
   173  		if err != nil {
   174  			return nil, nil, err
   175  		}
   176  		c = cache
   177  		queryRangeMiddleware = append(queryRangeMiddleware, InstrumentMiddleware("results_cache", metrics), queryCacheMiddleware)
   178  	}
   179  
   180  	if cfg.MaxRetries > 0 {
   181  		queryRangeMiddleware = append(queryRangeMiddleware, InstrumentMiddleware("retry", metrics), NewRetryMiddleware(log, cfg.MaxRetries, NewRetryMiddlewareMetrics(registerer)))
   182  	}
   183  
   184  	// Start cleanup. If cleaner stops or fail, we will simply not clean the metrics for inactive users.
   185  	_ = activeUsers.StartAsync(context.Background())
   186  	return func(next http.RoundTripper) http.RoundTripper {
   187  		// Finally, if the user selected any query range middleware, stitch it in.
   188  		if len(queryRangeMiddleware) > 0 {
   189  			queryrange := NewRoundTripper(next, codec, cfg.ForwardHeaders, queryRangeMiddleware...)
   190  			return RoundTripFunc(func(r *http.Request) (*http.Response, error) {
   191  				isQueryRange := strings.HasSuffix(r.URL.Path, "/query_range")
   192  				op := "query"
   193  				if isQueryRange {
   194  					op = "query_range"
   195  				}
   196  
   197  				tenantIDs, err := tenant.TenantIDs(r.Context())
   198  				// This should never happen anyways because we have auth middleware before this.
   199  				if err != nil {
   200  					return nil, err
   201  				}
   202  				userStr := tenant.JoinTenantIDs(tenantIDs)
   203  				activeUsers.UpdateUserTimestamp(userStr, time.Now())
   204  				queriesPerTenant.WithLabelValues(op, userStr).Inc()
   205  
   206  				if !isQueryRange {
   207  					return next.RoundTrip(r)
   208  				}
   209  				return queryrange.RoundTrip(r)
   210  			})
   211  		}
   212  		return next
   213  	}, c, nil
   214  }
   215  
   216  type roundTripper struct {
   217  	next    http.RoundTripper
   218  	handler Handler
   219  	codec   Codec
   220  	headers []string
   221  }
   222  
   223  // NewRoundTripper merges a set of middlewares into an handler, then inject it into the `next` roundtripper
   224  // using the codec to translate requests and responses.
   225  func NewRoundTripper(next http.RoundTripper, codec Codec, headers []string, middlewares ...Middleware) http.RoundTripper {
   226  	transport := roundTripper{
   227  		next:    next,
   228  		codec:   codec,
   229  		headers: headers,
   230  	}
   231  	transport.handler = MergeMiddlewares(middlewares...).Wrap(&transport)
   232  	return transport
   233  }
   234  
   235  func (q roundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
   236  
   237  	// include the headers specified in the roundTripper during decoding the request.
   238  	request, err := q.codec.DecodeRequest(r.Context(), r, q.headers)
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	if span := opentracing.SpanFromContext(r.Context()); span != nil {
   244  		request.LogToSpan(span)
   245  	}
   246  
   247  	response, err := q.handler.Do(r.Context(), request)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	return q.codec.EncodeResponse(r.Context(), response)
   253  }
   254  
   255  // Do implements Handler.
   256  func (q roundTripper) Do(ctx context.Context, r Request) (Response, error) {
   257  	request, err := q.codec.EncodeRequest(ctx, r)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	if err := user.InjectOrgIDIntoHTTPRequest(ctx, request); err != nil {
   263  		return nil, httpgrpc.Errorf(http.StatusBadRequest, err.Error())
   264  	}
   265  
   266  	response, err := q.next.RoundTrip(request)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  	defer func() {
   271  		io.Copy(ioutil.Discard, io.LimitReader(response.Body, 1024))
   272  		_ = response.Body.Close()
   273  	}()
   274  
   275  	return q.codec.DecodeResponse(ctx, response, r)
   276  }