Exemplo n.º 1
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
Exemplo n.º 2
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
Exemplo n.º 3
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.
        """
        if self.debug:
            log.msg("{}.start_container_component".format(self.__class__.__name__), id, config)

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

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

        realm = config['realm']
        componentcfg = ComponentConfig(realm=realm, extra=config.get('extra', None))

        # 1) create WAMP application component factory
        #
        if config['type'] == 'wamplet':

            package = config['package']
            entrypoint = config['entrypoint']

            try:
                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = pkg_resources.load_entry_point(package, 'autobahn.twisted.wamplet', entrypoint)

            except Exception as e:
                tb = traceback.format_exc()
                emsg = 'ERROR: failed to import WAMPlet {}.{} ("{}")'.format(package, entrypoint, e)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.cannot_import", emsg, tb)

            else:
                if self.debug:
                    log.msg("Creating component from WAMPlet {}.{}".format(package, entrypoint))

        elif config['type'] == 'class':

            qualified_classname = config['classname']

            try:
                c = qualified_classname.split('.')
                module_name, class_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)

                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = getattr(module, class_name)

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(qualified_classname, e)
                log.msg(emsg)
                log.msg("PYTHONPATH: {}".format(sys.path))
                raise ApplicationError("crossbar.error.class_import_failed", emsg)

            else:
                if self.debug:
                    log.msg("Creating component from class {}".format(qualified_classname))

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

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

        # WAMP application session factory
        #
        def create_session():
            return create_component(componentcfg)

        # 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 = CrossbarWampWebSocketClientFactory(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 = CrossbarWampRawSocketClientFactory(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,
                                                          reactor)

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

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

            # note that create_session (above) isn't called until we
            # reach onOpen() in the client WAMP session, so the
            # sunderlying ApplicationSession object hasn't yet even
            # been constructed at this point; see code in
            # autobahn/wamp/websocket.py:59 or so (in onOpen)

            # is wrapping onClose *really* the best way to do this?
            def close_wrapper(orig, was_clean, code, reason):
                self._publish_component_stop(component)
                del self.components[component.id]
                return orig(was_clean, code, reason)
            proto.onClose = partial(close_wrapper, proto.onClose)

            # 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 = "ERROR: could not connect container component to router - transport establishment failed ({})".format(err.value)
                log.msg(emsg)
                raise ApplicationError('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
Exemplo n.º 4
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.
        """
        if self.debug:
            log.msg(
                "{}.start_container_component".format(self.__class__.__name__),
                id, config)

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

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

        realm = config['realm']
        componentcfg = ComponentConfig(realm=realm,
                                       extra=config.get('extra', None))

        # 1) create WAMP application component factory
        #
        if config['type'] == 'wamplet':

            package = config['package']
            entrypoint = config['entrypoint']

            try:
                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = pkg_resources.load_entry_point(
                    package, 'autobahn.twisted.wamplet', entrypoint)

            except Exception as e:
                tb = traceback.format_exc()
                emsg = 'ERROR: failed to import WAMPlet {}.{} ("{}")'.format(
                    package, entrypoint, e)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.cannot_import", emsg,
                                       tb)

            else:
                if self.debug:
                    log.msg("Creating component from WAMPlet {}.{}".format(
                        package, entrypoint))

        elif config['type'] == 'class':

            qualified_classname = config['classname']

            try:
                c = qualified_classname.split('.')
                module_name, class_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)

                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = getattr(module, class_name)

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(
                    qualified_classname, e)
                log.msg(emsg)
                log.msg("PYTHONPATH: {}".format(sys.path))
                raise ApplicationError("crossbar.error.class_import_failed",
                                       emsg)

            else:
                if self.debug:
                    log.msg("Creating component from class {}".format(
                        qualified_classname))

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

        # 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:
                return create_component(componentcfg)
            except Exception:
                log.err(_why="Instantiating component failed")
                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, 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:
                    log.msg("Component '{}' closed, but not in set.".format(
                        component.id))
                    return r

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

                if reason:
                    log.msg(str(reason))
                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())
                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 = "ERROR: could not connect container component to router - transport establishment failed ({})".format(
                    err.value)
                log.msg(emsg)
                raise ApplicationError('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
Exemplo n.º 5
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.
        """
        if self.debug:
            log.msg("{}.start_container_component".format(self.__class__.__name__), id, config)

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

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

        realm = config['realm']
        componentcfg = ComponentConfig(realm=realm, extra=config.get('extra', None))

        # 1) create WAMP application component factory
        #
        if config['type'] == 'wamplet':

            package = config['package']
            entrypoint = config['entrypoint']

            try:
                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = pkg_resources.load_entry_point(package, 'autobahn.twisted.wamplet', entrypoint)

            except Exception as e:
                tb = traceback.format_exc()
                emsg = 'ERROR: failed to import WAMPlet {}.{} ("{}")'.format(package, entrypoint, e)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.cannot_import", emsg, tb)

            else:
                if self.debug:
                    log.msg("Creating component from WAMPlet {}.{}".format(package, entrypoint))

        elif config['type'] == 'class':

            qualified_classname = config['classname']

            try:
                c = qualified_classname.split('.')
                module_name, class_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)

                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = getattr(module, class_name)

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(qualified_classname, e)
                log.msg(emsg)
                log.msg("PYTHONPATH: {}".format(sys.path))
                raise ApplicationError("crossbar.error.class_import_failed", emsg)

            else:
                if self.debug:
                    log.msg("Creating component from class {}".format(qualified_classname))

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

        # 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:
                return create_component(componentcfg)
            except Exception:
                log.err(_why="Instantiating component failed")
                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,
                                                          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:
                    log.msg("Component '{}' closed, but not in set.".format(component.id))
                    return r

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

                if reason:
                    log.msg(str(reason))
                del self.components[component.id]
                self._publish_component_stop(component)
                component._stopped.callback(component.marshal())
                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 = "ERROR: could not connect container component to router - transport establishment failed ({})".format(err.value)
                log.msg(emsg)
                raise ApplicationError('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
Exemplo n.º 6
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
Exemplo n.º 7
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.
        """
        if self.debug:
            log.msg(
                "{}.start_container_component".format(self.__class__.__name__),
                id, config)

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

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

        realm = config['realm']
        componentcfg = ComponentConfig(realm=realm,
                                       extra=config.get('extra', None))

        # 1) create WAMP application component factory
        #
        if config['type'] == 'wamplet':

            package = config['package']
            entrypoint = config['entrypoint']

            try:
                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = pkg_resources.load_entry_point(
                    package, 'autobahn.twisted.wamplet', entrypoint)

            except Exception as e:
                tb = traceback.format_exc()
                emsg = 'ERROR: failed to import WAMPlet {}.{} ("{}")'.format(
                    package, entrypoint, e)
                log.msg(emsg)
                raise ApplicationError("crossbar.error.cannot_import", emsg,
                                       tb)

            else:
                if self.debug:
                    log.msg("Creating component from WAMPlet {}.{}".format(
                        package, entrypoint))

        elif config['type'] == 'class':

            qualified_classname = config['classname']

            try:
                c = qualified_classname.split('.')
                module_name, class_name = '.'.join(c[:-1]), c[-1]
                module = importlib.import_module(module_name)

                # create_component() is supposed to make instances of ApplicationSession later
                #
                create_component = getattr(module, class_name)

            except Exception as e:
                emsg = "Failed to import class '{}' - {}".format(
                    qualified_classname, e)
                log.msg(emsg)
                log.msg("PYTHONPATH: {}".format(sys.path))
                raise ApplicationError("crossbar.error.class_import_failed",
                                       emsg)

            else:
                if self.debug:
                    log.msg("Creating component from class {}".format(
                        qualified_classname))

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

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

        # WAMP application session factory
        #
        def create_session():
            return create_component(componentcfg)

        # 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 = CrossbarWampWebSocketClientFactory(
                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 = CrossbarWampRawSocketClientFactory(
                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, reactor)

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

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

            # publish event "on_component_start" to all but the caller
            #
            topic = 'crossbar.node.{}.worker.{}.container.on_component_start'.format(
                self.config.extra.node, self.config.extra.worker)
            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 = "ERROR: could not connect container component to router - transport establishment failed ({})".format(
                    err.value)
                log.msg(emsg)
                raise ApplicationError('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