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 }