github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/prometheus/handleroptions/fetch_options.go (about)

     1  // Copyright (c) 2019 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package handleroptions
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"fmt"
    27  	"math"
    28  	"net/http"
    29  	"strconv"
    30  	"strings"
    31  	"time"
    32  
    33  	"github.com/m3db/m3/src/dbnode/encoding"
    34  	"github.com/m3db/m3/src/dbnode/topology"
    35  	"github.com/m3db/m3/src/metrics/policy"
    36  	"github.com/m3db/m3/src/query/errors"
    37  	"github.com/m3db/m3/src/query/storage"
    38  	"github.com/m3db/m3/src/query/storage/m3/storagemetadata"
    39  	"github.com/m3db/m3/src/query/util"
    40  	xerrors "github.com/m3db/m3/src/x/errors"
    41  	"github.com/m3db/m3/src/x/headers"
    42  	xtime "github.com/m3db/m3/src/x/time"
    43  )
    44  
    45  type headerKey string
    46  
    47  const (
    48  	// RequestHeaderKey is the key which headers will be added to in the
    49  	// request context.
    50  	RequestHeaderKey headerKey = "RequestHeaderKey"
    51  	// StepParam is the step parameter.
    52  	StepParam = "step"
    53  	// LookbackParam is the lookback parameter.
    54  	LookbackParam = "lookback"
    55  	// TimeoutParam is the timeout parameter.
    56  	TimeoutParam = "timeout"
    57  
    58  	requireExhaustiveParam = "requireExhaustive"
    59  	requireNoWaitParam     = "requireNoWait"
    60  	maxInt64               = float64(math.MaxInt64)
    61  	minInt64               = float64(math.MinInt64)
    62  	maxTimeout             = 10 * time.Minute
    63  )
    64  
    65  // FetchOptionsBuilder builds fetch options based on a request and default
    66  // config.
    67  type FetchOptionsBuilder interface {
    68  	// NewFetchOptions parses an http request into fetch options.
    69  	NewFetchOptions(
    70  		ctx context.Context,
    71  		req *http.Request,
    72  	) (context.Context, *storage.FetchOptions, error)
    73  }
    74  
    75  // FetchOptionsBuilderOptions provides options to use when creating a
    76  // fetch options builder.
    77  type FetchOptionsBuilderOptions struct {
    78  	Limits        FetchOptionsBuilderLimitsOptions
    79  	RestrictByTag *storage.RestrictByTag
    80  	Timeout       time.Duration
    81  }
    82  
    83  // Validate validates the fetch options builder options.
    84  func (o FetchOptionsBuilderOptions) Validate() error {
    85  	if o.Limits.InstanceMultiple < 0 || (o.Limits.InstanceMultiple > 0 && o.Limits.InstanceMultiple < 1) {
    86  		return fmt.Errorf("InstanceMultiple must be 0 or >= 1: %v", o.Limits.InstanceMultiple)
    87  	}
    88  	return validateTimeout(o.Timeout)
    89  }
    90  
    91  // FetchOptionsBuilderLimitsOptions provides limits options to use when
    92  // creating a fetch options builder.
    93  type FetchOptionsBuilderLimitsOptions struct {
    94  	SeriesLimit                 int
    95  	InstanceMultiple            float32
    96  	DocsLimit                   int
    97  	RangeLimit                  time.Duration
    98  	ReturnedSeriesLimit         int
    99  	ReturnedDatapointsLimit     int
   100  	ReturnedSeriesMetadataLimit int
   101  	RequireExhaustive           bool
   102  	MaxMetricMetadataStats      int
   103  }
   104  
   105  type fetchOptionsBuilder struct {
   106  	opts FetchOptionsBuilderOptions
   107  }
   108  
   109  // NewFetchOptionsBuilder returns a new fetch options builder.
   110  func NewFetchOptionsBuilder(
   111  	opts FetchOptionsBuilderOptions,
   112  ) (FetchOptionsBuilder, error) {
   113  	if err := opts.Validate(); err != nil {
   114  		return nil, err
   115  	}
   116  	return fetchOptionsBuilder{opts: opts}, nil
   117  }
   118  
   119  // ParseLimit parses request limit from either header or query string.
   120  func ParseValue(req *http.Request, header, formValue string, defaultValue int) (int, error) {
   121  	if str := req.Header.Get(header); str != "" {
   122  		n, err := strconv.Atoi(str)
   123  		if err != nil {
   124  			err = fmt.Errorf(
   125  				"could not parse value: input=%s, err=%w", str, err)
   126  			return 0, err
   127  		}
   128  		return n, nil
   129  	}
   130  
   131  	if str := req.FormValue(formValue); str != "" {
   132  		n, err := strconv.Atoi(str)
   133  		if err != nil {
   134  			err = fmt.Errorf(
   135  				"could not parse value: input=%s, err=%w", str, err)
   136  			return 0, err
   137  		}
   138  		return n, nil
   139  	}
   140  
   141  	return defaultValue, nil
   142  }
   143  
   144  // ParseReadConsistencyLevel parses the ReadConsistencyLevel from either header or query string.
   145  func ParseReadConsistencyLevel(
   146  	req *http.Request, header, formValue string,
   147  ) (*topology.ReadConsistencyLevel, error) {
   148  	if str := req.Header.Get(header); str != "" {
   149  		v, err := topology.ParseReadConsistencyLevel(str)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		return &v, nil
   154  	}
   155  
   156  	if str := req.FormValue(formValue); str != "" {
   157  		v, err := topology.ParseReadConsistencyLevel(str)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		return &v, nil
   162  	}
   163  
   164  	return nil, nil
   165  }
   166  
   167  // ParseIterateEqualTimestampStrategy parses the IterateEqualTimestampStrategy from either header or query string.
   168  func ParseIterateEqualTimestampStrategy(
   169  	req *http.Request, header, formValue string,
   170  ) (*encoding.IterateEqualTimestampStrategy, error) {
   171  	if str := req.Header.Get(header); str != "" {
   172  		v, err := encoding.ParseIterateEqualTimestampStrategy(str)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		return &v, nil
   177  	}
   178  
   179  	if str := req.FormValue(formValue); str != "" {
   180  		v, err := encoding.ParseIterateEqualTimestampStrategy(str)
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  		return &v, nil
   185  	}
   186  
   187  	return nil, nil
   188  }
   189  
   190  // ParseDurationLimit parses request limit from either header or query string.
   191  func ParseDurationLimit(
   192  	req *http.Request,
   193  	header,
   194  	formValue string,
   195  	defaultLimit time.Duration,
   196  ) (time.Duration, error) {
   197  	if str := req.Header.Get(header); str != "" {
   198  		n, err := time.ParseDuration(str)
   199  		if err != nil {
   200  			err = fmt.Errorf(
   201  				"could not parse duration limit: input=%s, err=%w", str, err)
   202  			return 0, err
   203  		}
   204  		return n, nil
   205  	}
   206  
   207  	if str := req.FormValue(formValue); str != "" {
   208  		n, err := time.ParseDuration(str)
   209  		if err != nil {
   210  			err = fmt.Errorf(
   211  				"could not parse duration limit: input=%s, err=%w", str, err)
   212  			return 0, err
   213  		}
   214  		return n, nil
   215  	}
   216  
   217  	return defaultLimit, nil
   218  }
   219  
   220  // ParseInstanceMultiple parses request instance multiple from header.
   221  func ParseInstanceMultiple(req *http.Request, defaultValue float32) (float32, error) {
   222  	if str := req.Header.Get(headers.LimitInstanceMultipleHeader); str != "" {
   223  		v, err := strconv.ParseFloat(str, 32)
   224  		if err != nil {
   225  			err = fmt.Errorf(
   226  				"could not parse instance multiple: input=%s, err=%w", str, err)
   227  			return 0, err
   228  		}
   229  		return float32(v), nil
   230  	}
   231  	return defaultValue, nil
   232  }
   233  
   234  // ParseRequireExhaustive parses request limit require exhaustive from header or
   235  // query string.
   236  func ParseRequireExhaustive(req *http.Request, defaultValue bool) (bool, error) {
   237  	if str := req.Header.Get(headers.LimitRequireExhaustiveHeader); str != "" {
   238  		v, err := strconv.ParseBool(str)
   239  		if err != nil {
   240  			err = fmt.Errorf(
   241  				"could not parse limit: input=%s, err=%v", str, err)
   242  			return false, err
   243  		}
   244  		return v, nil
   245  	}
   246  
   247  	if str := req.FormValue(requireExhaustiveParam); str != "" {
   248  		v, err := strconv.ParseBool(str)
   249  		if err != nil {
   250  			err = fmt.Errorf(
   251  				"could not parse limit: input=%s, err=%v", str, err)
   252  			return false, err
   253  		}
   254  		return v, nil
   255  	}
   256  
   257  	return defaultValue, nil
   258  }
   259  
   260  // ParseRequireNoWait parses the no-wait behavior from header or
   261  // query string.
   262  func ParseRequireNoWait(req *http.Request) (bool, error) {
   263  	if str := req.Header.Get(headers.LimitRequireNoWaitHeader); str != "" {
   264  		v, err := strconv.ParseBool(str)
   265  		if err != nil {
   266  			err = fmt.Errorf(
   267  				"could not parse no-wait: input=%s, err=%w", str, err)
   268  			return false, err
   269  		}
   270  		return v, nil
   271  	}
   272  
   273  	if str := req.FormValue(requireNoWaitParam); str != "" {
   274  		v, err := strconv.ParseBool(str)
   275  		if err != nil {
   276  			err = fmt.Errorf(
   277  				"could not parse no-wait: input=%s, err=%w", str, err)
   278  			return false, err
   279  		}
   280  		return v, nil
   281  	}
   282  
   283  	return false, nil
   284  }
   285  
   286  // NewFetchOptions parses an http request into fetch options.
   287  func (b fetchOptionsBuilder) NewFetchOptions(
   288  	ctx context.Context,
   289  	req *http.Request,
   290  ) (context.Context, *storage.FetchOptions, error) {
   291  	ctx, fetchOpts, err := b.newFetchOptions(ctx, req)
   292  	if err != nil {
   293  		// Always invalid request if parsing fails params.
   294  		return nil, nil, xerrors.NewInvalidParamsError(err)
   295  	}
   296  	return ctx, fetchOpts, nil
   297  }
   298  
   299  func (b fetchOptionsBuilder) newFetchOptions(
   300  	ctx context.Context,
   301  	req *http.Request,
   302  ) (context.Context, *storage.FetchOptions, error) {
   303  	fetchOpts := storage.NewFetchOptions()
   304  
   305  	if source := req.Header.Get(headers.SourceHeader); len(source) > 0 {
   306  		fetchOpts.Source = []byte(source)
   307  	}
   308  
   309  	seriesLimit, err := ParseValue(req, headers.LimitMaxSeriesHeader,
   310  		"limit", b.opts.Limits.SeriesLimit)
   311  	if err != nil {
   312  		return nil, nil, err
   313  	}
   314  	fetchOpts.SeriesLimit = seriesLimit
   315  
   316  	instanceMultiple, err := ParseInstanceMultiple(req, b.opts.Limits.InstanceMultiple)
   317  	if err != nil {
   318  		return nil, nil, err
   319  	}
   320  	fetchOpts.InstanceMultiple = instanceMultiple
   321  
   322  	docsLimit, err := ParseValue(req, headers.LimitMaxDocsHeader,
   323  		"docsLimit", b.opts.Limits.DocsLimit)
   324  	if err != nil {
   325  		return nil, nil, err
   326  	}
   327  
   328  	fetchOpts.DocsLimit = docsLimit
   329  
   330  	rangeLimit, err := ParseDurationLimit(req, headers.LimitMaxRangeHeader,
   331  		"rangeLimit", b.opts.Limits.RangeLimit)
   332  	if err != nil {
   333  		return nil, nil, err
   334  	}
   335  
   336  	fetchOpts.RangeLimit = rangeLimit
   337  
   338  	returnedSeriesLimit, err := ParseValue(req, headers.LimitMaxReturnedSeriesHeader,
   339  		"returnedSeriesLimit", b.opts.Limits.ReturnedSeriesLimit)
   340  	if err != nil {
   341  		return nil, nil, err
   342  	}
   343  
   344  	fetchOpts.ReturnedSeriesLimit = returnedSeriesLimit
   345  
   346  	returnedDatapointsLimit, err := ParseValue(req, headers.LimitMaxReturnedDatapointsHeader,
   347  		"returnedDatapointsLimit", b.opts.Limits.ReturnedDatapointsLimit)
   348  	if err != nil {
   349  		return nil, nil, err
   350  	}
   351  
   352  	fetchOpts.ReturnedDatapointsLimit = returnedDatapointsLimit
   353  
   354  	returnedSeriesMetadataLimit, err := ParseValue(req, headers.LimitMaxReturnedSeriesMetadataHeader,
   355  		"returnedSeriesMetadataLimit", b.opts.Limits.ReturnedSeriesMetadataLimit)
   356  	if err != nil {
   357  		return nil, nil, err
   358  	}
   359  
   360  	fetchOpts.ReturnedSeriesMetadataLimit = returnedSeriesMetadataLimit
   361  
   362  	returnedMaxMetricMetadataStats, err := ParseValue(req, headers.LimitMaxMetricMetadataStatsHeader,
   363  		"returnedMaxMetricMetadataStats", b.opts.Limits.MaxMetricMetadataStats)
   364  	if err != nil {
   365  		return nil, nil, err
   366  	}
   367  
   368  	fetchOpts.MaxMetricMetadataStats = returnedMaxMetricMetadataStats
   369  
   370  	requireExhaustive, err := ParseRequireExhaustive(req, b.opts.Limits.RequireExhaustive)
   371  	if err != nil {
   372  		return nil, nil, err
   373  	}
   374  
   375  	fetchOpts.RequireExhaustive = requireExhaustive
   376  
   377  	requireNoWait, err := ParseRequireNoWait(req)
   378  	if err != nil {
   379  		return nil, nil, err
   380  	}
   381  
   382  	fetchOpts.RequireNoWait = requireNoWait
   383  
   384  	readConsistencyLevel, err := ParseReadConsistencyLevel(req, headers.ReadConsistencyLevelHeader,
   385  		"readConsistencyLevel")
   386  	if err != nil {
   387  		return nil, nil, err
   388  	}
   389  	if readConsistencyLevel != nil {
   390  		fetchOpts.ReadConsistencyLevel = readConsistencyLevel
   391  	}
   392  
   393  	iterateStrategy, err := ParseIterateEqualTimestampStrategy(req, headers.IterateEqualTimestampStrategyHeader,
   394  		"iterateEqualTimestampStrategyHeader")
   395  	if err != nil {
   396  		return nil, nil, err
   397  	}
   398  	if iterateStrategy != nil {
   399  		fetchOpts.IterateEqualTimestampStrategy = iterateStrategy
   400  	}
   401  
   402  	var (
   403  		metricsTypeHeaderFound          bool
   404  		metricsStoragePolicyHeaderFound bool
   405  	)
   406  	if str := req.Header.Get(headers.MetricsTypeHeader); str != "" {
   407  		metricsTypeHeaderFound = true
   408  		mt, err := storagemetadata.ParseMetricsType(str)
   409  		if err != nil {
   410  			err = fmt.Errorf(
   411  				"could not parse metrics type: input=%s, err=%v", str, err)
   412  			return nil, nil, err
   413  		}
   414  
   415  		fetchOpts.RestrictQueryOptions = newOrExistingRestrictQueryOptions(fetchOpts)
   416  		fetchOpts.RestrictQueryOptions.RestrictByType =
   417  			newOrExistingRestrictQueryOptionsRestrictByType(fetchOpts)
   418  		fetchOpts.RestrictQueryOptions.RestrictByType.MetricsType = mt
   419  	}
   420  
   421  	if str := req.Header.Get(headers.MetricsStoragePolicyHeader); str != "" {
   422  		metricsStoragePolicyHeaderFound = true
   423  		sp, err := policy.ParseStoragePolicy(str)
   424  		if err != nil {
   425  			err = fmt.Errorf(
   426  				"could not parse storage policy: input=%s, err=%v", str, err)
   427  			return nil, nil, err
   428  		}
   429  
   430  		fetchOpts.RestrictQueryOptions = newOrExistingRestrictQueryOptions(fetchOpts)
   431  		fetchOpts.RestrictQueryOptions.RestrictByType =
   432  			newOrExistingRestrictQueryOptionsRestrictByType(fetchOpts)
   433  		fetchOpts.RestrictQueryOptions.RestrictByType.StoragePolicy = sp
   434  	}
   435  
   436  	metricsRestrictByStoragePoliciesHeaderFound := false
   437  	if str := req.Header.Get(headers.MetricsRestrictByStoragePoliciesHeader); str != "" {
   438  		if metricsTypeHeaderFound || metricsStoragePolicyHeaderFound {
   439  			err = fmt.Errorf(
   440  				"restrict by policies is incompatible with M3-Metrics-Type and M3-Storage-Policy headers")
   441  			return nil, nil, err
   442  		}
   443  		metricsRestrictByStoragePoliciesHeaderFound = true
   444  		policyStrs := strings.Split(str, ";")
   445  		if len(policyStrs) == 0 {
   446  			err = fmt.Errorf(
   447  				"no policies specified with restrict by storage policies header")
   448  			return nil, nil, err
   449  		}
   450  		restrictByTypes := make([]*storage.RestrictByType, 0, len(policyStrs))
   451  		for _, policyStr := range policyStrs {
   452  			sp, err := policy.ParseStoragePolicy(policyStr)
   453  			if err != nil {
   454  				err = fmt.Errorf(
   455  					"could not parse storage policy: input=%s, err=%w", str, err)
   456  				return nil, nil, err
   457  			}
   458  			restrictByTypes = append(
   459  				restrictByTypes,
   460  				&storage.RestrictByType{
   461  					MetricsType:   storagemetadata.AggregatedMetricsType,
   462  					StoragePolicy: sp,
   463  				})
   464  		}
   465  
   466  		fetchOpts.RestrictQueryOptions = newOrExistingRestrictQueryOptions(fetchOpts)
   467  		fetchOpts.RestrictQueryOptions.RestrictByTypes =
   468  			newOrExistingRestrictQueryOptionsRestrictByTypes(fetchOpts)
   469  		fetchOpts.RestrictQueryOptions.RestrictByTypes = append(
   470  			fetchOpts.RestrictQueryOptions.RestrictByTypes, restrictByTypes...,
   471  		)
   472  	}
   473  
   474  	if str := req.Header.Get(headers.RestrictByTagsJSONHeader); str != "" {
   475  		// Allow header to override any default restrict by tags config.
   476  		var opts StringTagOptions
   477  		if err := json.Unmarshal([]byte(str), &opts); err != nil {
   478  			return nil, nil, err
   479  		}
   480  
   481  		tagOpts, err := opts.StorageOptions()
   482  		if err != nil {
   483  			return nil, nil, err
   484  		}
   485  
   486  		fetchOpts.RestrictQueryOptions = newOrExistingRestrictQueryOptions(fetchOpts)
   487  		fetchOpts.RestrictQueryOptions.RestrictByTag = tagOpts
   488  	} else if defaultTagOpts := b.opts.RestrictByTag; defaultTagOpts != nil {
   489  		// Apply defaults if not overridden by header.
   490  		fetchOpts.RestrictQueryOptions = newOrExistingRestrictQueryOptions(fetchOpts)
   491  		fetchOpts.RestrictQueryOptions.RestrictByTag = defaultTagOpts
   492  	}
   493  
   494  	if restrict := fetchOpts.RestrictQueryOptions; restrict != nil {
   495  		if err := restrict.Validate(); err != nil {
   496  			err = fmt.Errorf(
   497  				"could not validate restrict options: err=%v", err)
   498  			return nil, nil, err
   499  		}
   500  	}
   501  
   502  	// NB(r): Eventually all query parameters that are common across the
   503  	// implementations should be parsed here so they can be fed to the engine.
   504  	if step, ok, err := ParseStep(req); err != nil {
   505  		err = fmt.Errorf(
   506  			"could not parse step: err=%v", err)
   507  		return nil, nil, err
   508  	} else if ok {
   509  		fetchOpts.Step = step
   510  	}
   511  	if lookback, ok, err := ParseLookbackDuration(req); err != nil {
   512  		err = fmt.Errorf(
   513  			"could not parse lookback: err=%v", err)
   514  		return nil, nil, err
   515  	} else if ok {
   516  		fetchOpts.LookbackDuration = &lookback
   517  	}
   518  
   519  	if relatedQueryOpts, ok, err := ParseRelatedQueryOptions(req); err != nil {
   520  		err = fmt.Errorf(
   521  			"could not parse related query options: err=%w", err)
   522  		return nil, nil, err
   523  	} else if ok {
   524  		if metricsStoragePolicyHeaderFound || metricsTypeHeaderFound || metricsRestrictByStoragePoliciesHeaderFound {
   525  			err = fmt.Errorf(
   526  				"related queries are incompatible with M3-Metrics-Type, " +
   527  					"Restrict-By-Storage-Policies, and M3-Storage-Policy headers")
   528  			return nil, nil, err
   529  		}
   530  		fetchOpts.RelatedQueryOptions = relatedQueryOpts
   531  	}
   532  
   533  	fetchOpts.Timeout, err = ParseRequestTimeout(req, b.opts.Timeout)
   534  	if err != nil {
   535  		return nil, nil, fmt.Errorf("could not parse timeout: err=%w", err)
   536  	}
   537  
   538  	// Set timeout on the returned context.
   539  	ctx, _ = contextWithRequestAndTimeout(ctx, req, fetchOpts)
   540  	return ctx, fetchOpts, nil
   541  }
   542  
   543  func newOrExistingRestrictQueryOptions(
   544  	fetchOpts *storage.FetchOptions,
   545  ) *storage.RestrictQueryOptions {
   546  	if v := fetchOpts.RestrictQueryOptions; v != nil {
   547  		return v
   548  	}
   549  	return &storage.RestrictQueryOptions{}
   550  }
   551  
   552  func newOrExistingRestrictQueryOptionsRestrictByType(
   553  	fetchOpts *storage.FetchOptions,
   554  ) *storage.RestrictByType {
   555  	if v := fetchOpts.RestrictQueryOptions.RestrictByType; v != nil {
   556  		return v
   557  	}
   558  	return &storage.RestrictByType{}
   559  }
   560  
   561  func newOrExistingRestrictQueryOptionsRestrictByTypes(
   562  	fetchOpts *storage.FetchOptions,
   563  ) []*storage.RestrictByType {
   564  	if v := fetchOpts.RestrictQueryOptions.RestrictByTypes; v != nil {
   565  		return v
   566  	}
   567  	return make([]*storage.RestrictByType, 0)
   568  }
   569  
   570  // contextWithRequestAndTimeout sets up a context with the request's context
   571  // and the configured timeout.
   572  func contextWithRequestAndTimeout(
   573  	ctx context.Context,
   574  	r *http.Request,
   575  	opts *storage.FetchOptions,
   576  ) (context.Context, context.CancelFunc) {
   577  	ctx = context.WithValue(ctx, RequestHeaderKey, r.Header)
   578  	return context.WithTimeout(ctx, opts.Timeout)
   579  }
   580  
   581  // ParseStep parses the step duration for an HTTP request.
   582  func ParseStep(r *http.Request) (time.Duration, bool, error) {
   583  	step := r.FormValue(StepParam)
   584  	if step == "" {
   585  		return 0, false, nil
   586  	}
   587  	value, err := parseStep(r, StepParam)
   588  	if err != nil {
   589  		return 0, false, err
   590  	}
   591  	return value, true, err
   592  }
   593  
   594  func parseStep(r *http.Request, key string) (time.Duration, error) {
   595  	step, err := ParseDuration(r, key)
   596  	if err != nil {
   597  		return 0, err
   598  	}
   599  	if step <= 0 {
   600  		return 0, fmt.Errorf("expected postive step size, instead got: %d", step)
   601  	}
   602  	return step, nil
   603  }
   604  
   605  // ParseLookbackDuration parses a lookback duration for an HTTP request.
   606  func ParseLookbackDuration(r *http.Request) (time.Duration, bool, error) {
   607  	lookback := r.FormValue(LookbackParam)
   608  	if lookback == "" {
   609  		return 0, false, nil
   610  	}
   611  
   612  	if lookback == StepParam {
   613  		// Use the step size as lookback.
   614  		step, err := parseStep(r, StepParam)
   615  		if err != nil {
   616  			return 0, false, err
   617  		}
   618  		return step, true, nil
   619  	}
   620  
   621  	// Otherwise it is specified as duration value.
   622  	value, err := parseStep(r, LookbackParam)
   623  	if err != nil {
   624  		return 0, false, err
   625  	}
   626  
   627  	return value, true, nil
   628  }
   629  
   630  // ParseDuration parses a duration HTTP parameter.
   631  // nolint: unparam
   632  func ParseDuration(r *http.Request, key string) (time.Duration, error) {
   633  	str := strings.TrimSpace(r.FormValue(key))
   634  	if str == "" {
   635  		return 0, errors.ErrNotFound
   636  	}
   637  
   638  	value, durationErr := time.ParseDuration(str)
   639  	if durationErr == nil {
   640  		return value, nil
   641  	}
   642  
   643  	// Try parsing as a float value specifying seconds, the Prometheus default.
   644  	seconds, floatErr := strconv.ParseFloat(str, 64)
   645  	if floatErr == nil {
   646  		ts := seconds * float64(time.Second)
   647  		if ts > maxInt64 || ts < minInt64 {
   648  			return 0, fmt.Errorf("cannot parse duration='%s': as_float_err="+
   649  				"int64 overflow after float conversion", str)
   650  		}
   651  
   652  		return time.Duration(ts), nil
   653  	}
   654  
   655  	return 0, fmt.Errorf("cannot parse duration='%s': as_duration_err=%s, as_float_err=%s",
   656  		str, durationErr, floatErr)
   657  }
   658  
   659  // ParseRequestTimeout parses the input request timeout with a default.
   660  func ParseRequestTimeout(
   661  	r *http.Request,
   662  	configFetchTimeout time.Duration,
   663  ) (time.Duration, error) {
   664  	var timeout string
   665  	if v := r.FormValue(TimeoutParam); v != "" {
   666  		timeout = v
   667  	}
   668  	// Note: Header should take precedence.
   669  	if v := r.Header.Get(TimeoutParam); v != "" {
   670  		timeout = v
   671  	}
   672  	// Prefer the M3-Timeout header to the incorrect header using the param name. The param name should have never been
   673  	// allowed as a header, but we continue to support it for backwards compatibility.
   674  	if v := r.Header.Get(headers.TimeoutHeader); v != "" {
   675  		timeout = v
   676  	}
   677  
   678  	if timeout == "" {
   679  		return configFetchTimeout, nil
   680  	}
   681  
   682  	duration, err := time.ParseDuration(timeout)
   683  	if err != nil {
   684  		return 0, xerrors.NewInvalidParamsError(
   685  			fmt.Errorf("invalid 'timeout': %v", err))
   686  	}
   687  
   688  	if err := validateTimeout(duration); err != nil {
   689  		return 0, err
   690  	}
   691  
   692  	return duration, nil
   693  }
   694  
   695  // ParseRelatedQueryOptions parses the RelatedQueryOptions struct out of the request
   696  // it returns ok==false if no such options exist
   697  func ParseRelatedQueryOptions(r *http.Request) (*storage.RelatedQueryOptions, bool, error) {
   698  	str := r.Header.Get(headers.RelatedQueriesHeader)
   699  	if str == "" {
   700  		return nil, false, nil
   701  	}
   702  
   703  	vals := strings.Split(str, ";")
   704  	queryRanges := make([]storage.QueryTimespan, 0, len(vals))
   705  	for _, headerVal := range vals {
   706  		parts := strings.Split(headerVal, ":")
   707  		if len(parts) != 2 {
   708  			return nil, false, xerrors.NewInvalidParamsError(
   709  				fmt.Errorf("invalid '%s': expected colon-separated pair of start/end timestamps, but got %v",
   710  					headers.RelatedQueriesHeader,
   711  					headerVal))
   712  		}
   713  
   714  		startTS, endTS := parts[0], parts[1]
   715  		startTime, err := util.ParseTimeString(startTS)
   716  		if err != nil {
   717  			return nil, false, xerrors.NewInvalidParamsError(
   718  				fmt.Errorf("invalid '%s': Cannot parse %v to time in pair %v",
   719  					headers.RelatedQueriesHeader,
   720  					startTS,
   721  					headerVal))
   722  		}
   723  		endTime, err := util.ParseTimeString(endTS)
   724  		if err != nil {
   725  			return nil, false, xerrors.NewInvalidParamsError(
   726  				fmt.Errorf("invalid '%s': Cannot parse %v to time in pair %v", headers.RelatedQueriesHeader,
   727  					endTS, headerVal))
   728  		}
   729  		if startTime.After(endTime) {
   730  			return nil, false, xerrors.NewInvalidParamsError(
   731  				fmt.Errorf("invalid '%s': startTime after endTime in pair %v", headers.RelatedQueriesHeader,
   732  					headerVal))
   733  		}
   734  		val := storage.QueryTimespan{Start: xtime.ToUnixNano(startTime), End: xtime.ToUnixNano(endTime)}
   735  		queryRanges = append(queryRanges, val)
   736  	}
   737  
   738  	return &storage.RelatedQueryOptions{
   739  		Timespans: queryRanges,
   740  	}, true, nil
   741  }
   742  
   743  func validateTimeout(v time.Duration) error {
   744  	if v <= 0 {
   745  		return xerrors.NewInvalidParamsError(
   746  			fmt.Errorf("invalid 'timeout': less than or equal to zero %v", v))
   747  	}
   748  	if v > maxTimeout {
   749  		return xerrors.NewInvalidParamsError(
   750  			fmt.Errorf("invalid 'timeout': %v greater than max %v", v, maxTimeout))
   751  	}
   752  	return nil
   753  }