def publish_stopped(session, stop_details): self.log.info( "stopped component: {session} id={session_id}", session=class_name(session), session_id=session._session_id, ) topic = self._uri_prefix + '.on_component_stop' event = {u'id': id} caller = details.caller if details else None self.publish(topic, event, options=PublishOptions(exclude=caller)) return event
def add_pythonpath(self, paths, prepend=True, details=None): """ Add paths to Python module search paths. :param paths: List of paths. Relative paths will be resolved relative to the node directory. :type paths: list :param prepend: If `True`, prepend the given paths to the current paths. Otherwise append. :type prepend: bool """ if self.debug: log.msg("{}.add_pythonpath".format(self.__class__.__name__)) paths_added = [] for p in paths: ## transform all paths (relative to cbdir) into absolute paths ## path_to_add = os.path.abspath( os.path.join(self.config.extra.cbdir, p)) if os.path.isdir(path_to_add): paths_added.append({'requested': p, 'resolved': path_to_add}) else: emsg = "ERROR: cannot add Python search path '{}' - resolved path '{}' is not a directory".format( p, path_to_add) log.msg(emsg) raise ApplicationError('crossbar.error.invalid_argument', emsg, requested=p, resolved=path_to_add) ## now extend python module search path ## paths_added_resolved = [p['resolved'] for p in paths_added] if prepend: sys.path = paths_added_resolved + sys.path else: sys.path.extend(paths_added_resolved) ## publish event "on_pythonpath_add" to all but the caller ## topic = 'crossbar.node.{}.worker.{}.on_pythonpath_add'.format( self.config.extra.node, self.config.extra.worker) res = { 'paths': sys.path, 'paths_added': paths_added, 'prepend': prepend, 'who': details.authid } self.publish(topic, res, options=PublishOptions(exclude=[details.caller])) return res
def do_publish(reactor, session, options): publish_args = list(options.publish_args) publish_kwargs = {} if options.keyword is None else { k: v for k, v in options.keyword } yield session.publish(options.uri, *publish_args, options=PublishOptions(acknowledge=True), **publish_kwargs)
def stop_component(self, component_id, details=None): """ Stop a component currently running within this container. :param component_id: The ID of the component to stop. :type component_id: int :param details: Caller details. :type details: instance of :class:`autobahn.wamp.types.CallDetails` :returns: Stop information. :rtype: dict """ self.log.debug('{klass}.stop_component({component_id}, {details})', klass=self.__class__.__name__, component_id=component_id, details=details) if component_id not in self.components: raise ApplicationError( u'crossbar.error.no_such_object', 'no component with ID {} running in this container'.format( component_id)) component = self.components[component_id] try: component.proto.close() except: self.log.failure( "failed to close protocol on component '{component_id}': {log_failure}", component_id=component_id) raise else: # essentially just waiting for "on_component_stop" yield component._stopped stopped = { u'component_id': component_id, u'uptime': (datetime.utcnow() - component.started).total_seconds(), u'caller': { u'session': details.caller, u'authid': details.caller_authid, u'authrole': details.caller_authrole, } } del self.components[component_id] self.publish(u'{}.on_component_stopped'.format(self._uri_prefix), stopped, options=PublishOptions(exclude=details.caller)) returnValue(stopped)
def add_pythonpath(self, paths, prepend=True, details=None): """ Add paths to Python module search paths. This procedure is registered under WAMP URI ``crossbar.worker.<worker_id>.add_pythonpath``. :param paths: List of paths. Relative paths will be resolved relative to the node directory. :type paths: list[str] :param prepend: If `True`, prepend the given paths to the current paths. Otherwise append. :type prepend: bool """ self.log.debug("{klass}.add_pythonpath", klass=self.__class__.__name__) paths_added = [] for p in paths: # transform all paths (relative to cbdir) into absolute paths # path_to_add = os.path.abspath(os.path.join(self.config.extra.cbdir, p)) if os.path.isdir(path_to_add): paths_added.append({'requested': p, 'resolved': path_to_add}) else: emsg = "Cannot add Python search path '{}': resolved path '{}' is not a directory".format( p, path_to_add) self.log.error(emsg) raise ApplicationError('crossbar.error.invalid_argument', emsg, requested=p, resolved=path_to_add) # now extend python module search path # paths_added_resolved = [p['resolved'] for p in paths_added] if prepend: sys.path = paths_added_resolved + sys.path else: sys.path.extend(paths_added_resolved) # "It is important to note that the global working_set object is initialized from # sys.path when pkg_resources is first imported, but is only updated if you do all # future sys.path manipulation via pkg_resources APIs. If you manually modify sys.path, # you must invoke the appropriate methods on the working_set instance to keep it in sync." # # @see: https://pythonhosted.org/setuptools/pkg_resources.html#workingset-objects # for p in paths_added_resolved: pkg_resources.working_set.add_entry(p) # publish event "on_pythonpath_add" to all but the caller # topic = '{}.on_pythonpath_add'.format(self._uri_prefix) res = {'paths': sys.path, 'paths_added': paths_added, 'prepend': prepend, 'who': details.caller} self.publish(topic, res, options=PublishOptions(exclude=details.caller)) return res
def onJoin(self, details): print("session attached {}".format(details)) for topic in [u"com.example.history", u"com.example.no_history_here"]: print("publishing '{}' as retained event".format(topic)) pub = yield self.publish( topic, "some data, topic was '{}'".format(topic), options=PublishOptions(retain=True, acknowledge=True), ) print("published: {}".format(pub))
def set_cpu_affinity(self, cpus, details=None): """ Set CPU affinity of this process. **Usage:** This procedure is registered under * ``crossbar.worker.<worker_id>.set_cpu_affinity`` **Errors:** The procedure may raise the following errors: * ``crossbar.error.feature_unavailable`` - the required support packages are not installed **Events:** When the CPU affinity has been successfully set, an event is published to * ``crossbar.node.{}.worker.{}.on_cpu_affinity_set`` :param cpus: List of CPU IDs to set process affinity to. Each CPU ID must be from the list `[0 .. N_CPUs]`, where N_CPUs can be retrieved via ``crossbar.worker.<worker_id>.get_cpu_count``. :type cpus: list of int """ if not _HAS_PSUTIL: emsg = "Unable to set CPU affinity: required package 'psutil' is not installed" self.log.warn(emsg) raise ApplicationError(u"crossbar.error.feature_unavailable", emsg) try: p = psutil.Process(os.getpid()) p.cpu_affinity(cpus) new_affinity = p.cpu_affinity() except Exception as e: emsg = "Could not set CPU affinity: {}".format(e) self.log.failure(emsg) raise ApplicationError(u"crossbar.error.runtime_error", emsg) else: # publish info to all but the caller .. # cpu_affinity_set_topic = u'{}.on_cpu_affinity_set'.format(self._uri_prefix) cpu_affinity_set_info = { u'affinity': new_affinity, u'who': details.caller } self.publish(cpu_affinity_set_topic, cpu_affinity_set_info, options=PublishOptions(exclude=details.caller)) # .. and return info directly to caller # return new_affinity
def onJoin(self, details): print("Client session joined: {}".format(details)) ## call a procedure we are allowed to call (so this should succeed) ## try: res = yield self.call(u'com.example.add2', 2, 3) print("call result: {}".format(res)) except Exception as e: print("call error: {}".format(e)) ## (try to) register a procedure where we are not allowed to (so this should fail) ## try: reg = yield self.register(lambda x, y: x * y, u'com.example.mul2') except Exception as e: print("registration failed (this is expected!) {}".format(e)) ## publish to a couple of topics we are allowed to publish to. ## for topic in [ u'com.example.topic1', u'com.foobar.topic1']: try: yield self.publish(topic, "hello", options = PublishOptions(acknowledge = True)) print("ok, event published to topic {}".format(topic)) except Exception as e: print("publication to topic {} failed: {}".format(topic, e)) ## (try to) publish to a couple of topics we are not allowed to publish to (so this should fail) ## for topic in [ u'com.example.topic2', u'com.foobar.topic2']: try: yield self.publish(topic, "hello", options = PublishOptions(acknowledge = True)) print("ok, event published to topic {}".format(topic)) except Exception as e: print("publication to topic {} failed (this is expected!) {}".format(topic, e)) self.leave()
def on_data(self, changed, last): """ Hook that fires when xbox controller has emitted some data. """ self.publish(u'{}.on_data'.format(self._prefix), last, changed, options=PublishOptions(retain=True)) self.log.info( "on_data event published: last={last}, changed={changed}", changed=changed, last=last)
def on_event(*args, **kwargs): details = kwargs.pop('details') # FIXME: setup things so out (the node's) identity gets disclosed self.publish(uri, *args, options=PublishOptions(), **kwargs) self.log.info( "forwarded from {other} event to {me} ({dir}): args={args}, details={details}", other=other, me=self, dir=self._DIR, args=args, details=details, )
def square(val, details=None): print("square called from: {}".format(details.caller)) if val < 0: self.publish(u'com.myapp.square_on_nonpositive', val) elif val == 0: if details.caller: options = PublishOptions(exclude=[details.caller]) else: options = None self.publish(u'com.myapp.square_on_nonpositive', val, options=options) return val * val
def restart_component(self, component_id, reload_modules=False, details=None): """ Restart a component currently running within this container using the same configuration that was used when first starting the component. :param component_id: The ID of the component to restart. :type component_id: str :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 stopping/starting. """ if component_id not in self.components: raise ApplicationError( u'crossbar.error.no_such_object', 'no component with ID {} running in this container'.format( component_id)) component = self.components[component_id] stopped = yield self.stop_container_component(component_id, details=details) started = yield self.start_component(component_id, component.config, reload_modules=reload_modules, details=details) del stopped[u'caller'] del started[u'caller'] restarted = { u'stopped': stopped, u'started': started, u'caller': { u'session': details.caller, u'authid': details.caller_authid, u'authrole': details.caller_authrole, } } self.publish(u'{}.on_component_restarted'.format(self._uri_prefix), restarted, options=PublishOptions(exclude=details.caller)) returnValue(restarted)
def start_router_realm_role(self, realm_id, role_id, role_config, details=None): """ Start a role on a realm running on this router worker. :param id: The ID of the realm the role should be started on. :type id: str :param role_id: The ID of the role to start under. :type role_id: str :param config: The role configuration. :type config: dict :param details: Call details. :type details: :class:`autobahn.wamp.types.CallDetails` """ self.log.info( 'Starting role "{role_id}" on realm "{realm_id}" {method}', role_id=role_id, realm_id=realm_id, method=hltype(self.start_router_realm_role)) if realm_id not in self.realms: raise ApplicationError(u"crossbar.error.no_such_object", "No realm with ID '{}'".format(realm_id)) if role_id in self.realms[realm_id].roles: raise ApplicationError( u"crossbar.error.already_exists", "A role with ID '{}' already exists in realm with ID '{}'". format(role_id, realm_id)) self.realms[realm_id].roles[role_id] = RouterRealmRole( role_id, role_config) realm = self.realms[realm_id].config['name'] self._router_factory.add_role(realm, role_config) topic = u'{}.on_router_realm_role_started'.format(self._uri_prefix) event = self.realms[realm_id].roles[role_id].marshal() caller = details.caller if details else None self.publish(topic, event, options=PublishOptions(exclude=caller)) self.log.info('role {role_id} on realm {realm_id} started', realm_id=realm_id, role_id=role_id, role_config=role_config) return event
def shutdown(self, restart=False, mode=None, details=None): """ Explicitly stop this node. """ if self._shutdown_requested: # we're already shutting down .. ignore .. return self._shutdown_requested = True self.log.info( 'Node shutdown requested (restart={}, mode={}, reactor.running={}) ..' .format(restart, mode, self._reactor.running)) term_print('CROSSBAR:NODE_SHUTDOWN_REQUESTED') try: # node shutdown information shutdown_info = { u'node_id': self._node._node_id, u'restart': restart, u'mode': mode, u'who': details.caller if details else None, u'when': utcnow(), u'was_clean': self._shutdown_was_clean, } if self._node._shutdown_complete: self._node._shutdown_complete.callback(shutdown_info) # publish management API event yield self.publish(u'{}.on_shutdown'.format(self._uri_prefix), shutdown_info, options=PublishOptions( exclude=details.caller if details else None, acknowledge=True)) def stop_reactor(): try: self._reactor.stop() except ReactorNotRunning: pass _SHUTDOWN_DELAY = 0.2 self._reactor.callLater(_SHUTDOWN_DELAY, stop_reactor) except: self.log.failure() self._shutdown_requested = False raise else: returnValue(shutdown_info)
def publish_ready(self): # signal that this worker is ready for setup. the actual setup procedure # will either be sequenced from the local node configuration file or remotely # from a management service yield self.publish('{}.on_worker_ready'.format(self._uri_prefix), { 'type': self.WORKER_TYPE, 'id': self.config.extra.worker, 'pid': os.getpid(), }, options=PublishOptions(acknowledge=True)) self.log.debug("Worker '{worker}' running as PID {pid}", worker=self.config.extra.worker, pid=os.getpid()) term_print('CROSSBAR[{}]:WORKER_STARTED'.format(self.config.extra.worker))
def publish_stopped(session, stop_details): self.log.info( "stopped component: {session} id={session_id}", session=class_name(session), session_id=session._session_id, ) topic = self._uri_prefix + '.on_component_stop' event = {'id': id} caller = details.caller if details else None self.publish(topic, event, options=PublishOptions(exclude=caller)) if not started_d.called: started_d.errback(Exception("Session left before being ready")) return event
def 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 _stop_native_worker(self, worker_id, kill, details=None): if worker_id not in self._workers or not isinstance( self._workers[worker_id], NativeWorkerProcess): emsg = "Could not stop native worker: no native worker with ID '{}' currently running".format( worker_id) raise ApplicationError(u'crossbar.error.worker_not_running', emsg) worker = self._workers[worker_id] if worker.status != 'started': emsg = "Could not stop native worker: worker with ID '{}' is not in status 'started', but status: '{}')".format( worker_id, worker.status) raise ApplicationError(u'crossbar.error.worker_not_running', emsg) stop_info = { u'id': worker.id, u'type': worker.TYPE, u'kill': kill, u'who': details.caller if details else None, u'when': utcnow(), } # publish management API event # yield self.publish(u'{}.on_stop_requested'.format(self._uri_prefix), stop_info, options=PublishOptions( exclude=details.caller if details else None, acknowledge=True)) # send SIGKILL or SIGTERM to worker # if kill: self.log.info("Killing {worker_type} worker with ID '{worker_id}'", worker_type=worker.TYPE, worker_id=worker_id) self._workers[worker_id].proto.transport.signalProcess("KILL") else: self.log.info( "Stopping {worker_type} worker with ID '{worker_id}'", worker_type=worker.TYPE, worker_id=worker_id) self._workers[worker_id].factory.stopFactory() self._workers[worker_id].proto.transport.signalProcess('TERM') # wait until the worker is actually done before we return from # this call yield self._workers[worker_id].proto.is_closed returnValue(stop_info)
def _process(self, request, event): if 'topic' not in event: return self._deny_request( request, 400, "invalid request event - missing 'topic' in HTTP/POST body") topic = event.pop('topic') args = event.pop('args', []) kwargs = event.pop('kwargs', {}) options = event.pop('options', {}) publish_options = PublishOptions(acknowledge=True, exclude=options.get('exclude', None), eligible=options.get( 'eligible', None)) kwargs['options'] = publish_options # http://twistedmatrix.com/documents/current/web/howto/web-in-60/asynchronous-deferred.html d = self._session.publish(topic, *args, **kwargs) def on_publish_ok(pub): res = {'id': pub.id} self.log.debug("request succeeded with result {res}", res=res) body = json.dumps(res, separators=(',', ':')) if six.PY3: body = body.encode('utf8') request.setHeader('content-type', 'application/json; charset=UTF-8') request.setHeader( 'cache-control', 'no-store, no-cache, must-revalidate, max-age=0') request.setResponseCode(202) request.write(body) request.finish() def on_publish_error(err): emsg = "PublisherResource - request failed with error {0}\n".format( err.value) self.log.debug(emsg) request.setResponseCode(400) request.write(emsg) request.finish() d.addCallbacks(on_publish_ok, on_publish_error) return server.NOT_DONE_YET
def set_process_stats_monitoring(self, interval, details=None): """ Enable/disable periodic publication of process statistics. :param interval: The monitoring interval in seconds. Set to 0 to disable monitoring. :type interval: float """ self.log.debug("{cls}.set_process_stats_monitoring(interval = {interval})", cls=self.__class__.__name__, interval=interval) if self._pinfo: stats_monitor_set_topic = '{}.on_process_stats_monitoring_set'.format(self._uri_prefix) # stop and remove any existing monitor if self._pinfo_monitor: self._pinfo_monitor.stop() self._pinfo_monitor = None self.publish(stats_monitor_set_topic, 0, options=PublishOptions(exclude=details.caller)) # possibly start a new monitor if interval > 0: stats_topic = '{}.on_process_stats'.format(self._uri_prefix) def publish_stats(): stats = self._pinfo.get_stats() self._pinfo_monitor_seq += 1 stats[u'seq'] = self._pinfo_monitor_seq self.publish(stats_topic, stats) self._pinfo_monitor = LoopingCall(publish_stats) self._pinfo_monitor.start(interval) self.publish(stats_monitor_set_topic, interval, options=PublishOptions(exclude=details.caller)) else: emsg = "Cannot setup process statistics monitor: required packages not installed" raise ApplicationError(u"crossbar.error.feature_unavailable", emsg)
def _(session, details): print("joined: {}".format(session)) topic_name = u"io.crossbar.demo.public.foo" while session.is_connected(): print("publish to '{}'".format(topic_name)) session.publish( topic_name, 1, 2, foo="bar", options=PublishOptions(exclude_me=False), ) yield sleep(1)
def _tick(self): self._tick_no += 1 now = time.time() self._tick_sent[self._tick_no] = now try: pub = yield self.publish(u'{}.on_alive'.format(self._prefix), self._tick_no, options=PublishOptions(acknowledge=True, exclude_me=False)) except: self.log.failure() else: self.log.info('TICK sent [tick {}, pub {}]'.format( self._tick_no, pub.id))
def on_event(*args, **kwargs): details = kwargs.pop('details') topic = u"cdc." + details.topic try: yield self._management_session.publish( topic, *args, options=PublishOptions(acknowledge=True), **kwargs) except Exception as e: self.log.error(e) else: self.log.debug("Forwarded event on topic '{topic}'", topic=topic)
def publish_ready(self): # signal that this worker is ready for setup. the actual setup procedure # will either be sequenced from the local node configuration file or remotely # from a management service # yield self.publish('crossbar.node.{}.on_worker_ready'.format( self.config.extra.node), { 'type': self.WORKER_TYPE, 'id': self.config.extra.worker, 'pid': os.getpid() }, options=PublishOptions(acknowledge=True)) self.log.info("Worker ready to roll!")
def onJoin(self, details): def on_event(i): print("Got event: {}".format(i)) yield from self.subscribe(on_event, 'com.myapp.topic1') counter = 0 while True: publication = yield from self.publish('com.myapp.topic1', counter, options=PublishOptions(acknowledge=True, discloseMe=True, excludeMe=False)) print("Event published with publication ID {}".format(publication.id)) counter += 1 yield from asyncio.sleep(1)
def run(txn): ## FIXME: make the following into 1 (atomic) SQL statement ## => does SQLite feature "UPDATE .. RETURNING"? txn.execute("UPDATE votes SET count = count + 1 WHERE item = ?", [item]) txn.execute("SELECT count FROM votes WHERE item = ?", [item]) count = int(txn.fetchone()[0]) self.publish("com.votegame.onvote", item, count, options=PublishOptions(excludeMe=False)) return count
def onJoin(self, details): print("session attached") print(" authid: {}".format(details.authid)) print("authrole: {}".format(details.authrole)) def got_heartbeat(name, counter): print("hearbeat: {}: {}".format(name, counter)) for name in ['alice', 'bob', 'carol', 'dave', 'erin']: yield self.subscribe( partial(got_heartbeat, name), 'public.heartbeat.{}'.format(name), ) counter = 0 topic = 'public.heartbeat.alice' while True: print("publish '{}'".format(topic)) self.publish( topic, counter, options=PublishOptions( exclude_authid=['bob'], exclude=12345, ), ) if counter % 2: self.publish( topic, "to everyone with 'alpha' role", options=PublishOptions( eligible_authrole=['alpha'], exclude_me=False, ), ) counter += 1 yield sleep(3)
def test2(self): """ The following produces this trace: https://gist.githubusercontent.com/oberstet/510b1d5d1fe6a65f78d439dc6b678c33/raw/2a9c6959db987716d3ccbc20c5bec5ccb146aee9/gistfile1.txt """ # unacked publish to topic with no subscribers # topic = u'com.example.r{}'.format(random.randint(0, 10000)) self.publish(topic, u'hey') # acked publish to topic with no subscribers # topic = u'com.example.r{}'.format(random.randint(0, 10000)) yield self.publish(topic, u'hey', options=PublishOptions(acknowledge=True)) # unacked publish to topic with 1 subscriber (ourself) # topic = u'com.example.r{}'.format(random.randint(0, 10000)) sub = yield self.subscribe(lambda msg: print(msg), topic) self.publish(topic, u'hey', options=PublishOptions(exclude_me=False)) # acked publish to topic with 1 subscriber (ourself) # topic = u'com.example.r{}'.format(random.randint(0, 10000)) sub = yield self.subscribe(lambda msg: print(msg), topic) yield self.publish(topic, u'hey', options=PublishOptions(acknowledge=True, exclude_me=False)) # resubscribe to a topic we are already subscribed to # sub = yield self.subscribe(lambda msg: print(msg), topic)
def _stop_native_worker(self, wtype, id, kill, details=None): assert (wtype in ['router', 'container', 'websocket-testee']) if id not in self._workers or self._workers[id].TYPE != wtype: emsg = "Could not stop native worker: no {} worker with ID '{}' currently running".format( wtype, id) raise ApplicationError(u'crossbar.error.worker_not_running', emsg) worker = self._workers[id] if worker.status != 'started': emsg = "Could not stop native worker: worker with ID '{}' is not in status 'started', but status: '{}')".format( id, worker.status) raise ApplicationError(u'crossbar.error.worker_not_running', emsg) stop_info = { 'id': worker.id, 'type': wtype, 'kill': kill, 'who': details.caller if details else None, 'when': utcnow() } # publish management API event # yield self.publish( 'crossbar.node.{}.worker.{}.on_stop_requested'.format( self._node_id, worker.id), stop_info, options=PublishOptions( exclude=[details.caller] if details else None, acknowledge=True)) # send SIGKILL or SIGTERM to worker # if kill: self.log.info("Killing {wtype} worker with ID '{id}'", wtype=wtype, id=id) self._workers[id].proto.transport.signalProcess("KILL") else: self.log.info("Stopping {wtype} worker with ID '{id}'", wtype=wtype, id=id) self._workers[id].factory.stopFactory() self._workers[id].proto.transport.signalProcess('TERM') returnValue(stop_info)
async def onJoin(self, details): print('Seller session joined', details) try: delegate_key = self._ethkey_raw delegate_adr = self._ethkey.public_key.to_canonical_address() print('Using delegate adr:', delegate_adr) config = await self.call('xbr.marketmaker.get_config') print('Using market maker adr:', config['marketmaker']) market_maker_adr = binascii.a2b_hex(config['marketmaker'][2:]) api_id = uuid.UUID('627f1b5c-58c2-43b1-8422-a34f7d3f5a04').bytes topic = 'io.crossbar.example' counter = 1 # 5 XBR / 10s price = 5 * 10**18 interval = 10 seller = SimpleSeller(market_maker_adr, delegate_key) seller.add(api_id, topic, price, interval, None) balance = await seller.start(self) balance = int(balance / 10**18) print("Remaining balance: {} XBR".format(balance)) self.log.info( 'Seller session ready! Starting to publish events ..') running = True while running: payload = {'data': 'py-seller', 'counter': counter} key_id, enc_ser, ciphertext = await seller.wrap( api_id, topic, payload) pub = await self.publish( topic, key_id, enc_ser, ciphertext, options=PublishOptions(acknowledge=True)) print('Published event {}: {}'.format(pub.id, payload)) counter += 1 await sleep(1) except: self.log.failure() self.leave()