From 93a0c1958b5090ae14228179bfff52beeaef3722 Mon Sep 17 00:00:00 2001 From: weiihann Date: Thu, 13 Jun 2024 14:26:32 +0800 Subject: [PATCH] cmd/juno: add --instance flag --- cmd/juno/juno.go | 33 +++++++++- cmd/juno/juno_test.go | 146 ++++++++++++++++++++++++++++++++++++++++++ node/node.go | 1 + 3 files changed, 179 insertions(+), 1 deletion(-) diff --git a/cmd/juno/juno.go b/cmd/juno/juno.go index 0c68968e90..cc73299b57 100644 --- a/cmd/juno/juno.go +++ b/cmd/juno/juno.go @@ -82,6 +82,7 @@ const ( cnUnverifiableRangeF = "cn-unverifiable-range" callMaxStepsF = "rpc-call-max-steps" corsEnableF = "rpc-cors-enable" + instanceF = "instance" defaultConfig = "" defaulHost = "localhost" @@ -117,6 +118,8 @@ const ( defaultCallMaxSteps = 4_000_000 defaultGwTimeout = 5 * time.Second defaultCorsEnable = false + defaultInstance = 1 + defaultInstanceInc = 10 configFlagUsage = "The YAML configuration file." logLevelFlagUsage = "Options: trace, debug, info, warn, error." @@ -166,6 +169,9 @@ const ( callMaxStepsUsage = "Maximum number of steps to be executed in starknet_call requests. " + "The upper limit is 4 million steps, and any higher value will still be capped at 4 million." corsEnableUsage = "Enable CORS on RPC endpoints" + instanceUsage = "Configures the ports to avoid conflicts. Useful for running multiple instances on the same machine." + + " Changes to the following port numbers: - `grpc-port`, `http-port`, `metrics-port`, `pprof-port`, `ws-port`." + + " Each instance count increments the default value of respective ports by 10." ) var Version string @@ -211,7 +217,7 @@ func main() { // 3. The config struct is populated. // 4. Cobra calls the run function. // -//nolint:funlen +//nolint:funlen, gocyclo func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobra.Command { junoCmd := &cobra.Command{ Use: "juno [flags]", @@ -280,6 +286,30 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr } } + // Configure ports for multiple instances + if config.Instance > defaultInstance { + inc := defaultInstanceInc * (config.Instance - defaultInstance) + if !v.IsSet(httpPortF) { + config.HTTPPort = defaultHTTPPort + inc + } + + if !v.IsSet(wsPortF) { + config.WebsocketPort = defaultWSPort + inc + } + + if !v.IsSet(grpcPortF) { + config.GRPCPort = defaultGRPCPort + inc + } + + if !v.IsSet(metricsPortF) { + config.MetricsPort = defaultMetricsPort + inc + } + + if !v.IsSet(pprofPortF) { + config.PprofPort = defaultPprofPort + inc + } + } + return nil } @@ -345,6 +375,7 @@ func NewCmd(config *node.Config, run func(*cobra.Command, []string) error) *cobr junoCmd.Flags().Uint(callMaxStepsF, defaultCallMaxSteps, callMaxStepsUsage) junoCmd.Flags().Duration(gwTimeoutF, defaultGwTimeout, gwTimeoutUsage) junoCmd.Flags().Bool(corsEnableF, defaultCorsEnable, corsEnableUsage) + junoCmd.Flags().Uint(instanceF, defaultInstance, instanceUsage) junoCmd.MarkFlagsMutuallyExclusive(p2pFeederNodeF, p2pPeersF) junoCmd.AddCommand(GenP2PKeyPair()) diff --git a/cmd/juno/juno_test.go b/cmd/juno/juno_test.go index a677dc9007..59e1fed56c 100644 --- a/cmd/juno/juno_test.go +++ b/cmd/juno/juno_test.go @@ -680,6 +680,152 @@ func TestGenP2PKeyPair(t *testing.T) { require.NoError(t, cmd.Execute()) } +func TestInstance(t *testing.T) { + type ports struct { + HTTPPort uint16 + WebsocketPort uint16 + GRPCPort uint16 + MetricsPort uint16 + PprofPort uint16 + } + + tests := map[string]struct { + cfgFile bool + cfgFileContents string + env []string + inputArgs []string + expectedPorts *ports + }{ + "default instance on flag": { + inputArgs: []string{"--instance", "1"}, + expectedPorts: &ports{ + HTTPPort: 6060, + WebsocketPort: 6061, + GRPCPort: 6064, + MetricsPort: 9090, + PprofPort: 6062, + }, + }, + "instance 2 on flag": { + inputArgs: []string{"--instance", "2"}, + expectedPorts: &ports{ + HTTPPort: 6070, + WebsocketPort: 6071, + GRPCPort: 6074, + MetricsPort: 9100, + PprofPort: 6072, + }, + }, + "instance 2 on config": { + cfgFile: true, + cfgFileContents: `instance: 2`, + expectedPorts: &ports{ + HTTPPort: 6070, + WebsocketPort: 6071, + GRPCPort: 6074, + MetricsPort: 9100, + PprofPort: 6072, + }, + }, + "instance 2 on env": { + env: []string{"JUNO_INSTANCE", "2"}, + expectedPorts: &ports{ + HTTPPort: 6070, + WebsocketPort: 6071, + GRPCPort: 6074, + MetricsPort: 9100, + PprofPort: 6072, + }, + }, + "instance 2 on flag with ports override": { + inputArgs: []string{"--instance", "2", "--http-port", "8080", "--ws-port", "8081", "--grpc-port", "8084", "--metrics-port", "10000", "--pprof-port", "8082"}, + expectedPorts: &ports{ + HTTPPort: 8080, + WebsocketPort: 8081, + GRPCPort: 8084, + MetricsPort: 10000, + PprofPort: 8082, + }, + }, + "instance 2 on config with ports override": { + cfgFile: true, + cfgFileContents: `instance: 2 +http-port: 8080 +ws-port: 8081 +grpc-port: 8084 +metrics-port: 10000 +pprof-port: 8082`, + expectedPorts: &ports{ + HTTPPort: 8080, + WebsocketPort: 8081, + GRPCPort: 8084, + MetricsPort: 10000, + PprofPort: 8082, + }, + }, + "instance 2 on env with ports override": { + env: []string{"JUNO_INSTANCE", "2", "JUNO_HTTP_PORT", "8080", "JUNO_WS_PORT", "8081", "JUNO_GRPC_PORT", "8084", "JUNO_METRICS_PORT", "10000", "JUNO_PPROF_PORT", "8082"}, + expectedPorts: &ports{ + HTTPPort: 8080, + WebsocketPort: 8081, + GRPCPort: 8084, + MetricsPort: 10000, + PprofPort: 8082, + }, + }, + "instance 2 with a mix of flags, config and env": { + cfgFile: true, + cfgFileContents: `grpc-port: 8084`, + inputArgs: []string{"--instance", "2", "--http-port", "8083"}, + env: []string{"JUNO_INSTANCE", "2", "JUNO_WS_PORT", "8088"}, + expectedPorts: &ports{ + HTTPPort: 8083, + WebsocketPort: 8088, + GRPCPort: 8084, + MetricsPort: 9100, + PprofPort: 6072, + }, + }, + } + + junoEnv := unsetJunoPrefixedEnv(t) + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if tc.cfgFile { + fileN := tempCfgFile(t, tc.cfgFileContents) + tc.inputArgs = append(tc.inputArgs, "--config", fileN) + } + + require.True(t, len(tc.env)%2 == 0, "The number of env variables should be an even number") + + if len(tc.env) > 0 { + for i := 0; i < len(tc.env)/2; i++ { + require.NoError(t, os.Setenv(tc.env[2*i], tc.env[2*i+1])) + } + } + + config := new(node.Config) + cmd := juno.NewCmd(config, func(_ *cobra.Command, _ []string) error { return nil }) + cmd.SetArgs(tc.inputArgs) + require.NoError(t, cmd.Execute()) + + assert.Equal(t, tc.expectedPorts.HTTPPort, config.HTTPPort) + assert.Equal(t, tc.expectedPorts.WebsocketPort, config.WebsocketPort) + assert.Equal(t, tc.expectedPorts.GRPCPort, config.GRPCPort) + assert.Equal(t, tc.expectedPorts.MetricsPort, config.MetricsPort) + assert.Equal(t, tc.expectedPorts.PprofPort, config.PprofPort) + + if len(tc.env) > 0 { + for i := 0; i < len(tc.env)/2; i++ { + require.NoError(t, os.Unsetenv(tc.env[2*i])) + } + } + }) + } + setJunoPrefixedEnv(t, junoEnv) +} + func tempCfgFile(t *testing.T, cfg string) string { t.Helper() diff --git a/node/node.go b/node/node.go index de880617cf..c77cb559c5 100644 --- a/node/node.go +++ b/node/node.go @@ -64,6 +64,7 @@ type Config struct { Colour bool `mapstructure:"colour"` PendingPollInterval time.Duration `mapstructure:"pending-poll-interval"` RemoteDB string `mapstructure:"remote-db"` + Instance uint16 `mapstructure:"instance"` Metrics bool `mapstructure:"metrics"` MetricsHost string `mapstructure:"metrics-host"`