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  }