def reply(self, message, content, subject=None): """ Replies to a message :param message: Original message :param content: Content of the response :param subject: Reply message subject (same as request if None) :raise NoTransport: No transport/access found to send the reply """ # Normalize subject. By default, add a 'reply' prefix, # to avoid potential loops if not subject: subject = '/'.join(('reply', message.subject)) try: # Try to reuse the same transport self._fire_reply(beans.Message(subject, content), message) except NoTransport: # Continue... pass else: # No error return # If not possible: fire a standard reply try: # Fire the reply self.fire(message.sender, beans.Message(subject, content)) except KeyError: # Convert KeyError to NoTransport raise NoTransport(beans.Target(uid=message.sender), "No access to reply to {0}" .format(message.sender))
def __discover_forker(self): """ Discover the forker of this local Peer by sending to him the contact message """ host = self._forker_host port = self._forker_port path = self._forker_path if path.startswith('/'): # Remove the starting /, as it is added while forging the URL path = path[1:] # Normalize the address of the sender host = utils.normalize_ip(host) # Prepare the "extra" information, like for a reply extra = {'host': host, 'port': port, 'path': path} local_dump = self._directory.get_local_peer().dump() try: self._transport.fire( None, beans.Message(peer_contact.SUBJECT_DISCOVERY_STEP_1, local_dump), extra) except Exception as ex: _logger.exception("Error contacting peer: %s", ex)
def __discover_neighbor(self, peer_dump): """ Discover local neighbor Peer """ host = peer_dump['accesses']['http'][0] port = peer_dump['accesses']['http'][1] path = peer_dump['accesses']['http'][2] if path.startswith('/'): # Remove the starting /, as it is added while forging the URL path = path[1:] # Normalize the address of the sender host = utils.normalize_ip(host) # Prepare the "extra" information, like for a reply extra = {'host': host, 'port': port, 'path': path} local_dump = self._directory.get_local_peer().dump() try: self._transport.fire( None, beans.Message(peer_contact.SUBJECT_DISCOVERY_STEP_1, local_dump), extra) except Exception as ex: _logger.exception("Error contacting peer: %s", ex)
def __on_ready(self, joined, erroneous): """ Called when all MUC rooms have created or joined :param joined: List of joined rooms :param erroneous: List of room that couldn't be joined """ _logger.debug("Client joined rooms: %s", ", ".join(joined)) if erroneous: _logger.error("Error joining rooms: %s", ", ".join(erroneous)) # Register our local access local_peer = self._directory.get_local_peer() local_peer.set_access(self._access_id, XMPPAccess(self._bot.boundjid.full)) # Listen to peers going away from the main room self._bot.add_event_handler( "muc::{0}::got_offline".format(self.room_jid(local_peer.app_id)), self._on_room_out) if self._bot_lock: # We're on line, register our service _logger.debug("XMPP transport service activating...") self._controller = True _logger.info("XMPP transport service activated") self._bot_state = "created" _logger.info("changing XMPP bot state to : created") # Start the discovery handshake, with everybody _logger.debug("Sending discovery step 1...") message = beans.Message(peer_contact.SUBJECT_DISCOVERY_STEP_1, local_peer.dump()) self.__send_message("groupchat", self.room_jid(local_peer.app_id), message)
def post_group(self, io_handler, group, subject, *words): """ Post a message to the given group of peers """ def callback(_, message): """ Received a reply """ io_handler.write_line("Got answer to {0} from {1}:\n{2}", message.reply_to, message.sender, message.content) def errback(_, exception): """ Error during message transmission """ io_handler.write_line("Error posting message: {0} ({1})", type(exception).__name__, exception) try: uid = self._herald.post_group( group, beans.Message(subject, ' '.join(words)), callback, errback) except KeyError: io_handler.write_line("Unknown group: {0}", group) except NoTransport: io_handler.write_line("No transport to join {0}", group) else: io_handler.write_line("Message sent: {0}", uid)
def __discover_peer(self, host, port, path): """ Grabs the description of a peer using the Herald servlet :param host: Address which sent the heart beat :param port: Port of the Herald HTTP server :param path: Path to the Herald HTTP servlet """ if path.startswith('/'): # Remove the starting /, as it is added while forging the URL path = path[1:] # Normalize the address of the sender host = utils.normalize_ip(host) # Prepare the "extra" information, like for a reply extra = {'host': host, 'port': port, 'path': path} local_dump = self._directory.get_local_peer().dump() try: self._transport.fire( None, beans.Message(peer_contact.SUBJECT_DISCOVERY_STEP_1, local_dump), extra) except Exception as ex: _logger.exception("Error contacting peer: %s", ex)
def __send_message(self, kind, content): """ Fires a discovery message to all peers :param kind: Kind of discovery message :param content: Content of the message """ self._herald.fire_group('all', beans.Message(self.__subject(kind), content))
def _get_isolate_directory(self, uuid): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate msg = beans.Message(debug.agent.SUBJECT_GET_ISOLATE_DIRECTORY) reply = self._herald.send(uuid, msg) return reply.content else: # this is the local isolate return self._agent.get_isolate_directory()
def peer_registered(self, peer): """ A new peer has been registered in Herald: send it a contact information :param peer: The new peer """ # Send a contact message, with our list of endpoints endpoints = self._dump_endpoints(self._dispatcher.get_endpoints()) self._herald.fire(peer, beans.Message(self.__subject('contact'), endpoints))
def __send_message(self, kind, content, group=DEFAULT_TARGET_GROUP): """ Fires a discovery message to all peers :param kind: Kind of discovery message :param content: Content of the message :param group: Name of the group to whom the message is to be sent """ self._herald.fire_group(group, beans.Message(self.__subject(kind), content))
def _get_isolate_accesses(self, uuid): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate msg = beans.Message(debug.agent.SUBJECT_GET_ISOLATE_ACCESSES) reply = self._herald.send(uuid, msg) return json.loads(reply.content) else: # this is the local isolate return json.loads(self._agent.get_isolate_accesses())
def _get_instance_detail(self, uuid, instance_name): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate msg = beans.Message(debug.agent.SUBJECT_GET_INSTANCE_DETAIL, instance_name) reply = self._herald.send(uuid, msg) return json.loads(reply.content) else: # this is the local isolate return json.loads(self._agent.get_instance_detail(instance_name))
def _get_bundle_detail(self, uuid, bundle_id): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate msg = beans.Message(debug.agent.SUBJECT_GET_BUNDLE_DETAIL, bundle_id) reply = self._herald.send(uuid, msg) return json.loads(reply.content) else: # this is the local isolate return json.loads(self._agent.get_bundle_detail(bundle_id))
def flush(self): """ Sends buffered data to the target """ # Flush buffer line = self._buffer.getvalue() self._buffer = StringIO() # Send the message content = {"session_id": self._session, "text": line} self._herald.fire(self._peer, beans.Message(MSG_CLIENT_PRINT, content))
def stop(self): """ Stops the whole isolate """ # Send the "isolate stopping" signal _logger.warning(">>> Isolate will stop <<<") message = beans.Message(cohorte.monitor.SIGNAL_ISOLATE_STOPPING, self._context.get_property(cohorte.PROP_UID)) self._sender.fire_group('all', message) _logger.warning(">>> STOPPING isolate <<<") self._context.get_bundle(0).stop()
def readline(self): """ Waits for a line from the Herald client """ content = {"session_id": self._session} prompt_msg = self._herald.send( self._peer, beans.Message(MSG_CLIENT_PROMPT, content)) if prompt_msg.subject == MSG_SERVER_CLOSE: # Client closed its shell raise EOFError return prompt_msg.content
def stop(self, uid): """ Stops the given isolate :param uid: The UID if an isolate :raise KeyError: Unknown UID :raise OSError: Error killing the isolate """ # Keep the reference to the process (just in case) process = self._isolates[uid] if process.poll() is None: # Fire the stop message try: self._sender.fire( uid, beans.Message(cohorte.monitor.SIGNAL_STOP_ISOLATE)) except HeraldException: # Error sending message _logger.warn( "Isolate %s (PID: %d) didn't received the 'stop' " "signal: Kill it!", uid, process.pid) try: process.kill() except OSError as ex: # Error stopping the process _logger.warning("Can't kill the process: %s", ex) else: # Message sent try: # Wait a little _logger.info("Waiting for isolate %s (PID: %d) to stop...", uid, process.pid) self._utils.wait_pid(process.pid, self._timeout) _logger.info("Isolate stopped: %s (%d)", uid, process.pid) except cohorte.utils.TimeoutExpired: # The isolate didn't stop -> kill the process _logger.warn( "Isolate timed out: %s (%d). " "Trying to kill it", uid, process.pid) try: process.kill() except OSError as ex: # Error stopping the process _logger.warning("Can't kill the process: %s", ex) # Process stopped/killed without error try: del self._isolates[uid] except KeyError: # Isolate might already be removed from this component by the # forker pass
def fire(self, io_handler, target, subject, *words): """ Fires a message to the given peer. """ try: uid = self._herald.fire(target, beans.Message(subject, ' '.join(words))) except KeyError: io_handler.write_line("Unknown target: {0}", target) except NoTransport: io_handler.write_line("No transport to join {0}", target) else: io_handler.write_line("Message sent: {0}", uid)
def _get_isolate_detail(self, uuid): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate try: msg = beans.Message(debug.agent.SUBJECT_GET_ISOLATE_DETAIL) reply = self._herald.send(uuid, msg) return json.loads(reply.content) except KeyError: return None else: # this is the local isolate return json.loads(self._agent.get_isolate_detail())
def _set_isolate_logs_level(self, uuid, level): lp = self._directory.get_local_peer() if lp.uid != uuid: # this is another isolate msg = beans.Message(debug.agent.SUBJECT_SET_ISOLATE_LOGS_LEVEL, level) reply = self._herald.send(uuid, msg) return json.loads(reply.content) else: # this is the local isolate return json.loads(self._agent.set_isolate_logs_level(level)) # Not yet implemented in Python return None
def invalidate(self, context): """ Component invalidated """ try: # Send the stopping signal message = beans.Message(cohorte.monitor.SIGNAL_ISOLATE_STOPPING) self._sender.fire_group('monitors', message) except: _logger.info("Herald transports are not bound.") # Clear the context self._context = None self.__node_uid = None _logger.info("Isolate agent invalidated")
def fire_group(self, io_handler, group, subject, *words): """ Fires a message to the given group of peers. """ try: uid, missed = self._herald.fire_group( group, beans.Message(subject, ' '.join(words))) except KeyError: io_handler.write_line("Unknown group: {0}", group) except NoTransport: io_handler.write_line("No transport to join {0}", group) else: io_handler.write_line("Message sent: {0}", uid) if missed: io_handler.write_line("Missed peers: {0}", ",".join(missed))
def shutdown(self, io_handler): """ Shutdown all the platform (all the nodes) """ msg = beans.Message(cohorte.monitor.SIGNAL_STOP_PLATFORM) try: # send to other monitors self._herald.fire_group('monitors', msg) except herald.exceptions.HeraldException: pass # send to local peer msg2 = beans.MessageReceived(msg.uid, msg.subject, msg.content, "local", "local", "local") threading.Thread(target=self._herald.handle_message, args=[msg2]).start()
def __discover_neighbors(self): """ Discover local neighbor Peers. The list of the neighbor peers is retrieved from the forker. We should ensure that the forker if properly added to the local directory before sending him this contact message. """ try: reply = self._herald.send( self._forker.uid, beans.Message(SUBJECT_GET_NEIGHBORS_LIST, self._local_peer.uid)) if reply is not None: for peer_uid in reply.content: self.__discover_neighbor(reply.content[peer_uid]) except Exception as ex: _logger.exception("Error contacting forker peer to retrieve local neighbor peers: %s", ex)
def peer_registered(self, peer): """ A new Herald directory group has been set. Sends the "Ready" message to monitors, as soon as one of their peers has been detected """ if peer.node_uid == self.__node_uid \ and peer.name == cohorte.FORKER_NAME: # Send the "ready" signal try: self._sender.fire( peer, beans.Message(cohorte.monitor.SIGNAL_ISOLATE_READY)) except herald.exceptions.NoTransport as ex: _logger.error("No transport to notify monitors: %s", ex) else: _logger.info("Monitors notified of isolate readiness")
def get_isolate_http_port(self, uid): """ Retrieves the http port of the given isolate """ lp = self._directory.get_local_peer() if lp.uid != uid: msg = beans.Message(SUBJECT_GET_HTTP) reply = self._herald.send(uid, msg) return reply.content['http.port'] else: # Get the isolate HTTP port port = -1 svc_ref = self._context.get_service_reference( pelix.http.HTTP_SERVICE) if svc_ref is not None: port = svc_ref.get_property(pelix.http.HTTP_SERVICE_PORT) return port
def herald_join(self, nick, monitor_jid, key, groups=None): """ Requests to join Herald's XMPP groups :param nick: Multi-User Chat nick :param monitor_jid: JID of a monitor bot :param key: Key to send to the monitor bot :param groups: Groups to join """ # Update nick for the Invite MixIn self._nick = nick # Compute & send message groups_str = ",".join(str(group) for group in groups if group) if groups else "" msg = beans.Message("boostrap.invite", ":".join( ("invite", str(key or ''), groups_str))) self.__send_message("chat", monitor_jid, msg)
def send(self, io_handler, target, subject, *words): """ Sends a message to the given peer(s). Prints responses in the shell. """ try: # Send the message with a 10 seconds timeout # (we're blocking the shell here) result = self._herald.send(target, beans.Message(subject, ' '.join(words)), 10) except KeyError: io_handler.write_line("Unknown target: {0}", target) except NoTransport: io_handler.write_line("No transport to join {0}", target) except NoListener: io_handler.write_line("No listener for {0}", subject) except HeraldTimeout: io_handler.write_line("No response given before timeout") else: io_handler.write_line("Response: {0}", result.subject) io_handler.write_line(result.content)
def __room_in(self, data): """ Someone entered the main room :param data: MUC presence stanza """ uid = data['from'].resource room_jid = data['from'].bare local_peer = self._directory.get_local_peer() if uid == local_peer.uid and room_jid == self._room: # We're on line, in the main room, register our service self._controller = True # Register our local access local_peer.set_access(self._access_id, XMPPAccess(self._bot.boundjid.full)) # Send the "new comer" message message = beans.Message('herald/directory/newcomer', local_peer.dump()) self.__send_message("groupchat", room_jid, message)
def invalidate(self, _): """ Component invalidated """ with self.__lock: for session in self._sessions.values(): peer_uid = session.get(SESSION_CLIENT_ID) session_id = session.get(SESSION_SESSION_ID) try: self._herald.fire( peer_uid, beans.Message(MSG_CLIENT_CLOSE, session_id)) except HeraldException as ex: # Error sending message _logger.error("Error sending session close message: %s", ex) except KeyError: # Unknown peer: ignore pass # Clear sessions self._sessions.clear()