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  }