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
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
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
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
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
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
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