class RabbitNotifications(object): """ Wrapper to access notification methods, and catch possible exceptions """ def __init__(self, url): self.url = url self.message_defaults = { "source": "hub", "version": pkg_resources.require("ulearnhub")[0].version, } client_properties = { "product": "hub", "version": pkg_resources.require("ulearnhub")[0].version, "platform": 'Python {0.major}.{0.minor}.{0.micro}'.format(sys.version_info), } self.enabled = True try: self.client = RabbitClient(self.url, client_properties=client_properties, transport='gevent') except AttributeError: self.enabled = False except socket_error: raise ConnectionError("Could not connect to rabbitmq broker") def sync_acl(self, domain, context, username, tasks): """ Sends a Carrot (TM) notification of a new sync acl task """ # Send a conversation creation notification to rabbit message = RabbitMessage() message.prepare(self.message_defaults) message.update({ "user": { 'username': username, }, "domain": domain, "action": "modify", "object": "context", "data": { 'context': context, 'tasks': tasks } }) self.client.send('syncacl', json.dumps(message.packed), routing_key='')
class RabbitNotifications(object): """ Wrapper to access notification methods, and catch possible exceptions """ def __init__(self, request): self.request = request settings = getMAXSettings(request) self.url = settings.get('max_rabbitmq', '') self.message_defaults = settings.get('max_message_defaults', {}) self.enabled = True client_properties = { "product": "max", "version": pkg_resources.require('max')[0].version, "platform": 'Python {0.major}.{0.minor}.{0.micro}'.format(sys.version_info), "server": settings.get('max_server', '') } try: self.client = RabbitClient(self.url, client_properties=client_properties) except AttributeError: self.enabled = False except socket_error: raise ConnectionError("Could not connect to rabbitmq broker") def __getattribute__(self, name): """ Returns the requested method if notifier is enabled, otherwise performs a noop """ enabled = object.__getattribute__(self, 'enabled') if enabled or name in ['enabled', 'url', 'request', 'client', 'message_defaults']: return object.__getattribute__(self, name) else: return noop def restart_tweety(self): """ Sends a timestamp to tweety_restart queue, trough the default exchange (binding to tweety_restart queue is implicit in this special exchange) """ default_exchange = '' restart_request_time = datetime.datetime.now().strftime('%s.%f') self.client.send(default_exchange, restart_request_time, 'tweety_restart') #self.client.disconnect() def add_user(self, username): """ Creates the specified user exchange and bindings """ self.client.create_user(username) #self.client.disconnect() def delete_user(self, username): """ Deletes the specified user exchange and bindings """ self.client.delete_user(username) #self.client.disconnect() def bind_user_to_context(self, context, username): """ Creates a binding between user exchanges and a context """ context_id = context.getIdentifier() self.client.activity.bind_user(context_id, username) #self.client.disconnect() def unbind_user_from_context(self, context, username): """ Destroys a binding between user exchanges and a context """ context_id = context.getIdentifier() self.client.activity.unbind_user(context_id, username) #self.client.disconnect() def unbind_context(self, context): """ Destroys all bindings between a context and any user """ context_id = context.getIdentifier() self.client.activity.delete(context_id) #self.client.disconnect() def bind_user_to_conversation(self, conversation, username): """ Creates a binding between user exchanges and a conversation """ context_id = conversation.getIdentifier() self.client.conversations.bind_user(context_id, username) #self.client.disconnect() def unbind_user_from_conversation(self, conversation, username): """ Destroys a binding between user exchanges and a conversation """ context_id = conversation.getIdentifier() self.client.conversations.unbind_user(context_id, username) #self.client.disconnect() def unbind_conversation(self, conversation): """ Destroys all bindings between a conversation and any user """ context_id = conversation.getIdentifier() self.client.conversations.delete(context_id) #self.client.disconnect() def notify_context_activity(self, activity): """ Sends a Carrot (TM) notification of a new post on a context """ message = RabbitMessage() message.prepare(self.message_defaults) message.update({ "user": { 'username': self.request.actor['username'], 'displayname': self.request.actor['displayName'], }, "action": "add", "object": "activity", "data": { 'text': activity['object'].get('content', ''), 'activityid': str(activity['_id']) } }) self.client.send('activity', json.dumps(message.packed), activity['contexts'][0]['hash']) #self.client.disconnect() def notify_context_activity_comment(self, activity, comment): """ Sends a Carrot (TM) notification of a new post on a context """ message = RabbitMessage() message.prepare(self.message_defaults) message.update({ "user": { 'username': self.request.actor['username'], 'displayname': self.request.actor['displayName'], }, "action": "add", "object": "comment", "data": { 'text': comment['content'], 'activityid': str(activity['_id']), 'commentid': comment['id'] } }) self.client.send('activity', json.dumps(message.packed), activity['contexts'][0]['hash']) #self.client.disconnect() def add_conversation(self, conversation): """ Sends a Carrot (TM) notification of a new conversation creation """ conversation_id = conversation.getIdentifier() participants_usernames = [user['username'] for user in conversation['participants']] self.client.conversations.create(conversation_id, users=participants_usernames) # Send a conversation creation notification to rabbit message = RabbitMessage() message.prepare(self.message_defaults) message.update({ "user": { 'username': self.request.actor['username'], 'displayname': self.request.actor['displayName'], }, "action": "add", "object": "conversation", "data": {} }) self.client.send('conversations', json.dumps(message.packed), routing_key='{}.notifications'.format(conversation_id))
class ConsumerTests(MaxBunnyTestCase): def setUp(self): # Resets the global that holds the mocked stmp sent messages import maxbunny.tests maxbunny.tests.sent = [] self.log_patch = patch('maxbunny.consumer.BunnyConsumer.configure_logger', new=get_storing_logger) self.log_patch.start() self.smtp_patch = patch('smtplib.SMTP', new=MockSMTP) self.smtp_patch.start() self.server = RabbitClient(TEST_VHOST_URL) self.server.management.cleanup(delete_all=True) self.server.declare() self.server.ch.queue.declare( queue='tests', durable=True, auto_delete=False ) self.process = None def tearDown(self): self.log_patch.stop() self.smtp_patch.stop() self.server.get_all('tests') self.server.disconnect() try: self.process.terminate() except: pass # pragma: no cover def test_consumer_drop_no_uuid(self): """ Given a invalid non-json message When the consumer loop processes the message And the message triggers a Cancel exception Then the message is dropped And a warning is logged And the channel remains Open """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageCancel('Testing message drop')) self.process = ConsumerThread(consumer) self.server.send('', '', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped (NO_UUID), reason: Testing message drop') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drops_on_requeue_exception_without_uuid(self): """ Given a message without UUID field When the consumer loop processes the message And the message triggers a Requeue exception Then the message is requeued And a warning is logged And the channel remains Open And a mail notification is sent """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageRequeue('Test requeueing')) self.process = ConsumerThread(consumer) self.server.send('', '{}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped (NO_UUID), reason: Test requeueing') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drop_with_uuid(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a Cancel exception Then the message is dropped And a warning is logged And the channel remains Open And no mail notification is sent """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageCancel('Testing message drop', notify=False)) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 0) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped, reason: Testing message drop') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drop_with_notification(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a Cancel exception Then the message is dropped And a warning is logged And the channel remains Open And a mail notification is sent """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageCancel('Testing message drop', notify=True)) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped, reason: Testing message drop') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drop_no_recipient(self): """ Given a message with UUID field And a missing recipients smtp setting When the consumer loop processes the message And the message triggers a Cancel exception Then the message is dropped And a warning is logged And the channel remains Open And a mail notification is not sent """ runner = MockRunner('tests', 'maxbunny-norecipients.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageCancel('Testing message drop', notify=True)) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.6) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 0) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped, reason: Testing message drop') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drop_on_max_non_5xx_error(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a RequestError with status code different from 5xx Then the message is dropped And a warning is logged And the channel remains Open And a mail notification is sent """ from maxclient.client import RequestError runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, RequestError(401, 'Unauthorized')) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped, reason: Max server error: Unauthorized') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_drop_on_maxcarrot_exception(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a MaxCarrotParsingError Then the message is dropped And a warning is logged And the channel remains Open And a mail notification is sent """ from maxcarrot.message import MaxCarrotParsingError runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, MaxCarrotParsingError()) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message dropped, reason: MaxCarrot Parsing error') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_requeues_on_max_5xx_error(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a RequestError with status code different from 5xx Then the message is requeued And a warning is logged And the channel remains Open And a mail notification is sent """ from maxclient.client import RequestError runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, RequestError(500, 'Internal Server Error')) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message 0123456789 reueued, reason: Max server error: Internal Server Error') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 1) def test_consumer_requeues_on_requeue_exception(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers a Requeue exception Then the message is requeued And a warning is logged And the channel remains Open And a mail notification is sent And the id of the queued message is stored """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageRequeue('Test requeueing')) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message 0123456789 reueued, reason: Test requeueing') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 1) self.assertEqual(len(consumer.requeued), 1) self.assertIn('0123456789', consumer.requeued) def test_consumer_requeues_on_requeue_exception_unqueues_after(self): """ Given a message with UUID field When the consumer loop processes the message And the first time the message triggers a Requeue exception And the second time the message is succesfully processed Then the message is unqueued the second time And a warning is logged And the channel remains Open And a mail notification is sent And the id is removed from queued ones """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageRequeue('Test requeueing'), after=None) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message 0123456789 reueued, reason: Test requeueing') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) self.assertEqual(len(consumer.requeued), 0) def test_consumer_requeues_on_requeue_exception_drops_after(self): """ Given a message with UUID field When the consumer loop processes the message And the first time the message triggers a Requeue exception And the second time the message is triggers a Cancel exception Then the message is unqueued the second time And a warning is logged And the channel remains Open And a mail notification is sent And the id is removed from queued ones """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, BunnyMessageRequeue('Test requeueing'), after=BunnyMessageCancel('Testing message drop')) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 2) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 2) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message 0123456789 reueued, reason: Test requeueing') self.assertEqual(consumer.logger.warnings[1], 'Message dropped, reason: Testing message drop') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) self.assertEqual(len(consumer.requeued), 0) def test_consumer_requeues_on_unknown_exception(self): """ Given a message with UUID field When the consumer loop processes the message And the message triggers an unknown Exception Then the message is requeued And a warning is logged And the channel remains Open And a mail notification is sent """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner, Exception('Unknown exception')) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 1) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 1) self.assertTrue(self.process.isAlive()) self.assertEqual(consumer.logger.warnings[0], 'Message 0123456789 reueued, reason: Consumer failure: Unknown exception') self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 1) def test_consumer_stops_on_force_stop_connection(self): """ Given a running consumer When the rabbitmq connection is closed remotely Then the channel closes And a warning is logged """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner) self.process = ConsumerThread(consumer) self.process.start() sleep(0.2) # Leave a minimum life time to consumer self.server.management.force_close(consumer.remote(), 'Closed via ') sleep(1) # Leave a minimum time to consumer to stop self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.errors), 1) self.assertFalse(self.process.isAlive()) self.assertEqual(consumer.logger.errors[0], 'CONNECTION_FORCED - Closed via management plugin') sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0) def test_consumer_success(self): """ Given a message with UUID field When the consumer loop processes the message And the message suceeds Then the message is acks And nothing is logged And the channel remains Open And a no mail notification is sent """ runner = MockRunner('tests', 'maxbunny.ini', 'instances.ini') consumer = TestConsumer(runner) self.process = ConsumerThread(consumer) self.server.send('', '{"g": "0123456789"}', routing_key='tests') self.process.start() sleep(0.3) # give a minum time to mail to be sent from maxbunny.tests import sent # MUST import sent here to get current sent mails, self.assertEqual(len(sent), 0) self.assertEqual(len(consumer.logger.infos), 1) self.assertEqual(len(consumer.logger.warnings), 0) self.assertTrue(self.process.isAlive()) self.server.management.force_close(consumer.remote()) sleep(0.2) # Leave a minimum time to rabbitmq to release messages queued = self.server.get_all('tests') self.assertEqual(len(queued), 0)