Skip to content

Commit 8bc6244

Browse files
committed
Add --runtime-state-path parameter to support test orchestration
By default state will not be written, and providing --runtime-state-path will ensure runtime state (API URI, bootstrap address, and pid) are written to the provided path on startup and removed from the provided path on shutdown. The details in the written file support orchestrating nodes for testing: - pid: check if process is running, stop the process - uri: connect to the node's API - bootstrap address: used by other nodes to bootstrap themselves
1 parent f9a71be commit 8bc6244

File tree

7 files changed

+120
-0
lines changed

7 files changed

+120
-0
lines changed

api/server/mock_server.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/server/server.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ type Server interface {
6868
Dispatch() error
6969
// DispatchTLS starts the API server with the provided TLS certificate
7070
DispatchTLS(certBytes, keyBytes []byte) error
71+
// Return the uri used to access the api server. Set by the
72+
// Dispatch and DispatchTLS methods after binding a listener, so
73+
// may need to be called until a non-empty value is returned.
74+
GetURI() string
7175
// RegisterChain registers the API endpoints associated with this chain.
7276
// That is, add <route, handler> pairs to server so that API calls can be
7377
// made to the VM.
@@ -103,6 +107,12 @@ type server struct {
103107
router *router
104108

105109
srv *http.Server
110+
111+
// Synchronizes access to uri across this type's Dispatch*() methods
112+
// and another goroutine calling GetURI().
113+
uriLock sync.RWMutex
114+
// URI (https://host:port) to access the api server.
115+
uri string
106116
}
107117

108118
// New returns an instance of a Server.
@@ -170,13 +180,29 @@ func New(
170180
}, nil
171181
}
172182

183+
// Retrieve the uri used to access the server.
184+
func (s *server) GetURI() string {
185+
s.uriLock.RLock()
186+
defer s.uriLock.RUnlock()
187+
return s.uri
188+
}
189+
190+
// Set the uri used to access the server.
191+
func (s *server) setURI(uri string) {
192+
s.uriLock.Lock()
193+
defer s.uriLock.Unlock()
194+
s.uri = uri
195+
}
196+
173197
func (s *server) Dispatch() error {
174198
listenAddress := net.JoinHostPort(s.listenHost, s.listenPort)
175199
listener, err := net.Listen("tcp", listenAddress)
176200
if err != nil {
177201
return err
178202
}
179203

204+
s.setURI("http://" + listener.Addr().String())
205+
180206
ipPort, err := ips.ToIPPort(listener.Addr().String())
181207
if err != nil {
182208
s.log.Info("HTTP API server listening",
@@ -208,6 +234,8 @@ func (s *server) DispatchTLS(certBytes, keyBytes []byte) error {
208234
return err
209235
}
210236

237+
s.setURI("https://" + listener.Addr().String())
238+
211239
ipPort, err := ips.ToIPPort(listener.Addr().String())
212240
if err != nil {
213241
s.log.Info("HTTPS API server listening",

config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,8 @@ func GetNodeConfig(v *viper.Viper) (node.Config, error) {
14951495

14961496
nodeConfig.ChainDataDir = GetExpandedArg(v, ChainDataDirKey)
14971497

1498+
nodeConfig.RuntimeStatePath = v.GetString(RuntimeStatePathKey)
1499+
14981500
nodeConfig.ProvidedFlags = providedFlags(v)
14991501
return nodeConfig, nil
15001502
}

config/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ func addNodeFlags(fs *pflag.FlagSet) {
388388
fs.Float64(TracingSampleRateKey, 0.1, "The fraction of traces to sample. If >= 1, always sample. If <= 0, never sample")
389389
fs.StringToString(TracingHeadersKey, map[string]string{}, "The headers to provide the trace indexer")
390390
// TODO add flag to take in headers to send from exporter
391+
392+
fs.String(RuntimeStatePathKey, "", "The path to write runtime state to (including uri, bootstrap address and pid). If empty, runtime state will not be written.")
391393
}
392394

393395
// BuildFlagSet returns a complete set of flags for avalanchego

config/keys.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,4 +219,5 @@ const (
219219
TracingSampleRateKey = "tracing-sample-rate"
220220
TracingExporterTypeKey = "tracing-exporter-type"
221221
TracingHeadersKey = "tracing-headers"
222+
RuntimeStatePathKey = "runtime-state-path"
222223
)

node/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,4 +233,8 @@ type Config struct {
233233
// ChainDataDir is the root path for per-chain directories where VMs can
234234
// write arbitrary data.
235235
ChainDataDir string `json:"chainDataDir"`
236+
237+
// Path to write runtime state to (including uri, bootstrap
238+
// address and pid). If empty, runtime state will not be written.
239+
RuntimeStatePath string `json:"runtimeStatePath"`
236240
}

node/node.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package node
66
import (
77
"context"
88
"crypto"
9+
"encoding/json"
910
"errors"
1011
"fmt"
1112
"io"
@@ -144,6 +145,11 @@ type Node struct {
144145
networkNamespace string
145146
Net network.Network
146147

148+
// The bootstrap address will optionally be written to a runtime state
149+
// file to enable other nodes to be configured to use this node as a
150+
// beacon.
151+
bootstrapAddress string
152+
147153
// tlsKeyLogWriterCloser is a debug file handle that writes all the TLS
148154
// session keys. This value should only be non-nil during debugging.
149155
tlsKeyLogWriterCloser io.WriteCloser
@@ -254,6 +260,9 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error {
254260
)
255261
}
256262

263+
// Record the bound address to enable inclusion in runtime state file.
264+
n.bootstrapAddress = listener.Addr().String()
265+
257266
tlsKey, ok := n.Config.StakingTLSCert.PrivateKey.(crypto.Signer)
258267
if !ok {
259268
return errInvalidTLSKey
@@ -374,6 +383,60 @@ func (n *Node) initNetworking(primaryNetVdrs validators.Set) error {
374383
return err
375384
}
376385

386+
type NodeRuntimeState struct {
387+
// The process id of the node
388+
PID int
389+
// URI to access the node API
390+
// Format: [https|http]://[host]:[port]
391+
URI string
392+
// Address other nodes can use for bootstrapping
393+
// Format: [host]:[port]
394+
BootstrapAddress string
395+
}
396+
397+
// Write runtime state to the configured path. Supports the use of
398+
// dynamically chosen network ports with local network orchestration.
399+
func (n *Node) writeRuntimeState() {
400+
n.Log.Info("attempting to write runtime state to configured path", zap.String("path", n.Config.RuntimeStatePath))
401+
402+
uri := ""
403+
404+
// Wait until the API Server URI is available.
405+
ctx, cancel := context.WithTimeout(context.TODO(), 10*time.Second)
406+
defer cancel()
407+
for {
408+
select {
409+
case <-ctx.Done():
410+
n.Log.Error("failed to retrieve API Server URI before timeout")
411+
return
412+
default:
413+
uri = n.APIServer.GetURI()
414+
}
415+
if len(uri) > 0 {
416+
break
417+
}
418+
time.Sleep(time.Millisecond * 200)
419+
}
420+
421+
// Write the runtime state to disk
422+
runtimeState := &NodeRuntimeState{
423+
PID: os.Getpid(),
424+
URI: uri,
425+
BootstrapAddress: n.bootstrapAddress, // Set by network initialization
426+
}
427+
bytes, err := json.MarshalIndent(runtimeState, "", " ")
428+
if err != nil {
429+
n.Log.Error("failed to marshal runtime state", zap.Error(err))
430+
return
431+
}
432+
if err := os.WriteFile(n.Config.RuntimeStatePath, bytes, perms.ReadWrite); err != nil {
433+
n.Log.Error("failed to write runtime state", zap.Error(err))
434+
return
435+
}
436+
437+
n.Log.Info("wrote runtime state")
438+
}
439+
377440
// Dispatch starts the node's servers.
378441
// Returns when the node exits.
379442
func (n *Node) Dispatch() error {
@@ -400,6 +463,10 @@ func (n *Node) Dispatch() error {
400463
n.Shutdown(1)
401464
})
402465

466+
if len(n.Config.RuntimeStatePath) > 0 {
467+
go n.writeRuntimeState()
468+
}
469+
403470
// Add state sync nodes to the peer network
404471
for i, peerIP := range n.Config.StateSyncIPs {
405472
n.Net.ManuallyTrack(n.Config.StateSyncIDs[i], peerIP)
@@ -429,6 +496,17 @@ func (n *Node) Dispatch() error {
429496

430497
// Wait until the node is done shutting down before returning
431498
n.DoneShuttingDown.Wait()
499+
500+
if len(n.Config.RuntimeStatePath) > 0 {
501+
// Attempt to remove the runtime state path
502+
if err := os.Remove(n.Config.RuntimeStatePath); err != nil && !os.IsNotExist(err) {
503+
n.Log.Error("removal of runtime state file failed",
504+
zap.String("path", n.Config.RuntimeStatePath),
505+
zap.Error(err),
506+
)
507+
}
508+
}
509+
432510
return err
433511
}
434512

0 commit comments

Comments
 (0)