Example #1
0
    def test_unknown_types(self):
        """
        An unknown type will raise an exception.
        """
        config = {
            "type": "kjdfgdbfls",
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(("invalid component type 'kjdfgdbfls'"),
                      str(e.exception.args[0]))
Example #2
0
    def test_class_non_applicationsession(self):
        """
        Loading a class which does not subclass AppSession fails.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.badclass.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(("session not derived of ApplicationSession"),
                      str(e.exception.args[0]))
Example #3
0
    def test_unknown_types(self):
        """
        An unknown type will raise an exception.
        """
        config = {
            "type": "kjdfgdbfls",
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("invalid component type 'kjdfgdbfls'"),
            str(e.exception.args[0]))
Example #4
0
    def test_class_non_applicationsession(self):
        """
        Loading a class which does not subclass AppSession fails.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.badclass.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("session not derived of ApplicationSession"),
            str(e.exception.args[0]))
Example #5
0
    def test_class_syntaxerror(self):
        """
        Loading a class which has a SyntaxError raises that up.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.syntaxerror.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("Failed to import class 'crossbar.worker.test.examples.syntaxerr"
             "or.AppSession'"), str(e.exception.args[0]))
        self.assertIn(("SyntaxError"), str(e.exception.args[0]))
Example #6
0
    def test_class_syntaxerror(self):
        """
        Loading a class which has a SyntaxError raises that up.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.syntaxerror.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("Failed to import class 'crossbar.worker.test.examples.syntaxerr"
             "or.AppSession'"),
            str(e.exception.args[0]))
        self.assertIn(
            ("SyntaxError"),
            str(e.exception.args[0]))
Example #7
0
    def test_class_importerror(self):
        """
        Loading a class which has an import error upon import raises that
        error.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.importerror.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("Failed to import class 'crossbar.worker.test.examples.importerr"
             "or.AppSession'"), str(e.exception.args[0]))

        s = str(e.exception.args[0])
        self.assertTrue('ImportError' in s or 'ModuleNotFoundError' in s)
Example #8
0
    def test_class_importerror(self):
        """
        Loading a class which has an import error upon import raises that
        error.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.importerror.AppSession"
        }

        with self.assertRaises(ApplicationError) as e:
            _appsession_loader(config)

        self.assertIn(
            ("Failed to import class 'crossbar.worker.test.examples.importerr"
             "or.AppSession'"),
            str(e.exception.args[0]))

        s = str(e.exception.args[0])
        self.assertTrue('ImportError' in s or 'ModuleNotFoundError' in s)
Example #9
0
    def test_class_standard(self):
        """
        You can load a standard class.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.goodclass.AppSession"
        }

        klass = _appsession_loader(config)

        from .examples.goodclass import AppSession
        self.assertIs(klass, AppSession)
Example #10
0
    def test_class_standard(self):
        """
        You can load a standard class.
        """
        config = {
            "type": "class",
            "classname": "crossbar.worker.test.examples.goodclass.AppSession"
        }

        klass = _appsession_loader(config)

        from .examples.goodclass import AppSession
        self.assertIs(klass, AppSession)
Example #11
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str

        :param config: The component configuration.
        :type config: dict

        :param details: Call details.
        :type details: :class:`autobahn.wamp.types.CallDetails`
        """
        self.log.debug("{name}.start_router_component",
                       name=self.__class__.__name__)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(
                id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            self.personality.check_router_component(self.personality, config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration",
                                   emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(
                        ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(
                        u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(
                    ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration",
                                       emsg)

        # create component config
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(
            realm=realm,
            extra=extra,
            keyring=None,
            controller=self if self.config.extra.expose_controller else None,
            shared=self.components_shared
            if self.config.extra.expose_shared else None)
        create_component = _appsession_loader(config)

        # .. and create and add an WAMP application session to
        # run the component next to the router
        #
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg,
                    log_failure=fail)
                session.disconnect()

            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        # Note that 'join' is fired to listeners *before* onJoin runs,
        # so if you do 'yield self.leave()' in onJoin we'll still
        # publish "started" before "stopped".

        def publish_stopped(session, stop_details):
            self.log.info(
                "stopped component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_stop'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        def publish_started(session, start_details):
            self.log.info(
                "started component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_start'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        session.on('leave', publish_stopped)
        session.on('join', publish_started)

        self.components[id] = RouterComponent(id, config, session)
        self._router_session_factory.add(session,
                                         authrole=config.get(
                                             'role', u'anonymous'))
        self.log.debug(
            "Added component {id} (type '{name}')",
            id=id,
            name=class_name(session),
        )
Example #12
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str
        :param config: The component configuration.
        :type config: obj
        """
        self.log.debug("{}.start_router_component".format(self.__class__.__name__),
                       id=id, config=config)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_router_component(config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

        # create component config
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(realm=realm, extra=extra)
        create_component = _appsession_loader(config)

        # .. and create and add an WAMP application session to
        # run the component next to the router
        #
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg, log_failure=fail
                )
                session.disconnect()
            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        self.components[id] = RouterComponent(id, config, session)
        self._router_session_factory.add(session, authrole=config.get('role', u'anonymous'))
        self.log.debug("Added component {id}", id=id)
Example #13
0
    def start_component(self, component_id, config, reload_modules=False, details=None):
        """
        Starts a component in this container worker.

        :param component_id: The ID under which to start the component.
        :type component_id: str

        :param config: Component configuration.
        :type config: dict

        :param reload_modules: If `True`, enforce reloading of modules (user code)
           that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool

        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns: Component startup information.
        :rtype: dict
        """
        self.log.debug(u'{klass}.start_component({component_id}, {config})',
                       klass=self.__class__.__name__,
                       component_id=component_id,
                       config=config)

        # prohibit starting a component twice
        #
        if component_id in self.components:
            emsg = u'duplicate component "{}" - a component with this ID is already running (or starting)'.format(component_id)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check component configuration
        #
        try:
            self.personality.check_container_component(self.personality, config)
        except Exception as e:
            emsg = u'invalid container component configuration: {}'.format(e)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.invalid_configuration', emsg)
        else:
            self.log.debug(u'starting component "{component_id}" ..', component_id=component_id)

        # WAMP application component factory
        #
        realm = config.get(u'realm', None)
        extra = config.get(u'extra', None)
        controller = self if self.config.extra.expose_controller else None
        shared = self.components_shared if self.config.extra.expose_shared else None
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # prepare some cleanup code this connection goes away
        def _closed(session, was_clean):
            """
            This is moderate hack around the fact that we don't have any way
            to "listen" for a close event on websocket or rawsocket
            objects. Also, the rawsocket implementation doesn't have
            "a" function we can wrap anyway (they are asyncio vs
            Twisted specific), so for both WebSocket and rawsocket
            cases, we actually listen on the WAMP session for
            transport close notifications.

            Ideally we'd listen for "close" on the transport but this
            works fine for cleaning up the components.
            """
            if component_id not in self.components:
                self.log.warn(
                    "Component '{id}' closed, but not in set.",
                    id=component_id,
                )
                return

            if was_clean:
                self.log.info(
                    "Closed connection to '{id}'",
                    id=component_id,
                )
            else:
                self.log.error(
                    "Lost connection to component '{id}' uncleanly",
                    id=component_id,
                )

            component = self.components[component_id]
            del self.components[component_id]
            self._publish_component_stop(component)
            component._stopped.callback(component.marshal())
            del component

            # figure out if we need to shut down the container itself or not
            if not self.components:
                if self._exit_mode == self.SHUTDOWN_ON_LAST_COMPONENT_STOPPED:
                    self.log.info(
                        "Container is hosting no more components: stopping container in exit mode <{exit_mode}> ...",
                        exit_mode=self._exit_mode,
                    )
                    self.shutdown()
                else:
                    self.log.info(
                        "Container is hosting no more components: continue running in exit mode <{exit_mode}>",
                        exit_mode=self._exit_mode,
                    )
            else:
                self.log.info(
                    "Container is still hosting {component_count} components: continue running in exit mode <{exit_mode}>",
                    exit_mode=self._exit_mode,
                    component_count=len(self.components),
                )

        # WAMP application session factory
        #
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg, log_failure=fail,
                    )
                    session.disconnect()
                session._swallow_error = panic

                # see note above, for _closed -- we should be
                # listening for "the transport was closed", but
                # "session disconnect" is close enough (since there
                # are no "proper events" from websocket/rawsocket
                # implementations).
                session.on('disconnect', _closed)

                return session

            except Exception:
                self.log.failure(u'component instantiation failed: {log_failure.value}')
                raise

        # WAMP transport factory
        #
        transport_config = config[u'transport']

        if transport_config[u'type'] == u'websocket':

            # create a WAMP-over-WebSocket transport client factory
            transport_factory = WampWebSocketClientFactory(create_session, transport_config[u'url'])
            transport_factory.noisy = False

        elif transport_config[u'type'] == u'rawsocket':

            transport_factory = WampRawSocketClientFactory(create_session,
                                                           transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did check the config before
            raise Exception(u'logic error')

        # create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(transport_config[u'endpoint'],
                                                          self.config.extra.cbdir,
                                                          self._reactor,
                                                          self.log)

        # now, actually connect the client
        #
        d = endpoint.connect(transport_factory)

        def on_connect_success(proto):
            component = ContainerComponent(component_id, config, proto, None)
            self.components[component_id] = component

            # publish event "on_component_start" to all but the caller
            #
            uri = self._uri_prefix + u'.on_component_started'

            component_started = {
                u'id': component_id,
                u'config': config
            }

            self.publish(uri, component_started, options=PublishOptions(exclude=details.caller))

            return component_started

        def on_connect_error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = u'could not connect container component to router - transport establishment failed ({})'.format(err.value)
                self.log.warn(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here
                # should be subclasses of ConnectError)
                raise err

        d.addCallbacks(on_connect_success, on_connect_error)

        return d
Example #14
0
    def start_component(self,
                        component_id,
                        config,
                        reload_modules=False,
                        details=None):
        """
        Starts a component in this container worker.

        :param component_id: The ID under which to start the component.
        :type component_id: str

        :param config: Component configuration.
        :type config: dict

        :param reload_modules: If `True`, enforce reloading of modules (user code)
           that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool

        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns: Component startup information.
        :rtype: dict
        """
        self.log.debug(u'{klass}.start_component({component_id}, {config})',
                       klass=self.__class__.__name__,
                       component_id=component_id,
                       config=config)

        # prohibit starting a component twice
        #
        if component_id in self.components:
            emsg = u'duplicate component "{}" - a component with this ID is already running (or starting)'.format(
                component_id)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check component configuration
        #
        try:
            self.personality.check_container_component(self.personality,
                                                       config)
        except Exception as e:
            emsg = u'invalid container component configuration: {}'.format(e)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.invalid_configuration',
                                   emsg)
        else:
            self.log.debug(u'starting component "{component_id}" ..',
                           component_id=component_id)

        # WAMP application component factory
        #
        realm = config.get(u'realm', None)
        assert type(realm) == str

        extra = config.get(u'extra', {})
        assert type(extra) == dict

        # forward crossbar node base directory
        extra['cbdir'] = self.config.extra.cbdir

        # allow access to controller session
        controller = self if self.config.extra.expose_controller else None

        # expose an object shared between components
        shared = self.components_shared if self.config.extra.expose_shared else None

        # this is the component configuration provided to the components ApplicationSession
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)

        # define component ctor function
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # prepare some cleanup code in case this connection goes away
        def _closed(session, was_clean):
            """
            This is moderate hack around the fact that we don't have any way
            to "listen" for a close event on websocket or rawsocket
            objects. Also, the rawsocket implementation doesn't have
            "a" function we can wrap anyway (they are asyncio vs
            Twisted specific), so for both WebSocket and rawsocket
            cases, we actually listen on the WAMP session for
            transport close notifications.

            Ideally we'd listen for "close" on the transport but this
            works fine for cleaning up the components.
            """
            if component_id not in self.components:
                self.log.warn(
                    "Component '{id}' closed, but not in set.",
                    id=component_id,
                )
                return

            if was_clean:
                self.log.info(
                    "Closed connection to '{id}'",
                    id=component_id,
                )
            else:
                self.log.error(
                    "Lost connection to component '{id}' uncleanly",
                    id=component_id,
                )

            component = self.components[component_id]
            del self.components[component_id]
            self._publish_component_stop(component)
            component._stopped.callback(component.marshal())
            del component

            # figure out if we need to shut down the container itself or not
            if not was_clean and self._exit_mode == self.SHUTDOWN_ON_ANY_COMPONENT_FAILED:
                self.log.info(
                    "A component has failed: stopping container in exit mode <{exit_mode}> ...",
                    exit_mode=self._exit_mode,
                )
                self.shutdown()
                return

            if self._exit_mode == self.SHUTDOWN_ON_ANY_COMPONENT_STOPPED:
                self.log.info(
                    "A component has stopped: stopping container in exit mode <{exit_mode}> ...",
                    exit_mode=self._exit_mode,
                )
                self.shutdown()
                return

            if not self.components:
                if self._exit_mode == self.SHUTDOWN_ON_LAST_COMPONENT_STOPPED:
                    self.log.info(
                        "Container is hosting no more components: stopping container in exit mode <{exit_mode}> ...",
                        exit_mode=self._exit_mode,
                    )
                    self.shutdown()
                    return
                else:
                    self.log.info(
                        "Container is hosting no more components: continue running in exit mode <{exit_mode}>",
                        exit_mode=self._exit_mode,
                    )
            else:
                self.log.info(
                    "Container is still hosting {component_count} components: continue running in exit mode <{exit_mode}>",
                    exit_mode=self._exit_mode,
                    component_count=len(self.components),
                )

            # determine if we should re-start the component. Note that
            # we can only arrive here if we *didn't* decide to
            # shutdown above .. so if we have a shutdown mode of
            # SHUTDOWN_ON_ANY_COMPONENT_STOPPED will mean we never try
            # to re-start anything.
            if self._restart_mode and self._restart_mode != self.RESTART_NEVER:

                def restart_component():
                    # Think: if this below start_component() fails,
                    # we'll still schedule *exactly one* new re-start
                    # attempt for it, right?
                    self.log.info(
                        "Restarting component '{component_id}'",
                        component_id=component_id,
                    )
                    return self.start_component(
                        component_id,
                        config,
                        reload_modules=reload_modules,
                        details=details,
                    )

                # note we must yield to the reactor with
                # callLater(0, ..) to avoid infinite recurision if
                # we're stuck in a restart loop
                from twisted.internet import reactor
                if self._restart_mode == self.RESTART_ALWAYS:
                    reactor.callLater(0, restart_component)
                elif self._restart_mode == self.RESTART_FAILED and not was_clean:
                    reactor.callLater(0, restart_component)

        joined_d = Deferred()

        # WAMP application session factory
        #
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg,
                        log_failure=fail,
                    )
                    session.disconnect()

                session._swallow_error = panic

                # see note above, for _closed -- we should be
                # listening for "the transport was closed", but
                # "session disconnect" is close enough (since there
                # are no "proper events" from websocket/rawsocket
                # implementations).
                session.on('disconnect', _closed)

                # note, "ready" here means: onJoin and any on('join',
                # ..) handlers have all completed successfully. This
                # is necessary for container-components (as opposed to
                # router-components) to work as expected
                def _ready(s):
                    joined_d.callback(None)

                session.on('ready', _ready)

                def _left(s, details):
                    if not joined_d.called:
                        joined_d.errback(
                            ApplicationError(
                                details.reason,
                                details.message,
                            ))

                session.on('leave', _left)

                return session

            except Exception:
                self.log.failure(
                    u'component instantiation failed: {log_failure.value}')
                raise

        # WAMP transport factory
        #
        transport_config = config[u'transport']

        if transport_config[u'type'] == u'websocket':

            # create a WAMP-over-WebSocket transport client factory
            transport_factory = WampWebSocketClientFactory(
                create_session, transport_config[u'url'])
            transport_factory.noisy = False

            if 'options' in transport_config:
                set_websocket_options(transport_factory,
                                      transport_config['options'])

        elif transport_config[u'type'] == u'rawsocket':

            transport_factory = WampRawSocketClientFactory(
                create_session, transport_config)
            transport_factory.noisy = False

            if 'options' in transport_config:
                set_rawsocket_options(transport_factory,
                                      transport_config['options'])

        else:
            # should not arrive here, since we did check the config before
            raise Exception(u'logic error')

        # create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(
            transport_config[u'endpoint'], self.config.extra.cbdir,
            self._reactor, self.log)

        # now, actually connect the client
        #
        d = endpoint.connect(transport_factory)

        def on_connect_success(proto):
            component = ContainerComponent(component_id, config, proto, None)
            self.components[component_id] = component

            # publish event "on_component_start" to all but the caller
            #
            uri = self._uri_prefix + u'.on_component_started'

            component_started = {u'id': component_id, u'config': config}

            self.publish(uri,
                         component_started,
                         options=PublishOptions(exclude=details.caller))

            return component_started

        def on_connect_error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = u'could not connect container component to router - transport establishment failed ({})'.format(
                    err.value)
                self.log.warn(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here
                # should be subclasses of ConnectError)
                raise err

        def await_join(arg):
            """
            We don't want to consider this component working until its on_join
            has completed (see create_session() above where this is hooked up)
            """
            return joined_d

        d.addCallbacks(on_connect_success, on_connect_error)
        d.addCallback(await_join)

        return d
Example #15
0
    def start_container_component(self,
                                  id,
                                  config,
                                  reload_modules=False,
                                  details=None):
        """
        Starts a Class or WAMPlet in this component container.

        :param config: Component configuration.
        :type config: dict
        :param reload_modules: If `True`, enforce reloading of modules (user code)
                               that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool
        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns dict -- A dict with combined info from component starting.
        """
        self.log.debug("{klass}.start_container_component({id}, {config})",
                       klass=self.__class__.__name__,
                       id=id,
                       config=config)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component - a component with ID '{}'' is already running (or starting)".format(
                id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_container_component(config)
        except Exception as e:
            emsg = "Invalid container component configuration ({})".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration",
                                   emsg)
        else:
            self.log.debug("Starting {type}-component in container.",
                           type=config['type'])

        # 1) create WAMP application component factory
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(realm=realm, extra=extra)

        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            self.log.error("Component loading failed", log_failure=Failure())
            if 'No module named' in str(e):
                self.log.error("  Python module search paths:")
                for path in e.kwargs['pythonpath']:
                    self.log.error("    {path}", path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # WAMP application session factory
        # ultimately, this gets called once the connection is
        # establised, from onOpen in autobahn/wamp/websocket.py:59
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg,
                        log_failure=fail,
                    )
                    session.disconnect()

                session._swallow_error = panic
                return session
            except Exception:
                self.log.failure(
                    "Component instantiation failed: {log_failure.value}")
                raise

        # 2) create WAMP transport factory
        #
        transport_config = config['transport']

        # WAMP-over-WebSocket transport
        #
        if transport_config['type'] == 'websocket':

            # create a WAMP-over-WebSocket transport client factory
            #
            transport_factory = WampWebSocketClientFactory(
                create_session, transport_config['url'])
            transport_factory.noisy = False

        # WAMP-over-RawSocket transport
        #
        elif transport_config['type'] == 'rawsocket':

            transport_factory = WampRawSocketClientFactory(
                create_session, transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did `check_container_component()`
            raise Exception("logic error")

        # 3) create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(
            transport_config['endpoint'], self.config.extra.cbdir,
            self._reactor, self.log)

        # now connect the client
        #
        d = endpoint.connect(transport_factory)

        def success(proto):
            component = ContainerComponent(id, config, proto, None)
            self.components[id] = component

            # FIXME: this is a total hack.
            #
            def close_wrapper(orig, was_clean, code, reason):
                """
                Wrap our protocol's onClose so we can tell when the component
                exits.
                """
                r = orig(was_clean, code, reason)
                if component.id not in self.components:
                    self.log.warn("Component '{id}' closed, but not in set.",
                                  id=component.id)
                    return r

                if was_clean:
                    self.log.info(
                        "Closed connection to '{id}' with code '{code}'",
                        id=component.id,
                        code=code)
                else:
                    self.log.error(
                        "Lost connection to component '{id}' with code '{code}'.",
                        id=component.id,
                        code=code)

                if reason:
                    self.log.warn(str(reason))

                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())

                if not self.components:
                    self.log.info(
                        "Container is hosting no more components: shutting down."
                    )
                    self.stop_container()

                return r

            # FIXME: due to history, the following is currently the case:
            # ITransportHandler.onClose is implemented directly on WampWebSocketClientProtocol,
            # while with WampRawSocketClientProtocol, the ITransportHandler is implemented
            # by the object living on proto._session
            #
            if isinstance(proto, WampWebSocketClientProtocol):
                proto.onClose = partial(close_wrapper, proto.onClose)

            elif isinstance(proto, WampRawSocketClientProtocol):
                # FIXME: doesn't work without guard, since proto_.session is not yet there when
                # proto comes into existance ..
                if proto._session:
                    proto._session.onClose = partial(close_wrapper,
                                                     proto._session.onClose)
            else:
                raise Exception("logic error")

            # publish event "on_component_start" to all but the caller
            #
            topic = self._uri_prefix + '.container.on_component_start'
            event = {u'id': id}
            self.publish(topic,
                         event,
                         options=PublishOptions(exclude=details.caller))
            return event

        def error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = "Could not connect container component to router - transport establishment failed ({})".format(
                    err.value)
                self.log.error(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here should be subclasses of ConnectError)
                raise err

        d.addCallbacks(success, error)

        return d
Example #16
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str

        :param config: The component configuration.
        :type config: dict

        :param details: Call details.
        :type details: :class:`autobahn.wamp.types.CallDetails`
        """
        self.log.debug("{name}.start_router_component", name=self.__class__.__name__)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError('crossbar.error.already_running', emsg)

        started_d = Deferred()

        # check configuration
        #
        try:
            self.personality.check_router_component(self.personality, config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError("crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == 'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError("crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError("crossbar.error.invalid_configuration", emsg)

        # create component config
        #
        realm = config.get('realm', None)
        assert isinstance(realm, str)

        extra = config.get('extra', {})
        assert isinstance(extra, dict)

        # forward crossbar node base directory
        extra['cbdir'] = self.config.extra.cbdir

        # allow access to controller session
        controller = self if self.config.extra.expose_controller else None

        # expose an object shared between components
        shared = self.components_shared if self.config.extra.expose_shared else None

        # this is the component configuration provided to the components ApplicationSession
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)

        # define component ctor function
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error('component loading failed', log_failure=Failure())
            if 'No module named' in str(e):
                self.log.error('  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error('    {path}', path=path)
            raise

        # .. and create and add an WAMP application session to
        # run the component next to the router
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg, log_failure=fail
                )
                session.disconnect()
            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        # Note that 'join' is fired to listeners *before* onJoin runs,
        # so if you do 'yield self.leave()' in onJoin we'll still
        # publish "started" before "stopped".

        def publish_stopped(session, stop_details):
            self.log.info(
                "stopped component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_stop'
            event = {'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            if not started_d.called:
                started_d.errback(Exception("Session left before being ready"))
            return event

        def publish_ready(session):
            """
            when our component is ready, we publish .on_component_ready
            """
            self.log.info(
                "component ready: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_ready'
            event = {'id': id}
            self.publish(topic, event)
            started_d.callback(event)
            return event

        def publish_started(session, start_details):
            """
            when our component starts, we publish .on_component_start
            """

            # hook up handlers for "session is ready"
            session.on('ready', publish_ready)

            # publish .on_component_start
            self.log.info(
                "started component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_start'
            event = {'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        session.on('leave', publish_stopped)
        session.on('join', publish_started)

        self.components[id] = RouterComponent(id, config, session)
        router = self._router_factory.get(realm)
        self._router_session_factory.add(session, router, authrole=config.get('role', 'anonymous'))
        self.log.debug(
            "Added component {id} (type '{name}')",
            id=id,
            name=class_name(session),
        )
        return started_d
Example #17
0
    def start_component(self, component_id, config, reload_modules=False, details=None):
        """
        Starts a component in this container worker.

        :param component_id: The ID under which to start the component.
        :type component_id: str

        :param config: Component configuration.
        :type config: dict

        :param reload_modules: If `True`, enforce reloading of modules (user code)
           that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool

        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns: Component startup information.
        :rtype: dict
        """
        self.log.debug(u'{klass}.start_component({component_id}, {config})',
                       klass=self.__class__.__name__,
                       component_id=component_id,
                       config=config)

        # prohibit starting a component twice
        #
        if component_id in self.components:
            emsg = u'duplicate component "{}" - a component with this ID is already running (or starting)'.format(component_id)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check component configuration
        #
        try:
            self.personality.check_container_component(self.personality, config)
        except Exception as e:
            emsg = u'invalid container component configuration: {}'.format(e)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.invalid_configuration', emsg)
        else:
            self.log.debug(u'starting component "{component_id}" ..', component_id=component_id)

        # WAMP application component factory
        #
        realm = config.get(u'realm', None)
        assert type(realm) == str

        extra = config.get(u'extra', {})
        assert type(extra) == dict

        # forward crossbar node base directory
        extra['cbdir'] = self.config.extra.cbdir

        # allow access to controller session
        controller = self if self.config.extra.expose_controller else None

        # expose an object shared between components
        shared = self.components_shared if self.config.extra.expose_shared else None

        # this is the component configuration provided to the components ApplicationSession
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)

        # define component ctor function
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # prepare some cleanup code this connection goes away
        def _closed(session, was_clean):
            """
            This is moderate hack around the fact that we don't have any way
            to "listen" for a close event on websocket or rawsocket
            objects. Also, the rawsocket implementation doesn't have
            "a" function we can wrap anyway (they are asyncio vs
            Twisted specific), so for both WebSocket and rawsocket
            cases, we actually listen on the WAMP session for
            transport close notifications.

            Ideally we'd listen for "close" on the transport but this
            works fine for cleaning up the components.
            """
            if component_id not in self.components:
                self.log.warn(
                    "Component '{id}' closed, but not in set.",
                    id=component_id,
                )
                return

            if was_clean:
                self.log.info(
                    "Closed connection to '{id}'",
                    id=component_id,
                )
            else:
                self.log.error(
                    "Lost connection to component '{id}' uncleanly",
                    id=component_id,
                )

            component = self.components[component_id]
            del self.components[component_id]
            self._publish_component_stop(component)
            component._stopped.callback(component.marshal())
            del component

            # figure out if we need to shut down the container itself or not
            if not self.components:
                if self._exit_mode == self.SHUTDOWN_ON_LAST_COMPONENT_STOPPED:
                    self.log.info(
                        "Container is hosting no more components: stopping container in exit mode <{exit_mode}> ...",
                        exit_mode=self._exit_mode,
                    )
                    self.shutdown()
                else:
                    self.log.info(
                        "Container is hosting no more components: continue running in exit mode <{exit_mode}>",
                        exit_mode=self._exit_mode,
                    )
            else:
                self.log.info(
                    "Container is still hosting {component_count} components: continue running in exit mode <{exit_mode}>",
                    exit_mode=self._exit_mode,
                    component_count=len(self.components),
                )

        # WAMP application session factory
        #
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg, log_failure=fail,
                    )
                    session.disconnect()
                session._swallow_error = panic

                # see note above, for _closed -- we should be
                # listening for "the transport was closed", but
                # "session disconnect" is close enough (since there
                # are no "proper events" from websocket/rawsocket
                # implementations).
                session.on('disconnect', _closed)

                return session

            except Exception:
                self.log.failure(u'component instantiation failed: {log_failure.value}')
                raise

        # WAMP transport factory
        #
        transport_config = config[u'transport']

        if transport_config[u'type'] == u'websocket':

            # create a WAMP-over-WebSocket transport client factory
            transport_factory = WampWebSocketClientFactory(create_session, transport_config[u'url'])
            transport_factory.noisy = False

        elif transport_config[u'type'] == u'rawsocket':

            transport_factory = WampRawSocketClientFactory(create_session,
                                                           transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did check the config before
            raise Exception(u'logic error')

        # create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(transport_config[u'endpoint'],
                                                          self.config.extra.cbdir,
                                                          self._reactor,
                                                          self.log)

        # now, actually connect the client
        #
        d = endpoint.connect(transport_factory)

        def on_connect_success(proto):
            component = ContainerComponent(component_id, config, proto, None)
            self.components[component_id] = component

            # publish event "on_component_start" to all but the caller
            #
            uri = self._uri_prefix + u'.on_component_started'

            component_started = {
                u'id': component_id,
                u'config': config
            }

            self.publish(uri, component_started, options=PublishOptions(exclude=details.caller))

            return component_started

        def on_connect_error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = u'could not connect container component to router - transport establishment failed ({})'.format(err.value)
                self.log.warn(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here
                # should be subclasses of ConnectError)
                raise err

        d.addCallbacks(on_connect_success, on_connect_error)

        return d
Example #18
0
    def start_component(self, component_id, config, reload_modules=False, details=None):
        """
        Starts a component in this container worker.

        :param component_id: The ID under which to start the component.
        :type component_id: str

        :param config: Component configuration.
        :type config: dict

        :param reload_modules: If `True`, enforce reloading of modules (user code)
           that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool

        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns: Component startup information.
        :rtype: dict
        """
        self.log.debug(u'{klass}.start_component({component_id}, {config})',
                       klass=self.__class__.__name__,
                       component_id=component_id,
                       config=config)

        # prohibit starting a component twice
        #
        if component_id in self.components:
            emsg = u'duplicate component "{}" - a component with this ID is already running (or starting)'.format(component_id)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check component configuration
        #
        try:
            self.personality.check_container_component(self.personality, config)
        except Exception as e:
            emsg = u'invalid container component configuration: {}'.format(e)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.invalid_configuration', emsg)
        else:
            self.log.debug(u'starting component "{component_id}" ..', component_id=component_id)

        # WAMP application component factory
        #
        realm = config.get(u'realm', None)
        extra = config.get(u'extra', None)
        controller = self if self.config.extra.expose_controller else None
        shared = self.components_shared if self.config.extra.expose_shared else None
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # WAMP application session factory
        #
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg, log_failure=fail,
                    )
                    session.disconnect()
                session._swallow_error = panic

                return session

            except Exception:
                self.log.failure(u'component instantiation failed: {log_failure.value}')
                raise

        # WAMP transport factory
        #
        transport_config = config[u'transport']

        if transport_config[u'type'] == u'websocket':

            # create a WAMP-over-WebSocket transport client factory
            transport_factory = WampWebSocketClientFactory(create_session, transport_config[u'url'])
            transport_factory.noisy = False

        elif transport_config[u'type'] == u'rawsocket':

            transport_factory = WampRawSocketClientFactory(create_session,
                                                           transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did check the config before
            raise Exception(u'logic error')

        # create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(transport_config[u'endpoint'],
                                                          self.config.extra.cbdir,
                                                          self._reactor,
                                                          self.log)

        # now, actually connect the client
        #
        d = endpoint.connect(transport_factory)

        def on_connect_success(proto):
            component = ContainerComponent(component_id, config, proto, None)
            self.components[component_id] = component

            # FIXME: this is a total hack.
            #
            def close_wrapper(orig, was_clean, code, reason):
                """
                Wrap our protocol's onClose so we can tell when the component
                exits.
                """
                r = orig(was_clean, code, reason)
                if component.id not in self.components:
                    self.log.warn("Component '{id}' closed, but not in set.",
                                  id=component.id)
                    return r

                if was_clean:
                    self.log.info("Closed connection to '{id}' with code '{code}'",
                                  id=component.id, code=code)
                else:
                    self.log.error("Lost connection to component '{id}' with code '{code}'.",
                                   id=component.id, code=code)

                if reason:
                    self.log.warn(str(reason))

                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())

                if not self.components:
                    if self._exit_mode == self.SHUTDOWN_ON_LAST_COMPONENT_STOPPED:
                        self.log.info("Container is hosting no more components: stopping container in exit mode <{exit_mode}> ...", exit_mode=self._exit_mode)
                        self.shutdown()
                    else:
                        self.log.info("Container is hosting no more components: continue running in exit mode <{exit_mode}>", exit_mode=self._exit_mode)
                else:
                    self.log.info("Container is still hosting {component_count} components: continue running in exit mode <{exit_mode}>", exit_mode=self._exit_mode, component_count=len(self.components))

                return r

            # FIXME: due to history, the following is currently the case:
            # ITransportHandler.onClose is implemented directly on WampWebSocketClientProtocol,
            # while with WampRawSocketClientProtocol, the ITransportHandler is implemented
            # by the object living on proto._session
            #
            if isinstance(proto, WampWebSocketClientProtocol):
                proto.onClose = partial(close_wrapper, proto.onClose)

            elif isinstance(proto, WampRawSocketClientProtocol):
                # FIXME: doesn't work without guard, since proto_.session is not yet there when
                # proto comes into existance ..
                if proto._session:
                    proto._session.onClose = partial(close_wrapper, proto._session.onClose)
            else:
                raise Exception(u'logic error')

            # publish event "on_component_start" to all but the caller
            #
            uri = self._uri_prefix + u'.on_component_started'

            component_started = {
                u'id': component_id,
                u'config': config
            }

            self.publish(uri, component_started, options=PublishOptions(exclude=details.caller))

            return component_started

        def on_connect_error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = u'could not connect container component to router - transport establishment failed ({})'.format(err.value)
                self.log.warn(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here
                # should be subclasses of ConnectError)
                raise err

        d.addCallbacks(on_connect_success, on_connect_error)

        return d
Example #19
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str
        :param config: The component configuration.
        :type config: obj
        """
        self.log.debug("{}.start_router_component".format(self.__class__.__name__),
                       id=id, config=config)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_router_component(config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

        # create component config
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(realm=realm, extra=extra)
        create_component = _appsession_loader(config)

        # .. and create and add an WAMP application session to
        # run the component next to the router
        #
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg, log_failure=fail
                )
                session.disconnect()
            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        # Note that 'join' is fired to listeners *before* onJoin runs,
        # so if you do 'yield self.leave()' in onJoin we'll still
        # publish "started" before "stopped".

        def publish_stopped(session, stop_details):
            self.log.info(
                "stopped component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.container.on_component_stop'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        def publish_started(session, start_details):
            self.log.info(
                "started component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.container.on_component_start'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event
        session.on('leave', publish_stopped)
        session.on('join', publish_started)

        self.components[id] = RouterComponent(id, config, session)
        self._router_session_factory.add(session, authrole=config.get('role', u'anonymous'))
        self.log.debug(
            "Added component {id} (type '{name}')",
            id=id,
            name=class_name(session),
        )
Example #20
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str

        :param config: The component configuration.
        :type config: dict

        :param details: Call details.
        :type details: :class:`autobahn.wamp.types.CallDetails`
        """
        self.log.debug("{name}.start_router_component", name=self.__class__.__name__)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            self.personality.check_router_component(self.personality, config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

        # create component config
        #
        realm = config.get('realm', None)
        assert isinstance(realm, str)

        extra = config.get('extra', {})
        assert isinstance(extra, dict)

        # forward crossbar node base directory
        extra['cbdir'] = self.config.extra.cbdir

        # allow access to controller session
        controller = self if self.config.extra.expose_controller else None

        # expose an object shared between components
        shared = self.components_shared if self.config.extra.expose_shared else None

        # this is the component configuration provided to the components ApplicationSession
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)

        # define component ctor function
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # .. and create and add an WAMP application session to
        # run the component next to the router
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg, log_failure=fail
                )
                session.disconnect()
            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        # Note that 'join' is fired to listeners *before* onJoin runs,
        # so if you do 'yield self.leave()' in onJoin we'll still
        # publish "started" before "stopped".

        def publish_stopped(session, stop_details):
            self.log.info(
                "stopped component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_stop'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        def publish_started(session, start_details):
            self.log.info(
                "started component: {session} id={session_id}",
                session=class_name(session),
                session_id=session._session_id,
            )
            topic = self._uri_prefix + '.on_component_start'
            event = {u'id': id}
            caller = details.caller if details else None
            self.publish(topic, event, options=PublishOptions(exclude=caller))
            return event

        session.on('leave', publish_stopped)
        session.on('join', publish_started)

        self.components[id] = RouterComponent(id, config, session)
        router = self._router_factory.get(realm)
        self._router_session_factory.add(session, router, authrole=config.get('role', u'anonymous'))
        self.log.debug(
            "Added component {id} (type '{name}')",
            id=id,
            name=class_name(session),
        )
Example #21
0
    def start_router_component(self, id, config, details=None):
        """
        Dynamically start an application component to run next to the router in "embedded mode".

        :param id: The ID of the component to start.
        :type id: str
        :param config: The component configuration.
        :type config: obj
        """
        self.log.debug("{}.start_router_component".format(self.__class__.__name__),
                       id=id, config=config)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_router_component(config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)

        # create component config
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(realm=realm, extra=extra)
        create_component = _appsession_loader(config)

        # .. and create and add an WAMP application session to
        # run the component next to the router
        #
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error("Fatal error in component: {} - {}".format(msg, fail.value))
                session.disconnect()
            session._swallow_error = panic
        except Exception as e:
            msg = "{}".format(e).strip()
            self.log.error("Component instantiation failed:\n\n{err}", err=msg)
            raise

        self.components[id] = RouterComponent(id, config, session)
        self._router_session_factory.add(session, authrole=config.get('role', u'anonymous'))
        self.log.debug("Added component {id}", id=id)
Example #22
0
    def start_router_component(self, id, config, details=None):
        """
        Start an app component in this router worker.

        :param id: The ID of the component to start.
        :type id: str
        :param config: The component configuration.
        :type config: obj
        """
        self.log.debug("{}.start_router_component".format(
            self.__class__.__name__),
                       id=id,
                       config=config)

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component: a component with ID '{}'' is already running (or starting)".format(
                id)
            self.log.error(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check configuration
        #
        try:
            checkconfig.check_router_component(config)
        except Exception as e:
            emsg = "Invalid router component configuration: {}".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration",
                                   emsg)
        else:
            self.log.debug("Starting {type}-component on router.",
                           type=config['type'])

        # resolve references to other entities
        #
        references = {}
        for ref in config.get('references', []):
            ref_type, ref_id = ref.split(':')
            if ref_type == u'connection':
                if ref_id in self._connections:
                    references[ref] = self._connections[ref_id]
                else:
                    emsg = "cannot resolve reference '{}' - no '{}' with ID '{}'".format(
                        ref, ref_type, ref_id)
                    self.log.error(emsg)
                    raise ApplicationError(
                        u"crossbar.error.invalid_configuration", emsg)
            else:
                emsg = "cannot resolve reference '{}' - invalid reference type '{}'".format(
                    ref, ref_type)
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.invalid_configuration",
                                       emsg)

        # create component config
        #
        realm = config['realm']
        extra = config.get('extra', None)
        component_config = ComponentConfig(realm=realm, extra=extra)
        create_component = _appsession_loader(config)

        # .. and create and add an WAMP application session to
        # run the component next to the router
        #
        try:
            session = create_component(component_config)

            # any exception spilling out from user code in onXXX handlers is fatal!
            def panic(fail, msg):
                self.log.error(
                    "Fatal error in component: {msg} - {log_failure.value}",
                    msg=msg,
                    log_failure=fail)
                session.disconnect()

            session._swallow_error = panic
        except Exception:
            self.log.error(
                "Component instantiation failed",
                log_failure=Failure(),
            )
            raise

        def publish_stopped(session, details):
            topic = self._uri_prefix + '.container.on_component_stop'
            event = {u'id': id}
            session.publish(topic,
                            event,
                            options=PublishOptions(exclude=details.caller))
            return event

        def publish_started(session, details):
            topic = self._uri_prefix + '.container.on_component_start'
            event = {u'id': id}
            session.publish(topic,
                            event,
                            options=PublishOptions(exclude=details.caller))
            return event

        session.on('join', publish_started)
        session.on('leave', publish_stopped)

        self.components[id] = RouterComponent(id, config, session)
        self._router_session_factory.add(session,
                                         authrole=config.get(
                                             'role', u'anonymous'))
        self.log.debug("Added component {id}", id=id)
Example #23
0
    def start_container_component(self, id, config, reload_modules=False, details=None):
        """
        Starts a Class or WAMPlet in this component container.

        :param config: Component configuration.
        :type config: dict
        :param reload_modules: If `True`, enforce reloading of modules (user code)
                               that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool
        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns dict -- A dict with combined info from component starting.
        """
        self.log.debug(
            "{klass}.start_container_component({id}, {config})", klass=self.__class__.__name__, id=id, config=config
        )

        # prohibit starting a component twice
        #
        if id in self.components:
            emsg = "Could not start component - a component with ID '{}'' is already running (or starting)".format(id)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.already_running", emsg)

        # check configuration
        #
        try:
            checkconfig.check_container_component(config)
        except Exception as e:
            emsg = "Invalid container component configuration ({})".format(e)
            self.log.error(emsg)
            raise ApplicationError(u"crossbar.error.invalid_configuration", emsg)
        else:
            self.log.debug("Starting {type}-component in container.", type=config["type"])

        # 1) create WAMP application component factory
        #
        realm = config["realm"]
        extra = config.get("extra", None)
        component_config = ComponentConfig(realm=realm, extra=extra)

        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            msg = e.args[0]
            self.log.error("Component loading failed:\n\n{err}", err=msg)
            if "No module named" in msg:
                self.log.error("  Python module search paths:")
                for path in e.kwargs["pythonpath"]:
                    self.log.error("    {path}", path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # WAMP application session factory
        # ultimately, this gets called once the connection is
        # establised, from onOpen in autobahn/wamp/websocket.py:59
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error("Fatal error in component: {msg} - {log_failure.value}", msg=msg, log_failure=fail)
                    session.disconnect()

                session._swallow_error = panic
                return session
            except Exception as e:
                msg = "{}".format(e).strip()
                self.log.failure("Component instantiation failed: {msg}", msg=msg)
                raise

        # 2) create WAMP transport factory
        #
        transport_config = config["transport"]
        transport_debug = transport_config.get("debug", False)
        transport_debug_wamp = transport_config.get("debug_wamp", False)

        # WAMP-over-WebSocket transport
        #
        if transport_config["type"] == "websocket":

            # create a WAMP-over-WebSocket transport client factory
            #
            transport_factory = WampWebSocketClientFactory(
                create_session, transport_config["url"], debug=transport_debug, debug_wamp=transport_debug_wamp
            )
            transport_factory.noisy = False

        # WAMP-over-RawSocket transport
        #
        elif transport_config["type"] == "rawsocket":

            transport_factory = WampRawSocketClientFactory(create_session, transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did `check_container_component()`
            raise Exception("logic error")

        # 3) create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(
            transport_config["endpoint"], self.config.extra.cbdir, self._reactor
        )

        # now connect the client
        #
        d = endpoint.connect(transport_factory)

        def success(proto):
            component = ContainerComponent(id, config, proto, None)
            self.components[id] = component

            # FIXME: this is a total hack.
            #
            def close_wrapper(orig, was_clean, code, reason):
                """
                Wrap our protocol's onClose so we can tell when the component
                exits.
                """
                r = orig(was_clean, code, reason)
                if component.id not in self.components:
                    self.log.warn("Component '{id}' closed, but not in set.", id=component.id)
                    return r

                if was_clean:
                    self.log.info("Closed connection to '{id}' with code '{code}'", id=component.id, code=code)
                else:
                    self.log.error(
                        "Lost connection to component '{id}' with code '{code}'.", id=component.id, code=code
                    )

                if reason:
                    self.log.warn(str(reason))

                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())

                if not self.components:
                    self.log.info("Container is hosting no more components: shutting down.")
                    self.stop_container()

                return r

            # FIXME: due to history, the following is currently the case:
            # ITransportHandler.onClose is implemented directly on WampWebSocketClientProtocol,
            # while with WampRawSocketClientProtocol, the ITransportHandler is implemented
            # by the object living on proto._session
            #
            if isinstance(proto, WampWebSocketClientProtocol):
                proto.onClose = partial(close_wrapper, proto.onClose)

            elif isinstance(proto, WampRawSocketClientProtocol):
                # FIXME: doesn't work without guard, since proto_.session is not yet there when
                # proto comes into existance ..
                if proto._session:
                    proto._session.onClose = partial(close_wrapper, proto._session.onClose)
            else:
                raise Exception("logic error")

            # publish event "on_component_start" to all but the caller
            #
            topic = self._uri_prefix + ".container.on_component_start"
            event = {"id": id}
            self.publish(topic, event, options=PublishOptions(exclude=[details.caller]))
            return event

        def error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = "Could not connect container component to router - transport establishment failed ({})".format(
                    err.value
                )
                self.log.error(emsg)
                raise ApplicationError(u"crossbar.error.cannot_connect", emsg)
            else:
                # should not arrive here (since all errors arriving here should be subclasses of ConnectError)
                raise err

        d.addCallbacks(success, error)

        return d
Example #24
0
    def start_component(self,
                        component_id,
                        config,
                        reload_modules=False,
                        details=None):
        """
        Starts a component in this container worker.

        :param component_id: The ID under which to start the component.
        :type component_id: str

        :param config: Component configuration.
        :type config: dict

        :param reload_modules: If `True`, enforce reloading of modules (user code)
           that were modified (see: TrackingModuleReloader).
        :type reload_modules: bool

        :param details: Caller details.
        :type details: instance of :class:`autobahn.wamp.types.CallDetails`

        :returns: Component startup information.
        :rtype: dict
        """
        self.log.debug(u'{klass}.start_component({component_id}, {config})',
                       klass=self.__class__.__name__,
                       component_id=component_id,
                       config=config)

        # prohibit starting a component twice
        #
        if component_id in self.components:
            emsg = u'duplicate component "{}" - a component with this ID is already running (or starting)'.format(
                component_id)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.already_running', emsg)

        # check component configuration
        #
        try:
            checkconfig.check_container_component(config)
        except Exception as e:
            emsg = u'invalid container component configuration: {}'.format(e)
            self.log.debug(emsg)
            raise ApplicationError(u'crossbar.error.invalid_configuration',
                                   emsg)
        else:
            self.log.debug(u'starting component "{component_id}" ..',
                           component_id=component_id)

        # WAMP application component factory
        #
        realm = config.get(u'realm', None)
        extra = config.get(u'extra', None)
        controller = self if self.config.extra.expose_controller else None
        shared = self.components_shared if self.config.extra.expose_shared else None
        component_config = ComponentConfig(realm=realm,
                                           extra=extra,
                                           keyring=None,
                                           controller=controller,
                                           shared=shared)
        try:
            create_component = _appsession_loader(config)
        except ApplicationError as e:
            # for convenience, also log failed component loading
            self.log.error(u'component loading failed', log_failure=Failure())
            if u'No module named' in str(e):
                self.log.error(u'  Python module search paths:')
                for path in e.kwargs['pythonpath']:
                    self.log.error(u'    {path}', path=path)
            raise

        # force reload of modules (user code)
        #
        if reload_modules:
            self._module_tracker.reload()

        # WAMP application session factory
        #
        def create_session():
            try:
                session = create_component(component_config)

                # any exception spilling out from user code in onXXX handlers is fatal!
                def panic(fail, msg):
                    self.log.error(
                        "Fatal error in component: {msg} - {log_failure.value}",
                        msg=msg,
                        log_failure=fail,
                    )
                    session.disconnect()

                session._swallow_error = panic

                return session

            except Exception:
                self.log.failure(
                    u'component instantiation failed: {log_failure.value}')
                raise

        # WAMP transport factory
        #
        transport_config = config[u'transport']

        if transport_config[u'type'] == u'websocket':

            # create a WAMP-over-WebSocket transport client factory
            transport_factory = WampWebSocketClientFactory(
                create_session, transport_config[u'url'])
            transport_factory.noisy = False

        elif transport_config[u'type'] == u'rawsocket':

            transport_factory = WampRawSocketClientFactory(
                create_session, transport_config)
            transport_factory.noisy = False

        else:
            # should not arrive here, since we did check the config before
            raise Exception(u'logic error')

        # create and connect client endpoint
        #
        endpoint = create_connecting_endpoint_from_config(
            transport_config[u'endpoint'], self.config.extra.cbdir,
            self._reactor, self.log)

        # now, actually connect the client
        #
        d = endpoint.connect(transport_factory)

        def on_connect_success(proto):
            component = ContainerComponent(component_id, config, proto, None)
            self.components[component_id] = component

            # FIXME: this is a total hack.
            #
            def close_wrapper(orig, was_clean, code, reason):
                """
                Wrap our protocol's onClose so we can tell when the component
                exits.
                """
                r = orig(was_clean, code, reason)
                if component.id not in self.components:
                    self.log.warn("Component '{id}' closed, but not in set.",
                                  id=component.id)
                    return r

                if was_clean:
                    self.log.info(
                        "Closed connection to '{id}' with code '{code}'",
                        id=component.id,
                        code=code)
                else:
                    self.log.error(
                        "Lost connection to component '{id}' with code '{code}'.",
                        id=component.id,
                        code=code)

                if reason:
                    self.log.warn(str(reason))

                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())

                if not self.components:
                    self.log.info(
                        "Container is hosting no more components: stopping container ..."
                    )
                    self.stop()
                else:
                    self.log.info(
                        "Container is still hosting {component_count} components",
                        component_count=len(self.components))

                return r

            # FIXME: due to history, the following is currently the case:
            # ITransportHandler.onClose is implemented directly on WampWebSocketClientProtocol,
            # while with WampRawSocketClientProtocol, the ITransportHandler is implemented
            # by the object living on proto._session
            #
            if isinstance(proto, WampWebSocketClientProtocol):
                proto.onClose = partial(close_wrapper, proto.onClose)

            elif isinstance(proto, WampRawSocketClientProtocol):
                # FIXME: doesn't work without guard, since proto_.session is not yet there when
                # proto comes into existance ..
                if proto._session:
                    proto._session.onClose = partial(close_wrapper,
                                                     proto._session.onClose)
            else:
                raise Exception(u'logic error')

            # publish event "on_component_start" to all but the caller
            #
            uri = self._uri_prefix + u'.on_component_started'

            component_started = {u'id': component_id, u'config': config}

            self.publish(uri,
                         component_started,
                         options=PublishOptions(exclude=details.caller))

            return component_started

        def on_connect_error(err):
            # https://twistedmatrix.com/documents/current/api/twisted.internet.error.ConnectError.html
            if isinstance(err.value, internet.error.ConnectError):
                emsg = u'could not connect container component to router - transport establishment failed ({})'.format(
                    err.value)
                self.log.warn(emsg)
                raise ApplicationError(u'crossbar.error.cannot_connect', emsg)
            else:
                # should not arrive here (since all errors arriving here
                # should be subclasses of ConnectError)
                raise err

        d.addCallbacks(on_connect_success, on_connect_error)

        return d