docker.go 5.3 KB

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