github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/lokifrontend/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/grafana/loki/pkg/lokifrontend/frontend/transport" 33 "github.com/grafana/loki/pkg/lokifrontend/frontend/v1/frontendv1pb" 34 querier_worker "github.com/grafana/loki/pkg/querier/worker" 35 "github.com/grafana/loki/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) 65 testFrontend(t, defaultFrontendConfig(), handler, test, true, 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) 115 testFrontend(t, defaultFrontendConfig(), handler, test, true, 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) 180 tries.Store(0) 181 testFrontend(t, defaultFrontendConfig(), handler, test, true, 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 defer resp.Body.Close() 203 204 body, err := ioutil.ReadAll(resp.Body) 205 require.NoError(t, err) 206 207 assert.Equal(t, "Hello World", string(body)) 208 209 require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 210 # HELP cortex_query_frontend_queue_length Number of queries in the queue. 211 # TYPE cortex_query_frontend_queue_length gauge 212 cortex_query_frontend_queue_length{user="1"} 0 213 `), "cortex_query_frontend_queue_length")) 214 215 fr.cleanupInactiveUserMetrics("1") 216 217 require.NoError(t, testutil.GatherAndCompare(reg, strings.NewReader(` 218 # HELP cortex_query_frontend_queue_length Number of queries in the queue. 219 # TYPE cortex_query_frontend_queue_length gauge 220 `), "cortex_query_frontend_queue_length")) 221 } 222 223 testFrontend(t, defaultFrontendConfig(), handler, test, matchMaxConcurrency, reg) 224 } 225 } 226 227 func testFrontend(t *testing.T, config Config, handler http.Handler, test func(addr string, frontend *Frontend), matchMaxConcurrency bool, reg prometheus.Registerer) { 228 logger := log.NewNopLogger() 229 230 var workerConfig querier_worker.Config 231 flagext.DefaultValues(&workerConfig) 232 workerConfig.Parallelism = 1 233 workerConfig.MatchMaxConcurrency = matchMaxConcurrency 234 workerConfig.MaxConcurrentRequests = 1 235 236 // localhost:0 prevents firewall warnings on Mac OS X. 237 grpcListen, err := net.Listen("tcp", "localhost:0") 238 require.NoError(t, err) 239 workerConfig.FrontendAddress = grpcListen.Addr().String() 240 241 httpListen, err := net.Listen("tcp", "localhost:0") 242 require.NoError(t, err) 243 244 v1, err := New(config, limits{}, logger, reg) 245 require.NoError(t, err) 246 require.NotNil(t, v1) 247 require.NoError(t, services.StartAndAwaitRunning(context.Background(), v1)) 248 defer func() { 249 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), v1)) 250 }() 251 252 grpcServer := grpc.NewServer( 253 grpc.StreamInterceptor(otgrpc.OpenTracingStreamServerInterceptor(opentracing.GlobalTracer())), 254 ) 255 defer grpcServer.GracefulStop() 256 257 frontendv1pb.RegisterFrontendServer(grpcServer, v1) 258 259 // Default HTTP handler config. 260 handlerCfg := transport.HandlerConfig{} 261 flagext.DefaultValues(&handlerCfg) 262 263 rt := transport.AdaptGrpcRoundTripperToHTTPRoundTripper(v1) 264 r := mux.NewRouter() 265 r.PathPrefix("/").Handler(middleware.Merge( 266 middleware.AuthenticateUser, 267 middleware.Tracer{}, 268 ).Wrap(transport.NewHandler(handlerCfg, rt, logger, nil))) 269 270 httpServer := http.Server{ 271 Handler: r, 272 } 273 defer httpServer.Shutdown(context.Background()) //nolint:errcheck 274 275 go httpServer.Serve(httpListen) //nolint:errcheck 276 go grpcServer.Serve(grpcListen) //nolint:errcheck 277 278 var worker services.Service 279 worker, err = querier_worker.NewQuerierWorker(workerConfig, nil, httpgrpc_server.NewServer(handler), logger, nil) 280 require.NoError(t, err) 281 require.NoError(t, services.StartAndAwaitRunning(context.Background(), worker)) 282 283 test(httpListen.Addr().String(), v1) 284 285 require.NoError(t, services.StopAndAwaitTerminated(context.Background(), worker)) 286 } 287 288 func defaultFrontendConfig() Config { 289 config := Config{} 290 flagext.DefaultValues(&config) 291 return config 292 } 293 294 type limits struct { 295 queriers int 296 } 297 298 func (l limits) MaxQueriersPerUser(_ string) int { 299 return l.queriers 300 }