github.com/lbryio/lbcd@v0.22.119/integration/rpctest/node.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package rpctest
     6  
     7  import (
     8  	"fmt"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"runtime"
    15  	"time"
    16  
    17  	rpc "github.com/lbryio/lbcd/rpcclient"
    18  	btcutil "github.com/lbryio/lbcutil"
    19  )
    20  
    21  // nodeConfig contains all the args, and data required to launch a btcd process
    22  // and connect the rpc client to it.
    23  type nodeConfig struct {
    24  	rpcUser    string
    25  	rpcPass    string
    26  	listen     string
    27  	rpcListen  string
    28  	rpcConnect string
    29  	dataDir    string
    30  	logDir     string
    31  	profile    string
    32  	debugLevel string
    33  	extra      []string
    34  	prefix     string
    35  
    36  	exe          string
    37  	endpoint     string
    38  	certFile     string
    39  	keyFile      string
    40  	certificates []byte
    41  }
    42  
    43  // newConfig returns a newConfig with all default values.
    44  func newConfig(prefix, certFile, keyFile string, extra []string,
    45  	customExePath string) (*nodeConfig, error) {
    46  
    47  	var btcdPath string
    48  	if customExePath != "" {
    49  		btcdPath = customExePath
    50  	} else {
    51  		var err error
    52  		btcdPath, err = btcdExecutablePath()
    53  		if err != nil {
    54  			btcdPath = "lbcd"
    55  		}
    56  	}
    57  
    58  	a := &nodeConfig{
    59  		listen:    "127.0.0.1:18555",
    60  		rpcListen: "127.0.0.1:18556",
    61  		rpcUser:   "user",
    62  		rpcPass:   "pass",
    63  		extra:     extra,
    64  		prefix:    prefix,
    65  		exe:       btcdPath,
    66  		endpoint:  "ws",
    67  		certFile:  certFile,
    68  		keyFile:   keyFile,
    69  	}
    70  	if err := a.setDefaults(); err != nil {
    71  		return nil, err
    72  	}
    73  	return a, nil
    74  }
    75  
    76  // setDefaults sets the default values of the config. It also creates the
    77  // temporary data, and log directories which must be cleaned up with a call to
    78  // cleanup().
    79  func (n *nodeConfig) setDefaults() error {
    80  	datadir, err := ioutil.TempDir("", n.prefix+"-data")
    81  	if err != nil {
    82  		return err
    83  	}
    84  	n.dataDir = datadir
    85  	logdir, err := ioutil.TempDir("", n.prefix+"-logs")
    86  	if err != nil {
    87  		return err
    88  	}
    89  	n.logDir = logdir
    90  	cert, err := ioutil.ReadFile(n.certFile)
    91  	if err != nil {
    92  		return err
    93  	}
    94  	n.certificates = cert
    95  	return nil
    96  }
    97  
    98  // arguments returns an array of arguments that be used to launch the btcd
    99  // process.
   100  func (n *nodeConfig) arguments() []string {
   101  	args := []string{}
   102  	if n.rpcUser != "" {
   103  		// --rpcuser
   104  		args = append(args, fmt.Sprintf("--rpcuser=%s", n.rpcUser))
   105  	}
   106  	if n.rpcPass != "" {
   107  		// --rpcpass
   108  		args = append(args, fmt.Sprintf("--rpcpass=%s", n.rpcPass))
   109  	}
   110  	if n.listen != "" {
   111  		// --listen
   112  		args = append(args, fmt.Sprintf("--listen=%s", n.listen))
   113  	}
   114  	if n.rpcListen != "" {
   115  		// --rpclisten
   116  		args = append(args, fmt.Sprintf("--rpclisten=%s", n.rpcListen))
   117  	}
   118  	if n.rpcConnect != "" {
   119  		// --rpcconnect
   120  		args = append(args, fmt.Sprintf("--rpcconnect=%s", n.rpcConnect))
   121  	}
   122  	// --rpccert
   123  	args = append(args, fmt.Sprintf("--rpccert=%s", n.certFile))
   124  	// --rpckey
   125  	args = append(args, fmt.Sprintf("--rpckey=%s", n.keyFile))
   126  	if n.dataDir != "" {
   127  		// --datadir
   128  		args = append(args, fmt.Sprintf("--datadir=%s", n.dataDir))
   129  	}
   130  	if n.logDir != "" {
   131  		// --logdir
   132  		args = append(args, fmt.Sprintf("--logdir=%s", n.logDir))
   133  	}
   134  	if n.profile != "" {
   135  		// --profile
   136  		args = append(args, fmt.Sprintf("--profile=%s", n.profile))
   137  	}
   138  	if n.debugLevel != "" {
   139  		// --debuglevel
   140  		args = append(args, fmt.Sprintf("--debuglevel=%s", n.debugLevel))
   141  	}
   142  	args = append(args, n.extra...)
   143  	return args
   144  }
   145  
   146  // command returns the exec.Cmd which will be used to start the btcd process.
   147  func (n *nodeConfig) command() *exec.Cmd {
   148  	return exec.Command(n.exe, n.arguments()...)
   149  }
   150  
   151  // rpcConnConfig returns the rpc connection config that can be used to connect
   152  // to the btcd process that is launched via Start().
   153  func (n *nodeConfig) rpcConnConfig() rpc.ConnConfig {
   154  	return rpc.ConnConfig{
   155  		Host:                 n.rpcListen,
   156  		Endpoint:             n.endpoint,
   157  		User:                 n.rpcUser,
   158  		Pass:                 n.rpcPass,
   159  		Certificates:         n.certificates,
   160  		DisableAutoReconnect: true,
   161  	}
   162  }
   163  
   164  // String returns the string representation of this nodeConfig.
   165  func (n *nodeConfig) String() string {
   166  	return n.prefix
   167  }
   168  
   169  // cleanup removes the tmp data and log directories.
   170  func (n *nodeConfig) cleanup() error {
   171  	dirs := []string{
   172  		n.logDir,
   173  		n.dataDir,
   174  	}
   175  	var err error
   176  	for _, dir := range dirs {
   177  		if err = os.RemoveAll(dir); err != nil {
   178  			log.Printf("Cannot remove dir %s: %v", dir, err)
   179  		}
   180  	}
   181  	return err
   182  }
   183  
   184  // node houses the necessary state required to configure, launch, and manage a
   185  // btcd process.
   186  type node struct {
   187  	config *nodeConfig
   188  
   189  	cmd     *exec.Cmd
   190  	pidFile string
   191  
   192  	dataDir string
   193  }
   194  
   195  // newNode creates a new node instance according to the passed config. dataDir
   196  // will be used to hold a file recording the pid of the launched process, and
   197  // as the base for the log and data directories for btcd.
   198  func newNode(config *nodeConfig, dataDir string) (*node, error) {
   199  	return &node{
   200  		config:  config,
   201  		dataDir: dataDir,
   202  		cmd:     config.command(),
   203  	}, nil
   204  }
   205  
   206  // start creates a new btcd process, and writes its pid in a file reserved for
   207  // recording the pid of the launched process. This file can be used to
   208  // terminate the process in case of a hang, or panic. In the case of a failing
   209  // test case, or panic, it is important that the process be stopped via stop(),
   210  // otherwise, it will persist unless explicitly killed.
   211  func (n *node) start() error {
   212  	if err := n.cmd.Start(); err != nil {
   213  		return err
   214  	}
   215  
   216  	pid, err := os.Create(filepath.Join(n.dataDir,
   217  		fmt.Sprintf("%s.pid", n.config)))
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	n.pidFile = pid.Name()
   223  	if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil {
   224  		return err
   225  	}
   226  
   227  	if err := pid.Close(); err != nil {
   228  		return err
   229  	}
   230  
   231  	return nil
   232  }
   233  
   234  // stop interrupts the running btcd process process, and waits until it exits
   235  // properly. On windows, interrupt is not supported, so a kill signal is used
   236  // instead
   237  func (n *node) stop() error {
   238  	if n.cmd == nil || n.cmd.Process == nil {
   239  		// return if not properly initialized
   240  		// or error starting the process
   241  		return nil
   242  	}
   243  	defer n.cmd.Wait()
   244  	if runtime.GOOS == "windows" {
   245  		return n.cmd.Process.Signal(os.Kill)
   246  	}
   247  	return n.cmd.Process.Signal(os.Interrupt)
   248  }
   249  
   250  // cleanup cleanups process and args files. The file housing the pid of the
   251  // created process will be deleted, as well as any directories created by the
   252  // process.
   253  func (n *node) cleanup() error {
   254  	if n.pidFile != "" {
   255  		if err := os.Remove(n.pidFile); err != nil {
   256  			log.Printf("unable to remove file %s: %v", n.pidFile,
   257  				err)
   258  		}
   259  	}
   260  
   261  	return n.config.cleanup()
   262  }
   263  
   264  // shutdown terminates the running btcd process, and cleans up all
   265  // file/directories created by node.
   266  func (n *node) shutdown() error {
   267  	if err := n.stop(); err != nil {
   268  		return err
   269  	}
   270  	if err := n.cleanup(); err != nil {
   271  		return err
   272  	}
   273  	return nil
   274  }
   275  
   276  // genCertPair generates a key/cert pair to the paths provided.
   277  func genCertPair(certFile, keyFile string) error {
   278  	org := "rpctest autogenerated cert"
   279  	validUntil := time.Now().Add(10 * 365 * 24 * time.Hour)
   280  	cert, key, err := btcutil.NewTLSCertPair(org, validUntil, nil)
   281  	if err != nil {
   282  		return err
   283  	}
   284  
   285  	// Write cert and key files.
   286  	if err = ioutil.WriteFile(certFile, cert, 0666); err != nil {
   287  		return err
   288  	}
   289  	if err = ioutil.WriteFile(keyFile, key, 0600); err != nil {
   290  		os.Remove(certFile)
   291  		return err
   292  	}
   293  
   294  	return nil
   295  }