github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/roundtrip_test.go (about) 1 // Copyright (c) The Thanos Authors. 2 // Licensed under the Apache License 2.0. 3 4 package queryfrontend 5 6 import ( 7 "context" 8 "encoding/json" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/go-kit/log" 17 "github.com/prometheus/common/model" 18 "github.com/prometheus/prometheus/model/labels" 19 "github.com/prometheus/prometheus/promql/parser" 20 "github.com/weaveworks/common/user" 21 22 "github.com/efficientgo/core/testutil" 23 cortexcache "github.com/thanos-io/thanos/internal/cortex/chunk/cache" 24 "github.com/thanos-io/thanos/internal/cortex/cortexpb" 25 "github.com/thanos-io/thanos/internal/cortex/querier/queryrange" 26 cortexvalidation "github.com/thanos-io/thanos/internal/cortex/util/validation" 27 "github.com/thanos-io/thanos/pkg/store/labelpb" 28 ) 29 30 const ( 31 seconds = 1e3 // 1e3 milliseconds per second. 32 hour = 3600 * seconds 33 day = 24 * time.Hour 34 ) 35 36 var defaultLimits = &cortexvalidation.Limits{ 37 MaxQueryLength: model.Duration(7 * 24 * time.Hour), 38 MaxQueryParallelism: 14, 39 MaxCacheFreshness: model.Duration(time.Minute), 40 } 41 42 // fakeRoundTripper implements the RoundTripper interface. 43 type fakeRoundTripper struct { 44 *httptest.Server 45 host string 46 } 47 48 func newFakeRoundTripper() (*fakeRoundTripper, error) { 49 s := httptest.NewServer(nil) 50 u, err := url.Parse(s.URL) 51 if err != nil { 52 return nil, err 53 } 54 return &fakeRoundTripper{ 55 Server: s, 56 host: u.Host, 57 }, nil 58 } 59 60 // setHandler is used for mocking. 61 func (r *fakeRoundTripper) setHandler(h http.Handler) { 62 r.Config.Handler = h 63 } 64 65 func (r *fakeRoundTripper) RoundTrip(h *http.Request) (*http.Response, error) { 66 h.URL.Scheme = "http" 67 h.URL.Host = r.host 68 return http.DefaultTransport.RoundTrip(h) 69 } 70 71 // TestRoundTripRetryMiddleware tests the retry middleware. 72 func TestRoundTripRetryMiddleware(t *testing.T) { 73 testRequest := &ThanosQueryRangeRequest{ 74 Path: "/api/v1/query_range", 75 Start: 0, 76 End: 2 * hour, 77 Step: 10 * seconds, 78 Query: "foo", 79 } 80 81 testLabelsRequest := &ThanosLabelsRequest{Path: "/api/v1/labels", Start: 0, End: 2 * hour} 82 testSeriesRequest := &ThanosSeriesRequest{ 83 Path: "/api/v1/series", 84 Start: 0, 85 End: 2 * hour, 86 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 87 } 88 89 queryRangeCodec := NewThanosQueryRangeCodec(true) 90 labelsCodec := NewThanosLabelsCodec(true, 2*time.Hour) 91 92 for _, tc := range []struct { 93 name string 94 maxRetries int 95 req queryrange.Request 96 codec queryrange.Codec 97 handlerFunc func(fail bool) (*int, http.Handler) 98 fail bool 99 expected int 100 }{ 101 { 102 name: "no retry, get counter value 1", 103 maxRetries: 0, 104 req: testRequest, 105 codec: queryRangeCodec, 106 handlerFunc: promqlResults, 107 fail: true, 108 expected: 1, 109 }, 110 { 111 name: "retry set to 1", 112 maxRetries: 1, 113 req: testRequest, 114 codec: queryRangeCodec, 115 handlerFunc: promqlResults, 116 fail: true, 117 expected: 1, 118 }, 119 { 120 name: "retry set to 3", 121 maxRetries: 3, 122 fail: true, 123 req: testRequest, 124 codec: queryRangeCodec, 125 handlerFunc: promqlResults, 126 expected: 3, 127 }, 128 { 129 name: "labels requests: no retry, get counter value 1", 130 maxRetries: 0, 131 req: testLabelsRequest, 132 codec: labelsCodec, 133 handlerFunc: labelsResults, 134 fail: true, 135 expected: 1, 136 }, 137 { 138 name: "labels requests: retry set to 1", 139 maxRetries: 1, 140 req: testLabelsRequest, 141 codec: labelsCodec, 142 handlerFunc: labelsResults, 143 fail: true, 144 expected: 1, 145 }, 146 { 147 name: "labels requests: retry set to 3", 148 maxRetries: 3, 149 fail: true, 150 req: testLabelsRequest, 151 codec: labelsCodec, 152 handlerFunc: labelsResults, 153 expected: 3, 154 }, 155 { 156 name: "series requests: no retry, get counter value 1", 157 maxRetries: 0, 158 req: testSeriesRequest, 159 codec: labelsCodec, 160 handlerFunc: seriesResults, 161 fail: true, 162 expected: 1, 163 }, 164 { 165 name: "series requests: retry set to 1", 166 maxRetries: 1, 167 req: testSeriesRequest, 168 codec: labelsCodec, 169 handlerFunc: seriesResults, 170 fail: true, 171 expected: 1, 172 }, 173 { 174 name: "series requests: retry set to 3", 175 maxRetries: 3, 176 fail: true, 177 req: testSeriesRequest, 178 codec: labelsCodec, 179 handlerFunc: seriesResults, 180 expected: 3, 181 }, 182 } { 183 184 t.Run(tc.name, func(t *testing.T) { 185 tpw, err := NewTripperware( 186 Config{ 187 QueryRangeConfig: QueryRangeConfig{ 188 MaxRetries: tc.maxRetries, 189 Limits: defaultLimits, 190 SplitQueriesByInterval: day, 191 }, 192 LabelsConfig: LabelsConfig{ 193 MaxRetries: tc.maxRetries, 194 Limits: defaultLimits, 195 SplitQueriesByInterval: day, 196 }, 197 }, nil, log.NewNopLogger(), 198 ) 199 testutil.Ok(t, err) 200 201 rt, err := newFakeRoundTripper() 202 testutil.Ok(t, err) 203 res, handler := tc.handlerFunc(tc.fail) 204 rt.setHandler(handler) 205 206 ctx := user.InjectOrgID(context.Background(), "1") 207 httpReq, err := tc.codec.EncodeRequest(ctx, tc.req) 208 testutil.Ok(t, err) 209 210 _, err = tpw(rt).RoundTrip(httpReq) 211 testutil.Equals(t, tc.fail, err != nil) 212 213 testutil.Equals(t, tc.expected, *res) 214 }) 215 216 } 217 } 218 219 // TestRoundTripSplitIntervalMiddleware tests the split interval middleware. 220 func TestRoundTripSplitIntervalMiddleware(t *testing.T) { 221 testRequest := &ThanosQueryRangeRequest{ 222 Path: "/api/v1/query_range", 223 Start: 0, 224 End: 2 * hour, 225 Step: 10 * seconds, 226 Query: "foo", 227 } 228 229 testLabelsRequest := &ThanosLabelsRequest{ 230 Path: "/api/v1/labels", 231 Start: 0, 232 End: 2 * hour, 233 } 234 235 testSeriesRequest := &ThanosSeriesRequest{ 236 Path: "/api/v1/series", 237 Start: 0, 238 End: 2 * hour, 239 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 240 } 241 242 queryRangeCodec := NewThanosQueryRangeCodec(true) 243 labelsCodec := NewThanosLabelsCodec(true, 2*time.Hour) 244 245 for _, tc := range []struct { 246 name string 247 splitInterval time.Duration 248 querySplitThreshold time.Duration 249 maxSplitInterval time.Duration 250 minHorizontalShards int64 251 req queryrange.Request 252 codec queryrange.Codec 253 handlerFunc func(bool) (*int, http.Handler) 254 expected int 255 }{ 256 { 257 name: "split interval == 0, disable split", 258 req: testRequest, 259 handlerFunc: promqlResults, 260 codec: queryRangeCodec, 261 splitInterval: 0, 262 expected: 1, 263 }, 264 { 265 name: "won't be split. Interval == time range", 266 req: testRequest, 267 handlerFunc: promqlResults, 268 codec: queryRangeCodec, 269 splitInterval: 2 * time.Hour, 270 expected: 1, 271 }, 272 { 273 name: "won't be split. Interval > time range", 274 req: testRequest, 275 handlerFunc: promqlResults, 276 codec: queryRangeCodec, 277 splitInterval: day, 278 expected: 1, 279 }, 280 { 281 name: "split to 2 requests", 282 req: testRequest, 283 handlerFunc: promqlResults, 284 codec: queryRangeCodec, 285 splitInterval: 1 * time.Hour, 286 expected: 2, 287 }, 288 { 289 name: "split to 4 requests, due to min horizontal shards", 290 req: testRequest, 291 handlerFunc: promqlResults, 292 codec: queryRangeCodec, 293 splitInterval: 0, 294 querySplitThreshold: 30 * time.Minute, 295 maxSplitInterval: 4 * time.Hour, 296 minHorizontalShards: 4, 297 expected: 4, 298 }, 299 { 300 name: "split to 2 requests, due to maxSplitInterval", 301 req: testRequest, 302 handlerFunc: promqlResults, 303 codec: queryRangeCodec, 304 splitInterval: 0, 305 querySplitThreshold: 30 * time.Minute, 306 maxSplitInterval: 1 * time.Hour, 307 minHorizontalShards: 4, 308 expected: 2, 309 }, 310 { 311 name: "split to 2 requests, due to maxSplitInterval", 312 req: testRequest, 313 handlerFunc: promqlResults, 314 codec: queryRangeCodec, 315 splitInterval: 0, 316 querySplitThreshold: 2 * time.Hour, 317 maxSplitInterval: 4 * time.Hour, 318 minHorizontalShards: 4, 319 expected: 1, 320 }, 321 { 322 name: "labels request won't be split", 323 req: testLabelsRequest, 324 handlerFunc: labelsResults, 325 codec: labelsCodec, 326 splitInterval: day, 327 expected: 1, 328 }, 329 { 330 name: "labels request split to 2", 331 req: testLabelsRequest, 332 handlerFunc: labelsResults, 333 codec: labelsCodec, 334 splitInterval: 1 * time.Hour, 335 expected: 2, 336 }, 337 { 338 name: "series request won't be split", 339 req: testSeriesRequest, 340 handlerFunc: seriesResults, 341 codec: labelsCodec, 342 splitInterval: day, 343 expected: 1, 344 }, 345 { 346 name: "series request split to 2", 347 req: testSeriesRequest, 348 handlerFunc: seriesResults, 349 codec: labelsCodec, 350 splitInterval: 1 * time.Hour, 351 expected: 2, 352 }, 353 } { 354 355 t.Run(tc.name, func(t *testing.T) { 356 tpw, err := NewTripperware( 357 Config{ 358 QueryRangeConfig: QueryRangeConfig{ 359 Limits: defaultLimits, 360 SplitQueriesByInterval: tc.splitInterval, 361 MinQuerySplitInterval: tc.querySplitThreshold, 362 MaxQuerySplitInterval: tc.maxSplitInterval, 363 HorizontalShards: tc.minHorizontalShards, 364 }, 365 LabelsConfig: LabelsConfig{ 366 Limits: defaultLimits, 367 SplitQueriesByInterval: tc.splitInterval, 368 }, 369 }, nil, log.NewNopLogger(), 370 ) 371 testutil.Ok(t, err) 372 373 rt, err := newFakeRoundTripper() 374 testutil.Ok(t, err) 375 defer rt.Close() 376 res, handler := tc.handlerFunc(false) 377 rt.setHandler(handler) 378 379 ctx := user.InjectOrgID(context.Background(), "1") 380 httpReq, err := tc.codec.EncodeRequest(ctx, tc.req) 381 testutil.Ok(t, err) 382 383 _, err = tpw(rt).RoundTrip(httpReq) 384 testutil.Ok(t, err) 385 386 testutil.Equals(t, tc.expected, *res) 387 }) 388 } 389 } 390 391 // TestRoundTripQueryRangeCacheMiddleware tests the cache middleware. 392 func TestRoundTripQueryRangeCacheMiddleware(t *testing.T) { 393 testRequest := &ThanosQueryRangeRequest{ 394 Path: "/api/v1/query_range", 395 Start: 0, 396 End: 2 * hour, 397 Step: 10 * seconds, 398 MaxSourceResolution: 1 * seconds, 399 Dedup: true, // Deduplication is enabled by default. 400 Query: "foo", 401 } 402 403 testRequestWithoutDedup := &ThanosQueryRangeRequest{ 404 Path: "/api/v1/query_range", 405 Start: 0, 406 End: 2 * hour, 407 Step: 10 * seconds, 408 MaxSourceResolution: 1 * seconds, 409 Dedup: false, 410 Query: "foo", 411 } 412 413 // Same query params as testRequest, different maxSourceResolution 414 // but still in the same downsampling level, so it will be cached in this case. 415 testRequestSameLevelDownsampling := &ThanosQueryRangeRequest{ 416 Path: "/api/v1/query_range", 417 Start: 0, 418 End: 2 * hour, 419 Step: 10 * seconds, 420 MaxSourceResolution: 10 * seconds, 421 Dedup: true, 422 Query: "foo", 423 } 424 425 // Same query params as testRequest, different maxSourceResolution 426 // and downsampling level so it won't be cached in this case. 427 testRequestHigherLevelDownsampling := &ThanosQueryRangeRequest{ 428 Path: "/api/v1/query_range", 429 Start: 0, 430 End: 2 * hour, 431 Step: 10 * seconds, 432 MaxSourceResolution: 1 * hour, 433 Dedup: true, 434 Query: "foo", 435 } 436 437 // Same query params as testRequest, but with storeMatchers 438 testRequestWithStoreMatchers := &ThanosQueryRangeRequest{ 439 Path: "/api/v1/query_range", 440 Start: 0, 441 End: 2 * hour, 442 Step: 10 * seconds, 443 MaxSourceResolution: 1 * seconds, 444 StoreMatchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 445 Dedup: true, 446 Query: "foo", 447 } 448 449 cacheConf := &queryrange.ResultsCacheConfig{ 450 CacheConfig: cortexcache.Config{ 451 EnableFifoCache: true, 452 Fifocache: cortexcache.FifoCacheConfig{ 453 MaxSizeBytes: "1MiB", 454 MaxSizeItems: 1000, 455 Validity: time.Hour, 456 }, 457 }, 458 } 459 460 tpw, err := NewTripperware( 461 Config{ 462 QueryRangeConfig: QueryRangeConfig{ 463 Limits: defaultLimits, 464 ResultsCacheConfig: cacheConf, 465 SplitQueriesByInterval: day, 466 }, 467 }, nil, log.NewNopLogger(), 468 ) 469 testutil.Ok(t, err) 470 471 rt, err := newFakeRoundTripper() 472 testutil.Ok(t, err) 473 defer rt.Close() 474 res, handler := promqlResults(false) 475 rt.setHandler(handler) 476 477 for _, tc := range []struct { 478 name string 479 req queryrange.Request 480 handlerAndResult func() (*int, http.Handler) 481 expected int 482 }{ 483 {name: "first request", req: testRequest, expected: 1}, 484 {name: "same request as the first one, directly use cache", req: testRequest, expected: 1}, 485 {name: "same request as the first one but with dedup disabled, should not use cache", req: testRequestWithoutDedup, expected: 2}, 486 {name: "different max source resolution but still same level", req: testRequestSameLevelDownsampling, expected: 2}, 487 {name: "different max source resolution and different level", req: testRequestHigherLevelDownsampling, expected: 3}, 488 {name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 4}, 489 { 490 name: "request but will be partitioned", 491 req: &ThanosQueryRangeRequest{ 492 Path: "/api/v1/query_range", 493 Start: 0, 494 End: 25 * hour, 495 Step: 10 * seconds, 496 Dedup: true, 497 Query: "foo", 498 }, 499 expected: 6, 500 }, 501 { 502 name: "same query as the previous one", 503 req: &ThanosQueryRangeRequest{ 504 Path: "/api/v1/query_range", 505 Start: 0, 506 End: 25 * hour, 507 Step: 10 * seconds, 508 Dedup: true, 509 Query: "foo", 510 }, 511 expected: 6, 512 }, 513 } { 514 if !t.Run(tc.name, func(t *testing.T) { 515 ctx := user.InjectOrgID(context.Background(), "1") 516 httpReq, err := NewThanosQueryRangeCodec(true).EncodeRequest(ctx, tc.req) 517 testutil.Ok(t, err) 518 519 _, err = tpw(rt).RoundTrip(httpReq) 520 testutil.Ok(t, err) 521 522 testutil.Equals(t, tc.expected, *res) 523 }) { 524 break 525 } 526 } 527 } 528 529 func TestRoundTripQueryCacheWithShardingMiddleware(t *testing.T) { 530 testRequest := &ThanosQueryRangeRequest{ 531 Path: "/api/v1/query_range", 532 Start: 0, 533 End: 2 * hour, 534 Step: 10 * seconds, 535 Dedup: true, 536 Query: "sum by (pod) (memory_usage)", 537 Timeout: hour, 538 } 539 540 cacheConf := &queryrange.ResultsCacheConfig{ 541 CacheConfig: cortexcache.Config{ 542 EnableFifoCache: true, 543 Fifocache: cortexcache.FifoCacheConfig{ 544 MaxSizeBytes: "1MiB", 545 MaxSizeItems: 1000, 546 Validity: time.Hour, 547 }, 548 }, 549 } 550 551 tpw, err := NewTripperware( 552 Config{ 553 NumShards: 2, 554 QueryRangeConfig: QueryRangeConfig{ 555 Limits: defaultLimits, 556 ResultsCacheConfig: cacheConf, 557 SplitQueriesByInterval: day, 558 }, 559 }, nil, log.NewNopLogger(), 560 ) 561 testutil.Ok(t, err) 562 563 rt, err := newFakeRoundTripper() 564 testutil.Ok(t, err) 565 defer rt.Close() 566 res, handler := promqlResultsWithFailures(3) 567 rt.setHandler(handler) 568 569 for _, tc := range []struct { 570 name string 571 req queryrange.Request 572 err bool 573 expected int 574 }{ 575 { 576 name: "query with vertical sharding", 577 req: testRequest, 578 err: true, 579 expected: 2, 580 }, 581 { 582 name: "same query as before, both requests are executed", 583 req: testRequest, 584 err: true, 585 expected: 4, 586 }, 587 { 588 name: "same query as before, one request is executed", 589 req: testRequest, 590 err: false, 591 expected: 5, 592 }, 593 { 594 name: "same query as before again, no requests are executed", 595 req: testRequest, 596 err: false, 597 expected: 5, 598 }, 599 } { 600 if !t.Run(tc.name, func(t *testing.T) { 601 ctx := user.InjectOrgID(context.Background(), "1") 602 httpReq, err := NewThanosQueryRangeCodec(true).EncodeRequest(ctx, tc.req) 603 testutil.Ok(t, err) 604 605 _, err = tpw(rt).RoundTrip(httpReq) 606 if tc.err { 607 testutil.NotOk(t, err) 608 } else { 609 testutil.Ok(t, err) 610 } 611 612 testutil.Equals(t, tc.expected, *res) 613 }) { 614 break 615 } 616 } 617 } 618 619 // TestRoundTripLabelsCacheMiddleware tests the cache middleware for labels requests. 620 func TestRoundTripLabelsCacheMiddleware(t *testing.T) { 621 testRequest := &ThanosLabelsRequest{ 622 Path: "/api/v1/labels", 623 Start: 0, 624 End: 2 * hour, 625 } 626 627 // Same query params as testRequest, but with Matchers 628 testRequestWithMatchers := &ThanosLabelsRequest{ 629 Path: "/api/v1/labels", 630 Start: 0, 631 End: 2 * hour, 632 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 633 } 634 635 // Same query params as testRequest, but with storeMatchers 636 testRequestWithStoreMatchers := &ThanosLabelsRequest{ 637 Path: "/api/v1/labels", 638 Start: 0, 639 End: 2 * hour, 640 StoreMatchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 641 } 642 643 testLabelValuesRequestFoo := &ThanosLabelsRequest{ 644 Path: "/api/v1/label/foo/values", 645 Start: 0, 646 End: 2 * hour, 647 Label: "foo", 648 } 649 650 testLabelValuesRequestFooWithMatchers := &ThanosLabelsRequest{ 651 Path: "/api/v1/label/foo/values", 652 Start: 0, 653 End: 2 * hour, 654 Label: "foo", 655 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 656 } 657 658 testLabelValuesRequestBar := &ThanosLabelsRequest{ 659 Path: "/api/v1/label/bar/values", 660 Start: 0, 661 End: 2 * hour, 662 Label: "bar", 663 } 664 665 cacheConf := &queryrange.ResultsCacheConfig{ 666 CacheConfig: cortexcache.Config{ 667 EnableFifoCache: true, 668 Fifocache: cortexcache.FifoCacheConfig{ 669 MaxSizeBytes: "1MiB", 670 MaxSizeItems: 1000, 671 Validity: time.Hour, 672 }, 673 }, 674 } 675 676 tpw, err := NewTripperware( 677 Config{ 678 LabelsConfig: LabelsConfig{ 679 Limits: defaultLimits, 680 ResultsCacheConfig: cacheConf, 681 SplitQueriesByInterval: day, 682 }, 683 }, nil, log.NewNopLogger(), 684 ) 685 testutil.Ok(t, err) 686 687 rt, err := newFakeRoundTripper() 688 testutil.Ok(t, err) 689 defer rt.Close() 690 res, handler := labelsResults(false) 691 rt.setHandler(handler) 692 693 for _, tc := range []struct { 694 name string 695 req queryrange.Request 696 handlerAndResult func() (*int, http.Handler) 697 expected int 698 }{ 699 {name: "first request", req: testRequest, expected: 1}, 700 {name: "same request as the first one, directly use cache", req: testRequest, expected: 1}, 701 {name: "matchers requests won't go to cache", req: testRequestWithMatchers, expected: 2}, 702 {name: "same matchers requests, use cache", req: testRequestWithMatchers, expected: 2}, 703 {name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 3}, 704 {name: "label values request label name foo", req: testLabelValuesRequestFoo, expected: 4}, 705 {name: "same label values query, use cache", req: testLabelValuesRequestFoo, expected: 4}, 706 {name: "label values query with matchers, won't go to cache", req: testLabelValuesRequestFooWithMatchers, expected: 5}, 707 {name: "same label values query with matchers, use cache", req: testLabelValuesRequestFooWithMatchers, expected: 5}, 708 {name: "label values request different label", req: testLabelValuesRequestBar, expected: 6}, 709 { 710 name: "request but will be partitioned", 711 req: &ThanosLabelsRequest{ 712 Path: "/api/v1/labels", 713 Start: 0, 714 End: 25 * hour, 715 }, 716 expected: 8, 717 }, 718 { 719 name: "same query as the previous one", 720 req: &ThanosLabelsRequest{ 721 Path: "/api/v1/labels", 722 Start: 0, 723 End: 25 * hour, 724 }, 725 expected: 8, 726 }, 727 } { 728 if !t.Run(tc.name, func(t *testing.T) { 729 ctx := user.InjectOrgID(context.Background(), "1") 730 httpReq, err := NewThanosLabelsCodec(true, 24*time.Hour).EncodeRequest(ctx, tc.req) 731 testutil.Ok(t, err) 732 733 _, err = tpw(rt).RoundTrip(httpReq) 734 testutil.Ok(t, err) 735 736 testutil.Equals(t, tc.expected, *res) 737 }) { 738 break 739 } 740 } 741 } 742 743 // TestRoundTripSeriesCacheMiddleware tests the cache middleware for series requests. 744 func TestRoundTripSeriesCacheMiddleware(t *testing.T) { 745 testRequest := &ThanosSeriesRequest{ 746 Path: "/api/v1/series", 747 Start: 0, 748 End: 2 * hour, 749 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 750 Dedup: true, 751 } 752 753 testRequestWithoutDedup := &ThanosSeriesRequest{ 754 Path: "/api/v1/series", 755 Start: 0, 756 End: 2 * hour, 757 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 758 Dedup: false, 759 } 760 761 // Different matchers set with the first request. 762 testRequest2 := &ThanosSeriesRequest{ 763 Path: "/api/v1/series", 764 Start: 0, 765 End: 2 * hour, 766 Matchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "baz")}}, 767 Dedup: true, 768 } 769 770 // Same query params as testRequest, but with storeMatchers 771 testRequestWithStoreMatchers := &ThanosSeriesRequest{ 772 Path: "/api/v1/series", 773 Start: 0, 774 End: 2 * hour, 775 StoreMatchers: [][]*labels.Matcher{{labels.MustNewMatcher(labels.MatchEqual, "foo", "bar")}}, 776 } 777 778 cacheConf := &queryrange.ResultsCacheConfig{ 779 CacheConfig: cortexcache.Config{ 780 EnableFifoCache: true, 781 Fifocache: cortexcache.FifoCacheConfig{ 782 MaxSizeBytes: "1MiB", 783 MaxSizeItems: 1000, 784 Validity: time.Hour, 785 }, 786 }, 787 } 788 789 tpw, err := NewTripperware( 790 Config{ 791 LabelsConfig: LabelsConfig{ 792 Limits: defaultLimits, 793 ResultsCacheConfig: cacheConf, 794 SplitQueriesByInterval: day, 795 }, 796 }, nil, log.NewNopLogger(), 797 ) 798 testutil.Ok(t, err) 799 800 rt, err := newFakeRoundTripper() 801 testutil.Ok(t, err) 802 defer rt.Close() 803 res, handler := seriesResults(false) 804 rt.setHandler(handler) 805 806 for _, tc := range []struct { 807 name string 808 req queryrange.Request 809 handlerAndResult func() (*int, http.Handler) 810 expected int 811 }{ 812 {name: "first request", req: testRequest, expected: 1}, 813 {name: "same request as the first one, directly use cache", req: testRequest, expected: 1}, 814 {name: "same request as the first one but with dedup disabled, should not use cache", req: testRequestWithoutDedup, expected: 2}, 815 {name: "different series request, not use cache", req: testRequest2, expected: 3}, 816 {name: "storeMatchers requests won't go to cache", req: testRequestWithStoreMatchers, expected: 4}, 817 } { 818 819 if !t.Run(tc.name, func(t *testing.T) { 820 ctx := user.InjectOrgID(context.Background(), "1") 821 httpReq, err := NewThanosLabelsCodec(true, 24*time.Hour).EncodeRequest(ctx, tc.req) 822 testutil.Ok(t, err) 823 824 _, err = tpw(rt).RoundTrip(httpReq) 825 testutil.Ok(t, err) 826 827 testutil.Equals(t, tc.expected, *res) 828 }) { 829 break 830 } 831 } 832 } 833 834 // promqlResults is a mock handler used to test split and cache middleware. 835 // Modified from Loki https://github.com/grafana/loki/blob/master/pkg/querier/queryrange/roundtrip_test.go#L547. 836 func promqlResults(fail bool) (*int, http.Handler) { 837 count := 0 838 var lock sync.Mutex 839 q := queryrange.PrometheusResponse{ 840 Status: "success", 841 Data: queryrange.PrometheusData{ 842 ResultType: string(parser.ValueTypeMatrix), 843 Result: []queryrange.SampleStream{ 844 { 845 Labels: []cortexpb.LabelAdapter{}, 846 Samples: []cortexpb.Sample{ 847 {Value: 0, TimestampMs: 0}, 848 {Value: 1, TimestampMs: 1}, 849 }, 850 }, 851 }, 852 }, 853 } 854 855 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 856 lock.Lock() 857 defer lock.Unlock() 858 859 // Set fail in the response code to test retry. 860 if fail { 861 w.WriteHeader(500) 862 } 863 if err := json.NewEncoder(w).Encode(q); err != nil { 864 panic(err) 865 } 866 count++ 867 }) 868 } 869 870 // promqlResultsWithFailures is a mock handler used to test split and cache middleware. 871 // it will return a failed response numFailures times. 872 func promqlResultsWithFailures(numFailures int) (*int, http.Handler) { 873 count := 0 874 var lock sync.Mutex 875 q := queryrange.PrometheusResponse{ 876 Status: "success", 877 Data: queryrange.PrometheusData{ 878 ResultType: string(parser.ValueTypeMatrix), 879 Result: []queryrange.SampleStream{ 880 { 881 Labels: []cortexpb.LabelAdapter{}, 882 Samples: []cortexpb.Sample{ 883 {Value: 0, TimestampMs: 0}, 884 {Value: 1, TimestampMs: 1}, 885 }, 886 }, 887 }, 888 }, 889 } 890 891 cond := sync.NewCond(&sync.Mutex{}) 892 cond.L.Lock() 893 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 894 lock.Lock() 895 defer lock.Unlock() 896 897 // Set fail in the response code to test retry. 898 if numFailures > 0 { 899 numFailures-- 900 901 // Wait for a successful request. 902 // Release the lock to allow other requests to execute. 903 if numFailures == 0 { 904 lock.Unlock() 905 cond.Wait() 906 <-time.After(500 * time.Millisecond) 907 lock.Lock() 908 } 909 w.WriteHeader(500) 910 } 911 if err := json.NewEncoder(w).Encode(q); err != nil { 912 panic(err) 913 } 914 if numFailures == 0 { 915 cond.Broadcast() 916 } 917 count++ 918 }) 919 } 920 921 // labelsResults is a mock handler used to test split and cache middleware for label names and label values requests. 922 func labelsResults(fail bool) (*int, http.Handler) { 923 count := 0 924 var lock sync.Mutex 925 q := ThanosLabelsResponse{ 926 Status: "success", 927 Data: []string{"__name__", "job"}, 928 } 929 930 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 931 lock.Lock() 932 defer lock.Unlock() 933 934 // Set fail in the response code to test retry. 935 if fail { 936 w.WriteHeader(500) 937 } 938 if err := json.NewEncoder(w).Encode(q); err != nil { 939 panic(err) 940 } 941 count++ 942 }) 943 } 944 945 // seriesResults is a mock handler used to test split and cache middleware for series requests. 946 func seriesResults(fail bool) (*int, http.Handler) { 947 count := 0 948 var lock sync.Mutex 949 q := ThanosSeriesResponse{ 950 Status: "success", 951 Data: []labelpb.ZLabelSet{{Labels: []labelpb.ZLabel{{Name: "__name__", Value: "up"}, {Name: "foo", Value: "bar"}}}}, 952 } 953 954 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 955 lock.Lock() 956 defer lock.Unlock() 957 958 // Set fail in the response code to test retry. 959 if fail { 960 w.WriteHeader(500) 961 } 962 if err := json.NewEncoder(w).Encode(q); err != nil { 963 panic(err) 964 } 965 count++ 966 }) 967 }