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 }