github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/p2p/simulations/adapters/docker.go (about)

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