github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/p2p/simulations/adapters/exec.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar 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-aigar 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-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package adapters
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"crypto/ecdsa"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"net"
    29  	"net/http"
    30  	"os"
    31  	"os/exec"
    32  	"os/signal"
    33  	"path/filepath"
    34  	"strings"
    35  	"sync"
    36  	"syscall"
    37  	"time"
    38  
    39  	"github.com/AigarNetwork/aigar/log"
    40  	"github.com/AigarNetwork/aigar/node"
    41  	"github.com/AigarNetwork/aigar/p2p"
    42  	"github.com/AigarNetwork/aigar/p2p/enode"
    43  	"github.com/AigarNetwork/aigar/rpc"
    44  	"github.com/docker/docker/pkg/reexec"
    45  	"golang.org/x/net/websocket"
    46  )
    47  
    48  func init() {
    49  	// Register a reexec function to start a simulation node when the current binary is
    50  	// executed as "p2p-node" (rather than whatever the main() function would normally do).
    51  	reexec.Register("p2p-node", execP2PNode)
    52  }
    53  
    54  // ExecAdapter is a NodeAdapter which runs simulation nodes by executing the current binary
    55  // as a child process.
    56  type ExecAdapter struct {
    57  	// BaseDir is the directory under which the data directories for each
    58  	// simulation node are created.
    59  	BaseDir string
    60  
    61  	nodes map[enode.ID]*ExecNode
    62  }
    63  
    64  // NewExecAdapter returns an ExecAdapter which stores node data in
    65  // subdirectories of the given base directory
    66  func NewExecAdapter(baseDir string) *ExecAdapter {
    67  	return &ExecAdapter{
    68  		BaseDir: baseDir,
    69  		nodes:   make(map[enode.ID]*ExecNode),
    70  	}
    71  }
    72  
    73  // Name returns the name of the adapter for logging purposes
    74  func (e *ExecAdapter) Name() string {
    75  	return "exec-adapter"
    76  }
    77  
    78  // NewNode returns a new ExecNode using the given config
    79  func (e *ExecAdapter) NewNode(config *NodeConfig) (Node, error) {
    80  	if len(config.Services) == 0 {
    81  		return nil, errors.New("node must have at least one service")
    82  	}
    83  	for _, service := range config.Services {
    84  		if _, exists := serviceFuncs[service]; !exists {
    85  			return nil, fmt.Errorf("unknown node service %q", service)
    86  		}
    87  	}
    88  
    89  	// create the node directory using the first 12 characters of the ID
    90  	// as Unix socket paths cannot be longer than 256 characters
    91  	dir := filepath.Join(e.BaseDir, config.ID.String()[:12])
    92  	if err := os.Mkdir(dir, 0755); err != nil {
    93  		return nil, fmt.Errorf("error creating node directory: %s", err)
    94  	}
    95  
    96  	err := config.initDummyEnode()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	// generate the config
   102  	conf := &execNodeConfig{
   103  		Stack: node.DefaultConfig,
   104  		Node:  config,
   105  	}
   106  	if config.DataDir != "" {
   107  		conf.Stack.DataDir = config.DataDir
   108  	} else {
   109  		conf.Stack.DataDir = filepath.Join(dir, "data")
   110  	}
   111  
   112  	// these parameters are crucial for execadapter node to run correctly
   113  	conf.Stack.WSHost = "127.0.0.1"
   114  	conf.Stack.WSPort = 0
   115  	conf.Stack.WSOrigins = []string{"*"}
   116  	conf.Stack.WSExposeAll = true
   117  	conf.Stack.P2P.EnableMsgEvents = config.EnableMsgEvents
   118  	conf.Stack.P2P.NoDiscovery = true
   119  	conf.Stack.P2P.NAT = nil
   120  	conf.Stack.NoUSB = true
   121  
   122  	// listen on a localhost port, which we set when we
   123  	// initialise NodeConfig (usually a random port)
   124  	conf.Stack.P2P.ListenAddr = fmt.Sprintf(":%d", config.Port)
   125  
   126  	node := &ExecNode{
   127  		ID:      config.ID,
   128  		Dir:     dir,
   129  		Config:  conf,
   130  		adapter: e,
   131  	}
   132  	node.newCmd = node.execCommand
   133  	e.nodes[node.ID] = node
   134  	return node, nil
   135  }
   136  
   137  // ExecNode starts a simulation node by exec'ing the current binary and
   138  // running the configured services
   139  type ExecNode struct {
   140  	ID     enode.ID
   141  	Dir    string
   142  	Config *execNodeConfig
   143  	Cmd    *exec.Cmd
   144  	Info   *p2p.NodeInfo
   145  
   146  	adapter *ExecAdapter
   147  	client  *rpc.Client
   148  	wsAddr  string
   149  	newCmd  func() *exec.Cmd
   150  	key     *ecdsa.PrivateKey
   151  }
   152  
   153  // Addr returns the node's enode URL
   154  func (n *ExecNode) Addr() []byte {
   155  	if n.Info == nil {
   156  		return nil
   157  	}
   158  	return []byte(n.Info.Enode)
   159  }
   160  
   161  // Client returns an rpc.Client which can be used to communicate with the
   162  // underlying services (it is set once the node has started)
   163  func (n *ExecNode) Client() (*rpc.Client, error) {
   164  	return n.client, nil
   165  }
   166  
   167  // Start exec's the node passing the ID and service as command line arguments
   168  // and the node config encoded as JSON in an environment variable.
   169  func (n *ExecNode) Start(snapshots map[string][]byte) (err error) {
   170  	if n.Cmd != nil {
   171  		return errors.New("already started")
   172  	}
   173  	defer func() {
   174  		if err != nil {
   175  			n.Stop()
   176  		}
   177  	}()
   178  
   179  	// encode a copy of the config containing the snapshot
   180  	confCopy := *n.Config
   181  	confCopy.Snapshots = snapshots
   182  	confCopy.PeerAddrs = make(map[string]string)
   183  	for id, node := range n.adapter.nodes {
   184  		confCopy.PeerAddrs[id.String()] = node.wsAddr
   185  	}
   186  	confData, err := json.Marshal(confCopy)
   187  	if err != nil {
   188  		return fmt.Errorf("error generating node config: %s", err)
   189  	}
   190  
   191  	// start the one-shot server that waits for startup information
   192  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   193  	defer cancel()
   194  	statusURL, statusC := n.waitForStartupJSON(ctx)
   195  
   196  	// start the node
   197  	cmd := n.newCmd()
   198  	cmd.Stdout = os.Stdout
   199  	cmd.Stderr = os.Stderr
   200  	cmd.Env = append(os.Environ(),
   201  		envStatusURL+"="+statusURL,
   202  		envNodeConfig+"="+string(confData),
   203  	)
   204  	if err := cmd.Start(); err != nil {
   205  		return fmt.Errorf("error starting node: %s", err)
   206  	}
   207  	n.Cmd = cmd
   208  
   209  	// read the WebSocket address from the stderr logs
   210  	status := <-statusC
   211  	if status.Err != "" {
   212  		return errors.New(status.Err)
   213  	}
   214  	client, err := rpc.DialWebsocket(ctx, status.WSEndpoint, "http://localhost")
   215  	if err != nil {
   216  		return fmt.Errorf("can't connect to RPC server: %v", err)
   217  	}
   218  
   219  	// node ready :)
   220  	n.client = client
   221  	n.wsAddr = status.WSEndpoint
   222  	n.Info = status.NodeInfo
   223  	return nil
   224  }
   225  
   226  // waitForStartupJSON runs a one-shot HTTP server to receive a startup report.
   227  func (n *ExecNode) waitForStartupJSON(ctx context.Context) (string, chan nodeStartupJSON) {
   228  	var (
   229  		ch       = make(chan nodeStartupJSON, 1)
   230  		quitOnce sync.Once
   231  		srv      http.Server
   232  	)
   233  	l, err := net.Listen("tcp", "127.0.0.1:0")
   234  	if err != nil {
   235  		ch <- nodeStartupJSON{Err: err.Error()}
   236  		return "", ch
   237  	}
   238  	quit := func(status nodeStartupJSON) {
   239  		quitOnce.Do(func() {
   240  			l.Close()
   241  			ch <- status
   242  		})
   243  	}
   244  	srv.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   245  		var status nodeStartupJSON
   246  		if err := json.NewDecoder(r.Body).Decode(&status); err != nil {
   247  			status.Err = fmt.Sprintf("can't decode startup report: %v", err)
   248  		}
   249  		quit(status)
   250  	})
   251  	// Run the HTTP server, but don't wait forever and shut it down
   252  	// if the context is canceled.
   253  	go srv.Serve(l)
   254  	go func() {
   255  		<-ctx.Done()
   256  		quit(nodeStartupJSON{Err: "didn't get startup report"})
   257  	}()
   258  
   259  	url := "http://" + l.Addr().String()
   260  	return url, ch
   261  }
   262  
   263  // execCommand returns a command which runs the node locally by exec'ing
   264  // the current binary but setting argv[0] to "p2p-node" so that the child
   265  // runs execP2PNode
   266  func (n *ExecNode) execCommand() *exec.Cmd {
   267  	return &exec.Cmd{
   268  		Path: reexec.Self(),
   269  		Args: []string{"p2p-node", strings.Join(n.Config.Node.Services, ","), n.ID.String()},
   270  	}
   271  }
   272  
   273  // Stop stops the node by first sending SIGTERM and then SIGKILL if the node
   274  // doesn't stop within 5s
   275  func (n *ExecNode) Stop() error {
   276  	if n.Cmd == nil {
   277  		return nil
   278  	}
   279  	defer func() {
   280  		n.Cmd = nil
   281  	}()
   282  
   283  	if n.client != nil {
   284  		n.client.Close()
   285  		n.client = nil
   286  		n.wsAddr = ""
   287  		n.Info = nil
   288  	}
   289  
   290  	if err := n.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
   291  		return n.Cmd.Process.Kill()
   292  	}
   293  	waitErr := make(chan error)
   294  	go func() {
   295  		waitErr <- n.Cmd.Wait()
   296  	}()
   297  	select {
   298  	case err := <-waitErr:
   299  		return err
   300  	case <-time.After(5 * time.Second):
   301  		return n.Cmd.Process.Kill()
   302  	}
   303  }
   304  
   305  // NodeInfo returns information about the node
   306  func (n *ExecNode) NodeInfo() *p2p.NodeInfo {
   307  	info := &p2p.NodeInfo{
   308  		ID: n.ID.String(),
   309  	}
   310  	if n.client != nil {
   311  		n.client.Call(&info, "admin_nodeInfo")
   312  	}
   313  	return info
   314  }
   315  
   316  // ServeRPC serves RPC requests over the given connection by dialling the
   317  // node's WebSocket address and joining the two connections
   318  func (n *ExecNode) ServeRPC(clientConn net.Conn) error {
   319  	conn, err := websocket.Dial(n.wsAddr, "", "http://localhost")
   320  	if err != nil {
   321  		return err
   322  	}
   323  	var wg sync.WaitGroup
   324  	wg.Add(2)
   325  	join := func(src, dst net.Conn) {
   326  		defer wg.Done()
   327  		io.Copy(dst, src)
   328  		// close the write end of the destination connection
   329  		if cw, ok := dst.(interface {
   330  			CloseWrite() error
   331  		}); ok {
   332  			cw.CloseWrite()
   333  		} else {
   334  			dst.Close()
   335  		}
   336  	}
   337  	go join(conn, clientConn)
   338  	go join(clientConn, conn)
   339  	wg.Wait()
   340  	return nil
   341  }
   342  
   343  // Snapshots creates snapshots of the services by calling the
   344  // simulation_snapshot RPC method
   345  func (n *ExecNode) Snapshots() (map[string][]byte, error) {
   346  	if n.client == nil {
   347  		return nil, errors.New("RPC not started")
   348  	}
   349  	var snapshots map[string][]byte
   350  	return snapshots, n.client.Call(&snapshots, "simulation_snapshot")
   351  }
   352  
   353  // execNodeConfig is used to serialize the node configuration so it can be
   354  // passed to the child process as a JSON encoded environment variable
   355  type execNodeConfig struct {
   356  	Stack     node.Config       `json:"stack"`
   357  	Node      *NodeConfig       `json:"node"`
   358  	Snapshots map[string][]byte `json:"snapshots,omitempty"`
   359  	PeerAddrs map[string]string `json:"peer_addrs,omitempty"`
   360  }
   361  
   362  // execP2PNode starts a simulation node when the current binary is executed with
   363  // argv[0] being "p2p-node", reading the service / ID from argv[1] / argv[2]
   364  // and the node config from an environment variable.
   365  func execP2PNode() {
   366  	glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat()))
   367  	glogger.Verbosity(log.LvlInfo)
   368  	log.Root().SetHandler(glogger)
   369  	statusURL := os.Getenv(envStatusURL)
   370  	if statusURL == "" {
   371  		log.Crit("missing " + envStatusURL)
   372  	}
   373  
   374  	// Start the node and gather startup report.
   375  	var status nodeStartupJSON
   376  	stack, stackErr := startExecNodeStack()
   377  	if stackErr != nil {
   378  		status.Err = stackErr.Error()
   379  	} else {
   380  		status.WSEndpoint = "ws://" + stack.WSEndpoint()
   381  		status.NodeInfo = stack.Server().NodeInfo()
   382  	}
   383  
   384  	// Send status to the host.
   385  	statusJSON, _ := json.Marshal(status)
   386  	if _, err := http.Post(statusURL, "application/json", bytes.NewReader(statusJSON)); err != nil {
   387  		log.Crit("Can't post startup info", "url", statusURL, "err", err)
   388  	}
   389  	if stackErr != nil {
   390  		os.Exit(1)
   391  	}
   392  
   393  	// Stop the stack if we get a SIGTERM signal.
   394  	go func() {
   395  		sigc := make(chan os.Signal, 1)
   396  		signal.Notify(sigc, syscall.SIGTERM)
   397  		defer signal.Stop(sigc)
   398  		<-sigc
   399  		log.Info("Received SIGTERM, shutting down...")
   400  		stack.Stop()
   401  	}()
   402  	stack.Wait() // Wait for the stack to exit.
   403  }
   404  
   405  func startExecNodeStack() (*node.Node, error) {
   406  	// read the services from argv
   407  	serviceNames := strings.Split(os.Args[1], ",")
   408  
   409  	// decode the config
   410  	confEnv := os.Getenv(envNodeConfig)
   411  	if confEnv == "" {
   412  		return nil, fmt.Errorf("missing " + envNodeConfig)
   413  	}
   414  	var conf execNodeConfig
   415  	if err := json.Unmarshal([]byte(confEnv), &conf); err != nil {
   416  		return nil, fmt.Errorf("error decoding %s: %v", envNodeConfig, err)
   417  	}
   418  
   419  	// create enode record
   420  	nodeTcpConn, _ := net.ResolveTCPAddr("tcp", conf.Stack.P2P.ListenAddr)
   421  	if nodeTcpConn.IP == nil {
   422  		nodeTcpConn.IP = net.IPv4(127, 0, 0, 1)
   423  	}
   424  	conf.Node.initEnode(nodeTcpConn.IP, nodeTcpConn.Port, nodeTcpConn.Port)
   425  	conf.Stack.P2P.PrivateKey = conf.Node.PrivateKey
   426  	conf.Stack.Logger = log.New("node.id", conf.Node.ID.String())
   427  
   428  	// initialize the devp2p stack
   429  	stack, err := node.New(&conf.Stack)
   430  	if err != nil {
   431  		return nil, fmt.Errorf("error creating node stack: %v", err)
   432  	}
   433  
   434  	// register the services, collecting them into a map so we can wrap
   435  	// them in a snapshot service
   436  	services := make(map[string]node.Service, len(serviceNames))
   437  	for _, name := range serviceNames {
   438  		serviceFunc, exists := serviceFuncs[name]
   439  		if !exists {
   440  			return nil, fmt.Errorf("unknown node service %q", err)
   441  		}
   442  		constructor := func(nodeCtx *node.ServiceContext) (node.Service, error) {
   443  			ctx := &ServiceContext{
   444  				RPCDialer:   &wsRPCDialer{addrs: conf.PeerAddrs},
   445  				NodeContext: nodeCtx,
   446  				Config:      conf.Node,
   447  			}
   448  			if conf.Snapshots != nil {
   449  				ctx.Snapshot = conf.Snapshots[name]
   450  			}
   451  			service, err := serviceFunc(ctx)
   452  			if err != nil {
   453  				return nil, err
   454  			}
   455  			services[name] = service
   456  			return service, nil
   457  		}
   458  		if err := stack.Register(constructor); err != nil {
   459  			return stack, fmt.Errorf("error registering service %q: %v", name, err)
   460  		}
   461  	}
   462  
   463  	// register the snapshot service
   464  	err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
   465  		return &snapshotService{services}, nil
   466  	})
   467  	if err != nil {
   468  		return stack, fmt.Errorf("error starting snapshot service: %v", err)
   469  	}
   470  
   471  	// start the stack
   472  	if err = stack.Start(); err != nil {
   473  		err = fmt.Errorf("error starting stack: %v", err)
   474  	}
   475  	return stack, err
   476  }
   477  
   478  const (
   479  	envStatusURL  = "_P2P_STATUS_URL"
   480  	envNodeConfig = "_P2P_NODE_CONFIG"
   481  )
   482  
   483  // nodeStartupJSON is sent to the simulation host after startup.
   484  type nodeStartupJSON struct {
   485  	Err        string
   486  	WSEndpoint string
   487  	NodeInfo   *p2p.NodeInfo
   488  }
   489  
   490  // snapshotService is a node.Service which wraps a list of services and
   491  // exposes an API to generate a snapshot of those services
   492  type snapshotService struct {
   493  	services map[string]node.Service
   494  }
   495  
   496  func (s *snapshotService) APIs() []rpc.API {
   497  	return []rpc.API{{
   498  		Namespace: "simulation",
   499  		Version:   "1.0",
   500  		Service:   SnapshotAPI{s.services},
   501  	}}
   502  }
   503  
   504  func (s *snapshotService) Protocols() []p2p.Protocol {
   505  	return nil
   506  }
   507  
   508  func (s *snapshotService) Start(*p2p.Server) error {
   509  	return nil
   510  }
   511  
   512  func (s *snapshotService) Stop() error {
   513  	return nil
   514  }
   515  
   516  // SnapshotAPI provides an RPC method to create snapshots of services
   517  type SnapshotAPI struct {
   518  	services map[string]node.Service
   519  }
   520  
   521  func (api SnapshotAPI) Snapshot() (map[string][]byte, error) {
   522  	snapshots := make(map[string][]byte)
   523  	for name, service := range api.services {
   524  		if s, ok := service.(interface {
   525  			Snapshot() ([]byte, error)
   526  		}); ok {
   527  			snap, err := s.Snapshot()
   528  			if err != nil {
   529  				return nil, err
   530  			}
   531  			snapshots[name] = snap
   532  		}
   533  	}
   534  	return snapshots, nil
   535  }
   536  
   537  type wsRPCDialer struct {
   538  	addrs map[string]string
   539  }
   540  
   541  // DialRPC implements the RPCDialer interface by creating a WebSocket RPC
   542  // client of the given node
   543  func (w *wsRPCDialer) DialRPC(id enode.ID) (*rpc.Client, error) {
   544  	addr, ok := w.addrs[id.String()]
   545  	if !ok {
   546  		return nil, fmt.Errorf("unknown node: %s", id)
   547  	}
   548  	return rpc.DialWebsocket(context.Background(), addr, "http://localhost")
   549  }