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  }