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