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 }