Skip to content

Commit

Permalink
tmpnet: Separate node into orchestration, config and process
Browse files Browse the repository at this point in the history
This refactor is intended to improve maintainability by separating
node into coherent constituent parts and minimizing the exported API.
  • Loading branch information
marun committed Dec 11, 2023
1 parent e446b6a commit 05a7ca2
Show file tree
Hide file tree
Showing 14 changed files with 544 additions and 352 deletions.
2 changes: 1 addition & 1 deletion tests/e2e/faultinjection/duplicate_node_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var _ = ginkgo.Describe("Duplicate node handling", func() {
require.ErrorIs(err, context.DeadlineExceeded)

ginkgo.By("stopping the first new node")
require.NoError(node1.Stop())
require.NoError(node1.Stop(e2e.DefaultContext()))

ginkgo.By("checking that the second new node becomes healthy within timeout")
e2e.WaitForHealthy(node2)
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/p/interchain_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ var _ = e2e.DescribePChain("[Interchain Workflow]", ginkgo.Label(e2e.UsesCChainL
require.Positive(balance.Cmp(big.NewInt(0)))

ginkgo.By("stopping validator node to free up resources for a bootstrap check")
require.NoError(node.Stop())
require.NoError(node.Stop(e2e.DefaultContext()))

e2e.CheckBootstrapIsPossible(network)
})
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/p/staking_rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() {
})

ginkgo.By("stopping beta node to prevent it and its delegator from receiving a validation reward")
require.NoError(betaNode.Stop())
require.NoError(betaNode.Stop(e2e.DefaultContext()))

ginkgo.By("waiting until all validation periods are over")
// The beta validator was the last added and so has the latest end time. The
Expand Down Expand Up @@ -297,7 +297,7 @@ var _ = ginkgo.Describe("[Staking Rewards]", func() {
}

ginkgo.By("stopping alpha to free up resources for a bootstrap check")
require.NoError(alphaNode.Stop())
require.NoError(alphaNode.Stop(e2e.DefaultContext()))

e2e.CheckBootstrapIsPossible(network)
})
Expand Down
2 changes: 1 addition & 1 deletion tests/fixture/e2e/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,5 @@ func (te *TestEnvironment) NewPrivateNetwork() *tmpnet.Network {
privateNetworksDir := filepath.Join(sharedNetwork.Dir, PrivateNetworksDirName)
te.require.NoError(os.MkdirAll(privateNetworksDir, perms.ReadWriteExecute))

return StartNetwork(sharedNetwork.ExecPath, privateNetworksDir)
return StartNetwork(sharedNetwork.AvalancheGoPath, privateNetworksDir)
}
38 changes: 13 additions & 25 deletions tests/fixture/e2e/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,21 @@ func Eventually(condition func() bool, waitFor time.Duration, tick time.Duration
}
}

// Add an ephemeral node that is only intended to be used by a single test. Its ID and
// URI are not intended to be returned from the Network instance to minimize
// accessibility from other tests.
// Adds an ephemeral node intended to be used by a single test.
func AddEphemeralNode(network *tmpnet.Network, flags tmpnet.FlagsMap) *tmpnet.Node {
require := require.New(ginkgo.GinkgoT())

node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, flags)
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
node, err := network.AddEphemeralNode(ctx, ginkgo.GinkgoWriter, flags)
require.NoError(err)

// Ensure node is stopped on teardown. It's configuration is not removed to enable
// collection in CI to aid in troubleshooting failures.
ginkgo.DeferCleanup(func() {
tests.Outf("Shutting down ephemeral node %s\n", node.ID)
require.NoError(node.Stop())
tests.Outf("shutting down ephemeral node %q\n", node.ID)
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
require.NoError(node.Stop(ctx))
})

return node
}

Expand Down Expand Up @@ -197,26 +196,13 @@ func WithSuggestedGasPrice(ethClient ethclient.Client) common.Option {

// Verify that a new node can bootstrap into the network.
func CheckBootstrapIsPossible(network *tmpnet.Network) {
require := require.New(ginkgo.GinkgoT())

if len(os.Getenv(SkipBootstrapChecksEnvName)) > 0 {
tests.Outf("{{yellow}}Skipping bootstrap check due to the %s env var being set", SkipBootstrapChecksEnvName)
return
}
ginkgo.By("checking if bootstrap is possible with the current network state")

// Call network.AddEphemeralNode instead of AddEphemeralNode to support
// checking for bootstrap implicitly on teardown via a function registered
// with ginkgo.DeferCleanup. It's not possible to call DeferCleanup from
// within a function called by DeferCleanup.
node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, tmpnet.FlagsMap{})
require.NoError(err)

defer func() {
tests.Outf("Shutting down ephemeral node %s\n", node.ID)
require.NoError(node.Stop())
}()

node := AddEphemeralNode(network, tmpnet.FlagsMap{})
WaitForHealthy(node)
}

Expand All @@ -230,7 +216,7 @@ func StartNetwork(avalancheGoExecPath string, networkDir string) *tmpnet.Network
networkDir,
&tmpnet.Network{
NodeRuntimeConfig: tmpnet.NodeRuntimeConfig{
ExecPath: avalancheGoExecPath,
AvalancheGoPath: avalancheGoExecPath,
},
},
tmpnet.DefaultNodeCount,
Expand All @@ -239,7 +225,9 @@ func StartNetwork(avalancheGoExecPath string, networkDir string) *tmpnet.Network
require.NoError(err)
ginkgo.DeferCleanup(func() {
tests.Outf("Shutting down network\n")
require.NoError(network.Stop())
ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancel()
require.NoError(network.Stop(ctx))
})

tests.Outf("{{green}}Successfully started network{{/}}\n")
Expand Down
24 changes: 15 additions & 9 deletions tests/fixture/tmpnet/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# tmpnet (temporary network fixture)
# tmpnet - temporary network orchestration

This package implements a simple orchestrator for the avalanchego
nodes of a temporary network. Configuration is stored on disk, and
Expand Down Expand Up @@ -31,6 +31,8 @@ the following non-test files:
| genesis.go | | Creates test genesis |
| network.go | Network | Orchestrates and configures temporary networks |
| node.go | Node | Orchestrates and configures nodes |
| node_config.go | Node | Reads and writes node configuration |
| node_process.go | NodeProcess | Orchestrates node processes |
| utils.go | | Defines shared utility functions |

## Usage
Expand Down Expand Up @@ -147,9 +149,10 @@ HOME
├── NodeID-37E8UK3x2YFsHE3RdALmfWcppcZ1eTuj9 // The ID of a node is the name of its data dir
│ ├── chainData
│ │ └── ...
│ ├── config.json // Node flags
│ ├── config.json // Node runtime configuration
│ ├── db
│ │ └── ...
│ ├── flags.json // Node flags
│ ├── logs
│ │ └── ...
│ ├── plugins
Expand All @@ -160,11 +163,7 @@ HOME
│ └── config.json // C-Chain config for all nodes
├── defaults.json // Default flags and configuration for network
├── genesis.json // Genesis for all nodes
├── network.env // Sets network dir env to simplify use of network
└── ephemeral // Parent directory for ephemeral nodes (e.g. created by tests)
└─ NodeID-FdxnAvr4jK9XXAwsYZPgWAHW2QnwSZ // Data dir for an ephemeral node
└── ...
└── network.env // Sets network dir env var to simplify network usage
```

### Default flags and configuration
Expand Down Expand Up @@ -210,12 +209,19 @@ The data dir for a node is set by default to
non-default path by explicitly setting the `--data-dir`
flag.

#### Runtime config

The details required to configure a node's execution are written to
`[network-path]/[node-id]/config.json`. This file contains the
runtime-specific details like the path of the avalanchego binary to
start the node with.

#### Flags

All flags used to configure a node are written to
`[network-path]/[node-id]/config.json` so that a node can be
`[network-path]/[node-id]/flags.json` so that a node can be
configured with only a single argument:
`--config-file=/path/to/config.json`. This simplifies node launch and
`--config-file=/path/to/flags.json`. This simplifies node launch and
ensures all parameters used to launch a node can be modified by
editing the config file.

Expand Down
6 changes: 4 additions & 2 deletions tests/fixture/tmpnet/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func main() {
ExecPath: execPath,

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / Static analysis

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig (typecheck)

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / test_e2e_existing

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / unit_tests (self-hosted, linux, ARM64, focal)

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig

Check failure on line 65 in tests/fixture/tmpnet/cmd/main.go

View workflow job for this annotation

GitHub Actions / unit_tests (self-hosted, linux, ARM64, jammy)

unknown field ExecPath in struct literal of type tmpnet.NodeRuntimeConfig
},
}
ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkStartTimeout)
ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout)
defer cancel()
network, err := tmpnet.StartNetwork(ctx, os.Stdout, rootDir, network, int(nodeCount), int(preFundedKeyCount))
if err != nil {
Expand Down Expand Up @@ -105,7 +105,9 @@ func main() {
if len(networkDir) == 0 {
return errNetworkDirRequired
}
if err := tmpnet.StopNetwork(networkDir); err != nil {
ctx, cancel := context.WithTimeout(context.Background(), tmpnet.DefaultNetworkTimeout)
defer cancel()
if err := tmpnet.StopNetwork(ctx, networkDir); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Stopped network configured at: %s\n", networkDir)
Expand Down
13 changes: 7 additions & 6 deletions tests/fixture/tmpnet/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import (
const (
// Constants defining the names of shell variables whose value can
// configure temporary network orchestration.
AvalancheGoPathEnvName = "AVALANCHEGO_PATH"
NetworkDirEnvName = "TMPNET_NETWORK_DIR"
RootDirEnvName = "TMPNET_ROOT_DIR"
NetworkDirEnvName = "TMPNET_NETWORK_DIR"
RootDirEnvName = "TMPNET_ROOT_DIR"

DefaultNetworkStartTimeout = 2 * time.Minute
DefaultNodeInitTimeout = 10 * time.Second
DefaultNodeStopTimeout = 5 * time.Second
DefaultNetworkTimeout = 2 * time.Minute
DefaultNodeInitTimeout = 10 * time.Second
DefaultNodeStopTimeout = 5 * time.Second

// Minimum required to ensure connectivity-based health checks will pass
DefaultNodeCount = 2
Expand All @@ -28,6 +27,8 @@ const (

// A short minimum stake duration enables testing of staking logic.
DefaultMinStakeDuration = time.Second

defaultConfigFilename = "config.json"
)

// A set of flags appropriate for testing.
Expand Down
51 changes: 29 additions & 22 deletions tests/fixture/tmpnet/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ type Network struct {
}

// Adds a backend-agnostic ephemeral node to the network
func (n *Network) AddEphemeralNode(w io.Writer, flags FlagsMap) (*Node, error) {
func (n *Network) AddEphemeralNode(ctx context.Context, w io.Writer, flags FlagsMap) (*Node, error) {
if flags == nil {
flags = FlagsMap{}
}
return n.AddNode(w, &Node{
return n.AddNode(ctx, w, &Node{
Flags: flags,
}, true /* isEphemeral */)
}
Expand All @@ -116,7 +116,7 @@ func StartNetwork(
nodeCount int,
keyCount int,
) (*Network, error) {
if _, err := fmt.Fprintf(w, "Preparing configuration for new temporary network with %s\n", network.ExecPath); err != nil {
if _, err := fmt.Fprintf(w, "Preparing configuration for new temporary network with %s\n", network.AvalancheGoPath); err != nil {
return nil, err
}

Expand Down Expand Up @@ -198,12 +198,12 @@ func ReadNetwork(dir string) (*Network, error) {
}

// Stop the nodes of the network configured in the provided directory.
func StopNetwork(dir string) error {
func StopNetwork(ctx context.Context, dir string) error {
network, err := ReadNetwork(dir)
if err != nil {
return err
}
return network.Stop()
return network.Stop(ctx)
}

// Ensure the network has the configuration it needs to start.
Expand Down Expand Up @@ -291,8 +291,15 @@ func (n *Network) PopulateNodeConfig(node *Node, nodeParentDir string) error {
return err
}

// Ensure the node is configured with a runtime config
if node.RuntimeConfig == nil {
node.RuntimeConfig = &NodeRuntimeConfig{
AvalancheGoPath: n.AvalancheGoPath,
}
}

// Ensure the node's data dir is configured
dataDir := node.GetDataDir()
dataDir := node.getDataDir()
if len(dataDir) == 0 {
// NodeID will have been set by EnsureKeys
dataDir = filepath.Join(nodeParentDir, node.ID.String())
Expand Down Expand Up @@ -331,7 +338,7 @@ func (n *Network) Start(w io.Writer) error {
node.SetNetworkingConfig(0, 0, bootstrapIDs, bootstrapIPs)

// Write configuration to disk in preparation for node start
if err := node.WriteConfig(); err != nil {
if err := node.Write(); err != nil {
return err
}

Expand All @@ -340,7 +347,7 @@ func (n *Network) Start(w io.Writer) error {
// its staking port. The network will start faster with this
// synchronization due to the avoidance of exponential backoff
// if a node tries to connect to a beacon that is not ready.
if err := node.Start(w, n.ExecPath); err != nil {
if err := node.Start(w); err != nil {
return err
}

Expand Down Expand Up @@ -406,11 +413,11 @@ func (n *Network) GetURIs() []NodeURI {
}

// Stop all nodes in the network.
func (n *Network) Stop() error {
func (n *Network) Stop(ctx context.Context) error {
var errs []error
// Assume the nodes are loaded and the pids are current
for _, node := range n.Nodes {
if err := node.Stop(); err != nil {
if err := node.Stop(ctx); err != nil {
errs = append(errs, fmt.Errorf("failed to stop node %s: %w", node.ID, err))
}
}
Expand Down Expand Up @@ -476,9 +483,9 @@ func (n *Network) WriteCChainConfig() error {

// Used to marshal/unmarshal persistent network defaults.
type networkDefaults struct {
Flags FlagsMap
ExecPath string
PreFundedKeys []*secp256k1.PrivateKey
Flags FlagsMap
AvalancheGoPath string
PreFundedKeys []*secp256k1.PrivateKey
}

func (n *Network) GetDefaultsPath() string {
Expand All @@ -495,16 +502,16 @@ func (n *Network) ReadDefaults() error {
return fmt.Errorf("failed to unmarshal defaults: %w", err)
}
n.DefaultFlags = defaults.Flags
n.ExecPath = defaults.ExecPath
n.AvalancheGoPath = defaults.AvalancheGoPath
n.PreFundedKeys = defaults.PreFundedKeys
return nil
}

func (n *Network) WriteDefaults() error {
defaults := networkDefaults{
Flags: n.DefaultFlags,
ExecPath: n.ExecPath,
PreFundedKeys: n.PreFundedKeys,
Flags: n.DefaultFlags,
AvalancheGoPath: n.AvalancheGoPath,
PreFundedKeys: n.PreFundedKeys,
}
bytes, err := DefaultJSONMarshal(defaults)
if err != nil {
Expand Down Expand Up @@ -534,7 +541,7 @@ func (n *Network) WriteEnvFile() error {

func (n *Network) WriteNodes() error {
for _, node := range n.Nodes {
if err := node.WriteConfig(); err != nil {
if err := node.Write(); err != nil {
return err
}
}
Expand Down Expand Up @@ -611,7 +618,7 @@ func (n *Network) ReadAll() error {
return n.ReadNodes()
}

func (n *Network) AddNode(w io.Writer, node *Node, isEphemeral bool) (*Node, error) {
func (n *Network) AddNode(ctx context.Context, w io.Writer, node *Node, isEphemeral bool) (*Node, error) {
// Assume network configuration has been written to disk and is current in memory

if node == nil {
Expand Down Expand Up @@ -650,15 +657,15 @@ func (n *Network) AddNode(w io.Writer, node *Node, isEphemeral bool) (*Node, err
)
node.SetNetworkingConfig(httpPort, stakingPort, bootstrapIDs, bootstrapIPs)

if err := node.WriteConfig(); err != nil {
if err := node.Write(); err != nil {
return nil, err
}

err = node.Start(w, n.ExecPath)
err = node.Start(w)
if err != nil {
// Attempt to stop an unhealthy node to provide some assurance to the caller
// that an error condition will not result in a lingering process.
stopErr := node.Stop()
stopErr := node.Stop(ctx)
if stopErr != nil {
err = errors.Join(err, stopErr)
}
Expand Down
Loading

0 comments on commit 05a7ca2

Please sign in to comment.