github.com/linapex/ethereum-dpos-chinese@v0.0.0-20190316121959-b78b3a4a1ece/p2p/simulations/adapters/docker.go (about)

     1  
     2  //<developer>
     3  //    <name>linapex 曹一峰</name>
     4  //    <email>linapex@163.com</email>
     5  //    <wx>superexc</wx>
     6  //    <qqgroup>128148617</qqgroup>
     7  //    <url>https://jsq.ink</url>
     8  //    <role>pku engineer</role>
     9  //    <date>2019-03-16 12:09:44</date>
    10  //</624342660525592576>
    11  
    12  
    13  package adapters
    14  
    15  import (
    16  	"errors"
    17  	"fmt"
    18  	"io"
    19  	"io/ioutil"
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"runtime"
    24  	"strings"
    25  
    26  	"github.com/docker/docker/pkg/reexec"
    27  	"github.com/ethereum/go-ethereum/node"
    28  	"github.com/ethereum/go-ethereum/p2p/discover"
    29  )
    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  
    35  //Dockeradapter是在Docker中运行模拟节点的节点适配器。
    36  //容器。
    37  //
    38  //建立了一个包含当前二进制at/bin/p2p节点的Docker映像。
    39  //执行时运行基础服务(请参见说明
    40  //有关详细信息,请参阅execp2pnode函数)
    41  type DockerAdapter struct {
    42  	ExecAdapter
    43  }
    44  
    45  //newdockeradapter构建包含当前
    46  //二进制并返回dockeradapter
    47  func NewDockerAdapter() (*DockerAdapter, error) {
    48  //因为Docker容器在Linux上运行,而这个适配器运行
    49  //当前容器中的二进制文件,必须为Linux编译。
    50  //
    51  //要求这样做是合理的,因为打电话的人可以
    52  //在Docker容器中编译当前二进制文件。
    53  	if runtime.GOOS != "linux" {
    54  		return nil, ErrLinuxOnly
    55  	}
    56  
    57  	if err := buildDockerImage(); err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	return &DockerAdapter{
    62  		ExecAdapter{
    63  			nodes: make(map[discover.NodeID]*ExecNode),
    64  		},
    65  	}, nil
    66  }
    67  
    68  //name返回用于日志记录的适配器的名称
    69  func (d *DockerAdapter) Name() string {
    70  	return "docker-adapter"
    71  }
    72  
    73  //newnode使用给定的配置返回一个新的dockernode
    74  func (d *DockerAdapter) NewNode(config *NodeConfig) (Node, error) {
    75  	if len(config.Services) == 0 {
    76  		return nil, errors.New("node must have at least one service")
    77  	}
    78  	for _, service := range config.Services {
    79  		if _, exists := serviceFuncs[service]; !exists {
    80  			return nil, fmt.Errorf("unknown node service %q", service)
    81  		}
    82  	}
    83  
    84  //生成配置
    85  	conf := &execNodeConfig{
    86  		Stack: node.DefaultConfig,
    87  		Node:  config,
    88  	}
    89  	conf.Stack.DataDir = "/data"
    90  	conf.Stack.WSHost = "0.0.0.0"
    91  	conf.Stack.WSOrigins = []string{"*"}
    92  	conf.Stack.WSExposeAll = true
    93  	conf.Stack.P2P.EnableMsgEvents = false
    94  	conf.Stack.P2P.NoDiscovery = true
    95  	conf.Stack.P2P.NAT = nil
    96  	conf.Stack.NoUSB = true
    97  
    98  //监听给定端口上的所有接口,当我们
    99  //初始化nodeconfig(通常是随机端口)
   100  	conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
   101  
   102  	node := &DockerNode{
   103  		ExecNode: ExecNode{
   104  			ID:      config.ID,
   105  			Config:  conf,
   106  			adapter: &d.ExecAdapter,
   107  		},
   108  	}
   109  	node.newCmd = node.dockerCommand
   110  	d.ExecAdapter.nodes[node.ID] = &node.ExecNode
   111  	return node, nil
   112  }
   113  
   114  //dockernode包装execnode,但exec的是docker中的当前二进制文件
   115  //容器而不是本地
   116  type DockerNode struct {
   117  	ExecNode
   118  }
   119  
   120  //docker command返回一个命令,exec是docker中的二进制文件
   121  //容器。
   122  //
   123  //它使用了一个shell,这样我们就可以通过
   124  //使用--env标志将变量转换为容器。
   125  func (n *DockerNode) dockerCommand() *exec.Cmd {
   126  	return exec.Command(
   127  		"sh", "-c",
   128  		fmt.Sprintf(
   129  			`exec docker run --interactive --env _P2P_NODE_CONFIG="${_P2P_NODE_CONFIG}" %s p2p-node %s %s`,
   130  			dockerImage, strings.Join(n.Config.Node.Services, ","), n.ID.String(),
   131  		),
   132  	)
   133  }
   134  
   135  //DockerImage是为运行
   136  //仿真节点
   137  const dockerImage = "p2p-node"
   138  
   139  //buildDockerImage构建用于运行模拟的Docker映像
   140  //Docker容器中的节点。
   141  //
   142  //它将当前二进制文件添加为“p2p node”,以便运行execp2pnode
   143  //执行时。
   144  func buildDockerImage() error {
   145  //创建用作生成上下文的目录
   146  	dir, err := ioutil.TempDir("", "p2p-docker")
   147  	if err != nil {
   148  		return err
   149  	}
   150  	defer os.RemoveAll(dir)
   151  
   152  //将当前二进制文件复制到生成上下文中
   153  	bin, err := os.Open(reexec.Self())
   154  	if err != nil {
   155  		return err
   156  	}
   157  	defer bin.Close()
   158  	dst, err := os.OpenFile(filepath.Join(dir, "self.bin"), os.O_WRONLY|os.O_CREATE, 0755)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	defer dst.Close()
   163  	if _, err := io.Copy(dst, bin); err != nil {
   164  		return err
   165  	}
   166  
   167  //创建dockerfile
   168  	dockerfile := []byte(`
   169  FROM ubuntu:16.04
   170  RUN mkdir /data
   171  ADD self.bin /bin/p2p-node
   172  	`)
   173  	if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dockerfile, 0644); err != nil {
   174  		return err
   175  	}
   176  
   177  //运行“docker build”
   178  	cmd := exec.Command("docker", "build", "-t", dockerImage, dir)
   179  	cmd.Stdout = os.Stdout
   180  	cmd.Stderr = os.Stderr
   181  	if err := cmd.Run(); err != nil {
   182  		return fmt.Errorf("error building docker image: %s", err)
   183  	}
   184  
   185  	return nil
   186  }
   187