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 }