github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/querier/queryrange/roundtrip_test.go (about) 1 package queryrange 2 3 import ( 4 "bytes" 5 "context" 6 "io/ioutil" 7 "math" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "strconv" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/prometheus/prometheus/model/labels" 17 "github.com/prometheus/prometheus/promql" 18 "github.com/prometheus/prometheus/promql/parser" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 "github.com/weaveworks/common/httpgrpc" 22 "github.com/weaveworks/common/middleware" 23 "github.com/weaveworks/common/user" 24 25 "github.com/grafana/loki/pkg/logproto" 26 "github.com/grafana/loki/pkg/logqlmodel" 27 "github.com/grafana/loki/pkg/querier/queryrange/queryrangebase" 28 "github.com/grafana/loki/pkg/storage/chunk/cache" 29 "github.com/grafana/loki/pkg/storage/config" 30 util_log "github.com/grafana/loki/pkg/util/log" 31 "github.com/grafana/loki/pkg/util/marshal" 32 ) 33 34 var ( 35 testTime = time.Date(2019, 12, 2, 11, 10, 10, 10, time.UTC) 36 testConfig = Config{queryrangebase.Config{ 37 AlignQueriesWithStep: true, 38 MaxRetries: 3, 39 CacheResults: true, 40 ResultsCacheConfig: queryrangebase.ResultsCacheConfig{ 41 CacheConfig: cache.Config{ 42 EnableFifoCache: true, 43 Fifocache: cache.FifoCacheConfig{ 44 MaxSizeItems: 1024, 45 TTL: 24 * time.Hour, 46 }, 47 }, 48 }, 49 }} 50 matrix = promql.Matrix{ 51 { 52 Points: []promql.Point{ 53 { 54 T: toMs(testTime.Add(-4 * time.Hour)), 55 V: 0.013333333333333334, 56 }, 57 }, 58 Metric: []labels.Label{ 59 { 60 Name: "filename", 61 Value: `/var/hostlog/apport.log`, 62 }, 63 { 64 Name: "job", 65 Value: "varlogs", 66 }, 67 }, 68 }, 69 } 70 vector = promql.Vector{ 71 { 72 Point: promql.Point{ 73 T: toMs(testTime.Add(-4 * time.Hour)), 74 V: 0.013333333333333334, 75 }, 76 Metric: []labels.Label{ 77 { 78 Name: "filename", 79 Value: `/var/hostlog/apport.log`, 80 }, 81 { 82 Name: "job", 83 Value: "varlogs", 84 }, 85 }, 86 }, 87 } 88 streams = logqlmodel.Streams{ 89 { 90 Entries: []logproto.Entry{ 91 {Timestamp: testTime.Add(-4 * time.Hour), Line: "foo"}, 92 {Timestamp: testTime.Add(-1 * time.Hour), Line: "barr"}, 93 }, 94 Labels: `{filename="/var/hostlog/apport.log", job="varlogs"}`, 95 }, 96 } 97 98 series = logproto.SeriesResponse{ 99 Series: []logproto.SeriesIdentifier{ 100 { 101 Labels: map[string]string{"filename": "/var/hostlog/apport.log", "job": "varlogs"}, 102 }, 103 { 104 Labels: map[string]string{"filename": "/var/hostlog/test.log", "job": "varlogs"}, 105 }, 106 }, 107 } 108 ) 109 110 // those tests are mostly for testing the glue between all component and make sure they activate correctly. 111 func TestMetricsTripperware(t *testing.T) { 112 l := WithSplitByLimits(fakeLimits{maxSeries: math.MaxInt32, maxQueryParallelism: 1}, 4*time.Hour) 113 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, l, config.SchemaConfig{}, nil, nil) 114 if stopper != nil { 115 defer stopper.Stop() 116 } 117 require.NoError(t, err) 118 119 lreq := &LokiRequest{ 120 Query: `rate({app="foo"} |= "foo"[1m])`, 121 Limit: 1000, 122 Step: 30000, // 30sec 123 StartTs: testTime.Add(-6 * time.Hour), 124 EndTs: testTime, 125 Direction: logproto.FORWARD, 126 Path: "/query_range", 127 } 128 129 ctx := user.InjectOrgID(context.Background(), "1") 130 req, err := LokiCodec.EncodeRequest(ctx, lreq) 131 require.NoError(t, err) 132 133 req = req.WithContext(ctx) 134 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 135 require.NoError(t, err) 136 rt, err := newfakeRoundTripper() 137 require.NoError(t, err) 138 139 // testing retry 140 retries, h := counter() 141 rt.setHandler(h) 142 _, err = tpw(rt).RoundTrip(req) 143 // 3 retries configured. 144 require.GreaterOrEqual(t, *retries, 3) 145 require.Error(t, err) 146 rt.Close() 147 148 rt, err = newfakeRoundTripper() 149 require.NoError(t, err) 150 defer rt.Close() 151 152 // testing split interval 153 count, h := promqlResult(matrix) 154 rt.setHandler(h) 155 resp, err := tpw(rt).RoundTrip(req) 156 // 2 queries 157 require.Equal(t, 2, *count) 158 require.NoError(t, err) 159 lokiResponse, err := LokiCodec.DecodeResponse(ctx, resp, lreq) 160 require.NoError(t, err) 161 162 // testing cache 163 count, h = counter() 164 rt.setHandler(h) 165 cacheResp, err := tpw(rt).RoundTrip(req) 166 // 0 queries result are cached. 167 require.Equal(t, 0, *count) 168 require.NoError(t, err) 169 lokiCacheResponse, err := LokiCodec.DecodeResponse(ctx, cacheResp, lreq) 170 require.NoError(t, err) 171 172 require.Equal(t, lokiResponse.(*LokiPromResponse).Response, lokiCacheResponse.(*LokiPromResponse).Response) 173 } 174 175 func TestLogFilterTripperware(t *testing.T) { 176 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{maxQueryParallelism: 1}, config.SchemaConfig{}, nil, nil) 177 if stopper != nil { 178 defer stopper.Stop() 179 } 180 require.NoError(t, err) 181 rt, err := newfakeRoundTripper() 182 require.NoError(t, err) 183 defer rt.Close() 184 185 lreq := &LokiRequest{ 186 Query: `{app="foo"} |= "foo"`, 187 Limit: 1000, 188 StartTs: testTime.Add(-10 * time.Hour), // bigger than the limit 189 EndTs: testTime, 190 Direction: logproto.FORWARD, 191 Path: "/loki/api/v1/query_range", 192 } 193 194 ctx := user.InjectOrgID(context.Background(), "1") 195 req, err := LokiCodec.EncodeRequest(ctx, lreq) 196 require.NoError(t, err) 197 198 req = req.WithContext(ctx) 199 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 200 require.NoError(t, err) 201 202 // testing limit 203 count, h := promqlResult(streams) 204 rt.setHandler(h) 205 _, err = tpw(rt).RoundTrip(req) 206 require.Equal(t, 0, *count) 207 require.Error(t, err) 208 209 // set the query length back to normal 210 lreq.StartTs = testTime.Add(-6 * time.Hour) 211 req, err = LokiCodec.EncodeRequest(ctx, lreq) 212 require.NoError(t, err) 213 214 // testing retry 215 retries, h := counter() 216 rt.setHandler(h) 217 _, err = tpw(rt).RoundTrip(req) 218 require.GreaterOrEqual(t, *retries, 3) 219 require.Error(t, err) 220 } 221 222 func TestInstantQueryTripperware(t *testing.T) { 223 testShardingConfig := testConfig 224 testShardingConfig.ShardedQueries = true 225 tpw, stopper, err := NewTripperware(testShardingConfig, util_log.Logger, fakeLimits{maxQueryParallelism: 1}, config.SchemaConfig{}, nil, nil) 226 if stopper != nil { 227 defer stopper.Stop() 228 } 229 require.NoError(t, err) 230 rt, err := newfakeRoundTripper() 231 require.NoError(t, err) 232 defer rt.Close() 233 234 lreq := &LokiInstantRequest{ 235 Query: `sum by (job) (bytes_rate({cluster="dev-us-central-0"}[15m]))`, 236 Limit: 1000, 237 Direction: logproto.FORWARD, 238 Path: "/loki/api/v1/query", 239 } 240 241 ctx := user.InjectOrgID(context.Background(), "1") 242 req, err := LokiCodec.EncodeRequest(ctx, lreq) 243 require.NoError(t, err) 244 245 req = req.WithContext(ctx) 246 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 247 require.NoError(t, err) 248 249 count, h := promqlResult(vector) 250 rt.setHandler(h) 251 resp, err := tpw(rt).RoundTrip(req) 252 require.Equal(t, 1, *count) 253 require.NoError(t, err) 254 255 lokiResponse, err := LokiCodec.DecodeResponse(ctx, resp, lreq) 256 require.NoError(t, err) 257 require.IsType(t, &LokiPromResponse{}, lokiResponse) 258 } 259 260 func TestSeriesTripperware(t *testing.T) { 261 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{maxQueryLength: 48 * time.Hour, maxQueryParallelism: 1}, config.SchemaConfig{}, nil, nil) 262 if stopper != nil { 263 defer stopper.Stop() 264 } 265 require.NoError(t, err) 266 rt, err := newfakeRoundTripper() 267 require.NoError(t, err) 268 defer rt.Close() 269 270 lreq := &LokiSeriesRequest{ 271 Match: []string{`{job="varlogs"}`}, 272 StartTs: testTime.Add(-25 * time.Hour), // bigger than split by interval limit 273 EndTs: testTime, 274 Path: "/loki/api/v1/series", 275 } 276 277 ctx := user.InjectOrgID(context.Background(), "1") 278 req, err := LokiCodec.EncodeRequest(ctx, lreq) 279 require.NoError(t, err) 280 281 req = req.WithContext(ctx) 282 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 283 require.NoError(t, err) 284 285 count, h := seriesResult(series) 286 rt.setHandler(h) 287 resp, err := tpw(rt).RoundTrip(req) 288 // 2 queries 289 require.Equal(t, 2, *count) 290 require.NoError(t, err) 291 lokiSeriesResponse, err := LokiCodec.DecodeResponse(ctx, resp, lreq) 292 res, ok := lokiSeriesResponse.(*LokiSeriesResponse) 293 require.Equal(t, true, ok) 294 295 // make sure we return unique series since responses from 296 // SplitByInterval middleware might have duplicate series 297 require.Equal(t, series.Series, res.Data) 298 require.NoError(t, err) 299 } 300 301 func TestLabelsTripperware(t *testing.T) { 302 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{maxQueryLength: 48 * time.Hour, maxQueryParallelism: 1}, config.SchemaConfig{}, nil, nil) 303 if stopper != nil { 304 defer stopper.Stop() 305 } 306 require.NoError(t, err) 307 rt, err := newfakeRoundTripper() 308 require.NoError(t, err) 309 defer rt.Close() 310 311 lreq := &LokiLabelNamesRequest{ 312 StartTs: testTime.Add(-25 * time.Hour), // bigger than the limit 313 EndTs: testTime, 314 Path: "/loki/api/v1/labels", 315 } 316 317 ctx := user.InjectOrgID(context.Background(), "1") 318 req, err := LokiCodec.EncodeRequest(ctx, lreq) 319 require.NoError(t, err) 320 321 req = req.WithContext(ctx) 322 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 323 require.NoError(t, err) 324 325 handler := newFakeHandler( 326 // we expect 2 calls. 327 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 328 require.NoError(t, marshal.WriteLabelResponseJSON(logproto.LabelResponse{Values: []string{"foo", "bar", "blop"}}, w)) 329 }), 330 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 331 require.NoError(t, marshal.WriteLabelResponseJSON(logproto.LabelResponse{Values: []string{"foo", "bar", "blip"}}, w)) 332 }), 333 ) 334 rt.setHandler(handler) 335 resp, err := tpw(rt).RoundTrip(req) 336 // verify 2 calls have been made to downstream. 337 require.Equal(t, 2, handler.count) 338 require.NoError(t, err) 339 lokiLabelsResponse, err := LokiCodec.DecodeResponse(ctx, resp, lreq) 340 res, ok := lokiLabelsResponse.(*LokiLabelNamesResponse) 341 require.Equal(t, true, ok) 342 require.Equal(t, []string{"foo", "bar", "blop", "blip"}, res.Data) 343 require.Equal(t, "success", res.Status) 344 require.NoError(t, err) 345 } 346 347 func TestLogNoRegex(t *testing.T) { 348 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{}, config.SchemaConfig{}, nil, nil) 349 if stopper != nil { 350 defer stopper.Stop() 351 } 352 require.NoError(t, err) 353 rt, err := newfakeRoundTripper() 354 require.NoError(t, err) 355 defer rt.Close() 356 357 lreq := &LokiRequest{ 358 Query: `{app="foo"}`, // no regex so it should go to the querier 359 Limit: 1000, 360 StartTs: testTime.Add(-6 * time.Hour), 361 EndTs: testTime, 362 Direction: logproto.FORWARD, 363 Path: "/loki/api/v1/query_range", 364 } 365 366 ctx := user.InjectOrgID(context.Background(), "1") 367 req, err := LokiCodec.EncodeRequest(ctx, lreq) 368 require.NoError(t, err) 369 370 req = req.WithContext(ctx) 371 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 372 require.NoError(t, err) 373 374 count, h := promqlResult(streams) 375 rt.setHandler(h) 376 _, err = tpw(rt).RoundTrip(req) 377 require.Equal(t, 1, *count) 378 require.NoError(t, err) 379 } 380 381 func TestRegexpParamsSupport(t *testing.T) { 382 l := WithSplitByLimits(fakeLimits{maxSeries: 1, maxQueryParallelism: 2}, 4*time.Hour) 383 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, l, config.SchemaConfig{}, nil, nil) 384 if stopper != nil { 385 defer stopper.Stop() 386 } 387 require.NoError(t, err) 388 rt, err := newfakeRoundTripper() 389 require.NoError(t, err) 390 defer rt.Close() 391 392 lreq := &LokiRequest{ 393 Query: `{app="foo"}`, // no regex so it should go to the querier 394 Limit: 1000, 395 StartTs: testTime.Add(-6 * time.Hour), 396 EndTs: testTime, 397 Direction: logproto.FORWARD, 398 Path: "/loki/api/v1/query_range", 399 } 400 401 ctx := user.InjectOrgID(context.Background(), "1") 402 req, err := LokiCodec.EncodeRequest(ctx, lreq) 403 require.NoError(t, err) 404 405 // fudge a regexp params 406 params := req.URL.Query() 407 params.Set("regexp", "foo") 408 req.URL.RawQuery = params.Encode() 409 410 req = req.WithContext(ctx) 411 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 412 require.NoError(t, err) 413 414 count, h := promqlResult(streams) 415 rt.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 416 // the query params should contain the filter. 417 require.Contains(t, r.URL.Query().Get("query"), `|~ "foo"`) 418 h.ServeHTTP(rw, r) 419 })) 420 _, err = tpw(rt).RoundTrip(req) 421 require.Equal(t, 2, *count) // expecting the query to also be splitted since it has a filter. 422 require.NoError(t, err) 423 } 424 425 func TestPostQueries(t *testing.T) { 426 req, err := http.NewRequest(http.MethodPost, "/loki/api/v1/query_range", nil) 427 data := url.Values{ 428 "query": {`{app="foo"} |~ "foo"`}, 429 } 430 body := bytes.NewBufferString(data.Encode()) 431 req.Body = ioutil.NopCloser(body) 432 req.Header.Add("Content-Type", "application/x-www-form-urlencoded") 433 req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode()))) 434 req = req.WithContext(user.InjectOrgID(context.Background(), "1")) 435 require.NoError(t, err) 436 _, err = newRoundTripper( 437 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 438 t.Error("unexpected default roundtripper called") 439 return nil, nil 440 }), 441 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 442 return nil, nil 443 }), 444 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 445 t.Error("unexpected metric roundtripper called") 446 return nil, nil 447 }), 448 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 449 t.Error("unexpected series roundtripper called") 450 return nil, nil 451 }), 452 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 453 t.Error("unexpected labels roundtripper called") 454 return nil, nil 455 }), 456 queryrangebase.RoundTripFunc(func(*http.Request) (*http.Response, error) { 457 t.Error("unexpected instant roundtripper called") 458 return nil, nil 459 }), 460 fakeLimits{}, 461 ).RoundTrip(req) 462 require.NoError(t, err) 463 } 464 465 func TestEntriesLimitsTripperware(t *testing.T) { 466 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{maxEntriesLimitPerQuery: 5000}, config.SchemaConfig{}, nil, nil) 467 if stopper != nil { 468 defer stopper.Stop() 469 } 470 require.NoError(t, err) 471 rt, err := newfakeRoundTripper() 472 require.NoError(t, err) 473 defer rt.Close() 474 475 lreq := &LokiRequest{ 476 Query: `{app="foo"}`, // no regex so it should go to the querier 477 Limit: 10000, 478 StartTs: testTime.Add(-6 * time.Hour), 479 EndTs: testTime, 480 Direction: logproto.FORWARD, 481 Path: "/loki/api/v1/query_range", 482 } 483 484 ctx := user.InjectOrgID(context.Background(), "1") 485 req, err := LokiCodec.EncodeRequest(ctx, lreq) 486 require.NoError(t, err) 487 488 req = req.WithContext(ctx) 489 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 490 require.NoError(t, err) 491 492 _, err = tpw(rt).RoundTrip(req) 493 require.Equal(t, httpgrpc.Errorf(http.StatusBadRequest, "max entries limit per query exceeded, limit > max_entries_limit (10000 > 5000)"), err) 494 } 495 496 func TestEntriesLimitWithZeroTripperware(t *testing.T) { 497 tpw, stopper, err := NewTripperware(testConfig, util_log.Logger, fakeLimits{}, config.SchemaConfig{}, nil, nil) 498 if stopper != nil { 499 defer stopper.Stop() 500 } 501 require.NoError(t, err) 502 rt, err := newfakeRoundTripper() 503 require.NoError(t, err) 504 defer rt.Close() 505 506 lreq := &LokiRequest{ 507 Query: `{app="foo"}`, // no regex so it should go to the querier 508 Limit: 10000, 509 StartTs: testTime.Add(-6 * time.Hour), 510 EndTs: testTime, 511 Direction: logproto.FORWARD, 512 Path: "/loki/api/v1/query_range", 513 } 514 515 ctx := user.InjectOrgID(context.Background(), "1") 516 req, err := LokiCodec.EncodeRequest(ctx, lreq) 517 require.NoError(t, err) 518 519 req = req.WithContext(ctx) 520 err = user.InjectOrgIDIntoHTTPRequest(ctx, req) 521 require.NoError(t, err) 522 523 _, err = tpw(rt).RoundTrip(req) 524 require.NoError(t, err) 525 } 526 527 func Test_getOperation(t *testing.T) { 528 cases := []struct { 529 name string 530 path string 531 expectedOp string 532 }{ 533 { 534 name: "instant_query", 535 path: "/loki/api/v1/query", 536 expectedOp: InstantQueryOp, 537 }, 538 { 539 name: "range_query_prom", 540 path: "/prom/query", 541 expectedOp: QueryRangeOp, 542 }, 543 { 544 name: "range_query", 545 path: "/loki/api/v1/query_range", 546 expectedOp: QueryRangeOp, 547 }, 548 { 549 name: "series_query", 550 path: "/loki/api/v1/series", 551 expectedOp: SeriesOp, 552 }, 553 { 554 name: "series_query_prom", 555 path: "/prom/series", 556 expectedOp: SeriesOp, 557 }, 558 { 559 name: "labels_query", 560 path: "/loki/api/v1/labels", 561 expectedOp: LabelNamesOp, 562 }, 563 { 564 name: "labels_query_prom", 565 path: "/prom/labels", 566 expectedOp: LabelNamesOp, 567 }, 568 { 569 name: "label_query", 570 path: "/loki/api/v1/label", 571 expectedOp: LabelNamesOp, 572 }, 573 { 574 name: "labels_query_prom", 575 path: "/prom/label", 576 expectedOp: LabelNamesOp, 577 }, 578 { 579 name: "label_values_query", 580 path: "/loki/api/v1/label/__name__/values", 581 expectedOp: LabelNamesOp, 582 }, 583 { 584 name: "label_values_query_prom", 585 path: "/prom/label/__name__/values", 586 expectedOp: LabelNamesOp, 587 }, 588 } 589 590 for _, tc := range cases { 591 t.Run(tc.name, func(t *testing.T) { 592 got := getOperation(tc.path) 593 assert.Equal(t, tc.expectedOp, got) 594 }) 595 } 596 } 597 598 type fakeLimits struct { 599 maxQueryLength time.Duration 600 maxQueryParallelism int 601 maxQueryLookback time.Duration 602 maxEntriesLimitPerQuery int 603 maxSeries int 604 splits map[string]time.Duration 605 minShardingLookback time.Duration 606 } 607 608 func (f fakeLimits) QuerySplitDuration(key string) time.Duration { 609 if f.splits == nil { 610 return 0 611 } 612 return f.splits[key] 613 } 614 615 func (f fakeLimits) MaxQueryLength(string) time.Duration { 616 if f.maxQueryLength == 0 { 617 return time.Hour * 7 618 } 619 return f.maxQueryLength 620 } 621 622 func (f fakeLimits) MaxQueryParallelism(string) int { 623 return f.maxQueryParallelism 624 } 625 626 func (f fakeLimits) MaxEntriesLimitPerQuery(string) int { 627 return f.maxEntriesLimitPerQuery 628 } 629 630 func (f fakeLimits) MaxQuerySeries(string) int { 631 return f.maxSeries 632 } 633 634 func (f fakeLimits) MaxCacheFreshness(string) time.Duration { 635 return 1 * time.Minute 636 } 637 638 func (f fakeLimits) MaxQueryLookback(string) time.Duration { 639 return f.maxQueryLookback 640 } 641 642 func (f fakeLimits) MinShardingLookback(string) time.Duration { 643 return f.minShardingLookback 644 } 645 646 func counter() (*int, http.Handler) { 647 count := 0 648 var lock sync.Mutex 649 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 650 lock.Lock() 651 defer lock.Unlock() 652 count++ 653 }) 654 } 655 656 func promqlResult(v parser.Value) (*int, http.Handler) { 657 count := 0 658 var lock sync.Mutex 659 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 660 lock.Lock() 661 defer lock.Unlock() 662 if err := marshal.WriteQueryResponseJSON(logqlmodel.Result{Data: v}, w); err != nil { 663 panic(err) 664 } 665 count++ 666 }) 667 } 668 669 func seriesResult(v logproto.SeriesResponse) (*int, http.Handler) { 670 count := 0 671 var lock sync.Mutex 672 return &count, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 673 lock.Lock() 674 defer lock.Unlock() 675 if err := marshal.WriteSeriesResponseJSON(v, w); err != nil { 676 panic(err) 677 } 678 count++ 679 }) 680 } 681 682 type fakeHandler struct { 683 count int 684 lock sync.Mutex 685 calls []http.Handler 686 } 687 688 func newFakeHandler(calls ...http.Handler) *fakeHandler { 689 return &fakeHandler{calls: calls} 690 } 691 692 func (f *fakeHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 693 f.lock.Lock() 694 defer f.lock.Unlock() 695 f.calls[f.count].ServeHTTP(w, req) 696 f.count++ 697 } 698 699 type fakeRoundTripper struct { 700 *httptest.Server 701 host string 702 } 703 704 func newfakeRoundTripper() (*fakeRoundTripper, error) { 705 s := httptest.NewServer(nil) 706 u, err := url.Parse(s.URL) 707 if err != nil { 708 return nil, err 709 } 710 return &fakeRoundTripper{ 711 Server: s, 712 host: u.Host, 713 }, nil 714 } 715 716 func (s *fakeRoundTripper) setHandler(h http.Handler) { 717 s.Config.Handler = middleware.AuthenticateUser.Wrap(h) 718 } 719 720 func (s fakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { 721 r.URL.Scheme = "http" 722 r.URL.Host = s.host 723 return http.DefaultTransport.RoundTrip(r) 724 } 725 726 func toMs(t time.Time) int64 { 727 return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) 728 }