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 }