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