github.com/grafana/pyroscope@v1.18.0/pkg/scheduler/scheduler_test.go (about) 1 // SPDX-License-Identifier: AGPL-3.0-only 2 // Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/scheduler/scheduler_test.go 3 // Provenance-includes-license: Apache-2.0 4 // Provenance-includes-copyright: The Cortex Authors. 5 6 package scheduler 7 8 import ( 9 "context" 10 "fmt" 11 "io" 12 "net" 13 "net/http" 14 "net/http/httptest" 15 "strings" 16 "sync" 17 "testing" 18 "time" 19 20 "connectrpc.com/connect" 21 "github.com/go-kit/log" 22 "github.com/gorilla/mux" 23 "github.com/grafana/dskit/flagext" 24 "github.com/grafana/dskit/services" 25 "github.com/grafana/dskit/test" 26 "github.com/opentracing/opentracing-go" 27 "github.com/prometheus/client_golang/prometheus" 28 promtest "github.com/prometheus/client_golang/prometheus/testutil" 29 "github.com/stretchr/testify/require" 30 "github.com/uber/jaeger-client-go/config" 31 "golang.org/x/net/http2" 32 "golang.org/x/net/http2/h2c" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/credentials/insecure" 35 "google.golang.org/grpc/test/bufconn" 36 37 "github.com/grafana/pyroscope/pkg/frontend/frontendpb" 38 "github.com/grafana/pyroscope/pkg/scheduler/schedulerpb" 39 "github.com/grafana/pyroscope/pkg/scheduler/schedulerpb/schedulerpbconnect" 40 "github.com/grafana/pyroscope/pkg/util" 41 "github.com/grafana/pyroscope/pkg/util/httpgrpc" 42 "github.com/grafana/pyroscope/pkg/util/httpgrpcutil" 43 ) 44 45 const testMaxOutstandingPerTenant = 5 46 47 type schedulerArgs struct { 48 reg prometheus.Registerer 49 handlerOpts []connect.HandlerOption 50 dialOpts []grpc.DialOption 51 } 52 53 func setupScheduler(t *testing.T, args schedulerArgs) (*Scheduler, schedulerpb.SchedulerForFrontendClient, schedulerpb.SchedulerForQuerierClient) { 54 cfg := Config{ 55 DialOpts: args.dialOpts, 56 } 57 flagext.DefaultValues(&cfg) 58 cfg.MaxOutstandingPerTenant = testMaxOutstandingPerTenant 59 60 s, err := NewScheduler(cfg, &limits{queriers: 2}, log.NewNopLogger(), args.reg) 61 62 require.NoError(t, err) 63 64 server := httptest.NewUnstartedServer(nil) 65 mux := mux.NewRouter() 66 server.Config.Handler = h2c.NewHandler(mux, &http2.Server{}) 67 68 // Use an in-memory network connection to avoid test flake from network access. 69 listener := bufconn.Listen(256 << 10) 70 server.Listener = listener 71 72 server.Start() 73 require.NoError(t, err) 74 schedulerpbconnect.RegisterSchedulerForFrontendHandler(mux, s, args.handlerOpts...) 75 schedulerpbconnect.RegisterSchedulerForQuerierHandler(mux, s, args.handlerOpts...) 76 77 require.NoError(t, services.StartAndAwaitRunning(context.Background(), s)) 78 t.Cleanup(func() { 79 _ = services.StopAndAwaitTerminated(context.Background(), s) 80 server.Close() 81 }) 82 83 c, err := grpc.NewClient( 84 // Target address is irrelevant as we're using an in-memory connection. 85 // We simply need the DNS resolution to succeed. 86 "localhost:3030", 87 grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), 88 grpc.WithTransportCredentials(insecure.NewCredentials()), 89 ) 90 91 require.NoError(t, err) 92 93 t.Cleanup(func() { 94 _ = c.Close() 95 }) 96 97 return s, schedulerpb.NewSchedulerForFrontendClient(c), schedulerpb.NewSchedulerForQuerierClient(c) 98 } 99 100 func setupSchedulerWithHandlerOpts(t *testing.T, handlerOpts ...connect.HandlerOption) (*Scheduler, schedulerpb.SchedulerForFrontendClient, schedulerpb.SchedulerForQuerierClient) { 101 return setupScheduler(t, schedulerArgs{ 102 handlerOpts: handlerOpts, 103 }) 104 } 105 106 func Test_Timeout(t *testing.T) { 107 s, _, querierClient := setupSchedulerWithHandlerOpts(t, connect.WithInterceptors(util.WithTimeout(1*time.Second))) 108 ql, err := querierClient.QuerierLoop(context.Background()) 109 require.NoError(t, err) 110 require.NoError(t, ql.Send(&schedulerpb.QuerierToScheduler{QuerierID: "querier-1"})) 111 time.Sleep(2 * time.Second) 112 require.Equal(t, float64(0), s.requestQueue.GetConnectedQuerierWorkersMetric()) 113 } 114 115 func TestSchedulerBasicEnqueue(t *testing.T) { 116 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 117 118 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 119 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 120 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 121 QueryID: 1, 122 UserID: "test", 123 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 124 }) 125 126 { 127 querierLoop, err := querierClient.QuerierLoop(context.Background()) 128 require.NoError(t, err) 129 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{QuerierID: "querier-1"})) 130 131 msg2, err := querierLoop.Recv() 132 require.NoError(t, err) 133 require.Equal(t, uint64(1), msg2.QueryID) 134 require.Equal(t, "frontend-12345", msg2.FrontendAddress) 135 require.Equal(t, "GET", msg2.HttpRequest.Method) 136 require.Equal(t, "/hello", msg2.HttpRequest.Url) 137 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{})) 138 } 139 140 verifyNoPendingRequestsLeft(t, scheduler) 141 } 142 143 func TestSchedulerEnqueueWithCancel(t *testing.T) { 144 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 145 146 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 147 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 148 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 149 QueryID: 1, 150 UserID: "test", 151 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 152 }) 153 154 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 155 Type: schedulerpb.FrontendToSchedulerType_CANCEL, 156 QueryID: 1, 157 }) 158 159 querierLoop := initQuerierLoop(t, querierClient, "querier-1") 160 161 verifyQuerierDoesntReceiveRequest(t, querierLoop, 500*time.Millisecond) 162 verifyNoPendingRequestsLeft(t, scheduler) 163 } 164 165 func initQuerierLoop(t *testing.T, querierClient schedulerpb.SchedulerForQuerierClient, querier string) schedulerpb.SchedulerForQuerier_QuerierLoopClient { 166 querierLoop, err := querierClient.QuerierLoop(context.Background()) 167 require.NoError(t, err) 168 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{QuerierID: querier})) 169 170 return querierLoop 171 } 172 173 func TestSchedulerEnqueueByMultipleFrontendsWithCancel(t *testing.T) { 174 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 175 176 frontendLoop1 := initFrontendLoop(t, frontendClient, "frontend-1") 177 frontendLoop2 := initFrontendLoop(t, frontendClient, "frontend-2") 178 179 frontendToScheduler(t, frontendLoop1, &schedulerpb.FrontendToScheduler{ 180 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 181 QueryID: 1, 182 UserID: "test", 183 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello1"}, 184 }) 185 186 frontendToScheduler(t, frontendLoop2, &schedulerpb.FrontendToScheduler{ 187 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 188 QueryID: 1, 189 UserID: "test", 190 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello2"}, 191 }) 192 193 // Cancel first query by first frontend. 194 frontendToScheduler(t, frontendLoop1, &schedulerpb.FrontendToScheduler{ 195 Type: schedulerpb.FrontendToSchedulerType_CANCEL, 196 QueryID: 1, 197 }) 198 199 querierLoop := initQuerierLoop(t, querierClient, "querier-1") 200 201 // Let's verify that we can receive query 1 from frontend-2. 202 msg, err := querierLoop.Recv() 203 require.NoError(t, err) 204 require.Equal(t, uint64(1), msg.QueryID) 205 require.Equal(t, "frontend-2", msg.FrontendAddress) 206 // Must notify scheduler back about finished processing, or it will not send more requests (nor remove "current" request from pending ones). 207 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{})) 208 209 // But nothing else. 210 verifyQuerierDoesntReceiveRequest(t, querierLoop, 500*time.Millisecond) 211 verifyNoPendingRequestsLeft(t, scheduler) 212 } 213 214 func TestSchedulerEnqueueWithFrontendDisconnect(t *testing.T) { 215 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 216 217 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 218 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 219 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 220 QueryID: 1, 221 UserID: "test", 222 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 223 }) 224 225 // Wait until the frontend has connected to the scheduler. 226 test.Poll(t, time.Second, float64(1), func() interface{} { 227 return promtest.ToFloat64(scheduler.connectedFrontendClients) 228 }) 229 230 // Disconnect frontend. 231 require.NoError(t, frontendLoop.CloseSend()) 232 233 // Wait until the frontend has disconnected. 234 test.Poll(t, time.Second, float64(0), func() interface{} { 235 return promtest.ToFloat64(scheduler.connectedFrontendClients) 236 }) 237 238 querierLoop := initQuerierLoop(t, querierClient, "querier-1") 239 240 verifyQuerierDoesntReceiveRequest(t, querierLoop, 500*time.Millisecond) 241 verifyNoPendingRequestsLeft(t, scheduler) 242 } 243 244 func TestCancelRequestInProgress(t *testing.T) { 245 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 246 247 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 248 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 249 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 250 QueryID: 1, 251 UserID: "test", 252 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 253 }) 254 255 querierLoop, err := querierClient.QuerierLoop(context.Background()) 256 require.NoError(t, err) 257 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{QuerierID: "querier-1"})) 258 259 _, err = querierLoop.Recv() 260 require.NoError(t, err) 261 262 // At this point, scheduler assumes that querier is processing the request (until it receives empty QuerierToScheduler message back). 263 // Simulate frontend disconnect. 264 require.NoError(t, frontendLoop.CloseSend()) 265 266 // Add a little sleep to make sure that scheduler notices frontend disconnect. 267 time.Sleep(500 * time.Millisecond) 268 269 // Report back end of request processing. This should return error, since the QuerierLoop call has finished on scheduler. 270 // Note: testing on querierLoop.Context() cancellation didn't work :( 271 test.Poll(t, time.Second, io.EOF, func() interface{} { return querierLoop.Send(&schedulerpb.QuerierToScheduler{}) }) 272 273 verifyNoPendingRequestsLeft(t, scheduler) 274 } 275 276 func TestTracingContext(t *testing.T) { 277 scheduler, frontendClient, _ := setupScheduler(t, schedulerArgs{}) 278 279 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 280 281 closer, err := config.Configuration{}.InitGlobalTracer("test") 282 require.NoError(t, err) 283 defer closer.Close() 284 285 req := &schedulerpb.FrontendToScheduler{ 286 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 287 QueryID: 1, 288 UserID: "test", 289 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 290 FrontendAddress: "frontend-12345", 291 } 292 293 sp, _ := opentracing.StartSpanFromContext(context.Background(), "client") 294 _ = opentracing.GlobalTracer().Inject(sp.Context(), opentracing.HTTPHeaders, (*httpgrpcutil.HttpgrpcHeadersCarrier)(req.HttpRequest)) 295 296 frontendToScheduler(t, frontendLoop, req) 297 298 scheduler.pendingRequestsMu.Lock() 299 defer scheduler.pendingRequestsMu.Unlock() 300 require.Equal(t, 1, len(scheduler.pendingRequests)) 301 302 for _, r := range scheduler.pendingRequests { 303 require.NotNil(t, r.parentSpanContext) 304 } 305 } 306 307 func TestSchedulerShutdown_FrontendLoop(t *testing.T) { 308 scheduler, frontendClient, _ := setupScheduler(t, schedulerArgs{}) 309 310 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 311 312 // Stop the scheduler. This will disable receiving new requests from frontends. 313 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), scheduler)) 314 315 // We can still send request to scheduler, but we get shutdown error back. 316 require.NoError(t, frontendLoop.Send(&schedulerpb.FrontendToScheduler{ 317 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 318 QueryID: 1, 319 UserID: "test", 320 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 321 })) 322 323 msg, err := frontendLoop.Recv() 324 require.NoError(t, err) 325 require.Equal(t, schedulerpb.SchedulerToFrontendStatus_SHUTTING_DOWN, msg.Status) 326 } 327 328 func TestSchedulerShutdown_QuerierLoop(t *testing.T) { 329 scheduler, frontendClient, querierClient := setupScheduler(t, schedulerArgs{}) 330 331 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 332 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 333 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 334 QueryID: 1, 335 UserID: "test", 336 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 337 }) 338 339 // Scheduler now has 1 query. Let's connect querier and fetch it. 340 341 querierLoop, err := querierClient.QuerierLoop(context.Background()) 342 require.NoError(t, err) 343 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{QuerierID: "querier-1"})) 344 345 // Dequeue first query. 346 _, err = querierLoop.Recv() 347 require.NoError(t, err) 348 349 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), scheduler)) 350 351 // Unblock scheduler loop, to find next request. 352 err = querierLoop.Send(&schedulerpb.QuerierToScheduler{}) 353 require.NoError(t, err) 354 355 // This should now return with error, since scheduler is going down. 356 _, err = querierLoop.Recv() 357 require.Error(t, err) 358 } 359 360 func TestSchedulerMaxOutstandingRequests(t *testing.T) { 361 _, frontendClient, _ := setupScheduler(t, schedulerArgs{}) 362 363 for i := 0; i < testMaxOutstandingPerTenant; i++ { 364 // coming from different frontends 365 fl := initFrontendLoop(t, frontendClient, fmt.Sprintf("frontend-%d", i)) 366 require.NoError(t, fl.Send(&schedulerpb.FrontendToScheduler{ 367 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 368 QueryID: uint64(i), 369 UserID: "test", // for same user. 370 HttpRequest: &httpgrpc.HTTPRequest{}, 371 })) 372 373 msg, err := fl.Recv() 374 require.NoError(t, err) 375 require.Equal(t, schedulerpb.SchedulerToFrontendStatus_OK, msg.Status) 376 } 377 378 // One more query from the same user will trigger an error. 379 fl := initFrontendLoop(t, frontendClient, "extra-frontend") 380 require.NoError(t, fl.Send(&schedulerpb.FrontendToScheduler{ 381 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 382 QueryID: 0, 383 UserID: "test", 384 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 385 })) 386 387 msg, err := fl.Recv() 388 require.NoError(t, err) 389 require.Equal(t, schedulerpb.SchedulerToFrontendStatus_TOO_MANY_REQUESTS_PER_TENANT, msg.Status) 390 } 391 392 func TestSchedulerForwardsErrorToFrontend(t *testing.T) { 393 394 l := bufconn.Listen(256 << 10) 395 _, frontendClient, querierClient := setupScheduler(t, schedulerArgs{ 396 // Have the scheduler use the in-memory connection to call back into the frontend. 397 dialOpts: []grpc.DialOption{ 398 grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return l.Dial() }), 399 }, 400 }) 401 402 fm := &frontendMock{resp: map[uint64]*httpgrpc.HTTPResponse{}} 403 404 // Setup frontend grpc server 405 { 406 frontendGrpcServer := grpc.NewServer() 407 frontendpb.RegisterFrontendForQuerierServer(frontendGrpcServer, fm) 408 409 go func() { 410 _ = frontendGrpcServer.Serve(l) 411 }() 412 t.Cleanup(func() { 413 _ = l.Close() 414 }) 415 } 416 417 // After preparations, start frontend and querier. 418 frontendLoop := initFrontendLoop(t, frontendClient, "irrelevant://because-we-use-in-memory-connection") 419 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 420 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 421 QueryID: 100, 422 UserID: "test", 423 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 424 }) 425 426 // Scheduler now has 1 query. We now connect querier, fetch the request, and then close the connection. 427 // This will make scheduler to report error back to frontend. 428 429 querierLoop, err := querierClient.QuerierLoop(context.Background()) 430 require.NoError(t, err) 431 require.NoError(t, querierLoop.Send(&schedulerpb.QuerierToScheduler{QuerierID: "querier-1"})) 432 433 // Dequeue first query. 434 _, err = querierLoop.Recv() 435 require.NoError(t, err) 436 437 // Querier now disconnects, without sending empty message back. 438 require.NoError(t, querierLoop.CloseSend()) 439 440 // Verify that frontend was notified about request. 441 test.Poll(t, 2*time.Second, true, func() interface{} { 442 resp := fm.getRequest(100) 443 if resp == nil { 444 return false 445 } 446 447 require.Equal(t, int32(http.StatusInternalServerError), resp.Code) 448 return true 449 }) 450 } 451 452 func TestSchedulerMetrics(t *testing.T) { 453 reg := prometheus.NewPedanticRegistry() 454 455 scheduler, frontendClient, _ := setupScheduler(t, schedulerArgs{reg: reg}) 456 457 frontendLoop := initFrontendLoop(t, frontendClient, "frontend-12345") 458 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 459 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 460 QueryID: 1, 461 UserID: "test", 462 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 463 }) 464 frontendToScheduler(t, frontendLoop, &schedulerpb.FrontendToScheduler{ 465 Type: schedulerpb.FrontendToSchedulerType_ENQUEUE, 466 QueryID: 1, 467 UserID: "another", 468 HttpRequest: &httpgrpc.HTTPRequest{Method: "GET", Url: "/hello"}, 469 }) 470 471 require.NoError(t, promtest.GatherAndCompare(reg, strings.NewReader(` 472 # HELP pyroscope_query_scheduler_queue_length Number of queries in the queue. 473 # TYPE pyroscope_query_scheduler_queue_length gauge 474 pyroscope_query_scheduler_queue_length{tenant="another"} 1 475 pyroscope_query_scheduler_queue_length{tenant="test"} 1 476 `), "pyroscope_query_scheduler_queue_length")) 477 478 scheduler.cleanupMetricsForInactiveUser("test") 479 480 require.NoError(t, promtest.GatherAndCompare(reg, strings.NewReader(` 481 # HELP pyroscope_query_scheduler_queue_length Number of queries in the queue. 482 # TYPE pyroscope_query_scheduler_queue_length gauge 483 pyroscope_query_scheduler_queue_length{tenant="another"} 1 484 `), "pyroscope_query_scheduler_queue_length")) 485 } 486 487 func initFrontendLoop(t *testing.T, client schedulerpb.SchedulerForFrontendClient, frontendAddr string) schedulerpb.SchedulerForFrontend_FrontendLoopClient { 488 loop, err := client.FrontendLoop(context.Background()) 489 require.NoError(t, err) 490 491 require.NoError(t, loop.Send(&schedulerpb.FrontendToScheduler{ 492 Type: schedulerpb.FrontendToSchedulerType_INIT, 493 FrontendAddress: frontendAddr, 494 })) 495 496 // Scheduler acks INIT by sending OK back. 497 resp, err := loop.Recv() 498 require.NoError(t, err) 499 require.Equal(t, schedulerpb.SchedulerToFrontendStatus_OK, resp.Status) 500 501 return loop 502 } 503 504 func frontendToScheduler(t *testing.T, frontendLoop schedulerpb.SchedulerForFrontend_FrontendLoopClient, req *schedulerpb.FrontendToScheduler) { 505 require.NoError(t, frontendLoop.Send(req)) 506 msg, err := frontendLoop.Recv() 507 require.NoError(t, err) 508 require.Equal(t, schedulerpb.SchedulerToFrontendStatus_OK, msg.Status) 509 } 510 511 // If this verification succeeds, there will be leaked goroutine left behind. It will be cleaned once grpc server is shut down. 512 func verifyQuerierDoesntReceiveRequest(t *testing.T, querierLoop schedulerpb.SchedulerForQuerier_QuerierLoopClient, timeout time.Duration) { 513 ch := make(chan interface{}, 1) 514 515 go func() { 516 m, e := querierLoop.Recv() 517 if e != nil { 518 ch <- e 519 } else { 520 ch <- m 521 } 522 }() 523 524 select { 525 case val := <-ch: 526 require.Failf(t, "expected timeout", "got %v", val) 527 case <-time.After(timeout): 528 return 529 } 530 } 531 532 func verifyNoPendingRequestsLeft(t *testing.T, scheduler *Scheduler) { 533 test.Poll(t, 1*time.Second, 0, func() interface{} { 534 scheduler.pendingRequestsMu.Lock() 535 defer scheduler.pendingRequestsMu.Unlock() 536 return len(scheduler.pendingRequests) 537 }) 538 } 539 540 type limits struct { 541 queriers int 542 } 543 544 func (l limits) MaxQueriersPerTenant(_ string) int { 545 return l.queriers 546 } 547 548 type frontendMock struct { 549 mu sync.Mutex 550 resp map[uint64]*httpgrpc.HTTPResponse 551 552 frontendpb.UnimplementedFrontendForQuerierServer 553 } 554 555 func (f *frontendMock) QueryResult(_ context.Context, request *frontendpb.QueryResultRequest) (*frontendpb.QueryResultResponse, error) { 556 f.mu.Lock() 557 defer f.mu.Unlock() 558 559 f.resp[request.QueryID] = request.HttpResponse 560 return &frontendpb.QueryResultResponse{}, nil 561 } 562 563 func (f *frontendMock) getRequest(queryID uint64) *httpgrpc.HTTPResponse { 564 f.mu.Lock() 565 defer f.mu.Unlock() 566 567 return f.resp[queryID] 568 }