Skip to content

Commit 6abbd1a

Browse files
committed
fixup: Cleanup ephemeral node implementation
- rename 'temporary node' to 'ephemeral node' for clarity - ensure ephemeral node data is stored in the network path and not removed on teardown to allow archiving in CI to collect all data for a test run. - Remove `Node.Remove()` method since it is not being used
1 parent f9bade8 commit 6abbd1a

File tree

6 files changed

+76
-53
lines changed

6 files changed

+76
-53
lines changed

tests/e2e/e2e.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
"github.com/stretchr/testify/require"
1616

17-
"github.com/ava-labs/avalanchego/config"
1817
"github.com/ava-labs/avalanchego/tests"
1918
"github.com/ava-labs/avalanchego/tests/fixture"
2019
"github.com/ava-labs/avalanchego/tests/fixture/testnet"
@@ -117,23 +116,20 @@ func (te *TestEnvironment) NewWallet(keychain *secp256k1fx.Keychain) primary.Wal
117116
return wallet
118117
}
119118

120-
// Add a temporary node that is only intended to be used by a single test. Its ID and
119+
// Add an ephemeral node that is only intended to be used by a single test. Its ID and
121120
// URI are not intended to be returned from the Network instance to minimize
122121
// accessibility from other tests.
123-
func AddTemporaryNode(network testnet.Network, flags testnet.FlagsMap) testnet.Node {
122+
func AddEphemeralNode(network testnet.Network, flags testnet.FlagsMap) testnet.Node {
124123
require := require.New(ginkgo.GinkgoT())
125124

126-
// A temporary location ensures the node won't be accessible from
127-
// the Network instance.
128-
flags[config.DataDirKey] = ginkgo.GinkgoT().TempDir()
129-
130-
node, err := network.AddNode(ginkgo.GinkgoWriter, flags)
125+
node, err := network.AddEphemeralNode(ginkgo.GinkgoWriter, flags)
131126
require.NoError(err)
132127

133-
// Ensure node is stopped and its configuration removed on teardown
128+
// Ensure node is stopped on teardown. It's configuration is not removed to enable
129+
// collection in CI to aid in troubleshooting failures.
134130
ginkgo.DeferCleanup(func() {
135-
tests.Outf("Shutting down temporary node %s\n", node.GetID())
136-
require.NoError(node.Remove())
131+
tests.Outf("Shutting down ephemeral node %s\n", node.GetID())
132+
require.NoError(node.Stop())
137133
})
138134

139135
return node

tests/e2e/faultinjection/duplicate_node_id.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ package faultinjection
55

66
import (
77
"context"
8+
"fmt"
89

910
ginkgo "github.com/onsi/ginkgo/v2"
1011
"github.com/stretchr/testify/require"
1112

1213
"github.com/ava-labs/avalanchego/api/info"
13-
cfg "github.com/ava-labs/avalanchego/config"
14+
"github.com/ava-labs/avalanchego/config"
1415
"github.com/ava-labs/avalanchego/ids"
1516
"github.com/ava-labs/avalanchego/tests/e2e"
1617
"github.com/ava-labs/avalanchego/tests/fixture/testnet"
@@ -25,7 +26,7 @@ var _ = ginkgo.Describe("Duplicate node handling", func() {
2526
nodes := network.GetNodes()
2627

2728
ginkgo.By("creating new node")
28-
node1 := e2e.AddTemporaryNode(network, testnet.FlagsMap{})
29+
node1 := e2e.AddEphemeralNode(network, testnet.FlagsMap{})
2930
e2e.WaitForHealthy(node1)
3031

3132
ginkgo.By("checking that the new node is connected to its peers")
@@ -34,10 +35,14 @@ var _ = ginkgo.Describe("Duplicate node handling", func() {
3435
ginkgo.By("creating a second new node with the same staking keypair as the first new node")
3536
node1Flags := node1.GetConfig().Flags
3637
node2Flags := testnet.FlagsMap{
37-
cfg.StakingTLSKeyContentKey: node1Flags[cfg.StakingTLSKeyContentKey],
38-
cfg.StakingCertContentKey: node1Flags[cfg.StakingCertContentKey],
38+
config.StakingTLSKeyContentKey: node1Flags[config.StakingTLSKeyContentKey],
39+
config.StakingCertContentKey: node1Flags[config.StakingCertContentKey],
40+
// Construct a unique data dir to ensure the two nodes' data will be stored
41+
// separately. Usually the dir name is the node ID but in this one case the nodes have
42+
// the same node ID.
43+
config.DataDirKey: fmt.Sprintf("%s-second", node1Flags[config.DataDirKey]),
3944
}
40-
node2 := e2e.AddTemporaryNode(network, node2Flags)
45+
node2 := e2e.AddEphemeralNode(network, node2Flags)
4146

4247
ginkgo.By("checking that the second new node fails to become healthy within timeout")
4348
ctx, cancel := context.WithTimeout(context.Background(), e2e.DefaultNodeStartTimeout)

tests/fixture/testnet/interfaces.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type Network interface {
1616
GetConfig() NetworkConfig
1717
GetNodes() []Node
1818
AddNode(w io.Writer, flags FlagsMap) (Node, error)
19+
AddEphemeralNode(w io.Writer, flags FlagsMap) (Node, error)
1920
}
2021

2122
// Defines node capabilities supportable regardless of how a network is orchestrated.
@@ -26,5 +27,4 @@ type Node interface {
2627
IsHealthy(ctx context.Context) (bool, error)
2728
WaitForHealthy(ctx context.Context) error
2829
Stop() error
29-
Remove() error
3030
}

tests/fixture/testnet/local/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@ HOME
152152
│ └── config.json // C-Chain config for all nodes
153153
├── defaults.json // Default flags and configuration for network
154154
├── genesis.json // Genesis for all nodes
155-
└── network.env // Sets network dir env to simplify use of network
155+
├── network.env // Sets network dir env to simplify use of network
156+
└── ephemeral // Parent directory for ephemeral nodes (e.g. created by tests)
157+
└─ NodeID-FdxnAvr4jK9XXAwsYZPgWAHW2QnwSZ // Data dir for an ephemeral node
158+
└── ...
156159
157160
```
158161

tests/fixture/testnet/local/network.go

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,14 @@ import (
2424
"github.com/ava-labs/avalanchego/utils/set"
2525
)
2626

27-
// This interval was chosen to avoid spamming node APIs during
28-
// startup, as smaller intervals (e.g. 50ms) seemed to noticeably
29-
// increase the time for a network's nodes to be seen as healthy.
30-
const networkHealthCheckInterval = 200 * time.Millisecond
27+
const (
28+
// This interval was chosen to avoid spamming node APIs during
29+
// startup, as smaller intervals (e.g. 50ms) seemed to noticeably
30+
// increase the time for a network's nodes to be seen as healthy.
31+
networkHealthCheckInterval = 200 * time.Millisecond
32+
33+
defaultEphemeralDirName = "ephemeral"
34+
)
3135

3236
var (
3337
errInvalidNodeCount = errors.New("failed to populate local network config: non-zero node count is only valid for a network without nodes")
@@ -102,16 +106,26 @@ func (ln *LocalNetwork) GetNodes() []testnet.Node {
102106
return nodes
103107
}
104108

105-
// Adds a backend-agnostic node to the network
106-
func (ln *LocalNetwork) AddNode(w io.Writer, flags testnet.FlagsMap) (testnet.Node, error) {
109+
// Internal method supporting AddNode and AddEphemeralNode in creating a backend-agnostic node.
110+
func (ln *LocalNetwork) addNode(w io.Writer, flags testnet.FlagsMap, isEphemeral bool) (testnet.Node, error) {
107111
if flags == nil {
108112
flags = testnet.FlagsMap{}
109113
}
110114
return ln.AddLocalNode(w, &LocalNode{
111115
NodeConfig: testnet.NodeConfig{
112116
Flags: flags,
113117
},
114-
})
118+
}, isEphemeral)
119+
}
120+
121+
// Adds a backend-agnostic node to the network
122+
func (ln *LocalNetwork) AddNode(w io.Writer, flags testnet.FlagsMap) (testnet.Node, error) {
123+
return ln.addNode(w, flags, false /* isEphemeral */)
124+
}
125+
126+
// Adds a backend-agnostic ephemeral node to the network
127+
func (ln *LocalNetwork) AddEphemeralNode(w io.Writer, flags testnet.FlagsMap) (testnet.Node, error) {
128+
return ln.addNode(w, flags, true /* isEphemeral */)
115129
}
116130

117131
// Starts a new network stored under the provided root dir. Required
@@ -278,17 +292,18 @@ func (ln *LocalNetwork) PopulateLocalNetworkConfig(networkID uint32, nodeCount i
278292
for _, node := range ln.Nodes {
279293
// Ensure the node is configured for use with the network and
280294
// knows where to write its configuration.
281-
if err := ln.PopulateNodeConfig(node); err != nil {
295+
if err := ln.PopulateNodeConfig(node, ln.Dir); err != nil {
282296
return err
283297
}
284298
}
285299

286300
return nil
287301
}
288302

289-
// Ensure the provided node has the configuration it needs to
290-
// start. Requires that the network has valid genesis data.
291-
func (ln *LocalNetwork) PopulateNodeConfig(node *LocalNode) error {
303+
// Ensure the provided node has the configuration it needs to start. If the data dir is
304+
// not set, it will be defaulted to [nodeParentDir]/[node ID]. Requires that the
305+
// network has valid genesis data.
306+
func (ln *LocalNetwork) PopulateNodeConfig(node *LocalNode, nodeParentDir string) error {
292307
flags := node.Flags
293308

294309
// Set values common to all nodes
@@ -310,7 +325,7 @@ func (ln *LocalNetwork) PopulateNodeConfig(node *LocalNode) error {
310325
dataDir := node.GetDataDir()
311326
if len(dataDir) == 0 {
312327
// NodeID will have been set by EnsureKeys
313-
dataDir = filepath.Join(ln.Dir, node.NodeID.String())
328+
dataDir = filepath.Join(nodeParentDir, node.NodeID.String())
314329
flags[config.DataDirKey] = dataDir
315330
}
316331

@@ -645,15 +660,30 @@ func (ln *LocalNetwork) ReadAll() error {
645660
return ln.ReadNodes()
646661
}
647662

648-
func (ln *LocalNetwork) AddLocalNode(w io.Writer, node *LocalNode) (*LocalNode, error) {
663+
func (ln *LocalNetwork) AddLocalNode(w io.Writer, node *LocalNode, isEphemeral bool) (*LocalNode, error) {
649664
// Assume network configuration has been written to disk and is current in memory
650665

651666
if node == nil {
652667
// Set an empty data dir so that PopulateNodeConfig will know
653668
// to set the default of `[network dir]/[node id]`.
654669
node = NewLocalNode("")
655670
}
656-
if err := ln.PopulateNodeConfig(node); err != nil {
671+
672+
// Default to a data dir of [network-dir]/[node-ID]
673+
nodeParentDir := ln.Dir
674+
if isEphemeral {
675+
// For an ephemeral node, default to a data dir of [network-dir]/[ephemeral-dir][node-ID]
676+
// to provide a clear separation between nodes that are expected to expose stable API
677+
// endpoints and those that will live for only a short time (e.g. a node started by a test
678+
// and stopped on test teardown).
679+
//
680+
// The data for an ephemeral node is still in the file tree rooted at the network dir to
681+
// ensure that archiving the network dir in CI will collect all node data used for a test
682+
// run. Otherwise the data dir of ephemeral nodes could be stored in a random temp dir.
683+
nodeParentDir = filepath.Join(ln.Dir, defaultEphemeralDirName)
684+
}
685+
686+
if err := ln.PopulateNodeConfig(node, nodeParentDir); err != nil {
657687
return nil, err
658688
}
659689

tests/fixture/testnet/local/node.go

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -186,15 +186,16 @@ func (n *LocalNode) Start(w io.Writer, defaultExecPath string) error {
186186
return err
187187
}
188188

189-
// A temporary node directory won't match its node name.
190-
isTemporaryNode := filepath.Base(n.GetDataDir()) != n.NodeID.String()
191-
var nodeDescription string
192-
if isTemporaryNode {
193-
// Qualify the description for a temporary node with its path
194-
// since it won't be stored in the network's directory.
195-
nodeDescription = fmt.Sprintf("temporary node %q with path: %s", n.NodeID, n.GetDataDir())
196-
} else {
197-
nodeDescription = fmt.Sprintf("node %q", n.NodeID)
189+
// Determine appropriate level of node description detail
190+
nodeDescription := fmt.Sprintf("node %q", n.NodeID)
191+
isEphemeralNode := filepath.Base(filepath.Dir(n.GetDataDir())) == defaultEphemeralDirName
192+
if isEphemeralNode {
193+
nodeDescription = "ephemeral " + nodeDescription
194+
}
195+
nonDefaultNodeDir := filepath.Base(n.GetDataDir()) != n.NodeID.String()
196+
if nonDefaultNodeDir {
197+
// Only include the data dir if its base is not the default (the node ID)
198+
nodeDescription = fmt.Sprintf("%s with path: %s", nodeDescription, n.GetDataDir())
198199
}
199200

200201
go func() {
@@ -367,15 +368,3 @@ func (n *LocalNode) WaitForProcessContext(ctx context.Context) error {
367368
}
368369
return nil
369370
}
370-
371-
// Ensure the node is stopped and its configuration removed.
372-
func (n *LocalNode) Remove() error {
373-
// Ensure the node is stopped before removing its configuration
374-
if err := n.Stop(); err != nil {
375-
return fmt.Errorf("failed to stop node %q: %w", n.NodeID, err)
376-
}
377-
if err := os.RemoveAll(n.GetDataDir()); err != nil {
378-
return fmt.Errorf("failed to remove configuration for node %q: %w", n.NodeID, err)
379-
}
380-
return nil
381-
}

0 commit comments

Comments
 (0)