github.com/thanos-io/thanos@v0.32.5/pkg/queryfrontend/queryrange_codec_test.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  //nolint:goconst
     5  package queryfrontend
     6  
     7  import (
     8  	"context"
     9  	"net/http"
    10  	"testing"
    11  
    12  	"github.com/prometheus/prometheus/model/labels"
    13  	"github.com/weaveworks/common/httpgrpc"
    14  
    15  	"github.com/thanos-io/thanos/internal/cortex/querier/queryrange"
    16  
    17  	"github.com/efficientgo/core/testutil"
    18  	queryv1 "github.com/thanos-io/thanos/pkg/api/query"
    19  	"github.com/thanos-io/thanos/pkg/compact"
    20  )
    21  
    22  func TestQueryRangeCodec_DecodeRequest(t *testing.T) {
    23  	for _, tc := range []struct {
    24  		name            string
    25  		url             string
    26  		partialResponse bool
    27  		expectedError   error
    28  		expectedRequest *ThanosQueryRangeRequest
    29  	}{
    30  		{
    31  			name:            "instant query, no params set",
    32  			url:             "/api/v1/query",
    33  			partialResponse: false,
    34  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "" to a valid timestamp`),
    35  		},
    36  		{
    37  			name:            "cannot parse start",
    38  			url:             "/api/v1/query_range?start=foo",
    39  			partialResponse: false,
    40  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "foo" to a valid timestamp`),
    41  		},
    42  		{
    43  			name:            "cannot parse end",
    44  			url:             "/api/v1/query_range?start=123&end=bar",
    45  			partialResponse: false,
    46  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, `cannot parse "bar" to a valid timestamp`),
    47  		},
    48  		{
    49  			name:            "end before start",
    50  			url:             "/api/v1/query_range?start=123&end=0",
    51  			partialResponse: false,
    52  			expectedError:   errEndBeforeStart,
    53  		},
    54  		{
    55  			name:            "cannot parse step",
    56  			url:             "/api/v1/query_range?start=123&end=456&step=baz",
    57  			partialResponse: false,
    58  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse \"baz\" to a valid duration"),
    59  		},
    60  		{
    61  			name:            "step == 0",
    62  			url:             "/api/v1/query_range?start=123&end=456&step=0",
    63  			partialResponse: false,
    64  			expectedError:   errNegativeStep,
    65  		},
    66  		{
    67  			name:            "step too small",
    68  			url:             "/api/v1/query_range?start=0&end=11001&step=1",
    69  			partialResponse: false,
    70  			expectedError:   errStepTooSmall,
    71  		},
    72  		{
    73  			name:            "cannot parse dedup",
    74  			url:             "/api/v1/query_range?start=123&end=456&step=1&dedup=bar",
    75  			partialResponse: false,
    76  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter dedup"),
    77  		},
    78  		{
    79  			name:            "cannot parse downsampling resolution",
    80  			url:             "/api/v1/query_range?start=123&end=456&step=1&max_source_resolution=bar",
    81  			partialResponse: false,
    82  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter max_source_resolution"),
    83  		},
    84  		{
    85  			name:            "negative downsampling resolution",
    86  			url:             "/api/v1/query_range?start=123&end=456&step=1&max_source_resolution=-1",
    87  			partialResponse: false,
    88  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "negative max_source_resolution is not accepted. Try a positive integer"),
    89  		},
    90  		{
    91  			name: "auto downsampling enabled",
    92  			url:  "/api/v1/query_range?start=123&end=456&step=10&max_source_resolution=auto",
    93  			expectedRequest: &ThanosQueryRangeRequest{
    94  				Path:                "/api/v1/query_range",
    95  				Start:               123000,
    96  				End:                 456000,
    97  				Step:                10000,
    98  				MaxSourceResolution: 2000,
    99  				AutoDownsampling:    true,
   100  				Dedup:               true,
   101  				StoreMatchers:       [][]*labels.Matcher{},
   102  			},
   103  		},
   104  		{
   105  			name:            "cannot parse partial_response",
   106  			url:             "/api/v1/query_range?start=123&end=456&step=1&partial_response=bar",
   107  			partialResponse: false,
   108  			expectedError:   httpgrpc.Errorf(http.StatusBadRequest, "cannot parse parameter partial_response"),
   109  		},
   110  		{
   111  			name:            "partial_response default to true",
   112  			url:             "/api/v1/query_range?start=123&end=456&step=1",
   113  			partialResponse: true,
   114  			expectedRequest: &ThanosQueryRangeRequest{
   115  				Path:            "/api/v1/query_range",
   116  				Start:           123000,
   117  				End:             456000,
   118  				Step:            1000,
   119  				Dedup:           true,
   120  				PartialResponse: true,
   121  				StoreMatchers:   [][]*labels.Matcher{},
   122  			},
   123  		},
   124  		{
   125  			name:            "partial_response default to false, but set to true in query",
   126  			url:             "/api/v1/query_range?start=123&end=456&step=1&partial_response=true",
   127  			partialResponse: false,
   128  			expectedRequest: &ThanosQueryRangeRequest{
   129  				Path:            "/api/v1/query_range",
   130  				Start:           123000,
   131  				End:             456000,
   132  				Step:            1000,
   133  				Dedup:           true,
   134  				PartialResponse: true,
   135  				StoreMatchers:   [][]*labels.Matcher{},
   136  			},
   137  		},
   138  		{
   139  			name:            "replicaLabels",
   140  			url:             "/api/v1/query_range?start=123&end=456&step=1&replicaLabels[]=foo&replicaLabels[]=bar",
   141  			partialResponse: false,
   142  			expectedRequest: &ThanosQueryRangeRequest{
   143  				Path:          "/api/v1/query_range",
   144  				Start:         123000,
   145  				End:           456000,
   146  				Step:          1000,
   147  				Dedup:         true,
   148  				ReplicaLabels: []string{"foo", "bar"},
   149  				StoreMatchers: [][]*labels.Matcher{},
   150  			},
   151  		},
   152  		{
   153  			name:            "storeMatchers",
   154  			url:             `/api/v1/query_range?start=123&end=456&step=1&storeMatch[]={__address__="localhost:10901", cluster="test"}`,
   155  			partialResponse: false,
   156  			expectedRequest: &ThanosQueryRangeRequest{
   157  				Path:  "/api/v1/query_range",
   158  				Start: 123000,
   159  				End:   456000,
   160  				Step:  1000,
   161  				Dedup: true,
   162  				StoreMatchers: [][]*labels.Matcher{
   163  					{
   164  						labels.MustNewMatcher(labels.MatchEqual, "__address__", "localhost:10901"),
   165  						labels.MustNewMatcher(labels.MatchEqual, "cluster", "test"),
   166  					},
   167  				},
   168  			},
   169  		},
   170  		{
   171  			name:            "lookback_delta",
   172  			url:             `/api/v1/query_range?start=123&end=456&step=1&lookback_delta=1000`,
   173  			partialResponse: false,
   174  			expectedRequest: &ThanosQueryRangeRequest{
   175  				Path:          "/api/v1/query_range",
   176  				Start:         123000,
   177  				End:           456000,
   178  				Step:          1000,
   179  				Dedup:         true,
   180  				LookbackDelta: 1000000,
   181  				StoreMatchers: [][]*labels.Matcher{},
   182  			},
   183  		},
   184  	} {
   185  		t.Run(tc.name, func(t *testing.T) {
   186  			r, err := http.NewRequest(http.MethodGet, tc.url, nil)
   187  			testutil.Ok(t, err)
   188  
   189  			codec := NewThanosQueryRangeCodec(tc.partialResponse)
   190  			req, err := codec.DecodeRequest(context.Background(), r, nil)
   191  			if tc.expectedError != nil {
   192  				testutil.Equals(t, err, tc.expectedError)
   193  			} else {
   194  				testutil.Ok(t, err)
   195  				testutil.Equals(t, req, tc.expectedRequest)
   196  			}
   197  		})
   198  	}
   199  }
   200  
   201  func TestQueryRangeCodec_EncodeRequest(t *testing.T) {
   202  	for _, tc := range []struct {
   203  		name          string
   204  		expectedError error
   205  		checkFunc     func(r *http.Request) bool
   206  		req           queryrange.Request
   207  	}{
   208  		{
   209  			name:          "prometheus request, invalid format",
   210  			req:           &queryrange.PrometheusRequest{},
   211  			expectedError: httpgrpc.Errorf(http.StatusBadRequest, "invalid request format"),
   212  		},
   213  		{
   214  			name: "normal thanos request",
   215  			req: &ThanosQueryRangeRequest{
   216  				Start: 123000,
   217  				End:   456000,
   218  				Step:  1000,
   219  			},
   220  			checkFunc: func(r *http.Request) bool {
   221  				return r.FormValue("start") == "123" &&
   222  					r.FormValue("end") == "456" &&
   223  					r.FormValue("step") == "1"
   224  			},
   225  		},
   226  		{
   227  			name: "Dedup enabled",
   228  			req: &ThanosQueryRangeRequest{
   229  				Start: 123000,
   230  				End:   456000,
   231  				Step:  1000,
   232  				Dedup: true,
   233  			},
   234  			checkFunc: func(r *http.Request) bool {
   235  				return r.FormValue("start") == "123" &&
   236  					r.FormValue("end") == "456" &&
   237  					r.FormValue("step") == "1" &&
   238  					r.FormValue(queryv1.DedupParam) == "true"
   239  			},
   240  		},
   241  		{
   242  			name: "Partial response set to true",
   243  			req: &ThanosQueryRangeRequest{
   244  				Start:           123000,
   245  				End:             456000,
   246  				Step:            1000,
   247  				PartialResponse: true,
   248  			},
   249  			checkFunc: func(r *http.Request) bool {
   250  				return r.FormValue("start") == "123" &&
   251  					r.FormValue("end") == "456" &&
   252  					r.FormValue("step") == "1" &&
   253  					r.FormValue(queryv1.PartialResponseParam) == "true"
   254  			},
   255  		},
   256  		{
   257  			name: "Downsampling resolution set to 5m",
   258  			req: &ThanosQueryRangeRequest{
   259  				Start:               123000,
   260  				End:                 456000,
   261  				Step:                1000,
   262  				MaxSourceResolution: int64(compact.ResolutionLevel5m),
   263  			},
   264  			checkFunc: func(r *http.Request) bool {
   265  				return r.FormValue("start") == "123" &&
   266  					r.FormValue("end") == "456" &&
   267  					r.FormValue("step") == "1" &&
   268  					r.FormValue(queryv1.MaxSourceResolutionParam) == "300"
   269  			},
   270  		},
   271  		{
   272  			name: "Downsampling resolution set to 1h",
   273  			req: &ThanosQueryRangeRequest{
   274  				Start:               123000,
   275  				End:                 456000,
   276  				Step:                1000,
   277  				MaxSourceResolution: int64(compact.ResolutionLevel1h),
   278  			},
   279  			checkFunc: func(r *http.Request) bool {
   280  				return r.FormValue("start") == "123" &&
   281  					r.FormValue("end") == "456" &&
   282  					r.FormValue("step") == "1" &&
   283  					r.FormValue(queryv1.MaxSourceResolutionParam) == "3600"
   284  			},
   285  		},
   286  		{
   287  			name: "Lookback delta",
   288  			req: &ThanosQueryRangeRequest{
   289  				Start:         123000,
   290  				End:           456000,
   291  				Step:          1000,
   292  				LookbackDelta: 1000,
   293  			},
   294  			checkFunc: func(r *http.Request) bool {
   295  				return r.FormValue("start") == "123" &&
   296  					r.FormValue("end") == "456" &&
   297  					r.FormValue("step") == "1" &&
   298  					r.FormValue(queryv1.LookbackDeltaParam) == "1"
   299  			},
   300  		},
   301  	} {
   302  		t.Run(tc.name, func(t *testing.T) {
   303  			// Default partial response value doesn't matter when encoding requests.
   304  			codec := NewThanosQueryRangeCodec(false)
   305  			r, err := codec.EncodeRequest(context.TODO(), tc.req)
   306  			if tc.expectedError != nil {
   307  				testutil.Equals(t, err, tc.expectedError)
   308  			} else {
   309  				testutil.Ok(t, err)
   310  				testutil.Equals(t, tc.checkFunc(r), true)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  func BenchmarkQueryRangeCodecEncodeAndDecodeRequest(b *testing.B) {
   317  	codec := NewThanosQueryRangeCodec(true)
   318  	ctx := context.TODO()
   319  
   320  	req := &ThanosQueryRangeRequest{
   321  		Start:               123000,
   322  		End:                 456000,
   323  		Step:                1000,
   324  		MaxSourceResolution: int64(compact.ResolutionLevel1h),
   325  		Dedup:               true,
   326  	}
   327  
   328  	b.ReportAllocs()
   329  	b.ResetTimer()
   330  
   331  	for n := 0; n < b.N; n++ {
   332  		reqEnc, err := codec.EncodeRequest(ctx, req)
   333  		testutil.Ok(b, err)
   334  		_, err = codec.DecodeRequest(ctx, reqEnc, nil)
   335  		testutil.Ok(b, err)
   336  	}
   337  }