mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
p2p/simulations/adapters: remove docker adapter
This commit is contained in:
parent
f44533ce26
commit
0bc880a250
3 changed files with 10 additions and 234 deletions
|
|
@ -63,18 +63,6 @@ using the devp2p node stack rather than executing `main()`.
|
||||||
The nodes listen for devp2p connections and WebSocket RPC clients on random
|
The nodes listen for devp2p connections and WebSocket RPC clients on random
|
||||||
localhost ports.
|
localhost ports.
|
||||||
|
|
||||||
### DockerAdapter
|
|
||||||
|
|
||||||
The `DockerAdapter` is similar to the `ExecAdapter` but executes `docker run`
|
|
||||||
to run the node in a Docker container using a Docker image containing the
|
|
||||||
simulation binary at `/bin/p2p-node`.
|
|
||||||
|
|
||||||
The Docker image is built using `docker build` when the adapter is initialised,
|
|
||||||
meaning no prior setup is necessary other than having a working Docker client.
|
|
||||||
|
|
||||||
Each node listens on the external IP of the container and the default p2p and
|
|
||||||
RPC ports (`30303` and `8546` respectively).
|
|
||||||
|
|
||||||
## Network
|
## Network
|
||||||
|
|
||||||
A simulation network is created with an ID and default service (which is used
|
A simulation network is created with an ID and default service (which is used
|
||||||
|
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package adapters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/reexec"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrLinuxOnly = errors.New("DockerAdapter can only be used on Linux as it uses the current binary (which must be a Linux binary)")
|
|
||||||
)
|
|
||||||
|
|
||||||
// DockerAdapter is a NodeAdapter which runs simulation nodes inside Docker
|
|
||||||
// containers.
|
|
||||||
//
|
|
||||||
// A Docker image is built which contains the current binary at /bin/p2p-node
|
|
||||||
// which when executed runs the underlying service (see the description
|
|
||||||
// of the execP2PNode function for more details)
|
|
||||||
type DockerAdapter struct {
|
|
||||||
ExecAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDockerAdapter builds the p2p-node Docker image containing the current
|
|
||||||
// binary and returns a DockerAdapter
|
|
||||||
func NewDockerAdapter() (*DockerAdapter, error) {
|
|
||||||
// Since Docker containers run on Linux and this adapter runs the
|
|
||||||
// current binary in the container, it must be compiled for Linux.
|
|
||||||
//
|
|
||||||
// It is reasonable to require this because the caller can just
|
|
||||||
// compile the current binary in a Docker container.
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
return nil, ErrLinuxOnly
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := buildDockerImage(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DockerAdapter{
|
|
||||||
ExecAdapter{
|
|
||||||
nodes: make(map[enode.ID]*ExecNode),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the adapter for logging purposes
|
|
||||||
func (d *DockerAdapter) Name() string {
|
|
||||||
return "docker-adapter"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewNode returns a new DockerNode using the given config
|
|
||||||
func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
|
|
||||||
if len(config.Services) == 0 {
|
|
||||||
return nil, errors.New("node must have at least one service")
|
|
||||||
}
|
|
||||||
for _, service := range config.Services {
|
|
||||||
if _, exists := serviceFuncs[service]; !exists {
|
|
||||||
return nil, fmt.Errorf("unknown node service %q", service)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the config
|
|
||||||
conf := &execNodeConfig{
|
|
||||||
Stack: node.DefaultConfig,
|
|
||||||
Node: config,
|
|
||||||
}
|
|
||||||
conf.Stack.DataDir = "/data"
|
|
||||||
conf.Stack.WSHost = "0.0.0.0"
|
|
||||||
conf.Stack.WSOrigins = []string{"*"}
|
|
||||||
conf.Stack.WSExposeAll = true
|
|
||||||
conf.Stack.P2P.EnableMsgEvents = false
|
|
||||||
conf.Stack.P2P.NoDiscovery = true
|
|
||||||
conf.Stack.P2P.NAT = nil
|
|
||||||
conf.Stack.NoUSB = true
|
|
||||||
|
|
||||||
// listen on all interfaces on a given port, which we set when we
|
|
||||||
// initialise NodeConfig (usually a random port)
|
|
||||||
conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
|
|
||||||
|
|
||||||
node := &DockerNode{
|
|
||||||
ExecNode: ExecNode{
|
|
||||||
ID: config.ID,
|
|
||||||
Config: conf,
|
|
||||||
adapter: &d.ExecAdapter,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
node.newCmd = node.dockerCommand
|
|
||||||
d.ExecAdapter.nodes[node.ID] = &node.ExecNode
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DockerNode wraps an ExecNode but exec's the current binary in a docker
|
|
||||||
// container rather than locally
|
|
||||||
type DockerNode struct {
|
|
||||||
ExecNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// dockerCommand returns a command which exec's the binary in a Docker container.
|
|
||||||
func (n *DockerNode) dockerCommand() *exec.Cmd {
|
|
||||||
return exec.Command(
|
|
||||||
"docker", "run", "--interactive", "--env", envStatusURL, "--env", envNodeConfig,
|
|
||||||
dockerImage, "/bin/p2p-node",
|
|
||||||
strings.Join(n.Config.Node.Services, ","),
|
|
||||||
n.ID.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dockerImage is the name of the Docker image which gets built to run the
|
|
||||||
// simulation node
|
|
||||||
const dockerImage = "p2p-node"
|
|
||||||
|
|
||||||
// buildDockerImage builds the Docker image which is used to run the simulation
|
|
||||||
// node in a Docker container.
|
|
||||||
//
|
|
||||||
// It adds the current binary as "p2p-node" so that it runs execP2PNode
|
|
||||||
// when executed.
|
|
||||||
func buildDockerImage() error {
|
|
||||||
// create a directory to use as the build context
|
|
||||||
dir, err := ioutil.TempDir("", "p2p-docker")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// copy the current binary into the build context
|
|
||||||
bin, err := os.Open(reexec.Self())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer bin.Close()
|
|
||||||
dst, err := os.OpenFile(filepath.Join(dir, "self.bin"), os.O_WRONLY|os.O_CREATE, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dst.Close()
|
|
||||||
if _, err := io.Copy(dst, bin); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the Dockerfile
|
|
||||||
dockerfile := []byte(`
|
|
||||||
FROM ubuntu:16.04
|
|
||||||
RUN mkdir /data
|
|
||||||
ADD self.bin /bin/p2p-node
|
|
||||||
`)
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfile, 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// run 'docker build'
|
|
||||||
cmd := exec.Command("docker", "build", "-t", dockerImage, dir)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("error building docker image: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -44,12 +44,14 @@ import (
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecAdapter is a NodeAdapter which runs simulation nodes by executing the
|
func init() {
|
||||||
// current binary as a child process.
|
// Register a reexec function to start a simulation node when the current binary is
|
||||||
//
|
// executed as "p2p-node" (rather than whataver the main() function would normally do).
|
||||||
// An init hook is used so that the child process executes the node services
|
reexec.Register("p2p-node", execP2PNode)
|
||||||
// (rather than whataver the main() function would normally do), see the
|
}
|
||||||
// execP2PNode function for more information.
|
|
||||||
|
// ExecAdapter is a NodeAdapter which runs simulation nodes by executing the current binary
|
||||||
|
// as a child process.
|
||||||
type ExecAdapter struct {
|
type ExecAdapter struct {
|
||||||
// BaseDir is the directory under which the data directories for each
|
// BaseDir is the directory under which the data directories for each
|
||||||
// simulation node are created.
|
// simulation node are created.
|
||||||
|
|
@ -213,11 +215,10 @@ func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
|
||||||
func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
|
func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
|
||||||
var (
|
var (
|
||||||
ch = make(chan nodeStartupJSON, 1)
|
ch = make(chan nodeStartupJSON, 1)
|
||||||
ip = ExternalIP()
|
|
||||||
quitOnce sync.Once
|
quitOnce sync.Once
|
||||||
srv http.Server
|
srv http.Server
|
||||||
)
|
)
|
||||||
l, err := net.Listen("tcp", ip.String()+":0")
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- nodeStartupJSON{Err: err.Error()}
|
ch <- nodeStartupJSON{Err: err.Error()}
|
||||||
return "", ch
|
return "", ch
|
||||||
|
|
@ -337,12 +338,6 @@ func (n *ExecNode) Snapshots() (map[string][]byte, error) {
|
||||||
return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
|
return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// register a reexec function to start a devp2p node when the current
|
|
||||||
// binary is executed as "p2p-node"
|
|
||||||
reexec.Register("p2p-node", execP2PNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// execNodeConfig is used to serialize the node configuration so it can be
|
// execNodeConfig is used to serialize the node configuration so it can be
|
||||||
// passed to the child process as a JSON encoded environment variable
|
// passed to the child process as a JSON encoded environment variable
|
||||||
type execNodeConfig struct {
|
type execNodeConfig struct {
|
||||||
|
|
@ -352,22 +347,7 @@ type execNodeConfig struct {
|
||||||
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
|
PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalIP gets an external IP address so that Enode URL is usable
|
// execP2PNode starts a simulation node when the current binary is executed with
|
||||||
func ExternalIP() net.IP {
|
|
||||||
addrs, err := net.InterfaceAddrs()
|
|
||||||
if err != nil {
|
|
||||||
log.Crit("error getting IP address", "err", err)
|
|
||||||
}
|
|
||||||
for _, addr := range addrs {
|
|
||||||
if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() {
|
|
||||||
return ip.IP
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Warn("unable to determine explicit IP address, falling back to loopback")
|
|
||||||
return net.IP{127, 0, 0, 1}
|
|
||||||
}
|
|
||||||
|
|
||||||
// execP2PNode starts a devp2p node when the current binary is executed with
|
|
||||||
// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
|
// argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
|
||||||
// and the node config from an environment variable.
|
// and the node config from an environment variable.
|
||||||
func execP2PNode() {
|
func execP2PNode() {
|
||||||
|
|
@ -426,13 +406,6 @@ func startExecNodeStack() (*node.Node, error) {
|
||||||
conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
|
conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
|
||||||
conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
|
conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
|
||||||
|
|
||||||
if strings.HasPrefix(conf.Stack.P2P.ListenAddr, ":") {
|
|
||||||
conf.Stack.P2P.ListenAddr = ExternalIP().String() + conf.Stack.P2P.ListenAddr
|
|
||||||
}
|
|
||||||
if conf.Stack.WSHost == "0.0.0.0" {
|
|
||||||
conf.Stack.WSHost = ExternalIP().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the devp2p stack
|
// initialize the devp2p stack
|
||||||
stack, err := node.New(&conf.Stack)
|
stack, err := node.New(&conf.Stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue