Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
 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()
Beispiel #4
0
    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))
Beispiel #5
0
    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))
Beispiel #6
0
    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)
Beispiel #7
0
    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)
Beispiel #8
0
    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')
Beispiel #9
0
    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))
Beispiel #10
0
    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))
Beispiel #11
0
        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)
Beispiel #12
0
        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)
Beispiel #13
0
        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!')
Beispiel #14
0
        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!')
Beispiel #15
0
    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')
Beispiel #16
0
 def before_reactor_started():
     term_print('CROSSBAR:REACTOR_STARTING')
Beispiel #17
0
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)
Beispiel #18
0
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
Beispiel #19
0
 def before_reactor_started():
     term_print('CROSSBAR[{}]:REACTOR_STARTING'.format(options.worker))
Beispiel #20
0
 def before_reactor_started():
     term_print('CROSSBAR[{}]:REACTOR_STARTING'.format(options.worker))
Beispiel #21
0
 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')
Beispiel #22
0
 def after_reactor_started():
     term_print('CROSSBAR:REACTOR_STARTED')
Beispiel #23
0
 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()
Beispiel #24
0
 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')
Beispiel #25
0
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
Beispiel #26
0
    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))
Beispiel #27
0
 def before_reactor_started():
     term_print('CROSSBAR:REACTOR_STARTING')
Beispiel #28
0
 def before_reactor_stopped():
     term_print('CROSSBAR:REACTOR_STOPPING')
Beispiel #29
0
 def before_reactor_stopped():
     term_print('CROSSBAR:REACTOR_STOPPING')
Beispiel #30
0
 def after_reactor_started():
     term_print('CROSSBAR:REACTOR_STARTED')
Beispiel #31
0
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)
Beispiel #32
0
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)
Beispiel #33
0
    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))