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 }