Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/juno: add --instance flag #1905

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion cmd/juno/juno.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ const (
cnUnverifiableRangeF = "cn-unverifiable-range"
callMaxStepsF = "rpc-call-max-steps"
corsEnableF = "rpc-cors-enable"
instanceF = "instance"

defaultConfig = ""
defaulHost = "localhost"
Expand Down Expand Up @@ -117,6 +118,8 @@ const (
defaultCallMaxSteps = 4_000_000
defaultGwTimeout = 5 * time.Second
defaultCorsEnable = false
defaultInstance = 0
defaultInstanceInc = 10

configFlagUsage = "The YAML configuration file."
logLevelFlagUsage = "Options: trace, debug, info, warn, error."
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]",
Expand Down Expand Up @@ -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
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
}

Expand Down Expand Up @@ -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())

Expand Down
162 changes: 162 additions & 0 deletions cmd/juno/juno_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func TestConfigPrecedence(t *testing.T) {
defaultMaxHandles := 1024
defaultCallMaxSteps := uint(4_000_000)
defaultGwTimeout := 5 * time.Second
defaultInstance := uint16(0)

tests := map[string]struct {
cfgFile bool
Expand Down Expand Up @@ -111,6 +112,7 @@ func TestConfigPrecedence(t *testing.T) {
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"custom network config file": {
Expand Down Expand Up @@ -156,6 +158,7 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"default config with no flags": {
Expand Down Expand Up @@ -188,6 +191,7 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"config file path is empty string": {
Expand Down Expand Up @@ -220,6 +224,7 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"config file doesn't exist": {
Expand Down Expand Up @@ -257,6 +262,7 @@ cn-unverifiable-range: [0,10]
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"config file with all settings but without any other flags": {
Expand Down Expand Up @@ -296,6 +302,7 @@ pprof: true
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"config file with some settings but without any other flags": {
Expand Down Expand Up @@ -332,6 +339,7 @@ http-port: 4576
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"all flags without config file": {
Expand Down Expand Up @@ -367,6 +375,7 @@ http-port: 4576
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
PendingPollInterval: defaultPendingPollInterval,
Instance: defaultInstance,
},
},
"some flags without config file": {
Expand Down Expand Up @@ -402,6 +411,7 @@ http-port: 4576
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"all setting set in both config file and flags": {
Expand Down Expand Up @@ -461,6 +471,7 @@ db-cache-size: 8
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"some setting set in both config file and flags": {
Expand Down Expand Up @@ -499,6 +510,7 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"some setting set in default, config file and flags": {
Expand Down Expand Up @@ -533,6 +545,7 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"only set env variables": {
Expand Down Expand Up @@ -565,6 +578,7 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"some setting set in both env variables and flags": {
Expand Down Expand Up @@ -598,6 +612,7 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
"some setting set in both env variables and config file": {
Expand Down Expand Up @@ -632,6 +647,7 @@ network: sepolia
DBMaxHandles: defaultMaxHandles,
RPCCallMaxSteps: defaultCallMaxSteps,
GatewayTimeout: defaultGwTimeout,
Instance: defaultInstance,
},
},
}
Expand Down Expand Up @@ -680,6 +696,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", "0"},
expectedPorts: &ports{
HTTPPort: 6060,
WebsocketPort: 6061,
GRPCPort: 6064,
MetricsPort: 9090,
PprofPort: 6062,
},
},
"instance 1 on flag": {
inputArgs: []string{"--instance", "1"},
expectedPorts: &ports{
HTTPPort: 6070,
WebsocketPort: 6071,
GRPCPort: 6074,
MetricsPort: 9100,
PprofPort: 6072,
},
},
"instance 1 on config": {
cfgFile: true,
cfgFileContents: `instance: 1`,
expectedPorts: &ports{
HTTPPort: 6070,
WebsocketPort: 6071,
GRPCPort: 6074,
MetricsPort: 9100,
PprofPort: 6072,
},
},
"instance 1 on env": {
env: []string{"JUNO_INSTANCE", "1"},
expectedPorts: &ports{
HTTPPort: 6070,
WebsocketPort: 6071,
GRPCPort: 6074,
MetricsPort: 9100,
PprofPort: 6072,
},
},
"instance 1 on flag with ports override": {
inputArgs: []string{"--instance", "1", "--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 1 on config with ports override": {
cfgFile: true,
cfgFileContents: `instance: 1
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 1 on env with ports override": {
env: []string{"JUNO_INSTANCE", "1", "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 1 with a mix of flags, config and env": {
cfgFile: true,
cfgFileContents: `grpc-port: 8084`,
inputArgs: []string{"--instance", "1", "--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()

Expand Down
1 change: 1 addition & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
Loading