Skip to content

Commit

Permalink
tmpnet: Add support for subnets
Browse files Browse the repository at this point in the history
  • Loading branch information
maru-ava committed Dec 23, 2023
1 parent 6939831 commit 6b7231a
Show file tree
Hide file tree
Showing 14 changed files with 719 additions and 74 deletions.
3 changes: 2 additions & 1 deletion tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/onsi/gomega"

"github.com/ava-labs/avalanchego/tests/fixture/e2e"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"

// ensure test packages are scanned by ginkgo
_ "github.com/ava-labs/avalanchego/tests/e2e/banff"
Expand All @@ -35,7 +36,7 @@ func init() {

var _ = ginkgo.SynchronizedBeforeSuite(func() []byte {
// Run only once in the first ginkgo process
return e2e.NewTestEnvironment(flagVars).Marshal()
return e2e.NewTestEnvironment(flagVars, &tmpnet.Network{}).Marshal()
}, func(envBytes []byte) {
// Run in every ginkgo process

Expand Down
40 changes: 34 additions & 6 deletions tests/fixture/e2e/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/ava-labs/avalanchego/config"
"github.com/ava-labs/avalanchego/tests"
"github.com/ava-labs/avalanchego/tests/fixture"
"github.com/ava-labs/avalanchego/tests/fixture/tmpnet"
Expand All @@ -29,10 +30,9 @@ var Env *TestEnvironment
func InitSharedTestEnvironment(envBytes []byte) {
require := require.New(ginkgo.GinkgoT())
require.Nil(Env, "env already initialized")
Env = &TestEnvironment{
require: require,
}
Env = &TestEnvironment{}
require.NoError(json.Unmarshal(envBytes, Env))
Env.require = require
}

type TestEnvironment struct {
Expand All @@ -53,7 +53,7 @@ func (te *TestEnvironment) Marshal() []byte {
}

// Initialize a new test environment with a shared network (either pre-existing or newly created).
func NewTestEnvironment(flagVars *FlagVars) *TestEnvironment {
func NewTestEnvironment(flagVars *FlagVars, desiredNetwork *tmpnet.Network) *TestEnvironment {
require := require.New(ginkgo.GinkgoT())

networkDir := flagVars.NetworkDir()
Expand All @@ -65,10 +65,25 @@ func NewTestEnvironment(flagVars *FlagVars) *TestEnvironment {
network, err = tmpnet.ReadNetwork(networkDir)
require.NoError(err)
tests.Outf("{{yellow}}Using an existing network configured at %s{{/}}\n", network.Dir)

// Set the desired subnet configuration to ensure subsequent creation.
for _, subnet := range desiredNetwork.Subnets {
if existing := network.GetSubnet(subnet.Name); existing != nil {
// Already present
continue
}
network.Subnets = append(network.Subnets, subnet)
}
} else {
network = StartNetwork(flagVars.AvalancheGoExecPath(), DefaultNetworkDir)
network = desiredNetwork
StartNetwork(network, DefaultNetworkDir, flagVars.AvalancheGoExecPath(), flagVars.PluginDir())
}

// A new network will always need subnet creation and an existing
// network will also need subnets to be created the first time it
// is used.
require.NoError(network.CreateSubnets(DefaultContext(), ginkgo.GinkgoWriter))

uris := network.GetNodeURIs()
require.NotEmpty(uris, "network contains no nodes")
tests.Outf("{{green}}network URIs: {{/}} %+v\n", uris)
Expand All @@ -83,6 +98,7 @@ func NewTestEnvironment(flagVars *FlagVars) *TestEnvironment {
NetworkDir: network.Dir,
URIs: uris,
TestDataServerURI: testDataServerURI,
require: require,
}
}

Expand Down Expand Up @@ -127,10 +143,22 @@ func (te *TestEnvironment) NewPrivateNetwork() *tmpnet.Network {
sharedNetwork, err := tmpnet.ReadNetwork(te.NetworkDir)
te.require.NoError(err)

network := &tmpnet.Network{}

// The private networks dir is under the shared network dir to ensure it
// will be included in the artifact uploaded in CI.
privateNetworksDir := filepath.Join(sharedNetwork.Dir, PrivateNetworksDirName)
te.require.NoError(os.MkdirAll(privateNetworksDir, perms.ReadWriteExecute))

return StartNetwork(sharedNetwork.DefaultRuntimeConfig.AvalancheGoPath, privateNetworksDir)
pluginDir, err := sharedNetwork.DefaultFlags.GetStringVal(config.PluginDirKey)
te.require.NoError(err)

StartNetwork(
network,
privateNetworksDir,
sharedNetwork.DefaultRuntimeConfig.AvalancheGoPath,
pluginDir,
)

return network
}
19 changes: 15 additions & 4 deletions tests/fixture/e2e/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ import (

type FlagVars struct {
avalancheGoExecPath string
pluginDir string
networkDir string
useExistingNetwork bool
}

func (v *FlagVars) AvalancheGoExecPath() string {
return v.avalancheGoExecPath
}

func (v *FlagVars) PluginDir() string {
return v.pluginDir
}

func (v *FlagVars) NetworkDir() string {
if !v.useExistingNetwork {
return ""
Expand All @@ -27,10 +36,6 @@ func (v *FlagVars) NetworkDir() string {
return os.Getenv(tmpnet.NetworkDirEnvName)
}

func (v *FlagVars) AvalancheGoExecPath() string {
return v.avalancheGoExecPath
}

func (v *FlagVars) UseExistingNetwork() bool {
return v.useExistingNetwork
}
Expand All @@ -43,6 +48,12 @@ func RegisterFlags() *FlagVars {
os.Getenv(tmpnet.AvalancheGoPathEnvName),
fmt.Sprintf("avalanchego executable path (required if not using an existing network). Also possible to configure via the %s env variable.", tmpnet.AvalancheGoPathEnvName),
)
flag.StringVar(
&vars.pluginDir,
"plugin-dir",
os.ExpandEnv("$HOME/.avalanchego/plugins"),
"[optional] the dir containing VM plugins.",
)
flag.StringVar(
&vars.networkDir,
"network-dir",
Expand Down
25 changes: 15 additions & 10 deletions tests/fixture/e2e/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ const (
// contention.
DefaultTimeout = 2 * time.Minute

// Interval appropriate for network operations that should be
// retried periodically but not too often.
DefaultPollingInterval = 500 * time.Millisecond
DefaultPollingInterval = tmpnet.DefaultPollingInterval

// Setting this env will disable post-test bootstrap
// checks. Useful for speeding up iteration during test
// development.
SkipBootstrapChecksEnvName = "E2E_SKIP_BOOTSTRAP_CHECKS"

DefaultValidatorStartTimeDiff = tmpnet.DefaultValidatorStartTimeDiff

DefaultGasLimit = uint64(21000) // Standard gas limit

// An empty string prompts the use of the default path which ensures a
Expand Down Expand Up @@ -201,13 +201,20 @@ func CheckBootstrapIsPossible(network *tmpnet.Network) {
}

// Start a temporary network with the provided avalanchego binary.
func StartNetwork(avalancheGoExecPath string, rootNetworkDir string) *tmpnet.Network {
func StartNetwork(network *tmpnet.Network, rootNetworkDir string, avalancheGoExecPath string, pluginDir string) {
require := require.New(ginkgo.GinkgoT())

network, err := tmpnet.NewDefaultNetwork(ginkgo.GinkgoWriter, avalancheGoExecPath, tmpnet.DefaultNodeCount)
require.NoError(err)
require.NoError(network.Create(rootNetworkDir))
require.NoError(network.Start(DefaultContext(), ginkgo.GinkgoWriter))
require.NoError(
tmpnet.StartNewNetwork(
DefaultContext(),
ginkgo.GinkgoWriter,
network,
rootNetworkDir,
avalancheGoExecPath,
pluginDir,
tmpnet.DefaultNodeCount,
),
)

ginkgo.DeferCleanup(func() {
tests.Outf("Shutting down network\n")
Expand All @@ -217,6 +224,4 @@ func StartNetwork(avalancheGoExecPath string, rootNetworkDir string) *tmpnet.Net
})

tests.Outf("{{green}}Successfully started network{{/}}\n")

return network
}
16 changes: 13 additions & 3 deletions tests/fixture/tmpnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ the following non-test files:
| node.go | Node | Orchestrates and configures nodes |
| node_config.go | Node | Reads and writes node configuration |
| node_process.go | NodeProcess | Orchestrates node processes |
| subnet.go | Subnet | Orchestrates subnets |
| utils.go | | Defines shared utility functions |

## Usage
Expand Down Expand Up @@ -103,6 +104,10 @@ avalanchego on node start. The use of dynamic ports supports testing
with many temporary networks without having to manually select compatible
port ranges.

## Subnet configuration

TODO(marun)

## Configuration on disk

A temporary network relies on configuration written to disk in the following structure:
Expand All @@ -125,11 +130,16 @@ HOME
│ │ └── ...
│ └── process.json // Node process details (PID, API URI, staking address)
├── chains
│ └── C
│ └── config.json // C-Chain config for all nodes
│ ├── C
│ │ └── config.json // C-Chain config for all nodes
│ └── raZ51bwfepaSaZ1MNSRNYNs3ZPfj...U7pa3
│ └── config.json // Custom chain configuration for all nodes
├── config.json // Common configuration (including defaults and pre-funded keys)
├── genesis.json // Genesis for all nodes
└── network.env // Sets network dir env var to simplify network usage
├── network.env // Sets network dir env var to simplify network usage
└── subnets // Parent directory for subnet definitions
├─ subnet-a.json // Configuration for subnet-a and its chain(s)
└─ subnet-b.json // Configuration for subnet-b and its chain(s)
```

### Common networking configuration
Expand Down
50 changes: 34 additions & 16 deletions tests/fixture/tmpnet/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ var (
)

func main() {
var networkDir string
rootCmd := &cobra.Command{
Use: "tmpnetctl",
Short: "tmpnetctl commands",
}
rootCmd.PersistentFlags().StringVar(&networkDir, "network-dir", os.Getenv(tmpnet.NetworkDirEnvName), "The path to the configuration directory of a temporary network")

versionCmd := &cobra.Command{
Use: "version",
Expand All @@ -46,35 +48,38 @@ func main() {
rootCmd.AddCommand(versionCmd)

var (
rootDir string
execPath string
nodeCount uint8
rootDir string
avalancheGoPath string
pluginDir string
nodeCount uint8
)
startNetworkCmd := &cobra.Command{
Use: "start-network",
Short: "Start a new temporary network",
RunE: func(*cobra.Command, []string) error {
if len(execPath) == 0 {
if len(avalancheGoPath) == 0 {
return errAvalancheGoRequired
}

// Root dir will be defaulted on start if not provided

network, err := tmpnet.NewDefaultNetwork(os.Stdout, execPath, int(nodeCount))
if err != nil {
return err
}

if err := network.Create(rootDir); err != nil {
return err
}
network := &tmpnet.Network{}

// Extreme upper bound, should never take this long
networkStartTimeout := 2 * time.Minute

ctx, cancel := context.WithTimeout(context.Background(), networkStartTimeout)
defer cancel()
if err := network.Start(ctx, os.Stdout); err != nil {
err := tmpnet.StartNewNetwork(
ctx,
os.Stdout,
network,
rootDir,
avalancheGoPath,
pluginDir,
int(nodeCount),
)
if err != nil {
return err
}

Expand All @@ -98,11 +103,11 @@ func main() {
},
}
startNetworkCmd.PersistentFlags().StringVar(&rootDir, "root-dir", os.Getenv(tmpnet.RootDirEnvName), "The path to the root directory for temporary networks")
startNetworkCmd.PersistentFlags().StringVar(&execPath, "avalanchego-path", os.Getenv(tmpnet.AvalancheGoPathEnvName), "The path to an avalanchego binary")
startNetworkCmd.PersistentFlags().StringVar(&avalancheGoPath, "avalanchego-path", os.Getenv(tmpnet.AvalancheGoPathEnvName), "The path to an avalanchego binary")
startNetworkCmd.PersistentFlags().StringVar(&pluginDir, "plugin-dir", os.ExpandEnv("$HOME/.avalanchego/plugins"), "[optional] the dir containing VM plugins")
startNetworkCmd.PersistentFlags().Uint8Var(&nodeCount, "node-count", tmpnet.DefaultNodeCount, "Number of nodes the network should initially consist of")
rootCmd.AddCommand(startNetworkCmd)

var networkDir string
stopNetworkCmd := &cobra.Command{
Use: "stop-network",
Short: "Stop a temporary network",
Expand All @@ -119,9 +124,22 @@ func main() {
return nil
},
}
stopNetworkCmd.PersistentFlags().StringVar(&networkDir, "network-dir", os.Getenv(tmpnet.NetworkDirEnvName), "The path to the configuration directory of a temporary network")
rootCmd.AddCommand(stopNetworkCmd)

restartNetworkCmd := &cobra.Command{
Use: "restart-network",
Short: "Restart a temporary network",
RunE: func(*cobra.Command, []string) error {
if len(networkDir) == 0 {
return errNetworkDirRequired
}
ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout)
defer cancel()
return tmpnet.RestartNetwork(ctx, os.Stdout, networkDir)
},
}
rootCmd.AddCommand(restartNetworkCmd)

if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "tmpnetctl failed: %v\n", err)
os.Exit(1)
Expand Down
13 changes: 12 additions & 1 deletion tests/fixture/tmpnet/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,19 @@ import (
"time"

"github.com/ava-labs/avalanchego/config"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
)

const (
// Interval appropriate for network operations that should be
// retried periodically but not too often.
DefaultPollingInterval = 500 * time.Millisecond

// Validator start time must be a minimum of SyncBound from the
// current time for validator addition to succeed, and adding 20
// seconds provides a buffer in case of any delay in processing.
DefaultValidatorStartTimeDiff = executor.SyncBound + 20*time.Second

DefaultNetworkTimeout = 2 * time.Minute

// Minimum required to ensure connectivity-based health checks will pass
Expand Down Expand Up @@ -50,7 +60,8 @@ func DefaultChainConfigs() map[string]FlagsMap {
// values will be used. Available C-Chain configuration options are
// defined in the `github.com/ava-labs/coreth/evm` package.
"C": {
"log-level": "trace",
"warp-api-enabled": true,
"log-level": "trace",
},
}
}
Loading

0 comments on commit 6b7231a

Please sign in to comment.