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)
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()
def after_reactor_started(): term_print('CROSSBAR[{}]:REACTOR_STARTED'.format(options.worker)) if _HAS_VMPROF and options.vmprof: outfn = os.path.join(options.cbdir, '.vmprof-worker-{}-{}.dat'.format(options.worker, os.getpid())) _vm_prof['outfd'] = os.open(outfn, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(_vm_prof['outfd'], period=0.01) term_print('CROSSBAR[{}]:VMPROF_ENABLED:{}'.format(options.worker, outfn))
def shutdown(self, restart=False, mode=None, details=None): """ Explicitly stop this node. """ if self._shutdown_requested: # we're already shutting down .. ignore .. return self._shutdown_requested = True self.log.info( 'Node shutdown requested (restart={}, mode={}, reactor.running={}) ..' .format(restart, mode, self._reactor.running)) term_print('CROSSBAR:NODE_SHUTDOWN_REQUESTED') try: # shutdown any specific to the node controller yield self._shutdown(restart, mode) # node shutdown information shutdown_info = { u'node_id': self._node._node_id, u'restart': restart, u'mode': mode, u'who': details.caller if details else None, u'when': utcnow(), u'was_clean': self._shutdown_was_clean, } if self._node._shutdown_complete: self._node._shutdown_complete.callback(shutdown_info) # publish management API event yield self.publish(u'{}.on_shutdown'.format(self._uri_prefix), shutdown_info, options=PublishOptions( exclude=details.caller if details else None, acknowledge=True)) def stop_reactor(): try: self._reactor.stop() except ReactorNotRunning: pass _SHUTDOWN_DELAY = 0.2 self._reactor.callLater(_SHUTDOWN_DELAY, stop_reactor) except: self.log.failure() self._shutdown_requested = False raise else: returnValue(shutdown_info)
def shutdown(self, restart=False, mode=None, details=None): """ Explicitly stop this node. """ if self._shutdown_requested: # we're already shutting down .. ignore .. return self._shutdown_requested = True self.log.info('Node shutdown requested (restart={}, mode={}, reactor.running={}) ..'.format( restart, mode, self._reactor.running)) term_print('CROSSBAR:NODE_SHUTDOWN_REQUESTED') try: # shutdown any specific to the node controller yield self._shutdown(restart, mode) # node shutdown information shutdown_info = { u'node_id': self._node._node_id, u'restart': restart, u'mode': mode, u'who': details.caller if details else None, u'when': utcnow(), u'was_clean': self._shutdown_was_clean, } if self._node._shutdown_complete: self._node._shutdown_complete.callback(shutdown_info) # publish management API event yield self.publish( u'{}.on_shutdown'.format(self._uri_prefix), shutdown_info, options=PublishOptions(exclude=details.caller if details else None, acknowledge=True) ) def stop_reactor(): try: self._reactor.stop() except ReactorNotRunning: pass _SHUTDOWN_DELAY = 0.2 self._reactor.callLater(_SHUTDOWN_DELAY, stop_reactor) except: self.log.failure() self._shutdown_requested = False raise else: returnValue(shutdown_info)
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')
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'.format(options.worker))
def publish_ready(self): # signal that this worker is ready for setup. the actual setup procedure # will either be sequenced from the local node configuration file or remotely # from a management service yield self.publish('{}.on_worker_ready'.format(self._uri_prefix), { 'type': self.WORKER_TYPE, 'id': self.config.extra.worker, 'pid': os.getpid(), }, options=PublishOptions(acknowledge=True)) self.log.debug("Worker '{worker}' running as PID {pid}", worker=self.config.extra.worker, pid=os.getpid()) term_print('CROSSBAR[{}]:WORKER_STARTED'.format(self.config.extra.worker))
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)
def check_for_shutdown(was_successful): self.log.info('Checking for node shutdown: worker_exit_success={worker_exit_success}, shutdown_requested={shutdown_requested}, node_shutdown_triggers={node_shutdown_triggers}', worker_exit_success=was_successful, shutdown_requested=self._shutdown_requested, node_shutdown_triggers=self._node._node_shutdown_triggers) shutdown = self._shutdown_requested # automatically shutdown node whenever a worker ended (successfully, or with error) # if NODE_SHUTDOWN_ON_WORKER_EXIT in self._node._node_shutdown_triggers: self.log.info("Node worker ended, and trigger '{trigger}' is active: will shutdown node ..", trigger=NODE_SHUTDOWN_ON_WORKER_EXIT) term_print('CROSSBAR:NODE_SHUTDOWN_ON_WORKER_EXIT') shutdown = True # automatically shutdown node when worker ended with error # elif not was_successful and NODE_SHUTDOWN_ON_WORKER_EXIT_WITH_ERROR in self._node._node_shutdown_triggers: self.log.info("Node worker ended with error, and trigger '{trigger}' is active: will shutdown node ..", trigger=NODE_SHUTDOWN_ON_WORKER_EXIT_WITH_ERROR) term_print('CROSSBAR:NODE_SHUTDOWN_ON_WORKER_EXIT_WITH_ERROR') shutdown = True # automatically shutdown node when no more workers are left # elif len(self._workers) == 0 and NODE_SHUTDOWN_ON_LAST_WORKER_EXIT in self._node._node_shutdown_triggers: self.log.info("No more node workers running, and trigger '{trigger}' is active: will shutdown node ..", trigger=NODE_SHUTDOWN_ON_LAST_WORKER_EXIT) term_print('CROSSBAR:NODE_SHUTDOWN_ON_LAST_WORKER_EXIT') shutdown = True # initiate shutdown (but only if we are not already shutting down) # if shutdown: self.shutdown() else: self.log.info('Node will continue to run!')
def before_reactor_stopped(): term_print('CROSSBAR:REACTOR_STOPPING') if _HAS_VMPROF and options.vmprof and _vm_prof['outfd']: vmprof.disable() term_print('CROSSBAR:VMPROF_DISABLED')
def before_reactor_started(): term_print('CROSSBAR:REACTOR_STARTING')
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 # enable_vmprof = False if _HAS_VMPROF: enable_vmprof = options.vmprof node_options = personality.NodeOptions( debug_lifecycle=options.debug_lifecycle, debug_programflow=options.debug_programflow, enable_vmprof=enable_vmprof) 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_class} as node [realm={realm}, cbdir={cbdir}]', realm=hlid(node.realm), 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: config_source, config_path = 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 else: config_source = node.CONFIG_SOURCE_TO_STR[config_source] log.info( 'Node configuration loaded [config_source={config_source}, config_path={config_path}]', config_source=hl(config_source, bold=True, color='green'), config_path=hlid(config_path)) # if vmprof global profiling is enabled via command line option, this will carry # the file where vmprof writes its profile data if _HAS_VMPROF: _vm_prof = { # need to put this into a dict, since FDs are ints, and python closures can't # write to this otherwise 'outfd': None } # 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') if _HAS_VMPROF and options.vmprof: outfn = os.path.join( options.cbdir, '.vmprof-controller-{}.dat'.format(os.getpid())) _vm_prof['outfd'] = os.open(outfn, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(_vm_prof['outfd'], period=0.01) term_print('CROSSBAR:VMPROF_ENABLED:{}'.format(outfn)) def before_reactor_stopped(): term_print('CROSSBAR:REACTOR_STOPPING') if _HAS_VMPROF and options.vmprof and _vm_prof['outfd']: vmprof.disable() term_print('CROSSBAR:VMPROF_DISABLED') 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', 'startup', before_reactor_started) reactor.addSystemEventTrigger('after', 'startup', after_reactor_started) 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='green', 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 main(prog, args, reactor, personality): """ Entry point of Crossbar.io CLI. """ from crossbar import _util _util.set_flags_from_args(args) term_print('CROSSBAR:MAIN_ENTRY') # print banner and usage notes when started with empty args # if args is not None and '--help' not in args: # if all args are options (start with "-"), then we don't have a command, # but we need one! hence, print a usage message if not [x for x in args if not x.startswith('-')]: _print_usage(prog, personality) return # create the top-level parser # parser = argparse.ArgumentParser(prog=prog, description=personality.DESC) _add_debug_options(parser) # create subcommand parser # subparsers = parser.add_subparsers(dest='command', title='commands', help='Command to run (required)') subparsers.required = True # ############################################################# # "init" command # parser_init = subparsers.add_parser( 'init', help='Initialize a new Crossbar.io node.') parser_init.add_argument( '--appdir', type=str, default=None, help= "Application base directory where to create app and node from template." ) parser_init.set_defaults(func=_run_command_init) # "start" command # parser_start = subparsers.add_parser('start', help='Start a Crossbar.io node.') _add_log_arguments(parser_start) _add_cbdir_config(parser_start) parser_start.add_argument( '--shutdownafter', type=int, default=None, help='Automatically shutdown node after this many seconds.') if _HAS_VMPROF: parser_start.add_argument( '--vmprof', action='store_true', help='Profile node controller and native worker using vmprof.') parser_start.set_defaults(func=_run_command_start) # "stop" command # parser_stop = subparsers.add_parser('stop', help='Stop a Crossbar.io node.') parser_stop.add_argument( '--cbdir', type=str, default=None, help= "Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)" ) parser_stop.set_defaults(func=_run_command_stop) # "status" command # parser_status = subparsers.add_parser( 'status', help='Checks whether a Crossbar.io node is running.') parser_status.add_argument( '--cbdir', type=str, default=None, help= "Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)" ) parser_status.add_argument( '--assert', type=str, default=None, choices=['running', 'stopped'], help= ("If given, assert the node is in this state, otherwise exit with error." )) parser_status.set_defaults(func=_run_command_status) # "check" command # parser_check = subparsers.add_parser( 'check', help='Check a Crossbar.io node`s local configuration file.') _add_cbdir_config(parser_check) parser_check.set_defaults(func=_run_command_check) # "convert" command # parser_convert = subparsers.add_parser( 'convert', help= 'Convert a Crossbar.io node`s local configuration file from JSON to YAML or vice versa.' ) _add_cbdir_config(parser_convert) parser_convert.set_defaults(func=_run_command_convert) # "upgrade" command # parser_upgrade = subparsers.add_parser( 'upgrade', help= 'Upgrade a Crossbar.io node`s local configuration file to current configuration file format.' ) _add_cbdir_config(parser_upgrade) parser_upgrade.set_defaults(func=_run_command_upgrade) # "keygen" command # parser_keygen = subparsers.add_parser( 'keygen', help= 'Generate public/private keypairs for use with autobahn.wamp.cryptobox.KeyRing' ) parser_keygen.set_defaults(func=_run_command_keygen) # "keys" command # parser_keys = subparsers.add_parser( 'keys', help= 'Print Crossbar.io release and node key (public key part by default).') parser_keys.add_argument( '--cbdir', type=str, default=None, help= "Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)" ) parser_keys.add_argument( '--private', action='store_true', help='Print the node private key instead of the public key.') parser_keys.set_defaults(func=_run_command_keys) # "version" command # parser_version = subparsers.add_parser('version', help='Print software versions.') parser_version.set_defaults(func=_run_command_version) # "legal" command # parser_legal = subparsers.add_parser( 'legal', help='Print legal and licensing information.') parser_legal.set_defaults(func=_run_command_legal) # INTERNAL USE! start a worker (this is used by the controller to start worker processes # but cannot be used outside that context. # argparse.SUPPRESS does not work here =( so we obfuscate the name to discourage use. # parser_worker = subparsers.add_parser('_exec_worker', help='Program internal use.') parser_worker = worker_main.get_argument_parser(parser_worker) parser_worker.set_defaults(func=worker_main._run_command_exec_worker) # ############################################################# # parse cmd line args # options = parser.parse_args(args) options.argv = [prog] + args if hasattr(options, 'shutdownafter') and options.shutdownafter: options.shutdownafter = float(options.shutdownafter) # colored logging does not work on Windows, so overwrite it! # FIXME: however, printing the banner in color works at least now: # So maybe we can get the actual log output also working in color. if sys.platform == 'win32': options.color = False # Crossbar.io node directory # if hasattr(options, 'cbdir'): if not options.cbdir: if "CROSSBAR_DIR" in os.environ: options.cbdir = os.environ['CROSSBAR_DIR'] elif os.path.isdir('.crossbar'): options.cbdir = '.crossbar' else: options.cbdir = '.' options.cbdir = os.path.abspath(options.cbdir) # convenience: if --cbdir points to a config file, take # the config file's base dirname as node directory if os.path.isfile(options.cbdir): options.cbdir = os.path.dirname(options.cbdir) # convenience: auto-create directory if not existing if not os.path.isdir(options.cbdir): try: os.mkdir(options.cbdir) except Exception as e: print("Could not create node directory: {}".format(e)) sys.exit(1) else: print("Auto-created node directory {}".format(options.cbdir)) # Crossbar.io node configuration file # if hasattr(options, 'config'): # if not explicit config filename is given, try to auto-detect . if not options.config: for f in ['config.yaml', 'config.json']: fn = os.path.join(options.cbdir, f) if os.path.isfile(fn) and os.access(fn, os.R_OK): options.config = f break # Log directory # if hasattr(options, 'logdir'): if options.logdir: options.logdir = os.path.abspath( os.path.join(options.cbdir, options.logdir)) if not os.path.isdir(options.logdir): try: os.mkdir(options.logdir) except Exception as e: print("Could not create log directory: {}".format(e)) sys.exit(1) else: print("Auto-created log directory {}".format( options.logdir)) # Start the logger # _start_logging(options, reactor) term_print('CROSSBAR:LOGGING_STARTED') # run the subcommand selected # try: options.func(options, reactor=reactor, personality=personality) except SystemExit as e: # SystemExit(0) is okay! Anything other than that is bad and should be # re-raised. if e.args[0] != 0: raise
def before_reactor_started(): term_print('CROSSBAR[{}]:REACTOR_STARTING'.format(options.worker))
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')
def after_reactor_started(): term_print('CROSSBAR:REACTOR_STARTED')
def main(prog, args, reactor, personality): """ Entry point of Crossbar.io CLI. """ from crossbar import _util _util.set_flags_from_args(args) term_print('CROSSBAR:MAIN_ENTRY') # print banner and usage notes when started with empty args # if args is not None and '--help' not in args: # if all args are options (start with "-"), then we don't have a command, # but we need one! hence, print a usage message if not [x for x in args if not x.startswith('-')]: _print_usage(prog, personality) return # create the top-level parser # parser = argparse.ArgumentParser(prog=prog, description=personality.DESC) _add_debug_options(parser) # create subcommand parser # subparsers = parser.add_subparsers(dest='command', title='commands', help='Command to run (required)') subparsers.required = True # ############################################################# # "init" command # parser_init = subparsers.add_parser('init', help='Initialize a new Crossbar.io node.') parser_init.add_argument('--appdir', type=six.text_type, default=None, help="Application base directory where to create app and node from template.") parser_init.set_defaults(func=_run_command_init) # "start" command # parser_start = subparsers.add_parser('start', help='Start a Crossbar.io node.') _add_log_arguments(parser_start) _add_cbdir_config(parser_start) parser_start.add_argument('--shutdownafter', type=int, default=None, help='Automatically shutdown node after this many seconds.') parser_start.set_defaults(func=_run_command_start) # "stop" command # parser_stop = subparsers.add_parser('stop', help='Stop a Crossbar.io node.') parser_stop.add_argument('--cbdir', type=six.text_type, default=None, help="Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)") parser_stop.set_defaults(func=_run_command_stop) # "status" command # parser_status = subparsers.add_parser('status', help='Checks whether a Crossbar.io node is running.') parser_status.add_argument('--cbdir', type=six.text_type, default=None, help="Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)") parser_status.add_argument('--assert', type=six.text_type, default=None, choices=['running', 'stopped'], help=("If given, assert the node is in this state, otherwise exit with error.")) parser_status.set_defaults(func=_run_command_status) # "check" command # parser_check = subparsers.add_parser('check', help='Check a Crossbar.io node`s local configuration file.') _add_cbdir_config(parser_check) parser_check.set_defaults(func=_run_command_check) # "convert" command # parser_convert = subparsers.add_parser('convert', help='Convert a Crossbar.io node`s local configuration file from JSON to YAML or vice versa.') _add_cbdir_config(parser_convert) parser_convert.set_defaults(func=_run_command_convert) # "upgrade" command # parser_upgrade = subparsers.add_parser('upgrade', help='Upgrade a Crossbar.io node`s local configuration file to current configuration file format.') _add_cbdir_config(parser_upgrade) parser_upgrade.set_defaults(func=_run_command_upgrade) # "keygen" command # if False: parser_keygen = subparsers.add_parser( 'keygen', help='Generate public/private keypairs for use with autobahn.wamp.cryptobox.KeyRing' ) parser_keygen.set_defaults(func=_run_command_keygen) # "keys" command # parser_keys = subparsers.add_parser('keys', help='Print Crossbar.io release and node key (public key part by default).') parser_keys.add_argument('--cbdir', type=six.text_type, default=None, help="Crossbar.io node directory (overrides ${CROSSBAR_DIR} and the default ./.crossbar)") parser_keys.add_argument('--generate', action='store_true', help='Generate a new node key pair if none exists, or loads/checks existing.') parser_keys.add_argument('--private', action='store_true', help='Print the node private key instead of the public key.') parser_keys.set_defaults(func=_run_command_keys) # "version" command # parser_version = subparsers.add_parser('version', help='Print software versions.') parser_version.set_defaults(func=_run_command_version) # "legal" command # parser_legal = subparsers.add_parser('legal', help='Print legal and licensing information.') parser_legal.set_defaults(func=_run_command_legal) # INTERNAL USE! start a worker (this is used by the controller to start worker processes # but cannot be used outside that context. # argparse.SUPPRESS does not work here =( so we obfuscate the name to discourage use. # parser_worker = subparsers.add_parser('_exec_worker', help='Program internal use.') parser_worker = worker_main.get_argument_parser(parser_worker) parser_worker.set_defaults(func=worker_main._run_command_exec_worker) # ############################################################# # parse cmd line args # options = parser.parse_args(args) options.argv = [prog] + args if hasattr(options, 'shutdownafter') and options.shutdownafter: options.shutdownafter = float(options.shutdownafter) # colored logging does not work on Windows, so overwrite it! # FIXME: however, printing the banner in color works at least now: # So maybe we can get the actual log output also working in color. if sys.platform == 'win32': options.color = False # Crossbar.io node directory # if hasattr(options, 'cbdir'): if not options.cbdir: if "CROSSBAR_DIR" in os.environ: options.cbdir = os.environ['CROSSBAR_DIR'] elif os.path.isdir('.crossbar'): options.cbdir = '.crossbar' else: options.cbdir = '.' options.cbdir = os.path.abspath(options.cbdir) # convenience: if --cbdir points to a config file, take # the config file's base dirname as node directory if os.path.isfile(options.cbdir): options.cbdir = os.path.dirname(options.cbdir) # convenience: auto-create directory if not existing if not os.path.isdir(options.cbdir): try: os.mkdir(options.cbdir) except Exception as e: print("Could not create node directory: {}".format(e)) sys.exit(1) else: print("Auto-created node directory {}".format(options.cbdir)) # Crossbar.io node configuration file # if hasattr(options, 'config'): # if not explicit config filename is given, try to auto-detect . if not options.config: for f in ['config.yaml', 'config.json']: fn = os.path.join(options.cbdir, f) if os.path.isfile(fn) and os.access(fn, os.R_OK): options.config = f break # Log directory # if hasattr(options, 'logdir'): if options.logdir: options.logdir = os.path.abspath(os.path.join(options.cbdir, options.logdir)) if not os.path.isdir(options.logdir): try: os.mkdir(options.logdir) except Exception as e: print("Could not create log directory: {}".format(e)) sys.exit(1) else: print("Auto-created log directory {}".format(options.logdir)) # Start the logger # _start_logging(options, reactor) term_print('CROSSBAR:LOGGING_STARTED') # run the subcommand selected # try: options.func(options, reactor=reactor, personality=personality) except SystemExit as e: # SystemExit(0) is okay! Anything other than that is bad and should be # re-raised. if e.args[0] != 0: raise
def before_reactor_stopped(): term_print('CROSSBAR[{}]:REACTOR_STOPPING'.format(options.worker)) if options.vmprof and _vm_prof['outfd']: vmprof.disable() term_print('CROSSBAR[{}]:VMPROF_DISABLED'.format(options.worker))
def before_reactor_stopped(): term_print('CROSSBAR:REACTOR_STOPPING')
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) term_print('CROSSBAR[{}]:WORKER_STARTING'.format(options.worker)) # 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_type}-worker "{worker_id}" on node "{node_id}" (personality "{personality}") and local node management realm "{realm}" .. {worker_class}', worker_type=hl(klass.WORKER_TYPE), worker_id=hlid(options.worker), node_id=hlid(options.node), realm=hlid(options.realm), personality=hl(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 # if vmprof global profiling is enabled via command line option, this will carry # the file where vmprof writes its profile data if _HAS_VMPROF: _vm_prof = { # need to put this into a dict, since FDs are ints, and python closures can't # write to this otherwise 'outfd': None } # 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'.format(options.worker)) def after_reactor_started(): term_print('CROSSBAR[{}]:REACTOR_STARTED'.format(options.worker)) if _HAS_VMPROF and options.vmprof: outfn = os.path.join( options.cbdir, '.vmprof-worker-{}-{}.dat'.format(options.worker, os.getpid())) _vm_prof['outfd'] = os.open(outfn, os.O_RDWR | os.O_CREAT | os.O_TRUNC) vmprof.enable(_vm_prof['outfd'], period=0.01) term_print('CROSSBAR[{}]:VMPROF_ENABLED:{}'.format( options.worker, outfn)) def before_reactor_stopped(): term_print('CROSSBAR[{}]:REACTOR_STOPPING'.format(options.worker)) if _HAS_VMPROF and options.vmprof and _vm_prof['outfd']: vmprof.disable() term_print('CROSSBAR[{}]:VMPROF_DISABLED'.format(options.worker)) 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'.format(options.worker)) reactor.addSystemEventTrigger('before', 'startup', before_reactor_started) reactor.addSystemEventTrigger('after', 'startup', after_reactor_started) reactor.addSystemEventTrigger('before', 'shutdown', before_reactor_stopped) reactor.addSystemEventTrigger('after', 'shutdown', after_reactor_stopped) 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, '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 # log.info(hl('Entering event reactor ...', color='green', 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)
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 before_reactor_stopped(): term_print('CROSSBAR[{}]:REACTOR_STOPPING'.format(options.worker)) if _HAS_VMPROF and options.vmprof and _vm_prof['outfd']: vmprof.disable() term_print('CROSSBAR[{}]:VMPROF_DISABLED'.format(options.worker))