def publish_message(self, message, token): """ send a message to a specific device. (1:1) """ conn = self._get_blocking_amqp_conn() try: channel = conn.channel() logger.debug("Declaring incoming exchange '%s'", self.incoming_exchange_name) channel.exchange_declare( exchange=self.incoming_exchange_name, durable=True, type='direct', ) logger.debug("Publishing message to exchange '%s'", self.incoming_exchange_name) channel.basic_publish( exchange=self.incoming_exchange_name, routing_key=token, body=message, properties=pika.BasicProperties( content_type="text/plain", ), ) except: logger.error("Error publishing message to exchange") raise finally: logger.debug("Disconnecting from message broker") conn.disconnect()
def queue_message(self, message, queue_name): """ send a message to a specific outbound queue (1:1) """ conn = self._get_blocking_amqp_conn() try: channel = conn.channel() logger.debug("Declaring queue '%s' on message broker", queue_name) channel.queue_declare(queue=queue_name) logger.debug("Sending message to queue '%s' for processing", queue_name) channel.basic_publish( exchange='', routing_key=queue_name, body=message, properties=pika.BasicProperties( content_type="text/plain", ), ) except: logger.error("Error queueing message in message broker") raise finally: logger.debug("Disconnecting from message broker") conn.disconnect()
def get_pending_messages(self, username = None): result = [] if username is None: return result username = str(username) connection = self._get_blocking_amqp_conn() try: channel = connection.channel() logger.debug("Connecting to %s ", username) channel.queue_declare(queue = username, durable = True, exclusive = False, auto_delete = False) while connection.is_open: method, header, body = channel.basic_get(queue = username) if method.NAME == 'Basic.GetEmpty': # queue empty, stop gathering. connection.close() return result else: result.append(body) #channel.basic_ack(delivery_tag=method.delivery_tag) except Exception, e: logger.error("Crapsticks: %s" % str(e))
def send_broadcast(self, message, username): """ Send a message to all queues associated with the username exchange (1:N)""" user_exchange_name = username conn = self._get_blocking_amqp_conn() try: channel = conn.channel() logger.debug("Declaring exchange '%s'", user_exchange_name) channel.exchange_declare( exchange=user_exchange_name, durable=True, type='fanout', ) # Send message to user exchange so all other clients receive it logger.debug("Publishing message to exchange '%s'", user_exchange_name) channel.basic_publish( exchange=user_exchange_name, routing_key='', body=message, properties=pika.BasicProperties( content_type='text/plain', ), ) except: logger.error("Error sending broadcast message") raise finally: logger.debug("Closing AMQP connection to broker") conn.disconnect()
def send_broadcast(self, message, username, queue = None, origin = None): """ append message to user's out queue queue = user/_tok/en_as_path/new_message_token """ if message is None or username is None: raise NotifStorageException("incomplete storage request") max_msgs = int(self.config.get('redis.max_msgs_per_user', '200')) file_ok = False doc_path = self._user_storage_path(username) if queue is None: queue = origin; if not os.path.exists(doc_path): os.makedirs(doc_path) try: while (not file_ok): doc_file = new_token() file_ok = not os.path.isfile(os.path.join(doc_path, doc_file)) file_path = os.path.join(doc_path, doc_file) file = os.open(file_path, os.O_WRONLY | os.O_CREAT) os.write(file, message) os.close(file) except IOError, e: logger.error("Could not write message file %s" % str(e)) raise NotifStorageException("Error storing message content")
def _ensure_exchanges_exist(self, incoming_exchange_name, user_exchange_name): conn = self._get_blocking_amqp_conn() try: channel = conn.channel() # TODO: Decide if we should remove this call; the incoming exchange # should always exist before new_subscription is called logger.debug("Declaring incoming exchange %s", incoming_exchange_name) channel.exchange_declare( exchange=incoming_exchange_name, durable=True, type='direct', ) # TODO: Decide if we should remove this call; the user exchange # should always exist before new_subscription is called logger.debug("Declaring user exchange %s", user_exchange_name) channel.exchange_declare( exchange=user_exchange_name, durable=True, type='fanout', ) except: logger.error("Error ensuring exchanges exist on broker") raise finally: logger.debug("Closing AMQP connection to broker") conn.disconnect()
def _delete_binding(self, source_exch, dest_exch, routing_key): http_conn = self._get_http_conn() try: auth_headers = { 'Authorization': 'Basic ' + base64.b64encode('%s:%s' % \ (self.broker_user, self.broker_pass) ), } path = '/api/bindings/%s/e/%s/e/%s/%s' % ( urllib.quote_plus(self.broker_vhost), urllib.quote_plus(source_exch), urllib.quote_plus(dest_exch), # Encode twice because binding "properties" are URL-encoded before stored urllib.quote_plus(urllib.quote_plus(routing_key)) ) logger.debug("Sending HTTP DELETE request to broker %s", path) http_conn.request('DELETE', path, '', auth_headers) response = http_conn.getresponse() logger.debug("Broker returned response with status %s", response.status) if response.status != httplib.NO_CONTENT: logger.error("Unexpected response status '%s'", response.status) raise Exception("Unexpected response status '%s'", response.status) except: logger.error("Error deleting binding via HTTP") raise finally: logger.debug("Closing HTTP connection to broker") http_conn.close()
def _resolve_token(self, token): try: mapping = self.db.mapping.get({u'token': token, u'type': 'sub'}) if mapping is None or mapping.get('user', None) is None: return None return mapping.get('user') except OperationFailure, e: logger.error('Could not find mapping to user for token %s, %s', token, str(e)) return None
def delete_subscription(self, username, token): try: self.db.mapping.remove({u'token': token, u'user_id': username}) self.db.user.remove({'user_id': username, 'origin': token}) return True except OperationFailure, e: logger.error('Could not remove mappning for subscription %s' % str(e)) return False
def create_subscription(self, username, token): """Map token to username""" mapping_info = {u'token': token, u'user_id': username, u'type': 'sub', u'created': int(time.time())} try: self.db.mapping.insert(mapping_info, safe=True) return True except OperationFailure, e: logger.error('Could not create subscription: %s' % str(e)) return False
def user_info(self, username, user_info = None): try: if user_info is None: # Fetch the user's information user_info = self.redis.get('ui:%s' % username) if user_info is not None: return json.loads(user_info) else: return {} else: self.redis.set('ui:%s' % username, json.dumps(user_info)) return user_info except Exception, e: logger.error("Could not access user info %s, %s" % (username, str(e))) raise
def send_broadcast(self, message, username, origin = None): msg_content = {} ttl = int(self.config.get('notif_server.max_ttl_seconds', '259200')) # 3 days try: msg_content = json.loads(message) ttl = msg_content.get('ttl', ttl) self.db.message.save({u'user_id': username, 'origin': origin, 'message': message, 'expry': int(time.time() + ttl)}) #TODO:: Add in notification to tickle listening # clients (if desired) except OperationFailure, e: logger.error("Could not save message to %s from %s " % ( username, origin ))
def handle_deliveries(self, config=None, username=None, callback=None): if username is None: return handler = {'username': username, 'callback': callback} config['handler'] = handler try: class Deliv_Handler: def __init__(self, config=config): self.params = config def on_connected(self, connection): connection.channel(self.on_channel_open) def on_channel_open(self, channel): self.channel = channel self.channel.queue_declare(queue= self.config.get('channel_name'), durable=True, exclusive=False, auto_delete=False, callback=self.queue_declared) def on_queue_declared(self, frame): self.channel.basic_consume(self.handle_delivery, queue = self.config.get('channel_name')) def handle_delivery(self, channel, method, header, body): # publish the item. # print body callback(channel=channel, method=method, header=header, body=body) except Exception, e: logger.error("Unhandled exception %s", str(e)) raise
def create_client_queue(self, username): user_exchange_name = username # Create a unique client id to also be used as the name of the queue client_queue_name = "%x" % random.getrandbits(256) logger.info("Creating queue %s for user %s", client_queue_name, username) # Create the user exchange (if it doesn't already exist) and a client # queue with the generated name. conn = self._get_amqp_conn() try: channel = conn.channel() logger.debug("Declaring exchange %s", user_exchange_name) channel.exchange_declare( exchange=user_exchange_name, durable=True, type='fanout', ) logger.debug("Declaring queue %s", client_queue_name) channel.queue_declare(queue=client_queue_name, durable=True) logger.debug("Binding queue %s to exchange %s", client_queue_name, user_exchange_name, ) channel.queue_bind( exchange=user_exchange_name, queue=client_queue_name, ) except: logger.error("Error creating new client queue") raise finally: logger.debug("Closing AMQP connection to broker") conn.disconnect() return { 'queue_id': client_queue_name, 'host': self.broker_host, 'port': self.broker_amqp_port, }
def _add_binding(self, source_exch, dest_exch, routing_key): # XXX: OH EM GEE this is a hack. Creating Exchange-to-exchange (E2E) # bindings is a RabbitMQ-specific extension, so the pika AMQP # client doesn't support it. The RabbitMQ REST API does however, # so we do a quick HTTP POST here to create the binding. # # To do this properly, we'll probably have to roll our own version # of Pika which supports the exchange.bind method call. http_conn = self._get_http_conn() try: auth_headers = { 'Authorization': 'Basic ' + base64.b64encode('%s:%s' % \ (self.broker_user, self.broker_pass) ), 'Content-Type': 'application/json', } path = '/api/bindings/%s/e/%s/e/%s' % ( urllib.quote_plus(self.broker_vhost), urllib.quote_plus(source_exch), urllib.quote_plus(dest_exch) ) body = json.dumps({'routing_key': routing_key, 'arguments': []}) logger.debug("Sending HTTP POST request to broker %s", path) http_conn.request('POST', path, body, auth_headers) response = http_conn.getresponse() logger.debug("Broker returned response with status %s", response.status) if response.status != httplib.CREATED: logger.error("Unexpected response status '%s'", response.status) raise Exception("Unexpected response status '%s'", response.status) except: logger.error("Error adding binding via HTTP") raise finally: logger.debug("Closing HTTP connection to broker") http_conn.close()
def create_client_queue(self, username): logger.info("Creating incoming queue for user %s" % username) try: # Check to see if the user already has a token. # ut: user -> token user_token = self.redis.get('u2t:%s' % username) if user_token is None: user_token = new_token() self.redis.set('u2t:%s' % username, user_token) self.redis.set('t2u:%s' % user_token, username) info = {"created": int(time.time()), "state": 'active', "changedate": int(time.time())} self.redis.hmset('sin:%s:%s' % (username, user_token), info) return {'queue_id': user_token, 'port': self.config.get('notifserver.port', 80), 'host': self.config.get('notifserver.host')} except Exception, ex: logger.error("Could not create user queue %s" % str(ex))
def create_client_queue(self, username): # create the mapping record channel_info = {u'token': new_token(), u'user_id': username, u'type': 'queue', u'created': int(time.time()) } logger.info("Creating incoming queue %s for user %s" % ( channel_info.get('token'), username)) try: self.db.user.insert(channel_info, safe=True) return {'queue_id': channel_info['token'], 'host': self.config.get('notifserver.host'), 'port': self.config.get('notifserver.port')} except OperationFailure, e: logger.error('Could not create mapping: %s' % str(e)) return False
def create_client_queue(self, username): self.channel_info = {} self.channel_info['exchange_name'] = username self.channel_info['queue_name'] = new_token() # Create a unique client id to also be used as the name of the queue client_queue_name = new_token() logger.info("Creating queue %s for user %s", self.channel_info.get('queue_name'), self.channel_info.get('exchange_name')) # Create the user exchange (if it doesn't already exist) and a client # queue with the generated name. conn = self._get_blocking_amqp_conn() try: channel = conn.channel() logger.debug("Declaring exchange %s", self.channel_info.get('exchange_name')) channel.exchange_declare( exchange=self.channel_info.get('exchange_name'), durable=True, type='fanout', ) logger.debug("Declaring queue %s", client_queue_name) channel.queue_declare(queue=client_queue_name, durable=True) logger.debug("Binding queue %s to exchange %s", client_queue_name, self.channel_info.get('exchange_name'), ) channel.queue_bind( exchange=self.channel_info.get('exchange_name'), queue=client_queue_name, ) except Exception, e: logger.error("Error creating new client queue. %s" % e.message) raise NotifStorageException('Cannot create new client')
def create_subscription(self, username, token, origin = None): """ Map a token to a username """ # s2u: subscription to user # u2s: user subscriptions if username is None or token is None: return {} retObj = {'queue_id': token, 'port': self.config.get('notifserver.port', 80), 'host': self.config.get('notifserver.host')} prevToken = self.redis.get('ssu:%s:%s' % (username, origin)) if prevToken is not None: self.reactivate_subscription(username, prevToken, origin) token = prevToken retObj['queue_id'] = token return retObj mappedToken = self.redis.get("s2u:%s" % token) if mappedToken is not None: if self.redis.get("s2u:%s" % token) == username: return retObj logger.error("Token collision! %s" % token) return False self.redis.set("s2u:%s" % token, username) self.redis.set("ssu:%s:%s" % (username, origin), token) user_tokens = self.redis.lrange("u2s:%s" % username, 0, -1) if token not in user_tokens: self.redis.lpush("u2s:%s" % username, token) info = {"created": int(time.time()), "state": 'active', "changedate": int(time.time())} self.redis.hmset("sin:%s:%s" % (username, token), info) print ("Created sin:%s:%s" % (username, token)) if origin is not None: self.redis.hset("sin:%s:%s" % (username, token), "origin", origin) return retObj
'259200')) # 3 days try: msg_content = json.loads(message) ttl = msg_content.get('ttl', ttl) self.db.message.save({u'user_id': username, 'origin': origin, 'message': message, 'expry': int(time.time() + ttl)}) #TODO:: Add in notification to tickle listening # clients (if desired) except OperationFailure, e: logger.error("Could not save message to %s from %s " % ( username, origin )) except ValueError, e: logger.error("message is not valid JSON, ignoring %s" % str(e)) return False def get_pending_messages(self, username = None, since = None): result = [] if username is None: return result query = {u'user_id': username} # TODO: set a default 'since' ? if since is not None: query['ttl'] = {'$gt': since} return list(self.db.user.find(query)) def _purge(self): try: self.db.remove({'expry': {'$lt': int(time.time())}})
def _purge(self): try: self.db.remove({'expry': {'$lt': int(time.time())}}) except OperationFailure, e: logger.error("Could not purge old messages: %s", str(e))
def _get_blocking_amqp_conn(self): try: return pika.BlockingConnection(self.conn_params) except Exception, e: logger.error("Could not connect to amqp server %s", e.message) raise NotifStorageException("Could not connect to server")