PoC: Network simulation framework (#1555)
* simv2: wip * simulation: exec adapter start/stop * simulation: add node status to exec adapter * simulation: initial simulation code * simulation: exec adapter, configure path to executable * simulation: initial docker adapter * simulation: wip kubernetes adapter * simulation: kubernetes adapter proxy * simulation: implement GetAll/StartAll/StopAll * simulation: kuberentes adapter - set env vars and resource limits * simulation: discovery test * simulation: remove port definitions within docker adapter * simulation: simplify wait for healthy loop * simulation: get nat ip addr from interface * simulation: pull docker images automatically * simulation: NodeStatus -> NodeInfo * simulation: move discovery test to example dir * simulation: example snapshot usage * simulation: add goclient specific simulation * simulation: add peer connections to snapshot * simulation: close rpc client * simulation: don't export kubernetes proxy server * simulation: merge simulation code * simulation: don't export nodemap * simulation: rename SimulationSnapshot -> Snapshot * simulation: linting fixes * simulation: add k8s available helper func * simulation: vendor * simulation: fix 'no non-test Go files' when building * simulation: remove errors from interface methods where non were returned * simulation: run getHealthInfo check in parallel
This commit is contained in:
228
simulation/exec.go
Normal file
228
simulation/exec.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethersphere/swarm"
|
||||
)
|
||||
|
||||
// ExecAdapter can manage local exec nodes
|
||||
type ExecAdapter struct {
|
||||
config ExecAdapterConfig
|
||||
}
|
||||
|
||||
// ExecAdapterConfig is used to configure an ExecAdapter
|
||||
type ExecAdapterConfig struct {
|
||||
// Path to the executable
|
||||
ExecutablePath string `json:"executable"`
|
||||
// BaseDataDirectory stores all the nodes' data directories
|
||||
BaseDataDirectory string `json:"basedir"`
|
||||
}
|
||||
|
||||
// ExecNode is a node that is executed locally
|
||||
type ExecNode struct {
|
||||
adapter *ExecAdapter
|
||||
config NodeConfig
|
||||
cmd *exec.Cmd
|
||||
info NodeInfo
|
||||
}
|
||||
|
||||
// NewExecAdapter creates an ExecAdapter by receiving a ExecAdapterConfig
|
||||
func NewExecAdapter(config ExecAdapterConfig) (*ExecAdapter, error) {
|
||||
if _, err := os.Stat(config.BaseDataDirectory); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("'%s' directory does not exist", config.BaseDataDirectory)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(config.ExecutablePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("'%s' executable does not exist", config.ExecutablePath)
|
||||
}
|
||||
|
||||
absExec, err := filepath.Abs(config.ExecutablePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.ExecutablePath, err)
|
||||
}
|
||||
config.ExecutablePath = absExec
|
||||
|
||||
absDir, err := filepath.Abs(config.BaseDataDirectory)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get absolute path for %s: %v", config.BaseDataDirectory, err)
|
||||
}
|
||||
config.BaseDataDirectory = absDir
|
||||
|
||||
a := &ExecAdapter{
|
||||
config: config,
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// NewNode creates a new node
|
||||
func (a ExecAdapter) NewNode(config NodeConfig) Node {
|
||||
info := NodeInfo{
|
||||
ID: config.ID,
|
||||
}
|
||||
node := &ExecNode{
|
||||
config: config,
|
||||
adapter: &a,
|
||||
info: info,
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the adapter
|
||||
func (a ExecAdapter) Snapshot() AdapterSnapshot {
|
||||
return AdapterSnapshot{
|
||||
Type: "exec",
|
||||
Config: a.config,
|
||||
}
|
||||
}
|
||||
|
||||
// Info returns the node info
|
||||
func (n *ExecNode) Info() NodeInfo {
|
||||
return n.info
|
||||
}
|
||||
|
||||
// Start starts the node
|
||||
func (n *ExecNode) Start() error {
|
||||
// Check if command already exists
|
||||
if n.cmd != nil {
|
||||
return fmt.Errorf("node %s is already running", n.config.ID)
|
||||
}
|
||||
|
||||
// Create command line arguments
|
||||
args := []string{filepath.Base(n.adapter.config.ExecutablePath)}
|
||||
|
||||
// Create data directory for this node
|
||||
dir := n.dataDir()
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create node directory: %s", err)
|
||||
}
|
||||
|
||||
// Configure data directory
|
||||
args = append(args, "--datadir", dir)
|
||||
|
||||
// Configure IPC path
|
||||
args = append(args, "--ipcpath", n.ipcPath())
|
||||
|
||||
// Automatically allocate ports
|
||||
args = append(args, "--pprofport", "0")
|
||||
args = append(args, "--bzzport", "0")
|
||||
args = append(args, "--wsport", "0")
|
||||
args = append(args, "--port", "0")
|
||||
|
||||
// Append user defined arguments
|
||||
args = append(args, n.config.Args...)
|
||||
|
||||
// Start command
|
||||
n.cmd = &exec.Cmd{
|
||||
Path: n.adapter.config.ExecutablePath,
|
||||
Args: args,
|
||||
Dir: dir,
|
||||
Env: n.config.Env,
|
||||
Stdout: n.config.Stdout,
|
||||
Stderr: n.config.Stderr,
|
||||
}
|
||||
|
||||
if err := n.cmd.Start(); err != nil {
|
||||
n.cmd = nil
|
||||
return fmt.Errorf("error starting node %s: %s", n.config.ID, err)
|
||||
}
|
||||
|
||||
// Wait for the node to start
|
||||
var client *rpc.Client
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
n.Stop()
|
||||
}
|
||||
}()
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
client, err = rpc.Dial(n.ipcPath())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if client == nil {
|
||||
return fmt.Errorf("could not establish rpc connection. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
var swarminfo swarm.Info
|
||||
err = client.Call(&swarminfo, "bzz_info")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
var p2pinfo p2p.NodeInfo
|
||||
err = client.Call(&p2pinfo, "admin_nodeInfo")
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get info via rpc call. node %s: %v", n.config.ID, err)
|
||||
}
|
||||
|
||||
n.info = NodeInfo{
|
||||
ID: n.config.ID,
|
||||
Enode: p2pinfo.Enode,
|
||||
BzzAddr: swarminfo.BzzKey,
|
||||
RPCListen: n.ipcPath(),
|
||||
HTTPListen: fmt.Sprintf("http://localhost:%s", swarminfo.Port),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the node
|
||||
func (n *ExecNode) Stop() error {
|
||||
if n.cmd == nil {
|
||||
return nil
|
||||
}
|
||||
defer func() {
|
||||
n.cmd = nil
|
||||
}()
|
||||
// Try to gracefully terminate the process
|
||||
if err := n.cmd.Process.Signal(syscall.SIGTERM); err != nil {
|
||||
return n.cmd.Process.Kill()
|
||||
}
|
||||
|
||||
// Wait for the process to terminate or timeout
|
||||
waitErr := make(chan error)
|
||||
go func() {
|
||||
waitErr <- n.cmd.Wait()
|
||||
}()
|
||||
select {
|
||||
case err := <-waitErr:
|
||||
return err
|
||||
case <-time.After(20 * time.Second):
|
||||
return n.cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot returns a snapshot of the node
|
||||
func (n *ExecNode) Snapshot() (NodeSnapshot, error) {
|
||||
snap := NodeSnapshot{
|
||||
Config: n.config,
|
||||
}
|
||||
adapterSnap := n.adapter.Snapshot()
|
||||
snap.Adapter = &adapterSnap
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// ipcPath returns the path to the ipc socket
|
||||
func (n *ExecNode) ipcPath() string {
|
||||
ipcfile := "bzzd.ipc"
|
||||
// On windows we can have to use pipes
|
||||
if runtime.GOOS == "windows" {
|
||||
return `\\.\pipe\` + ipcfile
|
||||
}
|
||||
return fmt.Sprintf("%s/%s", n.dataDir(), ipcfile)
|
||||
}
|
||||
|
||||
// dataDir returns the path to the data directory that the node should use
|
||||
func (n *ExecNode) dataDir() string {
|
||||
return filepath.Join(n.adapter.config.BaseDataDirectory, string(n.config.ID))
|
||||
}
|
Reference in New Issue
Block a user