|
@@ -26,10 +26,120 @@ from collections import namedtuple
|
|
|
from mmgen.exception import *
|
|
|
from mmgen.common import *
|
|
|
|
|
|
-class CoinDaemon(MMGenObject):
|
|
|
- cfg_file_hdr = ''
|
|
|
+class Daemon(MMGenObject):
|
|
|
|
|
|
subclasses_must_implement = ('state','stop_cmd')
|
|
|
+ debug = False
|
|
|
+ wait = True
|
|
|
+ use_pidfile = True
|
|
|
+ conf_file = None
|
|
|
+
|
|
|
+ def subclass_init(self): pass
|
|
|
+
|
|
|
+ def exec_cmd_thread(self,cmd,check):
|
|
|
+ import threading
|
|
|
+ t = threading.Thread(target=self.exec_cmd,args=(cmd,check))
|
|
|
+ t.daemon = True
|
|
|
+ t.start()
|
|
|
+ Msg_r(' \b') # blocks w/o this...crazy
|
|
|
+
|
|
|
+ def exec_cmd(self,cmd,check):
|
|
|
+ cp = run(cmd,check=False,stdout=PIPE,stderr=PIPE)
|
|
|
+ if check and cp.returncode != 0:
|
|
|
+ raise MMGenCalledProcessError(cp)
|
|
|
+ return cp
|
|
|
+
|
|
|
+ def run_cmd(self,cmd,silent=False,check=True,is_daemon=False):
|
|
|
+ if is_daemon and not silent:
|
|
|
+ msg('Starting {} {}'.format(self.net_desc,self.desc))
|
|
|
+
|
|
|
+ if self.debug:
|
|
|
+ msg('\nExecuting: {}'.format(' '.join(cmd)))
|
|
|
+
|
|
|
+ if self.platform == 'win' and is_daemon:
|
|
|
+ cp = self.exec_cmd_thread(cmd,check)
|
|
|
+ else:
|
|
|
+ cp = self.exec_cmd(cmd,check)
|
|
|
+ if cp:
|
|
|
+ out = cp.stdout.decode().rstrip()
|
|
|
+ err = cp.stderr.decode().rstrip()
|
|
|
+ if out and (self.debug or not silent):
|
|
|
+ msg(out)
|
|
|
+ if err and (self.debug or (cp.returncode and not silent)):
|
|
|
+ msg(err)
|
|
|
+
|
|
|
+ return cp
|
|
|
+
|
|
|
+ @property
|
|
|
+ def pid(self):
|
|
|
+ return open(self.pidfile).read().strip() if self.use_pidfile else '(unknown)'
|
|
|
+
|
|
|
+ def cmd(self,action,*args,**kwargs):
|
|
|
+ return getattr(self,action)(*args,**kwargs)
|
|
|
+
|
|
|
+ def do_start(self,silent=False):
|
|
|
+ if not silent:
|
|
|
+ msg('Starting {} {}'.format(self.net_desc,self.desc))
|
|
|
+ return self.run_cmd(self.start_cmd,silent=True,is_daemon=True)
|
|
|
+
|
|
|
+ def do_stop(self,silent=False):
|
|
|
+ if not silent:
|
|
|
+ msg('Stopping {} {}'.format(self.net_desc,self.desc))
|
|
|
+ return self.run_cmd(self.stop_cmd,silent=True)
|
|
|
+
|
|
|
+ def cli(self,*cmds,silent=False,check=True):
|
|
|
+ return self.run_cmd(self.cli_cmd(*cmds),silent=silent,check=check)
|
|
|
+
|
|
|
+ def start(self,silent=False):
|
|
|
+ if self.is_ready:
|
|
|
+ if not silent:
|
|
|
+ m = '{} {} already running with pid {}'
|
|
|
+ msg(m.format(self.net_desc,self.desc,self.pid))
|
|
|
+ else:
|
|
|
+ os.makedirs(self.datadir,exist_ok=True)
|
|
|
+ if self.conf_file:
|
|
|
+ open('{}/{}'.format(self.datadir,self.conf_file),'w').write(self.cfg_file_hdr)
|
|
|
+ if self.use_pidfile and os.path.exists(self.pidfile):
|
|
|
+ # Parity just overwrites the data in an existing pidfile, leading to
|
|
|
+ # interesting consequences.
|
|
|
+ os.unlink(self.pidfile)
|
|
|
+ ret = self.do_start(silent=silent)
|
|
|
+ if self.wait:
|
|
|
+ self.wait_for_state('ready')
|
|
|
+ return ret
|
|
|
+
|
|
|
+ def stop(self,silent=False):
|
|
|
+ if self.is_ready:
|
|
|
+ ret = self.do_stop(silent=silent)
|
|
|
+ if self.wait:
|
|
|
+ self.wait_for_state('stopped')
|
|
|
+ return ret
|
|
|
+ else:
|
|
|
+ if not silent:
|
|
|
+ msg('{} {} not running'.format(self.net_desc,self.desc))
|
|
|
+ # rm -rf $datadir
|
|
|
+
|
|
|
+ def wait_for_state(self,req_state):
|
|
|
+ for i in range(200):
|
|
|
+ if self.state == req_state:
|
|
|
+ return True
|
|
|
+ time.sleep(0.2)
|
|
|
+ else:
|
|
|
+ die(2,'Daemon wait timeout for {} {} exceeded'.format(self.coin,self.network))
|
|
|
+
|
|
|
+ @property
|
|
|
+ def is_ready(self):
|
|
|
+ return self.state == 'ready'
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def check_implement(cls):
|
|
|
+ m = 'required method {}() missing in class {}'
|
|
|
+ for subcls in cls.__subclasses__():
|
|
|
+ for k in cls.subclasses_must_implement:
|
|
|
+ assert k in subcls.__dict__, m.format(k,subcls.__name__)
|
|
|
+
|
|
|
+class CoinDaemon(Daemon):
|
|
|
+ cfg_file_hdr = ''
|
|
|
|
|
|
network_ids = ('btc','btc_tn','btc_rt','bch','bch_tn','bch_rt','ltc','ltc_tn','ltc_rt','xmr','eth','etc')
|
|
|
|
|
@@ -43,12 +153,7 @@ class CoinDaemon(MMGenObject):
|
|
|
'etc': cd('Ethereum Classic','parity', 'parity', 'parity.conf', 8545,None,None)
|
|
|
}
|
|
|
|
|
|
- debug = False
|
|
|
- wait = True
|
|
|
- use_pidfile = True
|
|
|
-
|
|
|
testnet_arg = []
|
|
|
-
|
|
|
coind_args = []
|
|
|
cli_args = []
|
|
|
shared_args = []
|
|
@@ -70,7 +175,7 @@ class CoinDaemon(MMGenObject):
|
|
|
rel_datadir = os.path.join('test','daemons')
|
|
|
desc = 'test suite daemon'
|
|
|
elif not network_id.endswith('_rt'):
|
|
|
- raise RuntimeError('only test suite and regtest supported')
|
|
|
+ raise RuntimeError('only test suite and regtest supported for CoinDaemon')
|
|
|
|
|
|
if network_id.endswith('_tn'):
|
|
|
daemon_id = network_id[:-3]
|
|
@@ -122,49 +227,6 @@ class CoinDaemon(MMGenObject):
|
|
|
self.net_desc = '{} {}'.format(self.coin,self.network)
|
|
|
self.subclass_init()
|
|
|
|
|
|
- def subclass_init(self): pass
|
|
|
-
|
|
|
- def exec_cmd_thread(self,cmd,check):
|
|
|
- import threading
|
|
|
- t = threading.Thread(target=self.exec_cmd,args=(cmd,check))
|
|
|
- t.daemon = True
|
|
|
- t.start()
|
|
|
- Msg_r(' \b') # blocks w/o this...crazy
|
|
|
-
|
|
|
- def exec_cmd(self,cmd,check):
|
|
|
- cp = run(cmd,check=False,stdout=PIPE,stderr=PIPE)
|
|
|
- if check and cp.returncode != 0:
|
|
|
- raise MMGenCalledProcessError(cp)
|
|
|
- return cp
|
|
|
-
|
|
|
- def run_cmd(self,cmd,silent=False,check=True,is_daemon=False):
|
|
|
- if is_daemon and not silent:
|
|
|
- msg('Starting {} {}'.format(self.net_desc,self.desc))
|
|
|
-
|
|
|
- if self.debug:
|
|
|
- msg('\nExecuting: {}'.format(' '.join(cmd)))
|
|
|
-
|
|
|
- if self.platform == 'win' and is_daemon:
|
|
|
- cp = self.exec_cmd_thread(cmd,check)
|
|
|
- else:
|
|
|
- cp = self.exec_cmd(cmd,check)
|
|
|
- if cp:
|
|
|
- out = cp.stdout.decode().rstrip()
|
|
|
- err = cp.stderr.decode().rstrip()
|
|
|
- if out and (self.debug or not silent):
|
|
|
- msg(out)
|
|
|
- if err and (self.debug or (cp.returncode and not silent)):
|
|
|
- msg(err)
|
|
|
-
|
|
|
- return cp
|
|
|
-
|
|
|
- @property
|
|
|
- def pid(self):
|
|
|
- return open(self.pidfile).read().strip() if self.use_pidfile else '(unknown)'
|
|
|
-
|
|
|
- def cmd(self,action,*args,**kwargs):
|
|
|
- return getattr(self,action)(*args,**kwargs)
|
|
|
-
|
|
|
@property
|
|
|
def start_cmd(self):
|
|
|
return ([self.coind_exec]
|
|
@@ -188,67 +250,6 @@ class CoinDaemon(MMGenObject):
|
|
|
+ self.usr_shared_args
|
|
|
+ list(cmds))
|
|
|
|
|
|
- def do_start(self,silent=False):
|
|
|
- if not silent:
|
|
|
- msg('Starting {} {}'.format(self.net_desc,self.desc))
|
|
|
- return self.run_cmd(self.start_cmd,silent=True,is_daemon=True)
|
|
|
-
|
|
|
- def do_stop(self,silent=False):
|
|
|
- if not silent:
|
|
|
- msg('Stopping {} {}'.format(self.net_desc,self.desc))
|
|
|
- return self.run_cmd(self.stop_cmd,silent=True)
|
|
|
-
|
|
|
- def cli(self,*cmds,silent=False,check=True):
|
|
|
- return self.run_cmd(self.cli_cmd(*cmds),silent=silent,check=check)
|
|
|
-
|
|
|
- def start(self,silent=False):
|
|
|
- if self.is_ready:
|
|
|
- if not silent:
|
|
|
- m = '{} {} already running with pid {}'
|
|
|
- msg(m.format(self.net_desc,self.desc,self.pid))
|
|
|
- else:
|
|
|
- os.makedirs(self.datadir,exist_ok=True)
|
|
|
- if self.conf_file:
|
|
|
- open('{}/{}'.format(self.datadir,self.conf_file),'w').write(self.cfg_file_hdr)
|
|
|
- if self.use_pidfile and os.path.exists(self.pidfile):
|
|
|
- # Parity just overwrites the data in an existing pidfile, leading to
|
|
|
- # interesting consequences.
|
|
|
- os.unlink(self.pidfile)
|
|
|
- ret = self.do_start(silent=silent)
|
|
|
- if self.wait:
|
|
|
- self.wait_for_state('ready')
|
|
|
- return ret
|
|
|
-
|
|
|
- def stop(self,silent=False):
|
|
|
- if self.is_ready:
|
|
|
- ret = self.do_stop(silent=silent)
|
|
|
- if self.wait:
|
|
|
- self.wait_for_state('stopped')
|
|
|
- return ret
|
|
|
- else:
|
|
|
- if not silent:
|
|
|
- msg('{} {} not running'.format(self.net_desc,self.desc))
|
|
|
- # rm -rf $datadir
|
|
|
-
|
|
|
- def wait_for_state(self,req_state):
|
|
|
- for i in range(200):
|
|
|
- if self.state == req_state:
|
|
|
- return True
|
|
|
- time.sleep(0.2)
|
|
|
- else:
|
|
|
- die(2,'Daemon wait timeout for {} {} exceeded'.format(self.coin,self.network))
|
|
|
-
|
|
|
- @property
|
|
|
- def is_ready(self):
|
|
|
- return self.state == 'ready'
|
|
|
-
|
|
|
- @classmethod
|
|
|
- def check_implement(cls):
|
|
|
- m = 'required method {}() missing in class {}'
|
|
|
- for subcls in cls.__subclasses__():
|
|
|
- for k in cls.subclasses_must_implement:
|
|
|
- assert k in subcls.__dict__, m.format(k,subcls.__name__)
|
|
|
-
|
|
|
class BitcoinDaemon(CoinDaemon):
|
|
|
cfg_file_hdr = '# BitcoinDaemon config file\n'
|
|
|
|