github.com/humaniq/go-ethereum@v1.6.8-0.20171225131628-061223a13848/p2p/simulations/adapters/docker.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package adapters
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"runtime"
    28  	"strings"
    29  
    30  	"github.com/docker/docker/pkg/reexec"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/node"
    33  	"github.com/ethereum/go-ethereum/p2p/discover"
    34  )
    35  
    36  // DockerAdapter is a NodeAdapter which runs simulation nodes inside Docker
    37  // containers.
    38  //
    39  // A Docker image is built which contains the current binary at /bin/p2p-node
    40  // which when executed runs the underlying service (see the description
    41  // of the execP2PNode function for more details)
    42  type DockerAdapter struct {
    43  	ExecAdapter
    44  }
    45  
    46  // NewDockerAdapter builds the p2p-node Docker image containing the current
    47  // binary and returns a DockerAdapter
    48  func NewDockerAdapter() (*DockerAdapter, error) {
    49  	// Since Docker containers run on Linux and this adapter runs the
    50  	// current binary in the container, it must be compiled for Linux.
    51  	//
    52  	// It is reasonable to require this because the caller can just
    53  	// compile the current binary in a Docker container.
    54  	if runtime.GOOS != "linux" {
    55  		return nil, errors.New("DockerAdapter can only be used on Linux as it uses the current binary (which must be a Linux binary)")
    56  	}
    57  
    58  	if err := buildDockerImage(); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	return &DockerAdapter{
    63  		ExecAdapter{
    64  			nodes: make(map[discover.NodeID]*ExecNode),
    65  		},
    66  	}, nil
    67  }
    68  
    69  // Name returns the name of the adapter for logging purposes
    70  func (d *DockerAdapter) Name() string {
    71  	return "docker-adapter"
    72  }
    73  
    74  // NewNode returns a new DockerNode using the given config
    75  func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
    76  	if len(config.Services) == 0 {
    77  		return nil, errors.New("node must have at least one service")
    78  	}
    79  	for _, service := range config.Services {
    80  		if _, exists := serviceFuncs[service]; !exists {
    81  			return nil, fmt.Errorf("unknown node service %q", service)
    82  		}
    83  	}
    84  
    85  	// generate the config
    86  	conf := &execNodeConfig{
    87  		Stack: node.DefaultConfig,
    88  		Node:  config,
    89  	}
    90  	conf.Stack.DataDir = "/data"
    91  	conf.Stack.WSHost = "0.0.0.0"
    92  	conf.Stack.WSOrigins = []string{"*"}
    93  	conf.Stack.WSExposeAll = true
    94  	conf.Stack.P2P.EnableMsgEvents = false
    95  	conf.Stack.P2P.NoDiscovery = true
    96  	conf.Stack.P2P.NAT = nil
    97  	conf.Stack.NoUSB = true
    98  	conf.Stack.Logger = log.New("node.id", config.ID.String())
    99  
   100  	node := &DockerNode{
   101  		ExecNode: ExecNode{
   102  			ID:      config.ID,
   103  			Config:  conf,
   104  			adapter: &d.ExecAdapter,
   105  		},
   106  	}
   107  	node.newCmd = node.dockerCommand
   108  	d.ExecAdapter.nodes[node.ID] = &node.ExecNode
   109  	return node, nil
   110  }
   111  
   112  // DockerNode wraps an ExecNode but exec's the current binary in a docker
   113  // container rather than locally
   114  type DockerNode struct {
   115  	ExecNode
   116  }
   117  
   118  // dockerCommand returns a command which exec's the binary in a Docker
   119  // container.
   120  //
   121  // It uses a shell so that we can pass the _P2P_NODE_CONFIG environment
   122  // variable to the container using the --env flag.
   123  func (n *DockerNode) dockerCommand() *exec.Cmd {
   124  	return exec.Command(
   125  		"sh", "-c",
   126  		fmt.Sprintf(
   127  			`exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`,
   128  			dockerImage, strings.Join(n.Config.Node.Services, ","), n.ID.String(),
   129  		),
   130  	)
   131  }
   132  
   133  // dockerImage is the name of the Docker image which gets built to run the
   134  // simulation node
   135  const dockerImage = "p2p-node"
   136  
   137  // buildDockerImage builds the Docker image which is used to run the simulation
   138  // node in a Docker container.
   139  //
   140  // It adds the current binary as "p2p-node" so that it runs execP2PNode
   141  // when executed.
   142  func buildDockerImage() error {
   143  	// create a directory to use as the build context
   144  	dir, err := ioutil.TempDir("", "p2p-docker")
   145  	if err != nil {
   146  		return err
   147  	}
   148  	defer os.RemoveAll(dir)
   149  
   150  	// copy the current binary into the build context
   151  	bin, err := os.Open(reexec.Self())
   152  	if err != nil {
   153  		return err
   154  	}
   155  	defer bin.Close()
   156  	dst, err := os.OpenFile(filepath.Join(dir, "self.bin"), os.O_WRONLY|os.O_CREATE, 0755)
   157  	if err != nil {
   158  		return err
   159  	}
   160  	defer dst.Close()
   161  	if _, err := io.Copy(dst, bin); err != nil {
   162  		return err
   163  	}
   164  
   165  	// create the Dockerfile
   166  	dockerfile := []byte(`
   167  FROM ubuntu:16.04
   168  RUN mkdir /data
   169  ADD self.bin /bin/p2p-node
   170  	`)
   171  	if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfile, 0644); err != nil {
   172  		return err
   173  	}
   174  
   175  	// run 'docker build'
   176  	cmd := exec.Command("docker", "build", "-t", dockerImage, dir)
   177  	cmd.Stdout = os.Stdout
   178  	cmd.Stderr = os.Stderr
   179  	if err := cmd.Run(); err != nil {
   180  		return fmt.Errorf("error building docker image: %s", err)
   181  	}
   182  
   183  	return nil
   184  }