github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/p2p/simulations/adapters/docker.go (about)

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