github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/frontend/v1/queue_test.go (about) 1 package v1 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/go-kit/log" 12 "github.com/grafana/dskit/flagext" 13 "github.com/grafana/dskit/services" 14 "github.com/pkg/errors" 15 "github.com/stretchr/testify/require" 16 "github.com/weaveworks/common/httpgrpc" 17 "github.com/weaveworks/common/user" 18 "google.golang.org/grpc/metadata" 19 20 "github.com/cortexproject/cortex/pkg/frontend/v1/frontendv1pb" 21 ) 22 23 func setupFrontend(t *testing.T, config Config) (*Frontend, error) { 24 logger := log.NewNopLogger() 25 26 frontend, err := New(config, limits{queriers: 3}, logger, nil) 27 require.NoError(t, err) 28 29 t.Cleanup(func() { 30 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), frontend)) 31 }) 32 return frontend, nil 33 } 34 35 func testReq(ctx context.Context, reqID, user string) *request { 36 return &request{ 37 originalCtx: ctx, 38 err: make(chan error, 1), 39 request: &httpgrpc.HTTPRequest{ 40 // Good enough for testing. 41 Method: user, 42 Url: reqID, 43 }, 44 response: make(chan *httpgrpc.HTTPResponse, 1), 45 } 46 } 47 48 func TestDequeuesExpiredRequests(t *testing.T) { 49 var config Config 50 flagext.DefaultValues(&config) 51 config.MaxOutstandingPerTenant = 10 52 userID := "1" 53 54 f, err := setupFrontend(t, config) 55 require.NoError(t, err) 56 57 ctx := user.InjectOrgID(context.Background(), userID) 58 expired, cancel := context.WithCancel(ctx) 59 cancel() 60 61 good := 0 62 for i := 0; i < config.MaxOutstandingPerTenant; i++ { 63 var err error 64 if i%5 == 0 { 65 good++ 66 err = f.queueRequest(ctx, testReq(ctx, fmt.Sprintf("good-%d", i), userID)) 67 } else { 68 err = f.queueRequest(ctx, testReq(expired, fmt.Sprintf("expired-%d", i), userID)) 69 } 70 71 require.Nil(t, err) 72 } 73 74 // Calling Process will only return when client disconnects or context is finished. 75 // We use context timeout to stop Process call. 76 ctx2, cancel2 := context.WithTimeout(context.Background(), 1*time.Second) 77 defer cancel2() 78 79 m := &processServerMock{ctx: ctx2, querierID: "querier"} 80 err = f.Process(m) 81 require.EqualError(t, err, context.DeadlineExceeded.Error()) 82 83 // Verify that only non-expired requests were forwarded to querier. 84 for _, r := range m.requests { 85 require.True(t, strings.HasPrefix(r.Url, "good-"), r.Url) 86 } 87 require.Len(t, m.requests, good) 88 } 89 90 func TestRoundRobinQueues(t *testing.T) { 91 var config Config 92 flagext.DefaultValues(&config) 93 94 const ( 95 requests = 100 96 tenants = 10 97 ) 98 99 config.MaxOutstandingPerTenant = requests 100 101 f, err := setupFrontend(t, config) 102 require.NoError(t, err) 103 104 for i := 0; i < requests; i++ { 105 userID := fmt.Sprint(i / tenants) 106 ctx := user.InjectOrgID(context.Background(), userID) 107 108 err = f.queueRequest(ctx, testReq(ctx, fmt.Sprintf("%d", i), userID)) 109 require.NoError(t, err) 110 } 111 112 // Calling Process will only return when client disconnects or context is finished. 113 // We use context timeout to stop Process call. 114 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 115 defer cancel() 116 117 m := &processServerMock{ctx: ctx, querierID: "querier"} 118 err = f.Process(m) 119 require.EqualError(t, err, context.DeadlineExceeded.Error()) 120 121 require.Len(t, m.requests, requests) 122 for i, r := range m.requests { 123 intUserID, err := strconv.Atoi(r.Method) 124 require.NoError(t, err) 125 126 require.Equal(t, i%tenants, intUserID) 127 } 128 } 129 130 // This mock behaves as connected querier worker to frontend. It will remember each request 131 // that frontend sends, and reply with 200 HTTP status code. 132 type processServerMock struct { 133 ctx context.Context 134 querierID string 135 136 response *frontendv1pb.ClientToFrontend 137 138 requests []*httpgrpc.HTTPRequest 139 } 140 141 func (p *processServerMock) Send(client *frontendv1pb.FrontendToClient) error { 142 switch { 143 case client.GetType() == frontendv1pb.GET_ID: 144 p.response = &frontendv1pb.ClientToFrontend{ClientID: p.querierID} 145 return nil 146 147 case client.GetType() == frontendv1pb.HTTP_REQUEST: 148 p.requests = append(p.requests, client.HttpRequest) 149 p.response = &frontendv1pb.ClientToFrontend{HttpResponse: &httpgrpc.HTTPResponse{Code: 200}} 150 return nil 151 152 default: 153 return errors.New("unknown message") 154 } 155 } 156 157 func (p *processServerMock) Recv() (*frontendv1pb.ClientToFrontend, error) { 158 if p.response != nil { 159 m := p.response 160 p.response = nil 161 return m, nil 162 } 163 return nil, errors.New("no message") 164 } 165 166 func (p *processServerMock) SetHeader(_ metadata.MD) error { return nil } 167 func (p *processServerMock) SendHeader(_ metadata.MD) error { return nil } 168 func (p *processServerMock) SetTrailer(md metadata.MD) {} 169 func (p *processServerMock) Context() context.Context { return p.ctx } 170 func (p *processServerMock) SendMsg(m interface{}) error { return nil } 171 func (p *processServerMock) RecvMsg(m interface{}) error { return nil }