diff --git a/zaqar/notification/task/mailto.py b/zaqar/notification/task/mailto.py index 58eb077f..f13ec3af 100644 --- a/zaqar/notification/task/mailto.py +++ b/zaqar/notification/task/mailto.py @@ -36,6 +36,10 @@ class MailtoTask(object): for message in messages: p = subprocess.Popen(conf.notification.smtp_command.split(' '), stdin=subprocess.PIPE) + # NOTE(Eva-i): Unfortunately this will add 'queue_name' key to + # our original messages(dicts) which will be later consumed in + # the storage controller. It seems safe though. + message['queue_name'] = subscription['source'] msg = text.MIMEText(json.dumps(message)) msg["to"] = subscriber.path msg["from"] = subscription['options'].get('from', '') diff --git a/zaqar/notification/task/webhook.py b/zaqar/notification/task/webhook.py index 9f65d1d4..e2e0e7da 100644 --- a/zaqar/notification/task/webhook.py +++ b/zaqar/notification/task/webhook.py @@ -27,6 +27,10 @@ class WebhookTask(object): def execute(self, subscription, messages, **kwargs): try: for msg in messages: + # NOTE(Eva-i): Unfortunately this will add 'queue_name' key to + # our original messages(dicts) which will be later consumed in + # the storage controller. It seems safe though. + msg['queue_name'] = subscription['source'] requests.post(subscription['subscriber'], data=json.dumps(msg), headers={'Content-Type': 'application/json'}) diff --git a/zaqar/tests/unit/notification/test_notifier.py b/zaqar/tests/unit/notification/test_notifier.py index 3178ed2f..495fca2e 100644 --- a/zaqar/tests/unit/notification/test_notifier.py +++ b/zaqar/tests/unit/notification/test_notifier.py @@ -37,11 +37,28 @@ class NotifierTest(testing.TestBase): "total_bytes": "99614720"} } ] + # NOTE(Eva-i): NotifiedDriver adds "queue_name" key to each + # message (dictionary), so final notifications look like this + self.notifications = [{"ttl": 300, + "body": {"event": "BackupStarted", + "backup_id": + "c378813c-3f0b-11e2-ad92"}, + "queue_name": "fake_queue" + }, + {"body": {"event": "BackupProgress", + "current_bytes": "0", + "total_bytes": "99614720"}, + "queue_name": "fake_queue" + } + ] def test_webhook(self): - subscription = [{'subscriber': 'http://trigger_me'}, - {'subscriber': 'http://call_me'}, - {'subscriber': 'http://ping_me'}] + subscription = [{'subscriber': 'http://trigger_me', + 'source': 'fake_queue'}, + {'subscriber': 'http://call_me', + 'source': 'fake_queue'}, + {'subscriber': 'http://ping_me', + 'source': 'fake_queue'}] ctlr = mock.MagicMock() ctlr.list = mock.Mock(return_value=iter([subscription])) driver = notifier.NotifierDriver(subscription_controller=ctlr) @@ -50,24 +67,33 @@ class NotifierTest(testing.TestBase): driver.post('fake_queue', self.messages, self.client_id, self.project) driver.executor.shutdown() + # Let's deserialize "data" from JSON string to dict in each mock + # call, so we can do dict comparisons. JSON string comparisons + # often fail, because dict keys can be serialized in different + # order inside the string. + for call in mock_post.call_args_list: + call[1]['data'] = json.loads(call[1]['data']) + # These are not real calls. In real calls each "data" argument is + # serialized by json.dumps. But we made a substitution before, + # so it will work. mock_post.assert_has_calls([ mock.call(subscription[0]['subscriber'], - data=json.dumps(self.messages[0]), + data=self.notifications[0], headers=headers), mock.call(subscription[1]['subscriber'], - data=json.dumps(self.messages[0]), + data=self.notifications[0], headers=headers), mock.call(subscription[2]['subscriber'], - data=json.dumps(self.messages[0]), + data=self.notifications[0], headers=headers), mock.call(subscription[0]['subscriber'], - data=json.dumps(self.messages[1]), + data=self.notifications[1], headers=headers), mock.call(subscription[1]['subscriber'], - data=json.dumps(self.messages[1]), + data=self.notifications[1], headers=headers), mock.call(subscription[2]['subscriber'], - data=json.dumps(self.messages[1]), + data=self.notifications[1], headers=headers), ], any_order=True) self.assertEqual(6, len(mock_post.mock_calls)) @@ -75,9 +101,11 @@ class NotifierTest(testing.TestBase): @mock.patch('subprocess.Popen') def test_mailto(self, mock_popen): subscription = [{'subscriber': 'mailto:aaa@example.com', + 'source': 'fake_queue', 'options': {'subject': 'Hello', 'from': 'zaqar@example.com'}}, {'subscriber': 'mailto:bbb@example.com', + 'source': 'fake_queue', 'options': {'subject': 'Hello', 'from': 'zaqar@example.com'}}] ctlr = mock.MagicMock() @@ -87,19 +115,18 @@ class NotifierTest(testing.TestBase): msg = ('Content-Type: text/plain; charset="us-ascii"\n' 'MIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nto:' ' %(to)s\nfrom: %(from)s\nsubject: %(subject)s\n\n%(body)s') - mail1 = msg % {'to': subscription[0]['subscriber'][7:], 'from': 'zaqar@example.com', 'subject': 'Hello', - 'body': json.dumps(self.messages[0])} + 'body': json.dumps(self.notifications[0])} mail2 = msg % {'to': subscription[0]['subscriber'][7:], 'from': 'zaqar@example.com', 'subject': 'Hello', - 'body': json.dumps(self.messages[1])} + 'body': json.dumps(self.notifications[1])} mail3 = msg % {'to': subscription[1]['subscriber'][7:], 'from': 'zaqar@example.com', 'subject': 'Hello', - 'body': json.dumps(self.messages[0])} + 'body': json.dumps(self.notifications[0])} mail4 = msg % {'to': subscription[1]['subscriber'][7:], 'from': 'zaqar@example.com', 'subject': 'Hello', - 'body': json.dumps(self.messages[1])} + 'body': json.dumps(self.notifications[1])} def _communicate(msg): called.add(msg) @@ -112,7 +139,23 @@ class NotifierTest(testing.TestBase): driver.executor.shutdown() self.assertEqual(4, len(called)) - self.assertEqual({mail1, mail2, mail3, mail4}, called) + # Let's deserialize "body" from JSON string to dict and then serialize + # it back to JSON, but sorted, allowing us make comparisons. + mails = {mail1, mail2, mail3, mail4} + mail_options = [] + mail_bodies = [] + for mail in mails: + options, body = mail.split('\n\n') + mail_options.append(options) + mail_bodies.append(json.dumps(json.loads(body), sort_keys=True)) + called_options = [] + called_bodies = [] + for call in called: + options, body = call.split('\n\n') + called_options.append(options) + called_bodies.append(json.dumps(json.loads(body), sort_keys=True)) + self.assertEqual(sorted(mail_options), sorted(called_options)) + self.assertEqual(sorted(mail_bodies), sorted(called_bodies)) def test_post_no_subscriber(self): ctlr = mock.MagicMock() @@ -123,3 +166,17 @@ class NotifierTest(testing.TestBase): self.project) driver.executor.shutdown() self.assertEqual(0, mock_post.call_count) + + def test_proper_notification_data(self): + subscription = [{'subscriber': 'http://trigger_me', + 'source': 'fake_queue'}] + ctlr = mock.MagicMock() + ctlr.list = mock.Mock(return_value=iter([subscription])) + driver = notifier.NotifierDriver(subscription_controller=ctlr) + with mock.patch('requests.post') as mock_post: + driver.post('fake_queue', self.messages, self.client_id, + self.project) + driver.executor.shutdown() + self.assertEqual(2, mock_post.call_count) + self.assertEqual(self.notifications[1], + json.loads(mock_post.call_args[1]['data']))