def _fire_reply(self, message, reply_to): """ Tries to fire a reply to the given message :param message: Message to send as a reply :param reply_to: Message the first argument replies to :return: The UID of the sent message """ # Use the message source peer try: transport = self._transports[reply_to.access] except KeyError: # Reception transport is not available anymore... raise NoTransport(beans.Target(uid=reply_to.sender), "No reply transport for access {0}" .format(reply_to.access)) else: # Try to get the Peer bean. If unknown, consider that the # "extra" data will help the transport to reply try: peer = self._directory.get_peer(reply_to.sender) except KeyError: peer = None try: # Send the reply transport.fire(peer, message, reply_to.extra) except InvalidPeerAccess: raise NoTransport(beans.Target(uid=reply_to.sender), "Can't reply to {0} using {1} transport" .format(peer, reply_to.access)) else: # Reply sent. Stop here return message.uid
def fire(self, target, message): """ Fires (and forget) the given message to the target :param target: The UID of a Peer, or a Peer object :param message: A Message bean :return: The UID of the message sent :raise KeyError: Unknown peer UID :raise NoTransport: No transport found to send the message """ # Standard behavior # Get the Peer object if not isinstance(target, beans.Peer): peer = self._directory.get_peer(target) else: peer = target # Check if some transports are bound if not self._transports: raise NoTransport(beans.Target(uid=peer.uid), "No transport bound yet.") # Get accesses accesses = peer.get_accesses() for access in accesses: try: transport = self._transports[access] except KeyError: # No transport for this kind of access pass else: try: # Call it transport.fire(peer, message) except InvalidPeerAccess as ex: # Transport can't read peer access data _logger.debug("Error reading access for transport %s: %s", access, ex) except Exception as ex: # Exception during transport _logger.warning("Error using transport %s: %s", access, ex) else: # Success break else: # No transport for those accesses raise NoTransport(beans.Target(uid=peer.uid), "No working transport found for peer {0}" .format(peer)) return message.uid
def send(self, target, message, timeout=None): """ Sends a message, and waits for its reply :param target: The UID of a Peer, or a Peer object :param message: A Message bean :param timeout: Maximum time to wait for an answer :return: The reply message bean :raise KeyError: Unknown peer UID :raise NoTransport: No transport found to send the message :raise NoListener: Message received, but nobody was registered to listen to it :raise HeraldTimeout: Timeout raised before getting an answer """ # Prepare an event, which will be set when the answer will be received event = pelix.utilities.EventData() self.__waiting_events[message.uid] = event # Get the Peer object if not isinstance(target, beans.Peer): peer = self._directory.get_peer(target) else: peer = target try: # Fire the message self.fire(peer, message) # Message sent, wait for an answer if event.wait(timeout): if event.data is not None: return event.data else: # Message cancelled due to invalidation raise HeraldTimeout(beans.Target(uid=peer.uid), "Herald stops listening to messages", message) else: raise HeraldTimeout(beans.Target(uid=peer.uid), "Timeout reached before receiving a reply", message) finally: try: # Clean up del self.__waiting_events[message.uid] except KeyError: # Ignore errors at this point pass
def fire(self, peer, message, extra=None): """ Fires a message to a peer :param peer: A Peer bean :param message: Message bean to send :param extra: Extra information used in case of a reply :raise InvalidPeerAccess: No information found to access the peer :raise Exception: Error sending the request or on the server side """ # Get the request message UID, if any parent_uid = None if extra is not None: parent_uid = extra.get('parent_uid') # Try to read extra information url = self.__get_access(peer, extra) if not url: # No HTTP access description raise InvalidPeerAccess(beans.Target(uid=peer.uid), "No '{0}' access found" .format(self._access_id)) # Send the HTTP request (blocking) and raise an error if necessary headers, content = self.__prepare_message(message, parent_uid) response = self.__session.post(url, content, headers=headers) response.raise_for_status()
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 _handle_error(self, message, kind): """ Handles an error message :param message: MessageReceived bean, received from another peer :param kind: Kind of error """ if kind == 'no-listener': # No listener found for a given message # ... release send() calls try: # Get the original message UID and Subject uid = message.content['uid'] exception = NoListener(beans.Target(message.sender), uid, message.content['subject']) except KeyError: # Invalid error content... return try: # Unlock the poster with an exception self.__waiting_events.pop(uid).raise_exception(exception) except KeyError: # Nobody was waiting for the event pass # ... notify post() callers try: self.__waiting_posts[uid].errback(self, exception) except KeyError: # No error callback for this message pass
def fire(self, peer, message, extra=None): """ Fires a message to a peer :param peer: A Peer bean :param message: Message bean to send :param extra: Extra information used in case of a reply :raise InvalidPeerAccess: No information found to access the peer :raise Exception: Error sending the request or on the server side """ # Get the request message UID, if any parent_uid = None if extra is not None: parent_uid = extra.get('parent_uid') # Try to read extra information url = self.__get_access(peer, extra) if not url: # No HTTP access description raise InvalidPeerAccess( beans.Target(peer=peer), "No '{0}' access found".format(self._access_id)) # Send the HTTP request (blocking) and raise an error if necessary headers, content = self.__prepare_message(message, parent_uid, target_peer=peer) # Log before sending self._probe.store( herald.PROBE_CHANNEL_MSG_SEND, { "uid": message.uid, "timestamp": time.time(), "transport": ACCESS_ID, "subject": message.subject, "target": peer.uid if peer else "<unknown>", "transportTarget": url, "repliesTo": parent_uid or "" }) self._probe.store(herald.PROBE_CHANNEL_MSG_CONTENT, { "uid": message.uid, "content": content }) response = self.__post_message(url, content, headers) if response is None: # The error has been logged in post_message raise IOError("Error sending message {0}".format(message.uid)) else: # Raise an error if the status isn't 2XX response.raise_for_status()
def fire(self, peer, message, extra=None): """ Fires a message to a peer :param peer: A Peer bean :param message: Message to send :param extra: Extra information used in case of a reply """ # Get the request message UID, if any parent_uid = None if extra is not None: parent_uid = extra.get('parent_uid') # Try to read extra information jid = self.__get_jid(peer, extra) if jid: # Log before sending self._probe.store( herald.PROBE_CHANNEL_MSG_SEND, { "uid": message.uid, "timestamp": time.time(), "transport": ACCESS_ID, "subject": message.subject, "target": peer.uid if peer else "<unknown>", "transportTarget": str(jid), "repliesTo": parent_uid or "" }) # Send the XMPP message self.__send_message("chat", jid, message, parent_uid, target_peer=peer) else: # No XMPP access description raise InvalidPeerAccess( beans.Target(uid=peer.uid), "No '{0}' access found".format(self._access_id))
def fire(self, peer, message, extra=None): """ Fires a message to a peer :param peer: A Peer bean :param message: Message to send :param extra: Extra information used in case of a reply """ # Get the request message UID, if any parent_uid = None if extra is not None: parent_uid = extra.get('parent_uid') # Try to read extra information jid = self.__get_jid(peer, extra) if jid: # Send the XMPP message self.__send_message("chat", jid, message, parent_uid) else: # No XMPP access description raise InvalidPeerAccess( beans.Target(uid=peer.uid), "No '{0}' access found".format(self._access_id))
def post_group(self, group, message, callback, errback, timeout=180): """ Posts a message to a group of peers. The given methods will be called back as soon as a result is given, or in case of error. If no timeout is given, the message UID must be forgotten manually. :param group: The name of a group of peers :param message: A Message bean :param callback: Method to call back when a reply is received :param errback: Method to call back if an error occurs :param timeout: Time after which the message will be forgotten :return: The message UID :raise KeyError: Unknown group :raise NoTransport: No transport found to send the message """ # Get all peers known in the group all_peers = self._directory.get_peers_for_group(group) # Check if some transports are bound if not self._transports: raise NoTransport( beans.Target(group=group, uids=[peer.uid for peer in all_peers]), "No transport bound yet.") with self.__gc_lock: # Prepare an entry in the waiting posts self.__waiting_posts[message.uid] = \ _WaitingPost(callback, errback, timeout, False) # Find the common accesses accesses = {} for peer in all_peers: for access in peer.get_accesses(): accesses.setdefault(access, set()).add(peer) for access, access_peers in accesses.items(): if not access_peers: # Nothing to do continue try: transport = self._transports[access] except KeyError: # No transport for this kind of access pass else: try: # Call it transport.fire_group(group, access_peers, message) except InvalidPeerAccess: # Transport can't find group access data pass else: # Success: clean up waiting peers all_done = True for remaining_peers in accesses.values(): remaining_peers.difference_update(access_peers) if remaining_peers: # Still some peers to notify all_done = False if all_done: break return message.uid
def fire_group(self, group, message): """ Fires (and forget) the given message to the given group of peers :param group: The name of a group of peers :param message: A Message bean :return: A tuple: the UID of the message sent and the list of peers :raise KeyError: Unknown group :raise NoTransport: No transport found to send the message """ # Get all peers known in the group all_peers = self._directory.get_peers_for_group(group) if not all_peers: _logger.warning("No peer in group %s", group) return message.uid, set() # Check if some transports are bound if not self._transports: raise NoTransport( beans.Target(group=group, uids=[peer.uid for peer in all_peers]), "No transport bound yet.") # Find the common accesses accesses = {} for peer in all_peers: for access in peer.get_accesses(): accesses.setdefault(access, set()).add(peer) missing = [] for access, access_peers in accesses.items(): if not access_peers: # Nothing to do continue try: transport = self._transports[access] except KeyError: # No transport for this kind of access _logger.debug("No transport for %s", access) else: try: # Call it reached_peers = transport.fire_group(group, access_peers, message) if reached_peers is None: reached_peers = access_peers except InvalidPeerAccess as ex: # Transport can't find group access data _logger.debug("Missing access info: %s", ex) else: # Success: clean up waiting peers all_done = True for remaining_peers in accesses.values(): remaining_peers.difference_update(reached_peers) if remaining_peers: # Still some peers to notify all_done = False if all_done: break else: missing = set(itertools.chain(*accesses.values())) if missing: _logger.warning("Some peers haven't been notified: %s", ', '.join(str(peer) for peer in missing)) else: _logger.debug("No peer to send the message to.") return message.uid, missing