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 }