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