github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/frontend/v1/frontend_test.go (about)

     1  package v1
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"net/http"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/go-kit/log"
    14  	"github.com/gorilla/mux"
    15  	"github.com/grafana/dskit/flagext"
    16  	"github.com/grafana/dskit/services"
    17  	otgrpc "github.com/opentracing-contrib/go-grpc"
    18  	"github.com/opentracing-contrib/go-stdlib/nethttp"
    19  	"github.com/opentracing/opentracing-go"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	"github.com/prometheus/client_golang/prometheus/testutil"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	"github.com/uber/jaeger-client-go"
    25  	"github.com/uber/jaeger-client-go/config"
    26  	httpgrpc_server "github.com/weaveworks/common/httpgrpc/server"
    27  	"github.com/weaveworks/common/middleware"
    28  	"github.com/weaveworks/common/user"
    29  	"go.uber.org/atomic"
    30  	"google.golang.org/grpc"
    31  
    32  	"github.com/cortexproject/cortex/pkg/frontend/transport"
    33  	"github.com/cortexproject/cortex/pkg/frontend/v1/frontendv1pb"
    34  	querier_worker "github.com/cortexproject/cortex/pkg/querier/worker"
    35  	"github.com/cortexproject/cortex/pkg/scheduler/queue"
    36  )
    37  
    38  const (
    39  	query        = "/api/v1/query_range?end=1536716898&query=sum%28container_memory_rss%29+by+%28namespace%29&start=1536673680&step=120"
    40  	responseBody = `{"status":"success","data":{"resultType":"Matrix","result":[{"metric":{"foo":"bar"},"values":[[1536673680,"137"],[1536673780,"137"]]}]}}`
    41  )
    42  
    43  func TestFrontend(t *testing.T) {
    44  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    45  		_, err := w.Write([]byte("Hello World"))
    46  		require.NoError(t, err)
    47  	})
    48  	test := func(addr string, _ *Frontend) {
    49  		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/", addr), nil)
    50  		require.NoError(t, err)
    51  		err = user.InjectOrgIDIntoHTTPRequest(user.InjectOrgID(context.Background(), "1"), req)
    52  		require.NoError(t, err)
    53  
    54  		resp, err := http.DefaultClient.Do(req)
    55  		require.NoError(t, err)
    56  		require.Equal(t, 200, resp.StatusCode)
    57  
    58  		body, err := ioutil.ReadAll(resp.Body)
    59  		require.NoError(t, err)
    60  
    61  		assert.Equal(t, "Hello World", string(body))
    62  	}
    63  
    64  	testFrontend(t, defaultFrontendConfig(), handler, test, false, nil, nil)
    65  	testFrontend(t, defaultFrontendConfig(), handler, test, true, nil, nil)
    66  }
    67  
    68  func TestFrontendPropagateTrace(t *testing.T) {
    69  	closer, err := config.Configuration{}.InitGlobalTracer("test")
    70  	require.NoError(t, err)
    71  	defer closer.Close()
    72  
    73  	observedTraceID := make(chan string, 2)
    74  
    75  	handler := middleware.Tracer{}.Wrap(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    76  		sp := opentracing.SpanFromContext(r.Context())
    77  		defer sp.Finish()
    78  
    79  		traceID := fmt.Sprintf("%v", sp.Context().(jaeger.SpanContext).TraceID())
    80  		observedTraceID <- traceID
    81  
    82  		_, err = w.Write([]byte(responseBody))
    83  		require.NoError(t, err)
    84  	}))
    85  
    86  	test := func(addr string, _ *Frontend) {
    87  		sp, ctx := opentracing.StartSpanFromContext(context.Background(), "client")
    88  		defer sp.Finish()
    89  		traceID := fmt.Sprintf("%v", sp.Context().(jaeger.SpanContext).TraceID())
    90  
    91  		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/%s", addr, query), nil)
    92  		require.NoError(t, err)
    93  		req = req.WithContext(ctx)
    94  		err = user.InjectOrgIDIntoHTTPRequest(user.InjectOrgID(ctx, "1"), req)
    95  		require.NoError(t, err)
    96  
    97  		req, tr := nethttp.TraceRequest(opentracing.GlobalTracer(), req)
    98  		defer tr.Finish()
    99  
   100  		client := http.Client{
   101  			Transport: &nethttp.Transport{},
   102  		}
   103  		resp, err := client.Do(req)
   104  		require.NoError(t, err)
   105  		require.Equal(t, 200, resp.StatusCode)
   106  
   107  		defer resp.Body.Close()
   108  		_, err = ioutil.ReadAll(resp.Body)
   109  		require.NoError(t, err)
   110  
   111  		// Query should do one call.
   112  		assert.Equal(t, traceID, <-observedTraceID)
   113  	}
   114  	testFrontend(t, defaultFrontendConfig(), handler, test, false, nil, nil)
   115  	testFrontend(t, defaultFrontendConfig(), handler, test, true, nil, nil)
   116  }
   117  
   118  func TestFrontendCheckReady(t *testing.T) {
   119  	for _, tt := range []struct {
   120  		name             string
   121  		connectedClients int
   122  		msg              string
   123  		readyForRequests bool
   124  	}{
   125  		{"connected clients are ready", 3, "", true},
   126  		{"no url, no clients is not ready", 0, "not ready: number of queriers connected to query-frontend is 0", false},
   127  	} {
   128  		t.Run(tt.name, func(t *testing.T) {
   129  			f := &Frontend{
   130  				log: log.NewNopLogger(),
   131  				requestQueue: queue.NewRequestQueue(5, 0,
   132  					prometheus.NewGaugeVec(prometheus.GaugeOpts{}, []string{"user"}),
   133  					prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"user"}),
   134  				),
   135  			}
   136  			for i := 0; i < tt.connectedClients; i++ {
   137  				f.requestQueue.RegisterQuerierConnection("test")
   138  			}
   139  			err := f.CheckReady(context.Background())
   140  			errMsg := ""
   141  
   142  			if err != nil {
   143  				errMsg = err.Error()
   144  			}
   145  
   146  			require.Equal(t, tt.msg, errMsg)
   147  		})
   148  	}
   149  }
   150  
   151  // TestFrontendCancel ensures that when client requests are cancelled,
   152  // the underlying query is correctly cancelled _and not retried_.
   153  func TestFrontendCancel(t *testing.T) {
   154  	var tries atomic.Int32
   155  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   156  		<-r.Context().Done()
   157  		tries.Inc()
   158  	})
   159  	test := func(addr string, _ *Frontend) {
   160  		req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/", addr), nil)
   161  		require.NoError(t, err)
   162  		err = user.InjectOrgIDIntoHTTPRequest(user.InjectOrgID(context.Background(), "1"), req)
   163  		require.NoError(t, err)
   164  
   165  		ctx, cancel := context.WithCancel(context.Background())
   166  		req = req.WithContext(ctx)
   167  
   168  		go func() {
   169  			time.Sleep(100 * time.Millisecond)
   170  			cancel()
   171  		}()
   172  
   173  		_, err = http.DefaultClient.Do(req)
   174  		require.Error(t, err)
   175  
   176  		time.Sleep(100 * time.Millisecond)
   177  		assert.Equal(t, int32(1), tries.Load())
   178  	}
   179  	testFrontend(t, defaultFrontendConfig(), handler, test, false, nil, nil)
   180  	tries.Store(0)
   181  	testFrontend(t, defaultFrontendConfig(), handler, test, true, nil, nil)
   182  }
   183  
   184  func TestFrontendMetricsCleanup(t *testing.T) {
   185  	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   186  		_, err := w.Write([]byte("Hello World"))
   187  		require.NoError(t, err)
   188  	})
   189  
   190  	for _, matchMaxConcurrency := range []bool{false, true} {
   191  		reg := prometheus.NewPedanticRegistry()
   192  
   193  		test := func(addr string, fr *Frontend) {
   194  			req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/", addr), nil)
   195  			require.NoError(t, err)
   196  			err = user.InjectOrgIDIntoHTTPRequest(user.InjectOrgID(context.Background(), "1"), req)
   197  			require.NoError(t, err)
   198  
   199  			resp, err := http.DefaultClient.Do(req)
   200  			require.NoError(t, err)
   201  			require.Equal(t, 200, resp.StatusCode)
   202  
   203  			body, err := ioutil.ReadAll(resp.Body)
   204  			require.NoError(t, err)
   205  
   206  			assert.Equal(t, "Hello World", string(body))
   207  
   208  			require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(`
   209  				# HELP cortex_query_frontend_queue_length Number of queries in the queue.
   210  				# TYPE cortex_query_frontend_queue_length gauge
   211  				cortex_query_frontend_queue_length{user="1"} 0
   212  			`), "cortex_query_frontend_queue_length"))
   213  
   214  			fr.cleanupInactiveUserMetrics("1")
   215  
   216  			require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(`
   217  				# HELP cortex_query_frontend_queue_length Number of queries in the queue.
   218  				# TYPE cortex_query_frontend_queue_length gauge
   219  			`), "cortex_query_frontend_queue_length"))
   220  		}
   221  
   222  		testFrontend(t, defaultFrontendConfig(), handler, test, matchMaxConcurrency, nil, reg)
   223  	}
   224  }
   225  
   226  func testFrontend(t *testing.T, config Config, handler http.Handler, test func(addr string, frontend *Frontend), matchMaxConcurrency bool, l log.Logger, reg prometheus.Registerer) {
   227  	logger := log.NewNopLogger()
   228  	if l != nil {
   229  		logger = l
   230  	}
   231  
   232  	var workerConfig querier_worker.Config
   233  	flagext.DefaultValues(&workerConfig)
   234  	workerConfig.Parallelism = 1
   235  	workerConfig.MatchMaxConcurrency = matchMaxConcurrency
   236  	workerConfig.MaxConcurrentRequests = 1
   237  
   238  	// localhost:0 prevents firewall warnings on Mac OS X.
   239  	grpcListen, err := net.Listen("tcp", "localhost:0")
   240  	require.NoError(t, err)
   241  	workerConfig.FrontendAddress = grpcListen.Addr().String()
   242  
   243  	httpListen, err := net.Listen("tcp", "localhost:0")
   244  	require.NoError(t, err)
   245  
   246  	v1, err := New(config, limits{}, logger, reg)
   247  	require.NoError(t, err)
   248  	require.NotNil(t, v1)
   249  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), v1))
   250  	defer func() {
   251  		require.NoError(t, services.StopAndAwaitTerminated(context.Background(), v1))
   252  	}()
   253  
   254  	grpcServer := grpc.NewServer(
   255  		grpc.StreamInterceptor(otgrpc.OpenTracingStreamServerInterceptor(opentracing.GlobalTracer())),
   256  	)
   257  	defer grpcServer.GracefulStop()
   258  
   259  	frontendv1pb.RegisterFrontendServer(grpcServer, v1)
   260  
   261  	// Default HTTP handler config.
   262  	handlerCfg := transport.HandlerConfig{}
   263  	flagext.DefaultValues(&handlerCfg)
   264  
   265  	rt := transport.AdaptGrpcRoundTripperToHTTPRoundTripper(v1)
   266  	r := mux.NewRouter()
   267  	r.PathPrefix("/").Handler(middleware.Merge(
   268  		middleware.AuthenticateUser,
   269  		middleware.Tracer{},
   270  	).Wrap(transport.NewHandler(handlerCfg, rt, logger, nil)))
   271  
   272  	httpServer := http.Server{
   273  		Handler: r,
   274  	}
   275  	defer httpServer.Shutdown(context.Background()) //nolint:errcheck
   276  
   277  	go httpServer.Serve(httpListen) //nolint:errcheck
   278  	go grpcServer.Serve(grpcListen) //nolint:errcheck
   279  
   280  	var worker services.Service
   281  	worker, err = querier_worker.NewQuerierWorker(workerConfig, httpgrpc_server.NewServer(handler), logger, nil)
   282  	require.NoError(t, err)
   283  	require.NoError(t, services.StartAndAwaitRunning(context.Background(), worker))
   284  
   285  	test(httpListen.Addr().String(), v1)
   286  
   287  	require.NoError(t, services.StopAndAwaitTerminated(context.Background(), worker))
   288  }
   289  
   290  func defaultFrontendConfig() Config {
   291  	config := Config{}
   292  	flagext.DefaultValues(&config)
   293  	return config
   294  }
   295  
   296  type limits struct {
   297  	queriers int
   298  }
   299  
   300  func (l limits) MaxQueriersPerUser(_ string) int {
   301  	return l.queriers
   302  }