Example #1
0
def _run_command_keys(options, reactor, personality):
    """
    Subcommand "crossbar keys".
    """
    log = make_logger()

    # Generate a new node key pair (2 files), load and check
    _maybe_generate_key(options.cbdir)

    # Print keys

    # Release (public) key
    release_pubkey = _read_release_key()

    # Node key
    node_key = _read_node_key(options.cbdir, private=options.private)

    if options.private:
        key_title = 'Crossbar.io Node PRIVATE Key'
    else:
        key_title = 'Crossbar.io Node PUBLIC Key'

    log.info('')
    log.info('{key_title}',
             key_title=hl('Crossbar Software Release Key',
                          color='yellow',
                          bold=True))
    log.info('base64: {release_pubkey}',
             release_pubkey=release_pubkey['base64'])
    log.info(release_pubkey['qrcode'].strip())
    log.info('')
    log.info('{key_title}', key_title=hl(key_title, color='yellow', bold=True))
    log.info('hex: {node_key}', node_key=node_key['hex'])
    log.info(node_key['qrcode'].strip())
    log.info('')
Example #2
0
def _run_command_keys(options, reactor, personality):
    """
    Subcommand "crossbar keys".
    """
    log = make_logger()

    from crossbar.common.key import _read_node_key
    from crossbar.common.key import _read_release_key

    if options.generate:
        # Generate a new node key pair (2 files), load and check
        _maybe_generate_key(options.cbdir)
    else:
        # Print keys

        # Release (public) key
        release_pubkey = _read_release_key()

        # Node key
        node_key = _read_node_key(options.cbdir, private=options.private)

        if options.private:
            key_title = 'Crossbar.io Node PRIVATE Key'
        else:
            key_title = 'Crossbar.io Node PUBLIC Key'

        log.info('')
        log.info('{key_title}', key_title=hl('Crossbar Software Release Key', color='yellow', bold=True))
        log.info('base64: {release_pubkey}', release_pubkey=release_pubkey[u'base64'])
        log.info(release_pubkey[u'qrcode'].strip())
        log.info('')
        log.info('{key_title}', key_title=hl(key_title, color='yellow', bold=True))
        log.info('hex: {node_key}', node_key=node_key[u'hex'])
        log.info(node_key[u'qrcode'].strip())
        log.info('')
Example #3
0
    def start(self, prefix):
        """
        Start the Metadata manager.

        :return:
        """
        assert self._started is None, 'cannot start Metadata manager - already running!'

        regs = yield self._session.register(
            self,
            prefix=prefix,
            options=RegisterOptions(details_arg='details'))
        self._prefix = prefix
        procs = [reg.procedure for reg in regs]
        self.log.debug(
            'Mrealm controller {api} registered management procedures [{func}]:\n\n{procs}\n',
            api=hl('Metadata manager API', color='green', bold=True),
            func=hltype(self.start),
            procs=hl(pformat(procs), color='white', bold=True))

        self._started = time_ns()
        self.log.info(
            'Metadata manager ready for management realm {mrealm_oid}! [{func}]',
            mrealm_oid=hlid(self._mrealm_oid),
            func=hltype(self.start))
Example #4
0
    def onJoin(self, details):
        """
        Called when process has joined the node's management realm.
        """

        regs = yield self.register(
            self,
            prefix='{}.'.format(self._uri_prefix),
            options=RegisterOptions(details_arg='details'),
        )

        procs = []
        errors = []
        for reg in regs:
            if isinstance(reg, Failure):
                self.log.error("Failed to register management procedure: {f}",
                               f=reg,
                               log_failure=reg)
                errors.append(str(reg))
            else:
                procs.append(reg.procedure)

        if errors:
            raise ApplicationError('crossbar.error.cannot_start',
                                   'management API could not be initialized',
                                   errors=errors)
        else:
            self.log.debug(
                'Ok, registered {len_reg} management procedures on realm "{realm}" [{func}]:\n\n{procs}\n',
                len_reg=hlval(len(regs)),
                realm=hl(self.realm),
                func=hltype(self.onJoin),
                procs=hl(pformat(procs), color='white', bold=True))
        returnValue(regs)
Example #5
0
    def onJoin(self, details):
        self.log.info('Backend.onJoin(details={details})', details=details)

        self._pid = os.getpid()
        self._logname = self.config.extra.get('logname', None)
        self._url = self.config.extra.get('url', None)

        # run-time session statistics for CALLs
        self._received_calls_cnt = 0
        self._received_calls_bytes = 0
        self._received_calls_ff_cnt = 0

        uri = '{}.proc1'.format(self._logname)

        def proc1(logname, url, loop, counter, payload, details=None):
            fingerprint = hashlib.sha256(payload).digest()[:6]
            self.log.info(
                '{logprefix}: INVOCATION received for {uri} on pid={pid} from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, caller={caller}, caller_authid={caller_authid}, caller_authrole={caller_authrole}, forward_for={forward_for}',
                logprefix=hl('WAMP {}:{}'.format(self._url, self._logname),
                             color='blue',
                             bold=True),
                pid=hl(self._pid, color='blue', bold=True),
                sender=hl('{}:{}'.format(url, logname),
                          color='blue',
                          bold=True),
                loop=loop,
                counter=counter,
                procedure=details.procedure,
                payload_len=len(payload),
                uri=hl(uri, color='yellow', bold=True),
                fp=hl(binascii.b2a_hex(fingerprint).decode(),
                      color='blue',
                      bold=True),
                caller=hl(details.caller, color='blue', bold=True),
                caller_authid=hl(details.caller_authid,
                                 color='blue',
                                 bold=True),
                caller_authrole=hl(details.caller_authrole,
                                   color='blue',
                                   bold=True),
                forward_for=details.forward_for)

            fingerprint = hashlib.sha256(payload).digest()[:6]

            self._received_calls_cnt += 1
            self._received_calls_bytes += len(payload)
            if details.forward_for:
                self._received_calls_ff_cnt += 1

            return fingerprint, self._pid, self._logname, self._url

        yield self.register(proc1,
                            uri,
                            options=RegisterOptions(match='exact',
                                                    invoke='random',
                                                    details_arg='details'))

        self.log.info('*' * 80)
        self.log.info('Backend procedure registered at {uri}', uri=hl(uri))
        self.log.info('*' * 80)
Example #6
0
    def start_worker(self,
                     worker_id,
                     worker_type,
                     worker_options=None,
                     details=None):
        """
        Start a new worker process in the node.
        """
        if type(worker_id) != str or worker_id in ['controller', '']:
            raise Exception('invalid worker ID "{}"'.format(worker_id))

        self.log.info(
            'Starting {worker_type}-worker "{worker_id}" .. {worker_klass}',
            worker_type=hl(worker_type),
            worker_id=hlid(worker_id),
            worker_klass=hltype(NodeController.start_worker))

        if worker_type == 'guest':
            return self._start_guest_worker(worker_id,
                                            worker_options,
                                            details=details)

        elif worker_type in self._node._native_workers:
            return self._start_native_worker(worker_type,
                                             worker_id,
                                             worker_options,
                                             details=details)

        else:
            raise Exception('invalid worker type "{}"'.format(worker_type))
Example #7
0
    def __init__(self, config):
        ApplicationSession.__init__(self, config)
        self._connected_nodes = {}

        # public keys of superusers
        self._superusers = []
        if 'CROSSBAR_FABRIC_SUPERUSER' in os.environ:
            superuser_keyfile = os.environ['CROSSBAR_FABRIC_SUPERUSER'].strip()
            if superuser_keyfile:
                pubkey_file = os.path.abspath(
                    os.environ['CROSSBAR_FABRIC_SUPERUSER'])
                if not os.path.exists(pubkey_file):
                    raise Exception(
                        'superuser public key file {} (set from CROSSBAR_FABRIC_SUPERUSER env var) does not exist'
                        .format(pubkey_file))
                with open(pubkey_file, 'r') as f:
                    data = f.read()
                    pubkey_hex = None
                    for line in data.splitlines():
                        if line.startswith('public-key-ed25519'):
                            pubkey_hex = line.split(':')[1].strip()
                            break
                    if pubkey_hex is None:
                        raise Exception(
                            'no public key line found in super user public key file {}'
                            .format(pubkey_file))

                    self._superusers.append(pubkey_hex)
                    self.log.info(
                        hl('SUPERUSER public key {} loaded from {}'.format(
                            pubkey_hex, pubkey_file),
                           color='green',
                           bold=True))
Example #8
0
        def on_management_event(*args, **kwargs):
            if not (self._manager and self._manager.is_attached()):
                self.log.warn(
                    "Can't foward management event: CFC session not attached")
                return

            details = kwargs.pop('details')

            # a node local event such as 'crossbar.node.on_ready' is mogrified to 'local.crossbar.node.on_ready'
            # (one reason is that URIs such as 'wamp.*' and 'crossbar.*' are restricted to trusted sessions, and
            # the management bridge is connecting over network to the uplink CFC and hence can't be trusted)
            #
            topic = self._translate_uri(details.topic)

            try:
                yield self._manager.publish(
                    topic,
                    *args,
                    options=PublishOptions(acknowledge=True),
                    **kwargs)
            except Exception:
                self.log.failure(
                    "Failed to forward event on topic '{topic}': {log_failure.value}",
                    topic=topic,
                )
            else:
                if topic.endswith('.on_log'):
                    log = self.log.debug
                else:
                    log = self.log.debug

                log('Forwarded management {forward_type} to CFC [local_uri={local_topic}, remote_uri={remote_topic}]',
                    forward_type=hl('EVENT'),
                    local_topic=hlid(details.topic),
                    remote_topic=hlid(topic))
Example #9
0
            def forward(*args, **kwargs):
                details = kwargs.pop('details', None)
                if details:
                    match = pat.match(details.topic)
                    if match:
                        node_id, worker_id = match.groups()

                        # FIXME: map back from node authid (?) to node OID (as UUID string)?

                        self.log.debug(
                            'Forwarding CFC {forward_type} on mrealm {realm} from node {node_id} and worker {worker_id} [local=<{local_uri}>, remote=<{remote_uri}>]',
                            forward_type=hl('EVENT'),
                            local_uri=hlid(local_uri),
                            remote_uri=hlid(details.topic),
                            node_id=hluserid(node_id),
                            worker_id=hluserid(worker_id),
                            realm=hluserid(session._realm))

                        return session.publish(
                            local_uri,
                            node_id,
                            worker_id,
                            *args,
                            **kwargs,
                            options=PublishOptions(exclude_me=False))

                # should not arrive here
                session.log.warn(
                    'received unexpected event to forward for management API: local_uri={local_uri}, remote_uri={remote_uri}, remote_uri_regex={remote_uri_regex} details={details}',
                    local_uri=local_uri,
                    remote_uri=remote_uri,
                    remote_uri_regex=remote_uri_regex,
                    details=details)
Example #10
0
    def onJoin(self, details):

        self._prefix = self.config.extra['prefix']
        self._logname = self.config.extra['logname']
        self._count = 0

        self._pinfo = ProcessInfo()

        def echo(arg):
            self._count += 1
            return arg

        yield self.register(echo,
                            '{}.echo'.format(self._prefix),
                            options=RegisterOptions(invoke='roundrobin'))

        self.log.info('{} ready!'.format(self._logname))

        last = None
        while True:
            stats = self._pinfo.get_stats()
            if last:
                secs = (stats['time'] - last['time']) / 10**9
                calls_per_sec = int(round(self._count / secs, 0))
                ctx = round((stats['voluntary'] - last['voluntary']) / secs, 0)

                self.log.info(
                    '{logprefix}: {user} user, {system} system, {mem_percent} mem_percent, {ctx} ctx',
                    logprefix=hl('LOAD {}'.format(self._logname),
                                 color='cyan',
                                 bold=True),
                    user=stats['user'],
                    system=stats['system'],
                    mem_percent=round(stats['mem_percent'], 1),
                    ctx=ctx)
                self.log.info(
                    '{logprefix}: {calls} calls, {calls_per_sec} calls/second',
                    logprefix=hl('WAMP {}'.format(self._logname),
                                 color='cyan',
                                 bold=True),
                    calls=self._count,
                    calls_per_sec=hl(calls_per_sec, bold=True))

                self._count = 0

            last = stats
            yield sleep(10)
Example #11
0
 def shutdown():
     for p in pl:
         try:
             pid = p.transport.pid
             p.transport.signalProcess('KILL')
             print('Client with PID {} killed'.format(hl(pid, bold=True)))
         except ProcessExitedAlready:
             pass
Example #12
0
 def forward_call(*args, **kwargs):
     kwargs.pop('details', None)
     self.log.debug(
         'Forwarding management {forward_type} from CFC .. [remote_uri={remote_uri}, local_uri={local_uri}]',
         forward_type=hl('CALL'),
         local_uri=hlid(local_uri),
         remote_uri=hlid(remote_uri))
     return self.call(local_uri, *args, **kwargs)
Example #13
0
    def _process_block(self, w3, block_number, Events):
        """

        :param w3:
        :param block_number:
        :param Events:
        :return:
        """
        cnt = 0
        # filter by block, and XBR contract addresses
        # FIXME: potentially add filters for global data or market specific data for the markets started in this worker
        filter_params = {
            'address': [
                xbr.xbrtoken.address, xbr.xbrnetwork.address, xbr.xbrcatalog.address, xbr.xbrmarket.address,
                xbr.xbrchannel.address
            ],
            'fromBlock':
            block_number,
            'toBlock':
            block_number,
        }
        result = w3.eth.getLogs(filter_params)
        if result:
            for evt in result:
                receipt = w3.eth.getTransactionReceipt(evt['transactionHash'])
                for Event, handler in Events:
                    # FIXME: MismatchedABI pops up .. we silence this with errors=web3.logs.DISCARD
                    if hasattr(web3, 'logs') and web3.logs:
                        all_res = Event().processReceipt(receipt, errors=web3.logs.DISCARD)
                    else:
                        all_res = Event().processReceipt(receipt)
                    for res in all_res:
                        self.log.info('{handler} processing block {block_number} / txn {txn} with args {args}',
                                      handler=hl(handler.__name__),
                                      block_number=hlid(block_number),
                                      txn=hlid('0x' + binascii.b2a_hex(evt['transactionHash']).decode()),
                                      args=hlval(res.args))
                        handler(res.transactionHash, res.blockHash, res.args)
                        cnt += 1

        with self._db.begin(write=True) as txn:
            block = cfxdb.xbr.block.Block()
            block.timestamp = np.datetime64(time_ns(), 'ns')
            block.block_number = block_number
            # FIXME
            # block.block_hash = bytes()
            block.cnt_events = cnt
            self._xbr.blocks[txn, pack_uint256(block_number)] = block

        if cnt:
            self.log.info('Processed blockchain block {block_number}: processed {cnt} XBR events.',
                          block_number=hlid(block_number),
                          cnt=hlid(cnt))
        else:
            self.log.info('Processed blockchain block {block_number}: no XBR events found!',
                          block_number=hlid(block_number))

        return cnt
Example #14
0
    def proc1(self, logname, url, loop, counter, payload, details=None):
        fingerprint = hashlib.sha256(payload).digest()[:6]
        self.log.info(
            '{logprefix}: INVOCATION received on pid={pid} from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, caller={caller}, caller_authid={caller_authid}, caller_authrole={caller_authrole}, forward_for={forward_for}',
            logprefix=hl('WAMP {}:{}'.format(self._url, self._logname),
                         color='blue',
                         bold=True),
            pid=hl(self._pid, color='blue', bold=True),
            sender=hl('{}:{}'.format(url, logname), color='blue', bold=True),
            loop=loop,
            counter=counter,
            procedure=details.procedure,
            payload_len=len(payload),
            fp=hl(binascii.b2a_hex(fingerprint).decode(),
                  color='blue',
                  bold=True),
            caller=hl(details.caller, color='blue', bold=True),
            caller_authid=hl(details.caller_authid, color='blue', bold=True),
            caller_authrole=hl(details.caller_authrole,
                               color='blue',
                               bold=True),
            forward_for=details.forward_for)

        fingerprint = hashlib.sha256(payload).digest()[:6]

        self._received_calls_cnt += 1
        self._received_calls_bytes += len(payload)
        if details.forward_for:
            self._received_calls_ff_cnt += 1

        return fingerprint, self._pid, self._logname, self._url
Example #15
0
 def start(self, node_id=None):
     self.log.info(
         '{note} [{method}]',
         note=hl('Starting node (initialize master-node personality) ..',
                 color='green',
                 bold=True),
         method=hltype(FabricCenterNode.start))
     res = yield node.FabricNode.start(self, node_id)
     return res
Example #16
0
def _run_command_legal(options, reactor, personality, verbose=True):
    """
    Subcommand "crossbar legal".
    """
    if verbose:
        docs = [personality.LEGAL, personality.LICENSE, personality.LICENSE_FOR_API, personality.LICENSES_OSS]
    else:
        docs = [personality.LEGAL]

    print(hl('*' * 120, bold=True, color='yellow'))
    for package, resource_name in docs:
        filename = pkg_resources.resource_filename(package, resource_name)
        filepath = os.path.abspath(filename)
        print(hl('   ' + filepath + ' :\n', bold=False, color='yellow'))
        with open(filepath) as f:
            legal = f.read()
            print(hl(legal, bold=True, color='white'))
        print(hl('*' * 120, bold=True, color='yellow'))
Example #17
0
def _print_usage(prog, personality):
    print(hl(personality.BANNER, color='yellow', bold=True))
    print(
        'Type "{} --help" to get help, or "{} <command> --help" to get help on a specific command.'
        .format(prog, prog))
    print(
        'Type "{} legal" to read legal notices, terms of use and license and privacy information.'
        .format(prog))
    print('Type "{} version" to print detailed version information.'.format(
        prog))
Example #18
0
    def start(self, node_id=None):
        self.log.info('{note} [{method}]',
                      note=hl('Starting node (initialize edge-node personality) ..', color='green', bold=True),
                      method=hltype(FabricNode.start))

        # run watchdog at 5Hz
        self._watchdog_looper = LoopingCall(self._watchdog)
        self._watchdog_looper.start(.2)

        res = yield node.Node.start(self, node_id or self._node_id)
        return res
Example #19
0
    def start_router_realm(self, realm_id, realm_config, details=None):
        self.log.info('Starting router realm "{realm_id}" {method}',
                      realm_id=realm_id,
                      method=hltype(ExtRouterController.start_router_realm))

        # activate this to test:
        if False and realm_config['name'] == 'realm1':
            self.log.info(hl('Auto-renaming realm1 to realm001', color='green', bold=True))
            realm_config['name'] = 'realm001'

        return RouterController.start_router_realm(self, realm_id, realm_config, details)
Example #20
0
def _run_command_legal(options, reactor, personality, verbose=True):
    """
    Subcommand "crossbar legal".
    """
    if verbose:
        docs = [personality.LEGAL,
                personality.LICENSE,
                personality.LICENSE_FOR_API,
                personality.LICENSES_OSS]
    else:
        docs = [personality.LEGAL]

    print(hl('*' * 120, bold=True, color='yellow'))
    for package, resource_name in docs:
        filename = pkg_resources.resource_filename(package, resource_name)
        filepath = os.path.abspath(filename)
        print(hl('   ' + filepath + ' :\n', bold=False, color='yellow'))
        with open(filepath) as f:
            legal = f.read()
            print(hl(legal, bold=True, color='white'))
        print(hl('*' * 120, bold=True, color='yellow'))
Example #21
0
 def on_event1(self, logname, url, loop, counter, payload, details=None):
     fingerprint = hashlib.sha256(payload).digest()[:6]
     self.log.debug(
         '{logprefix}: EVENT received from {sender} -> loop={loop}, counter={counter}, len={payload_len}, fp={fp}, publisher={publisher}, publisher_authid={publisher_authid}, publisher_authole={publisher_authrole}, forward_for={forward_for}',
         logprefix=hl('WAMP {}:{}'.format(self._url, self._logname),
                      color='blue',
                      bold=True),
         sender=hl('{}:{}'.format(url, logname), color='blue', bold=True),
         loop=loop,
         counter=counter,
         topic=details.topic,
         payload_len=len(payload),
         fp=hl(binascii.b2a_hex(fingerprint).decode(),
               color='blue',
               bold=True),
         publisher=hl(details.publisher, color='blue', bold=True),
         publisher_authid=hl(details.publisher_authid,
                             color='blue',
                             bold=True),
         publisher_authrole=hl(details.publisher_authrole,
                               color='blue',
                               bold=True),
         forward_for=details.forward_for)
     self._received_cnt += 1
     self._received_bytes += len(payload)
     if details.forward_for:
         self._received_ff_cnt += 1
Example #22
0
 def check_result(result, uri):
     print('-' * 100, result)
     _fingerprint, _pid, _logname, _url = result.results[
         0]
     self.log.info(
         '{logprefix}: CALL RESULT for {uri} received from pid={pid}, logname={logname}, url={url}, callee={callee}, callee_authid={callee_authid}, callee_authrole={callee_authrole}, forward_for={forward_for}, fp={fp} => fp_equal={fp_equal}',
         logprefix=hl('WAMP {}:{}'.format(
             self._url, self._logname),
                      color='green',
                      bold=True),
         pid=hl(_pid, color='green', bold=True),
         logname=_logname,
         url=_url,
         fp=hl(binascii.b2a_hex(fingerprint).decode(),
               color='green',
               bold=True),
         fp_equal=(_fingerprint == fingerprint),
         uri=hl(uri, color='yellow', bold=True),
         callee=result.callee,
         callee_authid=result.callee_authid,
         callee_authrole=result.callee_authrole,
         forward_for=result.forward_for)
     assert _fingerprint == fingerprint
Example #23
0
    def start_market_maker(self, maker_id, config, details=None):
        """
        Starts a XBR Market Maker providing services in a specific XBR market.
        """
        if type(maker_id) != str:
            emsg = 'maker_id has invalid type {}'.format(type(maker_id))
            raise ApplicationError('wamp.error.invalid_argument', emsg)

        if not isinstance(config, Mapping):
            emsg = 'maker_id has invalid type {}'.format(type(config))
            raise ApplicationError('wamp.error.invalid_argument', emsg)

        if maker_id in self._makers:
            emsg = 'could not start market maker: a market maker with ID "{}" is already running (or starting)'.format(
                maker_id)
            raise ApplicationError('crossbar.error.already_running', emsg)

        self.personality.check_market_maker(self.personality, config)

        self.log.info('XBR Market Maker "{maker_id}" starting with config:\n{config}',
                      maker_id=hlid(maker_id),
                      config=pformat(config))

        maker = MarketMaker(self, maker_id, config, self._db, self._ipfs_files_dir)
        self._makers[maker_id] = maker
        self._maker_adr2id[maker.address] = maker_id

        yield maker.start()

        status = yield maker.status()
        self.log.info('{msg}: {accounts} local accounts, current block number is {current_block_no}',
                      msg=hl('Blockchain status', color='green', bold=True),
                      current_block_no=hlid(status['current_block_no']),
                      accounts=hlid(len(status['accounts'])))

        started = {
            'id': maker_id,
            'address': maker.address,
        }
        self.publish(u'{}.on_maker_started'.format(self._uri_prefix), started)

        self.log.info(
            'XBR Market Maker "{maker_id}" (address {maker_adr}) started. Now running {maker_cnt} market makers in total in this worker component.',
            maker_id=maker_id,
            maker_adr=maker.address,
            maker_cnt=len(self._makers))

        returnValue(started)
Example #24
0
def _run_command_version(options, reactor, personality):
    """
    Subcommand "crossbar version".
    """
    log = make_logger()

    v = _get_versions(reactor)

    def decorate(text, fg='white', bg=None, bold=True):
        return click.style(text, fg=fg, bg=bg, bold=bold)

    pad = " " * 22
    for line in personality.BANNER.splitlines():
        log.info(hl(line, color='yellow', bold=True))
    log.info("")
    log.info(" Crossbar.io        : {ver}", ver=decorate(v.crossbar_ver))
    log.info("   txaio            : {ver}", ver=decorate(v.txaio_ver))
    log.info("   Autobahn         : {ver}", ver=decorate(v.ab_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.ab_loc))
    log.debug("     UTF8 Validator : {ver}", ver=decorate(v.utf8_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.utf8_loc))
    log.debug("     XOR Masker     : {ver}", ver=decorate(v.xor_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.xor_loc))
    log.debug("     JSON Codec     : {ver}", ver=decorate(v.json_ver))
    log.debug("     MsgPack Codec  : {ver}", ver=decorate(v.msgpack_ver))
    log.debug("     CBOR Codec     : {ver}", ver=decorate(v.cbor_ver))
    log.debug("     UBJSON Codec   : {ver}", ver=decorate(v.ubjson_ver))
    log.info("   Twisted          : {ver}", ver=decorate(v.tx_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.tx_loc))
    log.info("   LMDB             : {ver}", ver=decorate(v.lmdb_ver))
    log.info("   Python           : {ver}/{impl}",
             ver=decorate(v.py_ver),
             impl=decorate(v.py_ver_detail))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(v.py_ver_string))
    if personality.NAME in (u'edge', u'master'):
        log.info(" Crossbar.io FX     : {ver}", ver=decorate(v.crossbarfx_ver))
        log.info("   NumPy            : {ver}", ver=decorate(v.numpy_ver))
        log.info("   zLMDB            : {ver}", ver=decorate(v.zlmdb_ver))
        log.info("   XBR              : {ver}", ver=decorate(v.xbr_ver))
    log.info(" Frozen executable  : {py_is_frozen}",
             py_is_frozen=decorate('yes' if v.py_is_frozen else 'no'))
    log.info(" Operating system   : {ver}", ver=decorate(v.platform))
    log.info(" Host machine       : {ver}", ver=decorate(v.machine))
    log.info(" Release key        : {release_pubkey}",
             release_pubkey=decorate(v.release_pubkey))
    log.info("")
Example #25
0
    def _stats(self, batch_id):
        stats = self._pinfo.get_stats()

        if self._last_stats:
            batch_duration = (stats['time'] - self._last_stats['time']) / 10**9
            ctx = round((stats['voluntary'] - self._last_stats['voluntary']) /
                        batch_duration, 0)
            self.log.info(
                '{logprefix}: {user} user, {system} system, {mem_percent} mem_percent, {ctx} ctx',
                logprefix=hl('LOAD {}.*'.format(self._logname),
                             color='magenta',
                             bold=True),
                logname=self._logname,
                user=stats['user'],
                system=stats['system'],
                mem_percent=round(stats['mem_percent'], 1),
                ctx=ctx)

        self._last_stats = stats
Example #26
0
            def forward(node_oid, *args, **kwargs):
                # remove the calling origin details (do not forward those 1:1 at least - FIXME)
                kwargs.pop('details', None)

                # map the node OID (given as UUID string) to the node WAMP authid for ..
                node_authid = session.map_node_oid_to_authid(node_oid)

                # .. creating the remote URI to be used on the management uplink
                _remote_uri = remote_uri.format(node_id=node_authid)

                self.log.debug(
                    'Forwarding CFC {forward_type} on mrealm {realm} to node "{node_authid}" [node_oid={node_oid}, local=<{local_uri}>, remote=<{remote_uri}>]',
                    forward_type=hl('CALL'),
                    local_uri=hlid(local_uri),
                    remote_uri=hlid(_remote_uri),
                    node_oid=hlid(node_oid),
                    node_authid=hluserid(node_authid),
                    realm=hluserid(session._realm))
                return session.call(_remote_uri, *args, **kwargs)
Example #27
0
    def _do_get(self, request):
        try:
            node_status = yield self._worker.call('crossbar.get_status')

            # FIXME:
            # master_status = yield self._service_agent.call('crossbarfabriccenter.domain.get_status')
            # master_license = yield self._service_agent.call('crossbarfabriccenter.domain.get_license')
            # master_version = yield self._service_agent.call('crossbarfabriccenter.domain.get_version')
            infos = {
                'node_status': node_status,
                # 'master_status': master_status,
                # 'master_license': master_license,
                # 'master_version': master_version,
            }
            self.log.debug('rendering {page} page using data\n{data}',
                           page=hl('pair-node', bold=True),
                           data=pformat(infos))
            self._delayedRender(infos, request)
        except:
            self.log.failure()
            request.finish()
Example #28
0
    def onJoin(self, details):
        """
        Called when process has joined the node's management realm.
        """

        regs = yield self.register(
            self,
            prefix='{}.'.format(self._uri_prefix),
            options=RegisterOptions(details_arg='details'),
        )

        self.log.info(
            'Ok, registered {len_reg} management procedures on realm "{realm}".',
            len_reg=hlval(len(regs)),
            realm=hl(self.realm))
        for reg in regs:
            if isinstance(reg, Failure):
                self.log.error("Failed to register: {f}",
                               f=reg,
                               log_failure=reg)
            else:
                self.log.debug('  {proc}', proc=hlid(reg.procedure))
        returnValue(regs)
Example #29
0
def main(reactor, args):
    colorama.init()

    pl = []
    for i in range(args.clients):
        proto = yield start_client(reactor, args, i)
        pl.append(proto)

        delay = random.expovariate(1)
        yield sleep(delay)

    pids = [p.transport.pid for p in pl]
    print('Started clients with PIDs {}'.format(hl(pids, bold=True)))

    def shutdown():
        for p in pl:
            try:
                pid = p.transport.pid
                p.transport.signalProcess('KILL')
                print('Client with PID {} killed'.format(hl(pid, bold=True)))
            except ProcessExitedAlready:
                pass

    reactor.addSystemEventTrigger('before', 'shutdown', shutdown)

    success = False
    dl = [p.all_done for p in pl]
    try:
        yield gatherResults(dl)
        success = True
    except Exception as e:
        print("FAILED:", e)

    if success:
        returnValue(0)
    else:
        returnValue(1)
Example #30
0
def _run_command_version(options, reactor, personality):
    """
    Subcommand "crossbar version".
    """
    log = make_logger()

    v = _get_versions(reactor)

    def decorate(text, fg='white', bg=None, bold=True):
        return click.style(text, fg=fg, bg=bg, bold=bold)

    for line in personality.BANNER.splitlines():
        log.info(hl(line, color='yellow', bold=True))
    log.info("")
    log.info(" Crossbar.io        : {ver}", ver=decorate(v.crossbar_ver))
    log.info("   txaio            : {ver}", ver=decorate(v.txaio_ver))
    log.info("   Autobahn         : {ver}", ver=decorate(v.ab_ver))
    log.info("     UTF8 Validator : {ver}", ver=decorate(v.utf8_ver))
    log.info("     XOR Masker     : {ver}", ver=decorate(v.xor_ver))
    log.info("     JSON Codec     : {ver}", ver=decorate(v.json_ver))
    log.info("     MsgPack Codec  : {ver}", ver=decorate(v.msgpack_ver))
    log.info("     CBOR Codec     : {ver}", ver=decorate(v.cbor_ver))
    log.info("     UBJSON Codec   : {ver}", ver=decorate(v.ubjson_ver))
    log.info("     FlatBuffers    : {ver}", ver=decorate(v.flatbuffers_ver))
    log.info("   Twisted          : {ver}", ver=decorate(v.tx_ver))
    log.info("   LMDB             : {ver}", ver=decorate(v.lmdb_ver))
    log.info("   Python           : {ver}/{impl}", ver=decorate(v.py_ver), impl=decorate(v.py_ver_detail))
    if personality.NAME in (u'edge', u'master'):
        log.info(" CrossbarFX         : {ver}", ver=decorate(v.crossbarfx_ver))
        log.info("   NumPy            : {ver}", ver=decorate(v.numpy_ver))
        log.info("   zLMDB            : {ver}", ver=decorate(v.zlmdb_ver))
        log.info("   XBR              : {ver}", ver=decorate(v.xbr_ver))
    log.info(" Frozen executable  : {py_is_frozen}", py_is_frozen=decorate('yes' if v.py_is_frozen else 'no'))
    log.info(" Operating system   : {ver}", ver=decorate(v.platform))
    log.info(" Host machine       : {ver}", ver=decorate(v.machine))
    log.info(" Release key        : {release_pubkey}", release_pubkey=decorate(v.release_pubkey))
    log.info("")
Example #31
0
def _run_command_status(options, reactor, personality):
    """
    Subcommand "crossbar status".
    """
    log = make_logger()

    # https://docs.python.org/2/library/os.html#os.EX_UNAVAILABLE
    # https://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3
    _EXIT_ERROR = getattr(os, 'EX_UNAVAILABLE', 1)

    # check if there is a Crossbar.io instance currently running from
    # the Crossbar.io node directory at all
    pid_data = _check_is_running(options.cbdir)

    # optional current state to assert
    _assert = options.__dict__['assert']
    if pid_data is None:
        if _assert == 'running':
            log.error('Assert status RUNNING failed: status is {}'.format(
                hl('STOPPED', color='red', bold=True)))
            sys.exit(_EXIT_ERROR)
        elif _assert == 'stopped':
            log.info('Assert status STOPPED succeeded: status is {}'.format(
                hl('STOPPED', color='green', bold=True)))
            sys.exit(0)
        else:
            log.info('Status is {}'.format(
                hl('STOPPED', color='white', bold=True)))
            sys.exit(0)
    else:
        if _assert == 'running':
            log.info('Assert status RUNNING succeeded: status is {}'.format(
                hl('RUNNING', color='green', bold=True)))
            sys.exit(0)
        elif _assert == 'stopped':
            log.error('Assert status STOPPED failed: status is {}'.format(
                hl('RUNNING', color='red', bold=True)))
            sys.exit(_EXIT_ERROR)
        else:
            log.info('Status is {}'.format(
                hl('RUNNING', color='white', bold=True)))
            sys.exit(0)
Example #32
0
def _run_command_status(options, reactor, personality):
    """
    Subcommand "crossbar status".
    """
    log = make_logger()

    # https://docs.python.org/2/library/os.html#os.EX_UNAVAILABLE
    # https://www.freebsd.org/cgi/man.cgi?query=sysexits&sektion=3
    _EXIT_ERROR = getattr(os, 'EX_UNAVAILABLE', 1)

    # check if there is a Crossbar.io instance currently running from
    # the Crossbar.io node directory at all
    pid_data = _check_is_running(options.cbdir)

    # optional current state to assert
    _assert = options.__dict__['assert']
    if pid_data is None:
        if _assert == 'running':
            log.error('Assert status RUNNING failed: status is {}'.format(hl('STOPPED', color='red', bold=True)))
            sys.exit(_EXIT_ERROR)
        elif _assert == 'stopped':
            log.info('Assert status STOPPED succeeded: status is {}'.format(hl('STOPPED', color='green', bold=True)))
            sys.exit(0)
        else:
            log.info('Status is {}'.format(hl('STOPPED', color='white', bold=True)))
            sys.exit(0)
    else:
        if _assert == 'running':
            log.info('Assert status RUNNING succeeded: status is {}'.format(hl('RUNNING', color='green', bold=True)))
            sys.exit(0)
        elif _assert == 'stopped':
            log.error('Assert status STOPPED failed: status is {}'.format(hl('RUNNING', color='red', bold=True)))
            sys.exit(_EXIT_ERROR)
        else:
            log.info('Status is {}'.format(hl('RUNNING', color='white', bold=True)))
            sys.exit(0)
Example #33
0
    async def _initialize_mrealm(self, mrealm):
        """
        Initializes a single management realm:

        1. start a respective realm on the router worker of this master node. Currently there is only one
           router worker statically configured (named "cfrouter1").
        2. start a couple of roles on the management realm (see BUILTIN_ROLES)
        3.

        :param mrealm: The management realm to initialize.
        :type mrealm: :class:`cfxdb.mrealm.management_realm.ManagementRealm`

        :return:
        """

        self.log.debug('{klass}._initialize_mrealm(mrealm={mrealm})',
                       klass=self.__class__.__name__,
                       mrealm=mrealm)
        # WAMP realm name of the realm to start
        realm_name = mrealm.name

        # router and container worker for realm to start
        router_id = mrealm.cf_router_worker or 'cfrouter1'
        container_id = mrealm.cf_container_worker or 'cfcontainer1'

        self.log.info(
            hl('Initializing management realm "{}" [router_id="{}", container_id="{}"] ..'
               .format(realm_name, router_id, container_id),
               color='red',
               bold=True))

        # start the management realm and roles on the configured router worker
        if realm_name not in self._router_realms:
            realm_config = {
                u'name': realm_name,
                u'options': {
                    u'enable_meta_api': True,

                    # FIXME: enabling this will break stopping and restarting an mrealm, as the
                    # procedures registered eg by the RouterServiceAgent session will stick around!
                    u'bridge_meta_api': False,
                }
            }
            realm_id = realm_name
            await self.call(
                u'crossbar.worker.{}.start_router_realm'.format(router_id),
                realm_id, realm_config)

            for role_id in [
                    u'owner-role', u'backend-role', u'node-role',
                    u'public-role'
            ]:
                await self.call(
                    u'crossbar.worker.{}.start_router_realm_role'.format(
                        router_id), realm_id, role_id, BUILTIN_ROLES[role_id])

            self._router_realms[realm_name] = True
        else:
            self.log.warn(
                'Management realm "{realm_name}" already initialized (skipped starting realm)',
                realm_name=realm_name)

        # start the configured container worker (if not yet started)
        if container_id not in self._container_workers:
            container_options = {
                "pythonpath": [".."],
                "expose_shared": True,
                "expose_controller": True,
                # "shutdown": "shutdown-on-last-component-stopped",
                "shutdown": "shutdown-manual",
                # "restart": "restart-on-failed"
                "restart": "restart-always"
            }
            await self.call(u'crossbar.start_worker', container_id,
                            u'container', container_options)

            self._container_workers[container_id] = {}
        else:
            self.log.warn(
                'Management realm "{realm_name}" backend container "{container_id}" already running (skipped starting container)',
                realm_name=realm_name,
                container_id=container_id)

        # start the management realm backend in the configured container worker
        component_id = realm_name
        if component_id not in self._container_workers[container_id]:
            mrealm_backend_extra = {
                u'mrealm': str(mrealm.oid),
                u'database': {
                    # the mrealm database path contains the mrealm UUID
                    u'dbfile':
                    os.path.join(self.config.extra['cbdir'],
                                 u'.db-mrealm-{}'.format(mrealm.oid)),

                    # hard-code max mrealm database size to 2GB
                    # https://github.com/crossbario/crossbar/issues/235
                    u'maxsize':
                    2**30 * 2,
                },
                u'controller-database': {
                    # forward controller database parameters, so that the mrealm backend can _also_ open
                    # the controller database (read-only)
                    u'dbfile': self._db.dbpath,
                    u'maxsize': self._db.maxsize,
                },
            }
            component_config = {
                u'type': u'class',
                u'classname': u'crossbar.master.mrealm.MrealmController',
                u'realm': realm_name,
                u'transport': {
                    # we connect back to the master router over UDS/RawSocket/CBOR
                    u'type': u'rawsocket',
                    u'endpoint': {
                        u'type': u'unix',
                        u'path': u'sock1'
                    },
                    u'serializer': u'cbor',
                },
                u'extra': mrealm_backend_extra
            }
            await self.call(
                u'crossbar.worker.{}.start_component'.format(container_id),
                component_id, component_config)

            self._container_workers[container_id][component_id] = True
        else:
            self.log.warn(
                'Management realm "{realm_name}" backend component "{component_id}" already running (skipped starting container component)',
                realm_name=realm_name,
                component_id=component_id)

        self.log.info(
            '{note} {func}',
            note=hl(
                'Ok, management realm "{}" initialized!'.format(realm_name),
                color='red',
                bold=True),
            func=hltype(self._initialize_mrealm))
Example #34
0
    async def onJoin(self, details: ComponentConfig):

        # handles to controller database and schema (already initialized/attached in node)
        #
        ready = self.config.extra['ready']
        cbdir = self.config.extra['cbdir']
        config = self.config.extra.get(u'database', {})

        # create database and attach tables to database slots
        #
        dbpath = config.get('path', '.db-controller')
        assert type(dbpath) == str
        dbpath = os.path.join(cbdir, dbpath)

        maxsize = config.get('maxsize', 128 * 2**20)
        assert type(maxsize) == int
        # allow maxsize 128kiB to 128GiB
        assert maxsize >= 128 * 1024 and maxsize <= 128 * 2**30

        self.log.info('{klass} starting [dbpath={dbpath}, maxsize={maxsize}]',
                      klass=self.__class__.__name__,
                      dbpath=hlid(dbpath),
                      maxsize=hlid(maxsize))

        self._db = zlmdb.Database(dbpath=dbpath,
                                  maxsize=maxsize,
                                  readonly=False,
                                  sync=True)
        self._db.__enter__()
        self._schema = GlobalSchema.attach(self._db)

        # initialize all currently existing mrealms
        try:
            await self._initialize_mrealms()
        except Exception:
            self.log.failure()
            raise

        # expose api on this object fo CFC clients
        domains = [(_CFC_GLOBAL_REALM, self.register)]
        for prefix, register in domains:
            registrations = await register(
                self,
                prefix=prefix,
                options=RegisterOptions(details_arg='details'))
            for reg in registrations:
                if type(reg) == Registration:
                    self.log.info(
                        'Registered CFC Global Realm "{realm}" API <{proc}>',
                        proc=reg.procedure,
                        realm=self._realm)
                else:
                    self.log.error('Error: <{}>'.format(reg.value.args[0]))

        # we are ready to roll
        self.log.info('{note} {klass}',
                      note=hl('Ok, master node "{}" booted and ready!'.format(
                          self._node._node_id),
                              color='red',
                              bold=True),
                      klass=hltype(self.onJoin))

        ready.callback(self)
Example #35
0
def _run_command_version(options, reactor, personality):
    """
    Subcommand "crossbar version".
    """
    log = make_logger()

    # Python
    py_ver = '.'.join([str(x) for x in list(sys.version_info[:3])])
    py_ver_string = "[%s]" % sys.version.replace('\n', ' ')

    if 'pypy_version_info' in sys.__dict__:
        py_ver_detail = "{}-{}".format(platform.python_implementation(), '.'.join(str(x) for x in sys.pypy_version_info[:3]))
    else:
        py_ver_detail = platform.python_implementation()

    # Pyinstaller (frozen EXE)
    py_is_frozen = getattr(sys, 'frozen', False)

    # Twisted / Reactor
    tx_ver = "%s-%s" % (_get_version('twisted'), reactor.__class__.__name__)
    tx_loc = "[%s]" % qual(reactor.__class__)

    # txaio
    txaio_ver = _get_version('txaio')

    # Autobahn
    ab_ver = _get_version('autobahn')
    ab_loc = "[%s]" % qual(WebSocketProtocol)

    # UTF8 Validator
    s = qual(Utf8Validator)
    if 'wsaccel' in s:
        utf8_ver = 'wsaccel-%s' % _get_version('wsaccel')
    elif s.startswith('autobahn'):
        utf8_ver = 'autobahn'
    else:
        # could not detect UTF8 validator type/version
        utf8_ver = '?'
    utf8_loc = "[%s]" % qual(Utf8Validator)

    # XOR Masker
    s = qual(XorMaskerNull)
    if 'wsaccel' in s:
        xor_ver = 'wsaccel-%s' % _get_version('wsaccel')
    elif s.startswith('autobahn'):
        xor_ver = 'autobahn'
    else:
        # could not detect XOR masker type/version
        xor_ver = '?'
    xor_loc = "[%s]" % qual(XorMaskerNull)

    # JSON Serializer
    supported_serializers = ['JSON']
    from autobahn.wamp.serializer import JsonObjectSerializer
    json_ver = JsonObjectSerializer.JSON_MODULE.__name__

    # If it's just 'json' then it's the stdlib one...
    if json_ver == 'json':
        json_ver = 'stdlib'
    else:
        json_ver = (json_ver + "-%s") % _get_version(json_ver)

    # MsgPack Serializer
    try:
        import umsgpack  # noqa
        msgpack_ver = 'u-msgpack-python-%s' % _get_version(umsgpack)
        supported_serializers.append('MessagePack')
    except ImportError:
        msgpack_ver = '-'

    # CBOR Serializer
    try:
        import cbor  # noqa
        cbor_ver = 'cbor-%s' % _get_version(cbor)
        supported_serializers.append('CBOR')
    except ImportError:
        cbor_ver = '-'

    # UBJSON Serializer
    try:
        import ubjson  # noqa
        ubjson_ver = 'ubjson-%s' % _get_version(ubjson)
        supported_serializers.append('UBJSON')
    except ImportError:
        ubjson_ver = '-'

    # LMDB
    try:
        import lmdb  # noqa
        lmdb_lib_ver = '.'.join([str(x) for x in lmdb.version()])
        lmdb_ver = '{}/lmdb-{}'.format(_get_version(lmdb), lmdb_lib_ver)
    except ImportError:
        lmdb_ver = '-'

    # crossbarfx
    try:
        from crossbarfx._version import __version__ as crossbarfx_ver  # noqa
    except ImportError:
        crossbarfx_ver = '-'

    # txaio-etcd
    try:
        import txaioetcd  # noqa
        txaioetcd_ver = _get_version(txaioetcd)
    except ImportError:
        txaioetcd_ver = '-'

    # Release Public Key
    from crossbar.common.key import _read_release_key
    release_pubkey = _read_release_key()

    def decorate(text, fg='white', bg=None, bold=True):
        return click.style(text, fg=fg, bg=bg, bold=bold)

    pad = " " * 22
    for line in personality.BANNER.splitlines():
        log.info(hl(line, color='yellow', bold=True))
    log.info("")
    log.info(" Crossbar.io        : {ver}", ver=decorate(crossbar.__version__))
    log.info("   Autobahn         : {ver}", ver=decorate(ab_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(ab_loc))
    log.debug("     txaio          : {ver}", ver=decorate(txaio_ver))
    log.debug("     UTF8 Validator : {ver}", ver=decorate(utf8_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(utf8_loc))
    log.debug("     XOR Masker     : {ver}", ver=decorate(xor_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(xor_loc))
    log.debug("     JSON Codec     : {ver}", ver=decorate(json_ver))
    log.debug("     MsgPack Codec  : {ver}", ver=decorate(msgpack_ver))
    log.debug("     CBOR Codec     : {ver}", ver=decorate(cbor_ver))
    log.debug("     UBJSON Codec   : {ver}", ver=decorate(ubjson_ver))
    log.info("   Twisted          : {ver}", ver=decorate(tx_ver))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(tx_loc))
    log.info("   LMDB             : {ver}", ver=decorate(lmdb_ver))
    log.info("   Python           : {ver}/{impl}", ver=decorate(py_ver), impl=decorate(py_ver_detail))
    log.trace("{pad}{debuginfo}", pad=pad, debuginfo=decorate(py_ver_string))
    if personality.NAME in (u'edge', u'master'):
        log.info(" Crossbar.io FX     : {ver}", ver=decorate(crossbarfx_ver))
    if personality.NAME in (u'master'):
        log.info("   txaioetcd        : {ver}", ver=decorate(txaioetcd_ver))
    log.info(" Frozen executable  : {py_is_frozen}", py_is_frozen=decorate('yes' if py_is_frozen else 'no'))
    log.info(" Operating system   : {ver}", ver=decorate(platform.platform()))
    log.info(" Host machine       : {ver}", ver=decorate(platform.machine()))
    log.info(" Release key        : {release_pubkey}", release_pubkey=decorate(release_pubkey[u'base64']))
    log.info("")
Example #36
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)
Example #37
0
def _print_usage(prog, personality):
    print(hl(personality.BANNER, color='yellow', bold=True))
    print('Type "{} --help" to get help, or "{} <command> --help" to get help on a specific command.'.format(prog, prog))
    print('Type "{} legal" to read legal notices, terms of use and license and privacy information.'.format(prog))
    print('Type "{} version" to print detailed version information.'.format(prog))
Example #38
0
    def boot_from_config(self, config):
        """
        Startup elements in the node as specified in the provided node configuration.
        """
        # get controller configuration subpart
        controller = config.get('controller', {})
        parallel_worker_start = controller.get('options', {}).get('enable_parallel_worker_start', False)

        self.log.info('{bootmsg} {method}',
                      bootmsg=hl('Booting node from local configuration [parallel_worker_start={}] ..'.format(parallel_worker_start),
                                 color='green', bold=True),
                      method=hltype(Node.boot_from_config))

        # start Manhole in node controller
        if 'manhole' in controller:
            yield self._controller.call(u'crossbar.start_manhole', controller['manhole'], options=CallOptions())
            self.log.debug("controller: manhole started")

        # startup all workers
        workers = config.get('workers', [])
        if len(workers):
            self.log.info(hl('Will start {} worker{} ..'.format(len(workers), 's' if len(workers) > 1 else ''), color='green', bold=True))
        else:
            self.log.info(hl('No workers configured, nothing to do', color='green', bold=True))

        dl = []
        for worker in workers:

            # worker ID
            if 'id' in worker:
                worker_id = worker['id']
            else:
                worker_id = u'worker{:03d}'.format(self._worker_no)
                worker['id'] = worker_id
                self._worker_no += 1

            # worker type: either a native worker ('router', 'container', ..), or a guest worker ('guest')
            worker_type = worker['type']

            # native worker processes setup
            if worker_type in self._native_workers:

                # set logname depending on native worker type
                worker_logname = '{} {}'.format(self._native_workers[worker_type]['logname'], hlid(worker_id))

                # any worker specific options
                worker_options = worker.get('options', {})

                # start the (native) worker
                self.log.info(
                    "Order node to start {worker_logname}",
                    worker_logname=worker_logname,
                )

                d = self._controller.call(u'crossbar.start_worker', worker_id, worker_type, worker_options, options=CallOptions())

                @inlineCallbacks
                def configure_worker(res, worker_logname, worker_type, worker_id, worker):
                    self.log.info(
                        "Ok, node has started {worker_logname}",
                        worker_logname=worker_logname,
                    )

                    # now configure the worker
                    self.log.info(
                        "Configuring {worker_logname} ..",
                        worker_logname=worker_logname,
                    )
                    method_name = '_configure_native_worker_{}'.format(worker_type.replace('-', '_'))
                    try:
                        config_fn = getattr(self, method_name)
                    except AttributeError:
                        raise ValueError(
                            "A native worker of type '{}' is configured but "
                            "there is no method '{}' on {}".format(worker_type, method_name, type(self))
                        )
                    yield config_fn(worker_logname, worker_id, worker)
                    self.log.info(
                        "Ok, {worker_logname} configured",
                        worker_logname=worker_logname,
                    )

                d.addCallback(configure_worker, worker_logname, worker_type, worker_id, worker)

            # guest worker processes setup
            elif worker_type == u'guest':

                # now actually start the (guest) worker ..

                # FIXME: start_worker() takes the whole configuration item for guest workers, whereas native workers
                # only take the options (which is part of the whole config item for the worker)
                d = self._controller.call(u'crossbar.start_worker', worker_id, worker_type, worker, options=CallOptions())

            else:
                raise Exception('logic error: unexpected worker_type="{}"'.format(worker_type))

            if parallel_worker_start:
                dl.append(d)
            else:
                yield d

        yield gatherResults(dl)

        self.log.info(hl('Ok, local node configuration booted successfully!', color='green', bold=True))
Example #39
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)

    # Ignore SIGINT so we get consistent behavior on control-C versus
    # sending SIGINT to the controller process. When the controller is
    # shutting down, it sends TERM to all its children but ctrl-C
    # handling will send a SIGINT to all the processes in the group
    # (so then the controller sends a TERM but the child already or
    # will very shortly get a SIGINT as well). Twisted installs signal
    # handlers, but not for SIGINT if there's already a custom one
    # present.
    def ignore(sig, frame):
        log.debug("Ignoring SIGINT in worker.")
    signal.signal(signal.SIGINT, ignore)

    # actually begin logging
    start_logging(None, options.loglevel)

    # get personality klass, eg "crossbar.personality.Personality"
    l = options.personality.split('.')
    personality_module, personality_klass = '.'.join(l[:-1]), l[-1]

    # now load the personality module and class
    _mod = importlib.import_module(personality_module)
    Personality = getattr(_mod, personality_klass)

    # get worker klass, eg "crossbar.worker.container.ContainerController"
    l = options.klass.split('.')
    worker_module, worker_klass = '.'.join(l[:-1]), l[-1]

    # now load the worker module and class
    _mod = importlib.import_module(worker_module)
    klass = getattr(_mod, worker_klass)

    log.info(
        'Starting worker "{worker_id}" for node "{node_id}" with personality "{personality}" {worker_class}',
        worker_id=options.worker,
        node_id=options.node,
        personality=Personality.NAME,
        worker_class=hltype(klass),
    )
    log.info(
        'Running as PID {pid} on {python}-{reactor}',
        pid=os.getpid(),
        python=platform.python_implementation(),
        reactor=qual(reactor.__class__).split('.')[-1],
    )
    if MEASURING_COVERAGE:
        log.info(hl('Code coverage measurements enabled (coverage={coverage_version}).', color='green', bold=True),
                 coverage_version=coverage.__version__)

    # set process title if requested to
    #
    try:
        import setproctitle
    except ImportError:
        log.debug("Could not set worker process title (setproctitle not installed)")
    else:
        if options.title:
            setproctitle.setproctitle(options.title)
        else:
            setproctitle.setproctitle('crossbar-worker [{}]'.format(options.klass))

    # node directory
    #
    options.cbdir = os.path.abspath(options.cbdir)
    os.chdir(options.cbdir)
    # log.msg("Starting from node directory {}".format(options.cbdir))

    # set process title if requested to
    #
    try:
        import setproctitle
    except ImportError:
        log.debug("Could not set worker process title (setproctitle not installed)")
    else:
        if options.title:
            setproctitle.setproctitle(options.title)
        else:
            setproctitle.setproctitle(
                'crossbar-worker [{}]'.format(options.klass)
            )

    from twisted.internet.error import ConnectionDone
    from autobahn.twisted.websocket import WampWebSocketServerProtocol

    class WorkerServerProtocol(WampWebSocketServerProtocol):

        def connectionLost(self, reason):
            # the behavior here differs slightly whether we're shutting down orderly
            # or shutting down because of "issues"
            if isinstance(reason.value, ConnectionDone):
                was_clean = True
            else:
                was_clean = False

            try:
                # this log message is unlikely to reach the controller (unless
                # only stdin/stdout pipes were lost, but not stderr)
                if was_clean:
                    log.info("Connection to node controller closed cleanly")
                else:
                    log.warn("Connection to node controller lost: {reason}", reason=reason)

                # give the WAMP transport a change to do it's thing
                WampWebSocketServerProtocol.connectionLost(self, reason)
            except:
                # we're in the process of shutting down .. so ignore ..
                pass
            finally:
                # after the connection to the node controller is gone,
                # the worker is "orphane", and should exit

                # determine process exit code
                if was_clean:
                    exit_code = 0
                else:
                    exit_code = 1

                # exit the whole worker process when the reactor has stopped
                reactor.addSystemEventTrigger('after', 'shutdown', os._exit, exit_code)

                # stop the reactor
                try:
                    reactor.stop()
                except ReactorNotRunning:
                    pass

    try:
        # define a WAMP application session factory
        #
        from autobahn.wamp.types import ComponentConfig

        def make_session():
            session_config = ComponentConfig(realm=options.realm, extra=options)
            session = klass(config=session_config, reactor=reactor, personality=Personality)
            return session

        # create a WAMP-over-WebSocket transport server factory
        #
        from autobahn.twisted.websocket import WampWebSocketServerFactory
        transport_factory = WampWebSocketServerFactory(make_session, u'ws://localhost')
        transport_factory.protocol = WorkerServerProtocol
        transport_factory.setProtocolOptions(failByDrop=False)

        # create a protocol instance and wire up to stdio
        #
        from twisted.python.runtime import platform as _platform
        from twisted.internet import stdio
        proto = transport_factory.buildProtocol(None)
        if _platform.isWindows():
            stdio.StandardIO(proto)
        else:
            stdio.StandardIO(proto, stdout=3)

        # now start reactor loop
        #
        if False:
            log.info("vmprof enabled.")

            import os
            import vmprof

            PROFILE_FILE = 'vmprof_{}.dat'.format(os.getpid())

            outfd = os.open(PROFILE_FILE, os.O_RDWR | os.O_CREAT | os.O_TRUNC)
            vmprof.enable(outfd, period=0.01)

            log.info(hl('Entering event reactor ...', color='cyan', bold=True))
            reactor.run()

            vmprof.disable()
        else:
            log.info(hl('Entering event reactor ...', color='cyan', bold=True))
            reactor.run()

    except Exception as e:
        log.info("Unhandled exception: {e}", e=e)
        if reactor.running:
            reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1)
            reactor.stop()
        else:
            sys.exit(1)