def _run_command_keys(options, reactor, personality): """ Subcommand "crossbar keys". """ log = make_logger() # Generate a new node key pair (2 files), load and check _maybe_generate_key(options.cbdir) # Print keys # Release (public) key release_pubkey = _read_release_key() # Node key node_key = _read_node_key(options.cbdir, private=options.private) if options.private: key_title = 'Crossbar.io Node PRIVATE Key' else: key_title = 'Crossbar.io Node PUBLIC Key' log.info('') log.info('{key_title}', key_title=hl('Crossbar Software Release Key', color='yellow', bold=True)) log.info('base64: {release_pubkey}', release_pubkey=release_pubkey['base64']) log.info(release_pubkey['qrcode'].strip()) log.info('') log.info('{key_title}', key_title=hl(key_title, color='yellow', bold=True)) log.info('hex: {node_key}', node_key=node_key['hex']) log.info(node_key['qrcode'].strip()) log.info('')
def _run_command_keys(options, reactor, personality): """ Subcommand "crossbar keys". """ log = make_logger() from crossbar.common.key import _read_node_key from crossbar.common.key import _read_release_key if options.generate: # Generate a new node key pair (2 files), load and check _maybe_generate_key(options.cbdir) else: # Print keys # Release (public) key release_pubkey = _read_release_key() # Node key node_key = _read_node_key(options.cbdir, private=options.private) if options.private: key_title = 'Crossbar.io Node PRIVATE Key' else: key_title = 'Crossbar.io Node PUBLIC Key' log.info('') log.info('{key_title}', key_title=hl('Crossbar Software Release Key', color='yellow', bold=True)) log.info('base64: {release_pubkey}', release_pubkey=release_pubkey[u'base64']) log.info(release_pubkey[u'qrcode'].strip()) log.info('') log.info('{key_title}', key_title=hl(key_title, color='yellow', bold=True)) log.info('hex: {node_key}', node_key=node_key[u'hex']) log.info(node_key[u'qrcode'].strip()) log.info('')
def start(self, prefix): """ Start the Metadata manager. :return: """ assert self._started is None, 'cannot start Metadata manager - already running!' regs = yield self._session.register( self, prefix=prefix, options=RegisterOptions(details_arg='details')) self._prefix = prefix procs = [reg.procedure for reg in regs] self.log.debug( 'Mrealm controller {api} registered management procedures [{func}]:\n\n{procs}\n', api=hl('Metadata manager API', color='green', bold=True), func=hltype(self.start), procs=hl(pformat(procs), color='white', bold=True)) self._started = time_ns() self.log.info( 'Metadata manager ready for management realm {mrealm_oid}! [{func}]', mrealm_oid=hlid(self._mrealm_oid), func=hltype(self.start))
def onJoin(self, details): """ Called when process has joined the node's management realm. """ regs = yield self.register( self, prefix='{}.'.format(self._uri_prefix), options=RegisterOptions(details_arg='details'), ) procs = [] errors = [] for reg in regs: if isinstance(reg, Failure): self.log.error("Failed to register management procedure: {f}", f=reg, log_failure=reg) errors.append(str(reg)) else: procs.append(reg.procedure) if errors: raise ApplicationError('crossbar.error.cannot_start', 'management API could not be initialized', errors=errors) else: self.log.debug( 'Ok, registered {len_reg} management procedures on realm "{realm}" [{func}]:\n\n{procs}\n', len_reg=hlval(len(regs)), realm=hl(self.realm), func=hltype(self.onJoin), procs=hl(pformat(procs), color='white', bold=True)) returnValue(regs)
def onJoin(self, details): self.log.info('Backend.onJoin(details={details})', details=details) self._pid = os.getpid() self._logname = self.config.extra.get('logname', None) self._url = self.config.extra.get('url', None) # run-time session statistics for CALLs self._received_calls_cnt = 0 self._received_calls_bytes = 0 self._received_calls_ff_cnt = 0 uri = '{}.proc1'.format(self._logname) def proc1(logname, url, loop, counter, payload, details=None): fingerprint = hashlib.sha256(payload).digest()[:6] self.log.info( '{logprefix}: INVOCATION received for {uri} on pid={pid} from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, caller={caller}, caller_authid={caller_authid}, caller_authrole={caller_authrole}, forward_for={forward_for}', logprefix=hl('WAMP {}:{}'.format(self._url, self._logname), color='blue', bold=True), pid=hl(self._pid, color='blue', bold=True), sender=hl('{}:{}'.format(url, logname), color='blue', bold=True), loop=loop, counter=counter, procedure=details.procedure, payload_len=len(payload), uri=hl(uri, color='yellow', bold=True), fp=hl(binascii.b2a_hex(fingerprint).decode(), color='blue', bold=True), caller=hl(details.caller, color='blue', bold=True), caller_authid=hl(details.caller_authid, color='blue', bold=True), caller_authrole=hl(details.caller_authrole, color='blue', bold=True), forward_for=details.forward_for) fingerprint = hashlib.sha256(payload).digest()[:6] self._received_calls_cnt += 1 self._received_calls_bytes += len(payload) if details.forward_for: self._received_calls_ff_cnt += 1 return fingerprint, self._pid, self._logname, self._url yield self.register(proc1, uri, options=RegisterOptions(match='exact', invoke='random', details_arg='details')) self.log.info('*' * 80) self.log.info('Backend procedure registered at {uri}', uri=hl(uri)) self.log.info('*' * 80)
def start_worker(self, worker_id, worker_type, worker_options=None, details=None): """ Start a new worker process in the node. """ if type(worker_id) != str or worker_id in ['controller', '']: raise Exception('invalid worker ID "{}"'.format(worker_id)) self.log.info( 'Starting {worker_type}-worker "{worker_id}" .. {worker_klass}', worker_type=hl(worker_type), worker_id=hlid(worker_id), worker_klass=hltype(NodeController.start_worker)) if worker_type == 'guest': return self._start_guest_worker(worker_id, worker_options, details=details) elif worker_type in self._node._native_workers: return self._start_native_worker(worker_type, worker_id, worker_options, details=details) else: raise Exception('invalid worker type "{}"'.format(worker_type))
def __init__(self, config): ApplicationSession.__init__(self, config) self._connected_nodes = {} # public keys of superusers self._superusers = [] if 'CROSSBAR_FABRIC_SUPERUSER' in os.environ: superuser_keyfile = os.environ['CROSSBAR_FABRIC_SUPERUSER'].strip() if superuser_keyfile: pubkey_file = os.path.abspath( os.environ['CROSSBAR_FABRIC_SUPERUSER']) if not os.path.exists(pubkey_file): raise Exception( 'superuser public key file {} (set from CROSSBAR_FABRIC_SUPERUSER env var) does not exist' .format(pubkey_file)) with open(pubkey_file, 'r') as f: data = f.read() pubkey_hex = None for line in data.splitlines(): if line.startswith('public-key-ed25519'): pubkey_hex = line.split(':')[1].strip() break if pubkey_hex is None: raise Exception( 'no public key line found in super user public key file {}' .format(pubkey_file)) self._superusers.append(pubkey_hex) self.log.info( hl('SUPERUSER public key {} loaded from {}'.format( pubkey_hex, pubkey_file), color='green', bold=True))
def on_management_event(*args, **kwargs): if not (self._manager and self._manager.is_attached()): self.log.warn( "Can't foward management event: CFC session not attached") return details = kwargs.pop('details') # a node local event such as 'crossbar.node.on_ready' is mogrified to 'local.crossbar.node.on_ready' # (one reason is that URIs such as 'wamp.*' and 'crossbar.*' are restricted to trusted sessions, and # the management bridge is connecting over network to the uplink CFC and hence can't be trusted) # topic = self._translate_uri(details.topic) try: yield self._manager.publish( topic, *args, options=PublishOptions(acknowledge=True), **kwargs) except Exception: self.log.failure( "Failed to forward event on topic '{topic}': {log_failure.value}", topic=topic, ) else: if topic.endswith('.on_log'): log = self.log.debug else: log = self.log.debug log('Forwarded management {forward_type} to CFC [local_uri={local_topic}, remote_uri={remote_topic}]', forward_type=hl('EVENT'), local_topic=hlid(details.topic), remote_topic=hlid(topic))
def forward(*args, **kwargs): details = kwargs.pop('details', None) if details: match = pat.match(details.topic) if match: node_id, worker_id = match.groups() # FIXME: map back from node authid (?) to node OID (as UUID string)? self.log.debug( 'Forwarding CFC {forward_type} on mrealm {realm} from node {node_id} and worker {worker_id} [local=<{local_uri}>, remote=<{remote_uri}>]', forward_type=hl('EVENT'), local_uri=hlid(local_uri), remote_uri=hlid(details.topic), node_id=hluserid(node_id), worker_id=hluserid(worker_id), realm=hluserid(session._realm)) return session.publish( local_uri, node_id, worker_id, *args, **kwargs, options=PublishOptions(exclude_me=False)) # should not arrive here session.log.warn( 'received unexpected event to forward for management API: local_uri={local_uri}, remote_uri={remote_uri}, remote_uri_regex={remote_uri_regex} details={details}', local_uri=local_uri, remote_uri=remote_uri, remote_uri_regex=remote_uri_regex, details=details)
def onJoin(self, details): self._prefix = self.config.extra['prefix'] self._logname = self.config.extra['logname'] self._count = 0 self._pinfo = ProcessInfo() def echo(arg): self._count += 1 return arg yield self.register(echo, '{}.echo'.format(self._prefix), options=RegisterOptions(invoke='roundrobin')) self.log.info('{} ready!'.format(self._logname)) last = None while True: stats = self._pinfo.get_stats() if last: secs = (stats['time'] - last['time']) / 10**9 calls_per_sec = int(round(self._count / secs, 0)) ctx = round((stats['voluntary'] - last['voluntary']) / secs, 0) self.log.info( '{logprefix}: {user} user, {system} system, {mem_percent} mem_percent, {ctx} ctx', logprefix=hl('LOAD {}'.format(self._logname), color='cyan', bold=True), user=stats['user'], system=stats['system'], mem_percent=round(stats['mem_percent'], 1), ctx=ctx) self.log.info( '{logprefix}: {calls} calls, {calls_per_sec} calls/second', logprefix=hl('WAMP {}'.format(self._logname), color='cyan', bold=True), calls=self._count, calls_per_sec=hl(calls_per_sec, bold=True)) self._count = 0 last = stats yield sleep(10)
def shutdown(): for p in pl: try: pid = p.transport.pid p.transport.signalProcess('KILL') print('Client with PID {} killed'.format(hl(pid, bold=True))) except ProcessExitedAlready: pass
def forward_call(*args, **kwargs): kwargs.pop('details', None) self.log.debug( 'Forwarding management {forward_type} from CFC .. [remote_uri={remote_uri}, local_uri={local_uri}]', forward_type=hl('CALL'), local_uri=hlid(local_uri), remote_uri=hlid(remote_uri)) return self.call(local_uri, *args, **kwargs)
def _process_block(self, w3, block_number, Events): """ :param w3: :param block_number: :param Events: :return: """ cnt = 0 # filter by block, and XBR contract addresses # FIXME: potentially add filters for global data or market specific data for the markets started in this worker filter_params = { 'address': [ xbr.xbrtoken.address, xbr.xbrnetwork.address, xbr.xbrcatalog.address, xbr.xbrmarket.address, xbr.xbrchannel.address ], 'fromBlock': block_number, 'toBlock': block_number, } result = w3.eth.getLogs(filter_params) if result: for evt in result: receipt = w3.eth.getTransactionReceipt(evt['transactionHash']) for Event, handler in Events: # FIXME: MismatchedABI pops up .. we silence this with errors=web3.logs.DISCARD if hasattr(web3, 'logs') and web3.logs: all_res = Event().processReceipt(receipt, errors=web3.logs.DISCARD) else: all_res = Event().processReceipt(receipt) for res in all_res: self.log.info('{handler} processing block {block_number} / txn {txn} with args {args}', handler=hl(handler.__name__), block_number=hlid(block_number), txn=hlid('0x' + binascii.b2a_hex(evt['transactionHash']).decode()), args=hlval(res.args)) handler(res.transactionHash, res.blockHash, res.args) cnt += 1 with self._db.begin(write=True) as txn: block = cfxdb.xbr.block.Block() block.timestamp = np.datetime64(time_ns(), 'ns') block.block_number = block_number # FIXME # block.block_hash = bytes() block.cnt_events = cnt self._xbr.blocks[txn, pack_uint256(block_number)] = block if cnt: self.log.info('Processed blockchain block {block_number}: processed {cnt} XBR events.', block_number=hlid(block_number), cnt=hlid(cnt)) else: self.log.info('Processed blockchain block {block_number}: no XBR events found!', block_number=hlid(block_number)) return cnt
def proc1(self, logname, url, loop, counter, payload, details=None): fingerprint = hashlib.sha256(payload).digest()[:6] self.log.info( '{logprefix}: INVOCATION received on pid={pid} from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, caller={caller}, caller_authid={caller_authid}, caller_authrole={caller_authrole}, forward_for={forward_for}', logprefix=hl('WAMP {}:{}'.format(self._url, self._logname), color='blue', bold=True), pid=hl(self._pid, color='blue', bold=True), sender=hl('{}:{}'.format(url, logname), color='blue', bold=True), loop=loop, counter=counter, procedure=details.procedure, payload_len=len(payload), fp=hl(binascii.b2a_hex(fingerprint).decode(), color='blue', bold=True), caller=hl(details.caller, color='blue', bold=True), caller_authid=hl(details.caller_authid, color='blue', bold=True), caller_authrole=hl(details.caller_authrole, color='blue', bold=True), forward_for=details.forward_for) fingerprint = hashlib.sha256(payload).digest()[:6] self._received_calls_cnt += 1 self._received_calls_bytes += len(payload) if details.forward_for: self._received_calls_ff_cnt += 1 return fingerprint, self._pid, self._logname, self._url
def start(self, node_id=None): self.log.info( '{note} [{method}]', note=hl('Starting node (initialize master-node personality) ..', color='green', bold=True), method=hltype(FabricCenterNode.start)) res = yield node.FabricNode.start(self, node_id) return res
def _run_command_legal(options, reactor, personality, verbose=True): """ Subcommand "crossbar legal". """ if verbose: docs = [personality.LEGAL, personality.LICENSE, personality.LICENSE_FOR_API, personality.LICENSES_OSS] else: docs = [personality.LEGAL] print(hl('*' * 120, bold=True, color='yellow')) for package, resource_name in docs: filename = pkg_resources.resource_filename(package, resource_name) filepath = os.path.abspath(filename) print(hl(' ' + filepath + ' :\n', bold=False, color='yellow')) with open(filepath) as f: legal = f.read() print(hl(legal, bold=True, color='white')) print(hl('*' * 120, bold=True, color='yellow'))
def _print_usage(prog, personality): print(hl(personality.BANNER, color='yellow', bold=True)) print( 'Type "{} --help" to get help, or "{} <command> --help" to get help on a specific command.' .format(prog, prog)) print( 'Type "{} legal" to read legal notices, terms of use and license and privacy information.' .format(prog)) print('Type "{} version" to print detailed version information.'.format( prog))
def start(self, node_id=None): self.log.info('{note} [{method}]', note=hl('Starting node (initialize edge-node personality) ..', color='green', bold=True), method=hltype(FabricNode.start)) # run watchdog at 5Hz self._watchdog_looper = LoopingCall(self._watchdog) self._watchdog_looper.start(.2) res = yield node.Node.start(self, node_id or self._node_id) return res
def start_router_realm(self, realm_id, realm_config, details=None): self.log.info('Starting router realm "{realm_id}" {method}', realm_id=realm_id, method=hltype(ExtRouterController.start_router_realm)) # activate this to test: if False and realm_config['name'] == 'realm1': self.log.info(hl('Auto-renaming realm1 to realm001', color='green', bold=True)) realm_config['name'] = 'realm001' return RouterController.start_router_realm(self, realm_id, realm_config, details)
def _run_command_legal(options, reactor, personality, verbose=True): """ Subcommand "crossbar legal". """ if verbose: docs = [personality.LEGAL, personality.LICENSE, personality.LICENSE_FOR_API, personality.LICENSES_OSS] else: docs = [personality.LEGAL] print(hl('*' * 120, bold=True, color='yellow')) for package, resource_name in docs: filename = pkg_resources.resource_filename(package, resource_name) filepath = os.path.abspath(filename) print(hl(' ' + filepath + ' :\n', bold=False, color='yellow')) with open(filepath) as f: legal = f.read() print(hl(legal, bold=True, color='white')) print(hl('*' * 120, bold=True, color='yellow'))
def on_event1(self, logname, url, loop, counter, payload, details=None): fingerprint = hashlib.sha256(payload).digest()[:6] self.log.debug( '{logprefix}: EVENT received from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, publisher={publisher}, publisher_authid={publisher_authid}, publisher_authole={publisher_authrole}, forward_for={forward_for}', logprefix=hl('WAMP {}:{}'.format(self._url, self._logname), color='blue', bold=True), sender=hl('{}:{}'.format(url, logname), color='blue', bold=True), loop=loop, counter=counter, topic=details.topic, payload_len=len(payload), fp=hl(binascii.b2a_hex(fingerprint).decode(), color='blue', bold=True), publisher=hl(details.publisher, color='blue', bold=True), publisher_authid=hl(details.publisher_authid, color='blue', bold=True), publisher_authrole=hl(details.publisher_authrole, color='blue', bold=True), forward_for=details.forward_for) self._received_cnt += 1 self._received_bytes += len(payload) if details.forward_for: self._received_ff_cnt += 1
def check_result(result, uri): print('-' * 100, result) _fingerprint, _pid, _logname, _url = result.results[ 0] self.log.info( '{logprefix}: CALL RESULT for {uri} received from pid={pid}, logname={logname}, url={url}, callee={callee}, callee_authid={callee_authid}, callee_authrole={callee_authrole}, forward_for={forward_for}, fp={fp} => fp_equal={fp_equal}', logprefix=hl('WAMP {}:{}'.format( self._url, self._logname), color='green', bold=True), pid=hl(_pid, color='green', bold=True), logname=_logname, url=_url, fp=hl(binascii.b2a_hex(fingerprint).decode(), color='green', bold=True), fp_equal=(_fingerprint == fingerprint), uri=hl(uri, color='yellow', bold=True), callee=result.callee, callee_authid=result.callee_authid, callee_authrole=result.callee_authrole, forward_for=result.forward_for) assert _fingerprint == fingerprint
def start_market_maker(self, maker_id, config, details=None): """ Starts a XBR Market Maker providing services in a specific XBR market. """ if type(maker_id) != str: emsg = 'maker_id has invalid type {}'.format(type(maker_id)) raise ApplicationError('wamp.error.invalid_argument', emsg) if not isinstance(config, Mapping): emsg = 'maker_id has invalid type {}'.format(type(config)) raise ApplicationError('wamp.error.invalid_argument', emsg) if maker_id in self._makers: emsg = 'could not start market maker: a market maker with ID "{}" is already running (or starting)'.format( maker_id) raise ApplicationError('crossbar.error.already_running', emsg) self.personality.check_market_maker(self.personality, config) self.log.info('XBR Market Maker "{maker_id}" starting with config:\n{config}', maker_id=hlid(maker_id), config=pformat(config)) maker = MarketMaker(self, maker_id, config, self._db, self._ipfs_files_dir) self._makers[maker_id] = maker self._maker_adr2id[maker.address] = maker_id yield maker.start() status = yield maker.status() self.log.info('{msg}: {accounts} local accounts, current block number is {current_block_no}', msg=hl('Blockchain status', color='green', bold=True), current_block_no=hlid(status['current_block_no']), accounts=hlid(len(status['accounts']))) started = { 'id': maker_id, 'address': maker.address, } self.publish(u'{}.on_maker_started'.format(self._uri_prefix), started) self.log.info( 'XBR Market Maker "{maker_id}" (address {maker_adr}) started. Now running {maker_cnt} market makers in total in this worker component.', maker_id=maker_id, maker_adr=maker.address, maker_cnt=len(self._makers)) returnValue(started)
def _run_command_version(options, reactor, personality): """ Subcommand "crossbar version". """ log = make_logger() v = _get_versions(reactor) def decorate(text, fg='white', bg=None, bold=True): return click.style(text, fg=fg, bg=bg, bold=bold) pad = " " * 22 for line in personality.BANNER.splitlines(): log.info(hl(line, color='yellow', bold=True)) log.info("") log.info(" Crossbar.io : {ver}", ver=decorate(v.crossbar_ver)) log.info(" txaio : {ver}", ver=decorate(v.txaio_ver)) log.info(" Autobahn : {ver}", ver=decorate(v.ab_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.ab_loc)) log.debug(" UTF8 Validator : {ver}", ver=decorate(v.utf8_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.utf8_loc)) log.debug(" XOR Masker : {ver}", ver=decorate(v.xor_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.xor_loc)) log.debug(" JSON Codec : {ver}", ver=decorate(v.json_ver)) log.debug(" MsgPack Codec : {ver}", ver=decorate(v.msgpack_ver)) log.debug(" CBOR Codec : {ver}", ver=decorate(v.cbor_ver)) log.debug(" UBJSON Codec : {ver}", ver=decorate(v.ubjson_ver)) log.info(" Twisted : {ver}", ver=decorate(v.tx_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.tx_loc)) log.info(" LMDB : {ver}", ver=decorate(v.lmdb_ver)) log.info(" Python : {ver}/{impl}", ver=decorate(v.py_ver), impl=decorate(v.py_ver_detail)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.py_ver_string)) if personality.NAME in (u'edge', u'master'): log.info(" Crossbar.io FX : {ver}", ver=decorate(v.crossbarfx_ver)) log.info(" NumPy : {ver}", ver=decorate(v.numpy_ver)) log.info(" zLMDB : {ver}", ver=decorate(v.zlmdb_ver)) log.info(" XBR : {ver}", ver=decorate(v.xbr_ver)) log.info(" Frozen executable : {py_is_frozen}", py_is_frozen=decorate('yes' if v.py_is_frozen else 'no')) log.info(" Operating system : {ver}", ver=decorate(v.platform)) log.info(" Host machine : {ver}", ver=decorate(v.machine)) log.info(" Release key : {release_pubkey}", release_pubkey=decorate(v.release_pubkey)) log.info("")
def _stats(self, batch_id): stats = self._pinfo.get_stats() if self._last_stats: batch_duration = (stats['time'] - self._last_stats['time']) / 10**9 ctx = round((stats['voluntary'] - self._last_stats['voluntary']) / batch_duration, 0) self.log.info( '{logprefix}: {user} user, {system} system, {mem_percent} mem_percent, {ctx} ctx', logprefix=hl('LOAD {}.*'.format(self._logname), color='magenta', bold=True), logname=self._logname, user=stats['user'], system=stats['system'], mem_percent=round(stats['mem_percent'], 1), ctx=ctx) self._last_stats = stats
def forward(node_oid, *args, **kwargs): # remove the calling origin details (do not forward those 1:1 at least - FIXME) kwargs.pop('details', None) # map the node OID (given as UUID string) to the node WAMP authid for .. node_authid = session.map_node_oid_to_authid(node_oid) # .. creating the remote URI to be used on the management uplink _remote_uri = remote_uri.format(node_id=node_authid) self.log.debug( 'Forwarding CFC {forward_type} on mrealm {realm} to node "{node_authid}" [node_oid={node_oid}, local=<{local_uri}>, remote=<{remote_uri}>]', forward_type=hl('CALL'), local_uri=hlid(local_uri), remote_uri=hlid(_remote_uri), node_oid=hlid(node_oid), node_authid=hluserid(node_authid), realm=hluserid(session._realm)) return session.call(_remote_uri, *args, **kwargs)
def _do_get(self, request): try: node_status = yield self._worker.call('crossbar.get_status') # FIXME: # master_status = yield self._service_agent.call('crossbarfabriccenter.domain.get_status') # master_license = yield self._service_agent.call('crossbarfabriccenter.domain.get_license') # master_version = yield self._service_agent.call('crossbarfabriccenter.domain.get_version') infos = { 'node_status': node_status, # 'master_status': master_status, # 'master_license': master_license, # 'master_version': master_version, } self.log.debug('rendering {page} page using data\n{data}', page=hl('pair-node', bold=True), data=pformat(infos)) self._delayedRender(infos, request) except: self.log.failure() request.finish()
def onJoin(self, details): """ Called when process has joined the node's management realm. """ regs = yield self.register( self, prefix='{}.'.format(self._uri_prefix), options=RegisterOptions(details_arg='details'), ) self.log.info( 'Ok, registered {len_reg} management procedures on realm "{realm}".', len_reg=hlval(len(regs)), realm=hl(self.realm)) for reg in regs: if isinstance(reg, Failure): self.log.error("Failed to register: {f}", f=reg, log_failure=reg) else: self.log.debug(' {proc}', proc=hlid(reg.procedure)) returnValue(regs)
def main(reactor, args): colorama.init() pl = [] for i in range(args.clients): proto = yield start_client(reactor, args, i) pl.append(proto) delay = random.expovariate(1) yield sleep(delay) pids = [p.transport.pid for p in pl] print('Started clients with PIDs {}'.format(hl(pids, bold=True))) def shutdown(): for p in pl: try: pid = p.transport.pid p.transport.signalProcess('KILL') print('Client with PID {} killed'.format(hl(pid, bold=True))) except ProcessExitedAlready: pass reactor.addSystemEventTrigger('before', 'shutdown', shutdown) success = False dl = [p.all_done for p in pl] try: yield gatherResults(dl) success = True except Exception as e: print("FAILED:", e) if success: returnValue(0) else: returnValue(1)
def _run_command_version(options, reactor, personality): """ Subcommand "crossbar version". """ log = make_logger() v = _get_versions(reactor) def decorate(text, fg='white', bg=None, bold=True): return click.style(text, fg=fg, bg=bg, bold=bold) for line in personality.BANNER.splitlines(): log.info(hl(line, color='yellow', bold=True)) log.info("") log.info(" Crossbar.io : {ver}", ver=decorate(v.crossbar_ver)) log.info(" txaio : {ver}", ver=decorate(v.txaio_ver)) log.info(" Autobahn : {ver}", ver=decorate(v.ab_ver)) log.info(" UTF8 Validator : {ver}", ver=decorate(v.utf8_ver)) log.info(" XOR Masker : {ver}", ver=decorate(v.xor_ver)) log.info(" JSON Codec : {ver}", ver=decorate(v.json_ver)) log.info(" MsgPack Codec : {ver}", ver=decorate(v.msgpack_ver)) log.info(" CBOR Codec : {ver}", ver=decorate(v.cbor_ver)) log.info(" UBJSON Codec : {ver}", ver=decorate(v.ubjson_ver)) log.info(" FlatBuffers : {ver}", ver=decorate(v.flatbuffers_ver)) log.info(" Twisted : {ver}", ver=decorate(v.tx_ver)) log.info(" LMDB : {ver}", ver=decorate(v.lmdb_ver)) log.info(" Python : {ver}/{impl}", ver=decorate(v.py_ver), impl=decorate(v.py_ver_detail)) if personality.NAME in (u'edge', u'master'): log.info(" CrossbarFX : {ver}", ver=decorate(v.crossbarfx_ver)) log.info(" NumPy : {ver}", ver=decorate(v.numpy_ver)) log.info(" zLMDB : {ver}", ver=decorate(v.zlmdb_ver)) log.info(" XBR : {ver}", ver=decorate(v.xbr_ver)) log.info(" Frozen executable : {py_is_frozen}", py_is_frozen=decorate('yes' if v.py_is_frozen else 'no')) log.info(" Operating system : {ver}", ver=decorate(v.platform)) log.info(" Host machine : {ver}", ver=decorate(v.machine)) log.info(" Release key : {release_pubkey}", release_pubkey=decorate(v.release_pubkey)) log.info("")
def _run_command_status(options, reactor, personality): """ Subcommand "crossbar status". """ log = make_logger() # https://docs.python.org/2/library/os.html#os.EX_UNAVAILABLE # https://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3 _EXIT_ERROR = getattr(os, 'EX_UNAVAILABLE', 1) # check if there is a Crossbar.io instance currently running from # the Crossbar.io node directory at all pid_data = _check_is_running(options.cbdir) # optional current state to assert _assert = options.__dict__['assert'] if pid_data is None: if _assert == 'running': log.error('Assert status RUNNING failed: status is {}'.format( hl('STOPPED', color='red', bold=True))) sys.exit(_EXIT_ERROR) elif _assert == 'stopped': log.info('Assert status STOPPED succeeded: status is {}'.format( hl('STOPPED', color='green', bold=True))) sys.exit(0) else: log.info('Status is {}'.format( hl('STOPPED', color='white', bold=True))) sys.exit(0) else: if _assert == 'running': log.info('Assert status RUNNING succeeded: status is {}'.format( hl('RUNNING', color='green', bold=True))) sys.exit(0) elif _assert == 'stopped': log.error('Assert status STOPPED failed: status is {}'.format( hl('RUNNING', color='red', bold=True))) sys.exit(_EXIT_ERROR) else: log.info('Status is {}'.format( hl('RUNNING', color='white', bold=True))) sys.exit(0)
def _run_command_status(options, reactor, personality): """ Subcommand "crossbar status". """ log = make_logger() # https://docs.python.org/2/library/os.html#os.EX_UNAVAILABLE # https://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3 _EXIT_ERROR = getattr(os, 'EX_UNAVAILABLE', 1) # check if there is a Crossbar.io instance currently running from # the Crossbar.io node directory at all pid_data = _check_is_running(options.cbdir) # optional current state to assert _assert = options.__dict__['assert'] if pid_data is None: if _assert == 'running': log.error('Assert status RUNNING failed: status is {}'.format(hl('STOPPED', color='red', bold=True))) sys.exit(_EXIT_ERROR) elif _assert == 'stopped': log.info('Assert status STOPPED succeeded: status is {}'.format(hl('STOPPED', color='green', bold=True))) sys.exit(0) else: log.info('Status is {}'.format(hl('STOPPED', color='white', bold=True))) sys.exit(0) else: if _assert == 'running': log.info('Assert status RUNNING succeeded: status is {}'.format(hl('RUNNING', color='green', bold=True))) sys.exit(0) elif _assert == 'stopped': log.error('Assert status STOPPED failed: status is {}'.format(hl('RUNNING', color='red', bold=True))) sys.exit(_EXIT_ERROR) else: log.info('Status is {}'.format(hl('RUNNING', color='white', bold=True))) sys.exit(0)
async def _initialize_mrealm(self, mrealm): """ Initializes a single management realm: 1. start a respective realm on the router worker of this master node. Currently there is only one router worker statically configured (named "cfrouter1"). 2. start a couple of roles on the management realm (see BUILTIN_ROLES) 3. :param mrealm: The management realm to initialize. :type mrealm: :class:`cfxdb.mrealm.management_realm.ManagementRealm` :return: """ self.log.debug('{klass}._initialize_mrealm(mrealm={mrealm})', klass=self.__class__.__name__, mrealm=mrealm) # WAMP realm name of the realm to start realm_name = mrealm.name # router and container worker for realm to start router_id = mrealm.cf_router_worker or 'cfrouter1' container_id = mrealm.cf_container_worker or 'cfcontainer1' self.log.info( hl('Initializing management realm "{}" [router_id="{}", container_id="{}"] ..' .format(realm_name, router_id, container_id), color='red', bold=True)) # start the management realm and roles on the configured router worker if realm_name not in self._router_realms: realm_config = { u'name': realm_name, u'options': { u'enable_meta_api': True, # FIXME: enabling this will break stopping and restarting an mrealm, as the # procedures registered eg by the RouterServiceAgent session will stick around! u'bridge_meta_api': False, } } realm_id = realm_name await self.call( u'crossbar.worker.{}.start_router_realm'.format(router_id), realm_id, realm_config) for role_id in [ u'owner-role', u'backend-role', u'node-role', u'public-role' ]: await self.call( u'crossbar.worker.{}.start_router_realm_role'.format( router_id), realm_id, role_id, BUILTIN_ROLES[role_id]) self._router_realms[realm_name] = True else: self.log.warn( 'Management realm "{realm_name}" already initialized (skipped starting realm)', realm_name=realm_name) # start the configured container worker (if not yet started) if container_id not in self._container_workers: container_options = { "pythonpath": [".."], "expose_shared": True, "expose_controller": True, # "shutdown": "shutdown-on-last-component-stopped", "shutdown": "shutdown-manual", # "restart": "restart-on-failed" "restart": "restart-always" } await self.call(u'crossbar.start_worker', container_id, u'container', container_options) self._container_workers[container_id] = {} else: self.log.warn( 'Management realm "{realm_name}" backend container "{container_id}" already running (skipped starting container)', realm_name=realm_name, container_id=container_id) # start the management realm backend in the configured container worker component_id = realm_name if component_id not in self._container_workers[container_id]: mrealm_backend_extra = { u'mrealm': str(mrealm.oid), u'database': { # the mrealm database path contains the mrealm UUID u'dbfile': os.path.join(self.config.extra['cbdir'], u'.db-mrealm-{}'.format(mrealm.oid)), # hard-code max mrealm database size to 2GB # https://github.com/crossbario/crossbar/issues/235 u'maxsize': 2**30 * 2, }, u'controller-database': { # forward controller database parameters, so that the mrealm backend can _also_ open # the controller database (read-only) u'dbfile': self._db.dbpath, u'maxsize': self._db.maxsize, }, } component_config = { u'type': u'class', u'classname': u'crossbar.master.mrealm.MrealmController', u'realm': realm_name, u'transport': { # we connect back to the master router over UDS/RawSocket/CBOR u'type': u'rawsocket', u'endpoint': { u'type': u'unix', u'path': u'sock1' }, u'serializer': u'cbor', }, u'extra': mrealm_backend_extra } await self.call( u'crossbar.worker.{}.start_component'.format(container_id), component_id, component_config) self._container_workers[container_id][component_id] = True else: self.log.warn( 'Management realm "{realm_name}" backend component "{component_id}" already running (skipped starting container component)', realm_name=realm_name, component_id=component_id) self.log.info( '{note} {func}', note=hl( 'Ok, management realm "{}" initialized!'.format(realm_name), color='red', bold=True), func=hltype(self._initialize_mrealm))
async def onJoin(self, details: ComponentConfig): # handles to controller database and schema (already initialized/attached in node) # ready = self.config.extra['ready'] cbdir = self.config.extra['cbdir'] config = self.config.extra.get(u'database', {}) # create database and attach tables to database slots # dbpath = config.get('path', '.db-controller') assert type(dbpath) == str dbpath = os.path.join(cbdir, dbpath) maxsize = config.get('maxsize', 128 * 2**20) assert type(maxsize) == int # allow maxsize 128kiB to 128GiB assert maxsize >= 128 * 1024 and maxsize <= 128 * 2**30 self.log.info('{klass} starting [dbpath={dbpath}, maxsize={maxsize}]', klass=self.__class__.__name__, dbpath=hlid(dbpath), maxsize=hlid(maxsize)) self._db = zlmdb.Database(dbpath=dbpath, maxsize=maxsize, readonly=False, sync=True) self._db.__enter__() self._schema = GlobalSchema.attach(self._db) # initialize all currently existing mrealms try: await self._initialize_mrealms() except Exception: self.log.failure() raise # expose api on this object fo CFC clients domains = [(_CFC_GLOBAL_REALM, self.register)] for prefix, register in domains: registrations = await register( self, prefix=prefix, options=RegisterOptions(details_arg='details')) for reg in registrations: if type(reg) == Registration: self.log.info( 'Registered CFC Global Realm "{realm}" API <{proc}>', proc=reg.procedure, realm=self._realm) else: self.log.error('Error: <{}>'.format(reg.value.args[0])) # we are ready to roll self.log.info('{note} {klass}', note=hl('Ok, master node "{}" booted and ready!'.format( self._node._node_id), color='red', bold=True), klass=hltype(self.onJoin)) ready.callback(self)
def _run_command_version(options, reactor, personality): """ Subcommand "crossbar version". """ log = make_logger() # Python py_ver = '.'.join([str(x) for x in list(sys.version_info[:3])]) py_ver_string = "[%s]" % sys.version.replace('\n', ' ') if 'pypy_version_info' in sys.__dict__: py_ver_detail = "{}-{}".format(platform.python_implementation(), '.'.join(str(x) for x in sys.pypy_version_info[:3])) else: py_ver_detail = platform.python_implementation() # Pyinstaller (frozen EXE) py_is_frozen = getattr(sys, 'frozen', False) # Twisted / Reactor tx_ver = "%s-%s" % (_get_version('twisted'), reactor.__class__.__name__) tx_loc = "[%s]" % qual(reactor.__class__) # txaio txaio_ver = _get_version('txaio') # Autobahn ab_ver = _get_version('autobahn') ab_loc = "[%s]" % qual(WebSocketProtocol) # UTF8 Validator s = qual(Utf8Validator) if 'wsaccel' in s: utf8_ver = 'wsaccel-%s' % _get_version('wsaccel') elif s.startswith('autobahn'): utf8_ver = 'autobahn' else: # could not detect UTF8 validator type/version utf8_ver = '?' utf8_loc = "[%s]" % qual(Utf8Validator) # XOR Masker s = qual(XorMaskerNull) if 'wsaccel' in s: xor_ver = 'wsaccel-%s' % _get_version('wsaccel') elif s.startswith('autobahn'): xor_ver = 'autobahn' else: # could not detect XOR masker type/version xor_ver = '?' xor_loc = "[%s]" % qual(XorMaskerNull) # JSON Serializer supported_serializers = ['JSON'] from autobahn.wamp.serializer import JsonObjectSerializer json_ver = JsonObjectSerializer.JSON_MODULE.__name__ # If it's just 'json' then it's the stdlib one... if json_ver == 'json': json_ver = 'stdlib' else: json_ver = (json_ver + "-%s") % _get_version(json_ver) # MsgPack Serializer try: import umsgpack # noqa msgpack_ver = 'u-msgpack-python-%s' % _get_version(umsgpack) supported_serializers.append('MessagePack') except ImportError: msgpack_ver = '-' # CBOR Serializer try: import cbor # noqa cbor_ver = 'cbor-%s' % _get_version(cbor) supported_serializers.append('CBOR') except ImportError: cbor_ver = '-' # UBJSON Serializer try: import ubjson # noqa ubjson_ver = 'ubjson-%s' % _get_version(ubjson) supported_serializers.append('UBJSON') except ImportError: ubjson_ver = '-' # LMDB try: import lmdb # noqa lmdb_lib_ver = '.'.join([str(x) for x in lmdb.version()]) lmdb_ver = '{}/lmdb-{}'.format(_get_version(lmdb), lmdb_lib_ver) except ImportError: lmdb_ver = '-' # crossbarfx try: from crossbarfx._version import __version__ as crossbarfx_ver # noqa except ImportError: crossbarfx_ver = '-' # txaio-etcd try: import txaioetcd # noqa txaioetcd_ver = _get_version(txaioetcd) except ImportError: txaioetcd_ver = '-' # Release Public Key from crossbar.common.key import _read_release_key release_pubkey = _read_release_key() def decorate(text, fg='white', bg=None, bold=True): return click.style(text, fg=fg, bg=bg, bold=bold) pad = " " * 22 for line in personality.BANNER.splitlines(): log.info(hl(line, color='yellow', bold=True)) log.info("") log.info(" Crossbar.io : {ver}", ver=decorate(crossbar.__version__)) log.info(" Autobahn : {ver}", ver=decorate(ab_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(ab_loc)) log.debug(" txaio : {ver}", ver=decorate(txaio_ver)) log.debug(" UTF8 Validator : {ver}", ver=decorate(utf8_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(utf8_loc)) log.debug(" XOR Masker : {ver}", ver=decorate(xor_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(xor_loc)) log.debug(" JSON Codec : {ver}", ver=decorate(json_ver)) log.debug(" MsgPack Codec : {ver}", ver=decorate(msgpack_ver)) log.debug(" CBOR Codec : {ver}", ver=decorate(cbor_ver)) log.debug(" UBJSON Codec : {ver}", ver=decorate(ubjson_ver)) log.info(" Twisted : {ver}", ver=decorate(tx_ver)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(tx_loc)) log.info(" LMDB : {ver}", ver=decorate(lmdb_ver)) log.info(" Python : {ver}/{impl}", ver=decorate(py_ver), impl=decorate(py_ver_detail)) log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(py_ver_string)) if personality.NAME in (u'edge', u'master'): log.info(" Crossbar.io FX : {ver}", ver=decorate(crossbarfx_ver)) if personality.NAME in (u'master'): log.info(" txaioetcd : {ver}", ver=decorate(txaioetcd_ver)) log.info(" Frozen executable : {py_is_frozen}", py_is_frozen=decorate('yes' if py_is_frozen else 'no')) log.info(" Operating system : {ver}", ver=decorate(platform.platform())) log.info(" Host machine : {ver}", ver=decorate(platform.machine())) log.info(" Release key : {release_pubkey}", release_pubkey=decorate(release_pubkey[u'base64'])) log.info("")
def _run_command_start(options, reactor, personality): """ Subcommand "crossbar start". """ # do not allow to run more than one Crossbar.io instance # from the same Crossbar.io node directory # pid_data = _check_is_running(options.cbdir) if pid_data: print("Crossbar.io is already running from node directory {} (PID {}).".format(options.cbdir, pid_data['pid'])) sys.exit(1) else: fp = os.path.join(options.cbdir, _PID_FILENAME) with open(fp, 'wb') as fd: argv = options.argv options_dump = vars(options) pid_data = { 'pid': os.getpid(), 'argv': argv, 'options': {x: y for x, y in options_dump.items() if x not in ["func", "argv"]} } fd.write("{}\n".format( json.dumps( pid_data, sort_keys=False, indent=4, separators=(', ', ': '), ensure_ascii=False ) ).encode('utf8')) # remove node PID file when reactor exits # def remove_pid_file(): fp = os.path.join(options.cbdir, _PID_FILENAME) if os.path.isfile(fp): os.remove(fp) reactor.addSystemEventTrigger('after', 'shutdown', remove_pid_file) log = make_logger() # represents the running Crossbar.io node # node_options = personality.NodeOptions(debug_lifecycle=options.debug_lifecycle, debug_programflow=options.debug_programflow) node = personality.Node(personality, options.cbdir, reactor=reactor, options=node_options) # print the banner, personality and node directory # for line in personality.BANNER.splitlines(): log.info(hl(line, color='yellow', bold=True)) log.info('') log.info('Initializing {node_personality} node from node directory {cbdir} {node_class}', node_personality=personality, cbdir=hlid(options.cbdir), node_class=hltype(personality.Node)) # possibly generate new node key # node.load_keys(options.cbdir) # check and load the node configuration # try: node.load_config(options.config) except InvalidConfigException as e: log.failure() log.error("Invalid node configuration") log.error("{e!s}", e=e) sys.exit(1) except: raise # https://twistedmatrix.com/documents/current/api/twisted.internet.interfaces.IReactorCore.html # Each "system event" in Twisted, such as 'startup', 'shutdown', and 'persist', has 3 phases: # 'before', 'during', and 'after' (in that order, of course). These events will be fired # internally by the Reactor. def before_reactor_started(): term_print('CROSSBAR:REACTOR_STARTING') def after_reactor_started(): term_print('CROSSBAR:REACTOR_STARTED') reactor.addSystemEventTrigger('before', 'startup', before_reactor_started) reactor.addSystemEventTrigger('after', 'startup', after_reactor_started) def before_reactor_stopped(): term_print('CROSSBAR:REACTOR_STOPPING') def after_reactor_stopped(): # FIXME: we are indeed reaching this line, however, # the log output does not work (it also doesnt work using # plain old print). Dunno why. # my theory about this issue is: by the time this line # is reached, Twisted has already closed the stdout/stderr # pipes. hence we do an evil trick: we directly write to # the process' controlling terminal # https://unix.stackexchange.com/a/91716/52500 term_print('CROSSBAR:REACTOR_STOPPED') reactor.addSystemEventTrigger('before', 'shutdown', before_reactor_stopped) reactor.addSystemEventTrigger('after', 'shutdown', after_reactor_stopped) # now actually start the node .. # exit_info = {'was_clean': None} def start_crossbar(): term_print('CROSSBAR:NODE_STARTING') # # ****** main entry point of node ****** # d = node.start() # node started successfully, and later .. def on_startup_success(_shutdown_complete): term_print('CROSSBAR:NODE_STARTED') shutdown_complete = _shutdown_complete['shutdown_complete'] # .. exits, signaling exit status _inside_ the result returned def on_shutdown_success(shutdown_info): exit_info['was_clean'] = shutdown_info['was_clean'] log.info('on_shutdown_success: was_clean={was_clean}', shutdown_info['was_clean']) # should not arrive here: def on_shutdown_error(err): exit_info['was_clean'] = False log.error("on_shutdown_error: {tb}", tb=failure_format_traceback(err)) shutdown_complete.addCallbacks(on_shutdown_success, on_shutdown_error) # node could not even start def on_startup_error(err): term_print('CROSSBAR:NODE_STARTUP_FAILED') exit_info['was_clean'] = False log.error("Could not start node: {tb}", tb=failure_format_traceback(err)) if reactor.running: reactor.stop() d.addCallbacks(on_startup_success, on_startup_error) # Call a function when the reactor is running. If the reactor has not started, the callable # will be scheduled to run when it does start. reactor.callWhenRunning(start_crossbar) # Special feature to automatically shutdown the node after this many seconds if options.shutdownafter: @inlineCallbacks def _shutdown(): term_print('CROSSBAR:SHUTDOWN_AFTER_FIRED') shutdown_info = yield node.stop() exit_info['was_clean'] = shutdown_info['was_clean'] term_print('CROSSBAR:SHUTDOWN_AFTER_COMPLETE') reactor.callLater(options.shutdownafter, _shutdown) # now enter event loop .. # log.info(hl('Entering event reactor ...', color='cyan', bold=True)) term_print('CROSSBAR:REACTOR_ENTERED') reactor.run() # once the reactor has finally stopped, we get here, and at that point, # exit_info['was_clean'] MUST have been set before - either to True or to False # (otherwise we are missing a code path to handle in above) # exit the program with exit code depending on whether the node has been cleanly shut down if exit_info['was_clean'] is True: term_print('CROSSBAR:EXIT_WITH_SUCCESS') sys.exit(0) elif exit_info['was_clean'] is False: term_print('CROSSBAR:EXIT_WITH_ERROR') sys.exit(1) else: term_print('CROSSBAR:EXIT_WITH_INTERNAL_ERROR') sys.exit(1)
def _print_usage(prog, personality): print(hl(personality.BANNER, color='yellow', bold=True)) print('Type "{} --help" to get help, or "{} <command> --help" to get help on a specific command.'.format(prog, prog)) print('Type "{} legal" to read legal notices, terms of use and license and privacy information.'.format(prog)) print('Type "{} version" to print detailed version information.'.format(prog))
def boot_from_config(self, config): """ Startup elements in the node as specified in the provided node configuration. """ # get controller configuration subpart controller = config.get('controller', {}) parallel_worker_start = controller.get('options', {}).get('enable_parallel_worker_start', False) self.log.info('{bootmsg} {method}', bootmsg=hl('Booting node from local configuration [parallel_worker_start={}] ..'.format(parallel_worker_start), color='green', bold=True), method=hltype(Node.boot_from_config)) # start Manhole in node controller if 'manhole' in controller: yield self._controller.call(u'crossbar.start_manhole', controller['manhole'], options=CallOptions()) self.log.debug("controller: manhole started") # startup all workers workers = config.get('workers', []) if len(workers): self.log.info(hl('Will start {} worker{} ..'.format(len(workers), 's' if len(workers) > 1 else ''), color='green', bold=True)) else: self.log.info(hl('No workers configured, nothing to do', color='green', bold=True)) dl = [] for worker in workers: # worker ID if 'id' in worker: worker_id = worker['id'] else: worker_id = u'worker{:03d}'.format(self._worker_no) worker['id'] = worker_id self._worker_no += 1 # worker type: either a native worker ('router', 'container', ..), or a guest worker ('guest') worker_type = worker['type'] # native worker processes setup if worker_type in self._native_workers: # set logname depending on native worker type worker_logname = '{} {}'.format(self._native_workers[worker_type]['logname'], hlid(worker_id)) # any worker specific options worker_options = worker.get('options', {}) # start the (native) worker self.log.info( "Order node to start {worker_logname}", worker_logname=worker_logname, ) d = self._controller.call(u'crossbar.start_worker', worker_id, worker_type, worker_options, options=CallOptions()) @inlineCallbacks def configure_worker(res, worker_logname, worker_type, worker_id, worker): self.log.info( "Ok, node has started {worker_logname}", worker_logname=worker_logname, ) # now configure the worker self.log.info( "Configuring {worker_logname} ..", worker_logname=worker_logname, ) method_name = '_configure_native_worker_{}'.format(worker_type.replace('-', '_')) try: config_fn = getattr(self, method_name) except AttributeError: raise ValueError( "A native worker of type '{}' is configured but " "there is no method '{}' on {}".format(worker_type, method_name, type(self)) ) yield config_fn(worker_logname, worker_id, worker) self.log.info( "Ok, {worker_logname} configured", worker_logname=worker_logname, ) d.addCallback(configure_worker, worker_logname, worker_type, worker_id, worker) # guest worker processes setup elif worker_type == u'guest': # now actually start the (guest) worker .. # FIXME: start_worker() takes the whole configuration item for guest workers, whereas native workers # only take the options (which is part of the whole config item for the worker) d = self._controller.call(u'crossbar.start_worker', worker_id, worker_type, worker, options=CallOptions()) else: raise Exception('logic error: unexpected worker_type="{}"'.format(worker_type)) if parallel_worker_start: dl.append(d) else: yield d yield gatherResults(dl) self.log.info(hl('Ok, local node configuration booted successfully!', color='green', bold=True))
def _run_command_exec_worker(options, reactor=None, personality=None): """ Entry point into (native) worker processes. This wires up stuff such that a worker instance is talking WAMP-over-stdio to the node controller. """ import os import sys import platform import signal # https://coverage.readthedocs.io/en/coverage-4.4.2/subprocess.html#measuring-sub-processes MEASURING_COVERAGE = False if 'COVERAGE_PROCESS_START' in os.environ: try: import coverage except ImportError: pass else: # The following will read the environment variable COVERAGE_PROCESS_START, # and that should be set to the .coveragerc file: # # export COVERAGE_PROCESS_START=${PWD}/.coveragerc # coverage.process_startup() MEASURING_COVERAGE = True # we use an Autobahn utility to import the "best" available Twisted reactor from autobahn.twisted.choosereactor import install_reactor reactor = install_reactor(options.reactor) # make sure logging to something else than stdio is setup _first_ from crossbar._logging import make_JSON_observer, cb_logging_aware from txaio import make_logger, start_logging from twisted.logger import globalLogPublisher from twisted.python.reflect import qual log = make_logger() # Print a magic phrase that tells the capturing logger that it supports # Crossbar's rich logging print(cb_logging_aware, file=sys.__stderr__) sys.__stderr__.flush() flo = make_JSON_observer(sys.__stderr__) globalLogPublisher.addObserver(flo) # Ignore SIGINT so we get consistent behavior on control-C versus # sending SIGINT to the controller process. When the controller is # shutting down, it sends TERM to all its children but ctrl-C # handling will send a SIGINT to all the processes in the group # (so then the controller sends a TERM but the child already or # will very shortly get a SIGINT as well). Twisted installs signal # handlers, but not for SIGINT if there's already a custom one # present. def ignore(sig, frame): log.debug("Ignoring SIGINT in worker.") signal.signal(signal.SIGINT, ignore) # actually begin logging start_logging(None, options.loglevel) # get personality klass, eg "crossbar.personality.Personality" l = options.personality.split('.') personality_module, personality_klass = '.'.join(l[:-1]), l[-1] # now load the personality module and class _mod = importlib.import_module(personality_module) Personality = getattr(_mod, personality_klass) # get worker klass, eg "crossbar.worker.container.ContainerController" l = options.klass.split('.') worker_module, worker_klass = '.'.join(l[:-1]), l[-1] # now load the worker module and class _mod = importlib.import_module(worker_module) klass = getattr(_mod, worker_klass) log.info( 'Starting worker "{worker_id}" for node "{node_id}" with personality "{personality}" {worker_class}', worker_id=options.worker, node_id=options.node, personality=Personality.NAME, worker_class=hltype(klass), ) log.info( 'Running as PID {pid} on {python}-{reactor}', pid=os.getpid(), python=platform.python_implementation(), reactor=qual(reactor.__class__).split('.')[-1], ) if MEASURING_COVERAGE: log.info(hl('Code coverage measurements enabled (coverage={coverage_version}).', color='green', bold=True), coverage_version=coverage.__version__) # set process title if requested to # try: import setproctitle except ImportError: log.debug("Could not set worker process title (setproctitle not installed)") else: if options.title: setproctitle.setproctitle(options.title) else: setproctitle.setproctitle('crossbar-worker [{}]'.format(options.klass)) # node directory # options.cbdir = os.path.abspath(options.cbdir) os.chdir(options.cbdir) # log.msg("Starting from node directory {}".format(options.cbdir)) # set process title if requested to # try: import setproctitle except ImportError: log.debug("Could not set worker process title (setproctitle not installed)") else: if options.title: setproctitle.setproctitle(options.title) else: setproctitle.setproctitle( 'crossbar-worker [{}]'.format(options.klass) ) from twisted.internet.error import ConnectionDone from autobahn.twisted.websocket import WampWebSocketServerProtocol class WorkerServerProtocol(WampWebSocketServerProtocol): def connectionLost(self, reason): # the behavior here differs slightly whether we're shutting down orderly # or shutting down because of "issues" if isinstance(reason.value, ConnectionDone): was_clean = True else: was_clean = False try: # this log message is unlikely to reach the controller (unless # only stdin/stdout pipes were lost, but not stderr) if was_clean: log.info("Connection to node controller closed cleanly") else: log.warn("Connection to node controller lost: {reason}", reason=reason) # give the WAMP transport a change to do it's thing WampWebSocketServerProtocol.connectionLost(self, reason) except: # we're in the process of shutting down .. so ignore .. pass finally: # after the connection to the node controller is gone, # the worker is "orphane", and should exit # determine process exit code if was_clean: exit_code = 0 else: exit_code = 1 # exit the whole worker process when the reactor has stopped reactor.addSystemEventTrigger('after', 'shutdown', os._exit, exit_code) # stop the reactor try: reactor.stop() except ReactorNotRunning: pass try: # define a WAMP application session factory # from autobahn.wamp.types import ComponentConfig def make_session(): session_config = ComponentConfig(realm=options.realm, extra=options) session = klass(config=session_config, reactor=reactor, personality=Personality) return session # create a WAMP-over-WebSocket transport server factory # from autobahn.twisted.websocket import WampWebSocketServerFactory transport_factory = WampWebSocketServerFactory(make_session, u'ws://localhost') transport_factory.protocol = WorkerServerProtocol transport_factory.setProtocolOptions(failByDrop=False) # create a protocol instance and wire up to stdio # from twisted.python.runtime import platform as _platform from twisted.internet import stdio proto = transport_factory.buildProtocol(None) if _platform.isWindows(): stdio.StandardIO(proto) else: stdio.StandardIO(proto, stdout=3) # now start reactor loop # if False: log.info("vmprof enabled.") import os import vmprof PROFILE_FILE = 'vmprof_{}.dat'.format(os.getpid()) outfd = os.open(PROFILE_FILE, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(outfd, period=0.01) log.info(hl('Entering event reactor ...', color='cyan', bold=True)) reactor.run() vmprof.disable() else: log.info(hl('Entering event reactor ...', color='cyan', bold=True)) reactor.run() except Exception as e: log.info("Unhandled exception: {e}", e=e) if reactor.running: reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1) reactor.stop() else: sys.exit(1)