github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/queryrangebase/query_range_test.go (about) 1 package queryrangebase 2 3 import ( 4 "bytes" 5 "context" 6 "io/ioutil" 7 "net/http" 8 "strconv" 9 "testing" 10 11 jsoniter "github.com/json-iterator/go" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 "github.com/weaveworks/common/httpgrpc" 15 "github.com/weaveworks/common/user" 16 17 "github.com/grafana/loki/pkg/logproto" 18 ) 19 20 func TestRequest(t *testing.T) { 21 // Create a Copy parsedRequest to assign the expected headers to the request without affecting other tests using the global. 22 // The test below adds a Test-Header header to the request and expects it back once the encode/decode of request is done via PrometheusCodec 23 parsedRequestWithHeaders := *parsedRequest 24 parsedRequestWithHeaders.Headers = reqHeaders 25 for i, tc := range []struct { 26 url string 27 expected Request 28 expectedErr error 29 }{ 30 { 31 url: query, 32 expected: &parsedRequestWithHeaders, 33 }, 34 { 35 url: "api/v1/query_range?start=foo", 36 expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"start\"; cannot parse \"foo\" to a valid timestamp"), 37 }, 38 { 39 url: "api/v1/query_range?start=123&end=bar", 40 expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"end\"; cannot parse \"bar\" to a valid timestamp"), 41 }, 42 { 43 url: "api/v1/query_range?start=123&end=0", 44 expectedErr: errEndBeforeStart, 45 }, 46 { 47 url: "api/v1/query_range?start=123&end=456&step=baz", 48 expectedErr: httpgrpc.Errorf(http.StatusBadRequest, "invalid parameter \"step\"; cannot parse \"baz\" to a valid duration"), 49 }, 50 { 51 url: "api/v1/query_range?start=123&end=456&step=-1", 52 expectedErr: errNegativeStep, 53 }, 54 { 55 url: "api/v1/query_range?start=0&end=11001&step=1", 56 expectedErr: errStepTooSmall, 57 }, 58 } { 59 t.Run(strconv.Itoa(i), func(t *testing.T) { 60 r, err := http.NewRequest("GET", tc.url, nil) 61 require.NoError(t, err) 62 r.Header.Add("Test-Header", "test") 63 64 ctx := user.InjectOrgID(context.Background(), "1") 65 66 // Get a deep copy of the request with Context changed to ctx 67 r = r.Clone(ctx) 68 69 req, err := PrometheusCodec.DecodeRequest(ctx, r, []string{"Test-Header"}) 70 if err != nil { 71 require.EqualValues(t, tc.expectedErr, err) 72 return 73 } 74 require.EqualValues(t, tc.expected, req) 75 76 rdash, err := PrometheusCodec.EncodeRequest(context.Background(), req) 77 require.NoError(t, err) 78 require.EqualValues(t, tc.url, rdash.RequestURI) 79 }) 80 } 81 } 82 83 func TestResponse(t *testing.T) { 84 r := *parsedResponse 85 r.Headers = respHeaders 86 for i, tc := range []struct { 87 body string 88 expected *PrometheusResponse 89 }{ 90 { 91 body: responseBody, 92 expected: &r, 93 }, 94 } { 95 t.Run(strconv.Itoa(i), func(t *testing.T) { 96 response := &http.Response{ 97 StatusCode: 200, 98 Header: http.Header{"Content-Type": []string{"application/json"}}, 99 Body: ioutil.NopCloser(bytes.NewBuffer([]byte(tc.body))), 100 } 101 resp, err := PrometheusCodec.DecodeResponse(context.Background(), response, nil) 102 require.NoError(t, err) 103 assert.Equal(t, tc.expected, resp) 104 105 // Reset response, as the above call will have consumed the body reader. 106 response = &http.Response{ 107 StatusCode: 200, 108 Header: http.Header{"Content-Type": []string{"application/json"}}, 109 Body: ioutil.NopCloser(bytes.NewBuffer([]byte(tc.body))), 110 ContentLength: int64(len(tc.body)), 111 } 112 resp2, err := PrometheusCodec.EncodeResponse(context.Background(), resp) 113 require.NoError(t, err) 114 assert.Equal(t, response, resp2) 115 }) 116 } 117 } 118 119 func TestMergeAPIResponses(t *testing.T) { 120 for _, tc := range []struct { 121 name string 122 input []Response 123 expected Response 124 }{ 125 { 126 name: "No responses shouldn't panic and return a non-null result and result type.", 127 input: []Response{}, 128 expected: &PrometheusResponse{ 129 Status: StatusSuccess, 130 Data: PrometheusData{ 131 ResultType: matrix, 132 Result: []SampleStream{}, 133 }, 134 }, 135 }, 136 137 { 138 name: "A single empty response shouldn't panic.", 139 input: []Response{ 140 &PrometheusResponse{ 141 Data: PrometheusData{ 142 ResultType: matrix, 143 Result: []SampleStream{}, 144 }, 145 }, 146 }, 147 expected: &PrometheusResponse{ 148 Status: StatusSuccess, 149 Data: PrometheusData{ 150 ResultType: matrix, 151 Result: []SampleStream{}, 152 }, 153 }, 154 }, 155 156 { 157 name: "Multiple empty responses shouldn't panic.", 158 input: []Response{ 159 &PrometheusResponse{ 160 Data: PrometheusData{ 161 ResultType: matrix, 162 Result: []SampleStream{}, 163 }, 164 }, 165 &PrometheusResponse{ 166 Data: PrometheusData{ 167 ResultType: matrix, 168 Result: []SampleStream{}, 169 }, 170 }, 171 }, 172 expected: &PrometheusResponse{ 173 Status: StatusSuccess, 174 Data: PrometheusData{ 175 ResultType: matrix, 176 Result: []SampleStream{}, 177 }, 178 }, 179 }, 180 181 { 182 name: "Basic merging of two responses.", 183 input: []Response{ 184 &PrometheusResponse{ 185 Data: PrometheusData{ 186 ResultType: matrix, 187 Result: []SampleStream{ 188 { 189 Labels: []logproto.LabelAdapter{}, 190 Samples: []logproto.LegacySample{ 191 {Value: 0, TimestampMs: 0}, 192 {Value: 1, TimestampMs: 1}, 193 }, 194 }, 195 }, 196 }, 197 }, 198 &PrometheusResponse{ 199 Data: PrometheusData{ 200 ResultType: matrix, 201 Result: []SampleStream{ 202 { 203 Labels: []logproto.LabelAdapter{}, 204 Samples: []logproto.LegacySample{ 205 {Value: 2, TimestampMs: 2}, 206 {Value: 3, TimestampMs: 3}, 207 }, 208 }, 209 }, 210 }, 211 }, 212 }, 213 expected: &PrometheusResponse{ 214 Status: StatusSuccess, 215 Data: PrometheusData{ 216 ResultType: matrix, 217 Result: []SampleStream{ 218 { 219 Labels: []logproto.LabelAdapter{}, 220 Samples: []logproto.LegacySample{ 221 {Value: 0, TimestampMs: 0}, 222 {Value: 1, TimestampMs: 1}, 223 {Value: 2, TimestampMs: 2}, 224 {Value: 3, TimestampMs: 3}, 225 }, 226 }, 227 }, 228 }, 229 }, 230 }, 231 232 { 233 name: "Merging of responses when labels are in different order.", 234 input: []Response{ 235 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[0,"0"],[1,"1"]]}]}}`), 236 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`), 237 }, 238 expected: &PrometheusResponse{ 239 Status: StatusSuccess, 240 Data: PrometheusData{ 241 ResultType: matrix, 242 Result: []SampleStream{ 243 { 244 Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}}, 245 Samples: []logproto.LegacySample{ 246 {Value: 0, TimestampMs: 0}, 247 {Value: 1, TimestampMs: 1000}, 248 {Value: 2, TimestampMs: 2000}, 249 {Value: 3, TimestampMs: 3000}, 250 }, 251 }, 252 }, 253 }, 254 }, 255 }, 256 257 { 258 name: "Merging of samples where there is single overlap.", 259 input: []Response{ 260 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"]]}]}}`), 261 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"]]}]}}`), 262 }, 263 expected: &PrometheusResponse{ 264 Status: StatusSuccess, 265 Data: PrometheusData{ 266 ResultType: matrix, 267 Result: []SampleStream{ 268 { 269 Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}}, 270 Samples: []logproto.LegacySample{ 271 {Value: 1, TimestampMs: 1000}, 272 {Value: 2, TimestampMs: 2000}, 273 {Value: 3, TimestampMs: 3000}, 274 }, 275 }, 276 }, 277 }, 278 }, 279 }, 280 { 281 name: "Merging of samples where there is multiple partial overlaps.", 282 input: []Response{ 283 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[1,"1"],[2,"2"],[3,"3"]]}]}}`), 284 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`), 285 }, 286 expected: &PrometheusResponse{ 287 Status: StatusSuccess, 288 Data: PrometheusData{ 289 ResultType: matrix, 290 Result: []SampleStream{ 291 { 292 Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}}, 293 Samples: []logproto.LegacySample{ 294 {Value: 1, TimestampMs: 1000}, 295 {Value: 2, TimestampMs: 2000}, 296 {Value: 3, TimestampMs: 3000}, 297 {Value: 4, TimestampMs: 4000}, 298 {Value: 5, TimestampMs: 5000}, 299 }, 300 }, 301 }, 302 }, 303 }, 304 }, 305 { 306 name: "Merging of samples where there is complete overlap.", 307 input: []Response{ 308 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"a":"b","c":"d"},"values":[[2,"2"],[3,"3"]]}]}}`), 309 mustParse(t, `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"c":"d","a":"b"},"values":[[2,"2"],[3,"3"],[4,"4"],[5,"5"]]}]}}`), 310 }, 311 expected: &PrometheusResponse{ 312 Status: StatusSuccess, 313 Data: PrometheusData{ 314 ResultType: matrix, 315 Result: []SampleStream{ 316 { 317 Labels: []logproto.LabelAdapter{{Name: "a", Value: "b"}, {Name: "c", Value: "d"}}, 318 Samples: []logproto.LegacySample{ 319 {Value: 2, TimestampMs: 2000}, 320 {Value: 3, TimestampMs: 3000}, 321 {Value: 4, TimestampMs: 4000}, 322 {Value: 5, TimestampMs: 5000}, 323 }, 324 }, 325 }, 326 }, 327 }, 328 }} { 329 t.Run(tc.name, func(t *testing.T) { 330 output, err := PrometheusCodec.MergeResponse(tc.input...) 331 require.NoError(t, err) 332 require.Equal(t, tc.expected, output) 333 }) 334 } 335 } 336 337 func mustParse(t *testing.T, response string) Response { 338 var resp PrometheusResponse 339 // Needed as goimports automatically add a json import otherwise. 340 json := jsoniter.ConfigCompatibleWithStandardLibrary 341 require.NoError(t, json.Unmarshal([]byte(response), &resp)) 342 return &resp 343 }