github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/cortex/cortex_test.go (about) 1 package cortex 2 3 import ( 4 "bytes" 5 "context" 6 "flag" 7 "io" 8 "net" 9 "net/url" 10 "strconv" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/grafana/dskit/flagext" 16 "github.com/grafana/dskit/kv" 17 "github.com/grafana/dskit/ring" 18 "github.com/grafana/dskit/services" 19 "github.com/prometheus/client_golang/prometheus" 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 "github.com/weaveworks/common/server" 23 "go.uber.org/atomic" 24 "google.golang.org/grpc" 25 26 "github.com/cortexproject/cortex/pkg/chunk/aws" 27 "github.com/cortexproject/cortex/pkg/chunk/storage" 28 "github.com/cortexproject/cortex/pkg/frontend/v1/frontendv1pb" 29 "github.com/cortexproject/cortex/pkg/ingester" 30 "github.com/cortexproject/cortex/pkg/ruler" 31 "github.com/cortexproject/cortex/pkg/scheduler/schedulerpb" 32 "github.com/cortexproject/cortex/pkg/storage/bucket" 33 "github.com/cortexproject/cortex/pkg/storage/bucket/s3" 34 "github.com/cortexproject/cortex/pkg/storage/tsdb" 35 ) 36 37 func TestCortex(t *testing.T) { 38 rulerURL, err := url.Parse("inmemory:///rules") 39 require.NoError(t, err) 40 41 cfg := Config{ 42 // Include the network names here explicitly. When CLI flags are registered 43 // these values are set as defaults but since we aren't registering them, we 44 // need to include the defaults here. These were hardcoded in a previous version 45 // of weaveworks server. 46 Server: server.Config{ 47 GRPCListenNetwork: server.DefaultNetwork, 48 HTTPListenNetwork: server.DefaultNetwork, 49 }, 50 Storage: storage.Config{ 51 Engine: storage.StorageEngineBlocks, // makes config easier 52 }, 53 Ingester: ingester.Config{ 54 BlocksStorageConfig: tsdb.BlocksStorageConfig{ 55 Bucket: bucket.Config{ 56 Backend: bucket.S3, 57 S3: s3.Config{ 58 Endpoint: "localhost", 59 }, 60 }, 61 }, 62 LifecyclerConfig: ring.LifecyclerConfig{ 63 RingConfig: ring.Config{ 64 KVStore: kv.Config{ 65 Store: "inmemory", 66 }, 67 ReplicationFactor: 3, 68 }, 69 InfNames: []string{"en0", "eth0", "lo0", "lo"}, 70 }, 71 }, 72 BlocksStorage: tsdb.BlocksStorageConfig{ 73 Bucket: bucket.Config{ 74 Backend: bucket.S3, 75 S3: s3.Config{ 76 Endpoint: "localhost", 77 }, 78 }, 79 BucketStore: tsdb.BucketStoreConfig{ 80 ChunkPoolMinBucketSizeBytes: tsdb.ChunkPoolDefaultMinBucketSize, 81 ChunkPoolMaxBucketSizeBytes: tsdb.ChunkPoolDefaultMaxBucketSize, 82 IndexCache: tsdb.IndexCacheConfig{ 83 Backend: tsdb.IndexCacheBackendInMemory, 84 }, 85 }, 86 }, 87 Ruler: ruler.Config{ 88 StoreConfig: ruler.RuleStoreConfig{ 89 Type: "s3", 90 S3: aws.S3Config{ 91 S3: flagext.URLValue{ 92 URL: rulerURL, 93 }, 94 }, 95 }, 96 }, 97 98 Target: []string{All, Compactor}, 99 } 100 101 c, err := New(cfg) 102 require.NoError(t, err) 103 104 serviceMap, err := c.ModuleManager.InitModuleServices(cfg.Target...) 105 require.NoError(t, err) 106 require.NotNil(t, serviceMap) 107 108 for m, s := range serviceMap { 109 // make sure each service is still New 110 require.Equal(t, services.New, s.State(), "module: %s", m) 111 } 112 113 // check random modules that we expect to be configured when using Target=All 114 require.NotNil(t, serviceMap[Server]) 115 require.NotNil(t, serviceMap[IngesterService]) 116 require.NotNil(t, serviceMap[Ring]) 117 require.NotNil(t, serviceMap[DistributorService]) 118 119 // check that compactor is configured which is not part of Target=All 120 require.NotNil(t, serviceMap[Compactor]) 121 } 122 123 func TestConfigValidation(t *testing.T) { 124 for _, tc := range []struct { 125 name string 126 getTestConfig func() *Config 127 expectedError error 128 }{ 129 { 130 name: "should pass validation if the http prefix is empty", 131 getTestConfig: func() *Config { 132 return newDefaultConfig() 133 }, 134 expectedError: nil, 135 }, 136 { 137 name: "should pass validation if the http prefix starts with /", 138 getTestConfig: func() *Config { 139 configuration := newDefaultConfig() 140 configuration.HTTPPrefix = "/test" 141 return configuration 142 }, 143 expectedError: nil, 144 }, 145 { 146 name: "should fail validation for invalid prefix", 147 getTestConfig: func() *Config { 148 configuration := newDefaultConfig() 149 configuration.HTTPPrefix = "test" 150 return configuration 151 }, 152 expectedError: errInvalidHTTPPrefix, 153 }, 154 } { 155 t.Run(tc.name, func(t *testing.T) { 156 err := tc.getTestConfig().Validate(nil) 157 if tc.expectedError != nil { 158 require.Equal(t, tc.expectedError, err) 159 } else { 160 require.NoError(t, err) 161 } 162 }) 163 } 164 } 165 166 func TestGrpcAuthMiddleware(t *testing.T) { 167 prepareGlobalMetricsRegistry(t) 168 169 cfg := Config{ 170 AuthEnabled: true, // We must enable this to enable Auth middleware for gRPC server. 171 Server: getServerConfig(t), 172 Target: []string{API}, // Something innocent that doesn't require much config. 173 } 174 175 msch := &mockGrpcServiceHandler{} 176 ctx := context.Background() 177 178 // Setup server, using Cortex config. This includes authentication middleware. 179 { 180 c, err := New(cfg) 181 require.NoError(t, err) 182 183 serv, err := c.initServer() 184 require.NoError(t, err) 185 186 schedulerpb.RegisterSchedulerForQuerierServer(c.Server.GRPC, msch) 187 frontendv1pb.RegisterFrontendServer(c.Server.GRPC, msch) 188 189 require.NoError(t, services.StartAndAwaitRunning(ctx, serv)) 190 defer func() { 191 require.NoError(t, services.StopAndAwaitTerminated(ctx, serv)) 192 }() 193 } 194 195 conn, err := grpc.Dial(net.JoinHostPort(cfg.Server.GRPCListenAddress, strconv.Itoa(cfg.Server.GRPCListenPort)), grpc.WithInsecure()) 196 require.NoError(t, err) 197 defer func() { 198 require.NoError(t, conn.Close()) 199 }() 200 201 { 202 // Verify that we can call frontendClient.NotifyClientShutdown without user in the context, and we don't get any error. 203 require.False(t, msch.clientShutdownCalled.Load()) 204 frontendClient := frontendv1pb.NewFrontendClient(conn) 205 _, err = frontendClient.NotifyClientShutdown(ctx, &frontendv1pb.NotifyClientShutdownRequest{ClientID: "random-client-id"}) 206 require.NoError(t, err) 207 require.True(t, msch.clientShutdownCalled.Load()) 208 } 209 210 { 211 // Verify that we can call schedulerClient.NotifyQuerierShutdown without user in the context, and we don't get any error. 212 require.False(t, msch.querierShutdownCalled.Load()) 213 schedulerClient := schedulerpb.NewSchedulerForQuerierClient(conn) 214 _, err = schedulerClient.NotifyQuerierShutdown(ctx, &schedulerpb.NotifyQuerierShutdownRequest{QuerierID: "random-querier-id"}) 215 require.NoError(t, err) 216 require.True(t, msch.querierShutdownCalled.Load()) 217 } 218 } 219 220 func TestFlagDefaults(t *testing.T) { 221 c := Config{} 222 223 f := flag.NewFlagSet("test", flag.PanicOnError) 224 c.RegisterFlags(f) 225 226 buf := bytes.Buffer{} 227 228 f.SetOutput(&buf) 229 f.PrintDefaults() 230 231 const delim = '\n' 232 233 minTimeChecked := false 234 pingWithoutStreamChecked := false 235 for { 236 line, err := buf.ReadString(delim) 237 if err == io.EOF { 238 break 239 } 240 241 require.NoError(t, err) 242 243 if strings.Contains(line, "-server.grpc.keepalive.min-time-between-pings") { 244 nextLine, err := buf.ReadString(delim) 245 require.NoError(t, err) 246 assert.Contains(t, nextLine, "(default 10s)") 247 minTimeChecked = true 248 } 249 250 if strings.Contains(line, "-server.grpc.keepalive.ping-without-stream-allowed") { 251 nextLine, err := buf.ReadString(delim) 252 require.NoError(t, err) 253 assert.Contains(t, nextLine, "(default true)") 254 pingWithoutStreamChecked = true 255 } 256 } 257 258 require.True(t, minTimeChecked) 259 require.True(t, pingWithoutStreamChecked) 260 261 require.Equal(t, true, c.Server.GRPCServerPingWithoutStreamAllowed) 262 require.Equal(t, 10*time.Second, c.Server.GRPCServerMinTimeBetweenPings) 263 } 264 265 // Generates server config, with gRPC listening on random port. 266 func getServerConfig(t *testing.T) server.Config { 267 listen, err := net.Listen("tcp", "localhost:0") 268 require.NoError(t, err) 269 270 host, port, err := net.SplitHostPort(listen.Addr().String()) 271 require.NoError(t, err) 272 require.NoError(t, listen.Close()) 273 274 portNum, err := strconv.Atoi(port) 275 require.NoError(t, err) 276 277 return server.Config{ 278 HTTPListenNetwork: server.DefaultNetwork, 279 GRPCListenNetwork: server.DefaultNetwork, 280 281 GRPCListenAddress: host, 282 GRPCListenPort: portNum, 283 284 GPRCServerMaxRecvMsgSize: 1024, 285 } 286 } 287 288 type mockGrpcServiceHandler struct { 289 clientShutdownCalled atomic.Bool 290 querierShutdownCalled atomic.Bool 291 } 292 293 func (m *mockGrpcServiceHandler) NotifyClientShutdown(_ context.Context, _ *frontendv1pb.NotifyClientShutdownRequest) (*frontendv1pb.NotifyClientShutdownResponse, error) { 294 m.clientShutdownCalled.Store(true) 295 return &frontendv1pb.NotifyClientShutdownResponse{}, nil 296 } 297 298 func (m *mockGrpcServiceHandler) NotifyQuerierShutdown(_ context.Context, _ *schedulerpb.NotifyQuerierShutdownRequest) (*schedulerpb.NotifyQuerierShutdownResponse, error) { 299 m.querierShutdownCalled.Store(true) 300 return &schedulerpb.NotifyQuerierShutdownResponse{}, nil 301 } 302 303 func (m *mockGrpcServiceHandler) Process(_ frontendv1pb.Frontend_ProcessServer) error { 304 panic("implement me") 305 } 306 307 func (m *mockGrpcServiceHandler) QuerierLoop(_ schedulerpb.SchedulerForQuerier_QuerierLoopServer) error { 308 panic("implement me") 309 } 310 311 func prepareGlobalMetricsRegistry(t *testing.T) { 312 oldReg, oldGat := prometheus.DefaultRegisterer, prometheus.DefaultGatherer 313 314 reg := prometheus.NewRegistry() 315 prometheus.DefaultRegisterer, prometheus.DefaultGatherer = reg, reg 316 317 t.Cleanup(func() { 318 prometheus.DefaultRegisterer, prometheus.DefaultGatherer = oldReg, oldGat 319 }) 320 }