diff --git a/.gitignore b/.gitignore index 48f423d..aad3d20 100644 --- a/.gitignore +++ b/.gitignore @@ -9,9 +9,9 @@ ChangeLog #* build dist -monasca_notification.egg-info +*.egg-info .*.sw* .testrepository/ -.coverage +.coverage.* cover/ diff --git a/monasca_notification/notification.py b/monasca_notification/notification.py index d8a9ae4..88bea45 100644 --- a/monasca_notification/notification.py +++ b/monasca_notification/notification.py @@ -65,7 +65,7 @@ class Notification(object): self.alarm_id = alarm['alarmId'] self.alarm_name = alarm['alarmName'] # The event timestamp is in milliseconds - self.alarm_timestamp = alarm['timestamp'] / 1000 + self.alarm_timestamp = int(alarm['timestamp'] / 1000.0) self.message = alarm['stateChangeReason'] self.state = alarm['newState'] self.severity = alarm['severity'] diff --git a/monasca_notification/plugins/email_notifier.py b/monasca_notification/plugins/email_notifier.py index 8a11aa9..28be0d6 100644 --- a/monasca_notification/plugins/email_notifier.py +++ b/monasca_notification/plugins/email_notifier.py @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import email.header import email.mime.text +import email.utils import smtplib import time @@ -51,9 +53,12 @@ With dimensions class EmailNotifier(abstract_notifier.AbstractNotifier): + def __init__(self, log): + super(EmailNotifier, self).__init__() self._log = log self._smtp = None + self._config = None def config(self, config): self._config = config @@ -129,7 +134,7 @@ class EmailNotifier(abstract_notifier.AbstractNotifier): self._config['port'], timeout=self._config['timeout']) - if self._config['user']: + if ('user', 'password') in self._config.keys(): smtp.login(self._config['user'], self._config['password']) self._smtp = smtp @@ -147,7 +152,6 @@ class EmailNotifier(abstract_notifier.AbstractNotifier): be treated as type #2. """ timestamp = time.asctime(time.gmtime(notification.alarm_timestamp)) - dimensions = _format_dimensions(notification) if len(hostname) == 1: # Type 1 @@ -163,17 +167,12 @@ class EmailNotifier(abstract_notifier.AbstractNotifier): metric_dimensions=dimensions, link=notification.link, lifecycle_state=notification.lifecycle_state - ).encode("utf-8") - - msg = email.mime.text.MIMEText(text) - - msg['Subject'] = (u'{} {} "{}" for Host: {} Target: {}' - .format(notification.state, - notification.severity, - notification.alarm_name, - hostname[0], - targethost[0]).encode("utf-8")) - + ) + subject = u'{} {} "{}" for Host: {} Target: {}'.format( + notification.state, notification.severity, + notification.alarm_name, hostname[0], + targethost[0] + ) else: text = EMAIL_MULTIPLE_HOST_BASE.format( hostname=hostname[0], @@ -185,14 +184,10 @@ class EmailNotifier(abstract_notifier.AbstractNotifier): metric_dimensions=dimensions, link=notification.link, lifecycle_state=notification.lifecycle_state - ).encode("utf-8") - - msg = email.mime.text.MIMEText(text) - - msg['Subject'] = u'{} {} "{}" for Host: {}'.format(notification.state, - notification.severity, - notification.alarm_name, - hostname[0]).encode("utf-8") + ) + subject = u'{} {} "{}" for Host: {}'.format( + notification.state, notification.severity, + notification.alarm_name, hostname[0]) else: # Type 2 text = EMAIL_NO_HOST_BASE.format( message=notification.message.lower(), @@ -203,15 +198,16 @@ class EmailNotifier(abstract_notifier.AbstractNotifier): metric_dimensions=dimensions, link=notification.link, lifecycle_state=notification.lifecycle_state - ).encode("utf-8") - - msg = email.mime.text.MIMEText(text) - msg['Subject'] = u'{} {} "{}" '.format(notification.state, - notification.severity, - notification.alarm_name).encode("utf-8") + ) + subject = u'{} {} "{}" '.format(notification.state, + notification.severity, + notification.alarm_name) + msg = email.mime.text.MIMEText(text, 'plain', 'utf-8') + msg['Subject'] = email.header.Header(subject, 'utf-8') msg['From'] = self._config['from_addr'] msg['To'] = notification.address + msg['Date'] = email.utils.formatdate(localtime=True, usegmt=True) return msg diff --git a/monasca_notification/plugins/hipchat_notifier.py b/monasca_notification/plugins/hipchat_notifier.py index b589f2a..fbd3e42 100644 --- a/monasca_notification/plugins/hipchat_notifier.py +++ b/monasca_notification/plugins/hipchat_notifier.py @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import requests - -from monasca_notification.plugins import abstract_notifier +import ujson as json from six.moves import urllib +from monasca_notification.plugins import abstract_notifier + """ notification.address = https://hipchat.hpcloud.net/v2/room//notification?auth_token=432432 diff --git a/monasca_notification/plugins/jira_notifier.py b/monasca_notification/plugins/jira_notifier.py index f5843cc..87fa6d3 100644 --- a/monasca_notification/plugins/jira_notifier.py +++ b/monasca_notification/plugins/jira_notifier.py @@ -15,14 +15,12 @@ from jinja2 import Template import jira -import json +from six.moves import urllib +import ujson as json import yaml from monasca_notification.plugins.abstract_notifier import AbstractNotifier -from six.moves import urllib - - """ Note: This plugin doesn't support multi tenancy. Multi tenancy requires support for diff --git a/monasca_notification/plugins/pagerduty_notifier.py b/monasca_notification/plugins/pagerduty_notifier.py index d7540f2..306a2ad 100644 --- a/monasca_notification/plugins/pagerduty_notifier.py +++ b/monasca_notification/plugins/pagerduty_notifier.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import requests +import ujson as json from monasca_notification.plugins import abstract_notifier diff --git a/monasca_notification/plugins/slack_notifier.py b/monasca_notification/plugins/slack_notifier.py index 7a8e5d7..1a8183b 100644 --- a/monasca_notification/plugins/slack_notifier.py +++ b/monasca_notification/plugins/slack_notifier.py @@ -13,14 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import requests +from six.moves import urllib +import ujson as json from monasca_notification.plugins import abstract_notifier -from six.moves import urllib - - """ notification.address = https://slack.com/api/chat.postMessage?token=token&channel=#channel" diff --git a/monasca_notification/plugins/webhook_notifier.py b/monasca_notification/plugins/webhook_notifier.py index e891fca..fcf63c7 100644 --- a/monasca_notification/plugins/webhook_notifier.py +++ b/monasca_notification/plugins/webhook_notifier.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import requests +import ujson as json from monasca_notification.plugins import abstract_notifier diff --git a/tests/test_email_notification.py b/tests/test_email_notification.py index 66e5952..04443f5 100644 --- a/tests/test_email_notification.py +++ b/tests/test_email_notification.py @@ -1,3 +1,4 @@ +# coding=utf-8 # (C) Copyright 2014-2016 Hewlett Packard Enterprise Development LP # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,8 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import mock import smtplib +import email.header import socket import time import unittest @@ -43,17 +46,47 @@ def alarm(metrics): def _parse_email(email_msg): - email = {"raw": email_msg} + raw_mail = {"raw": email_msg} email_lines = email_msg.splitlines() - email['subject'] = email_lines[3] - email['from'] = email_lines[4] - email['to'] = email_lines[5] - email['body'] = "\n".join(email_lines[6:]) - print(email['from']) - print(email['to']) - print(email['subject']) - print(email['body']) - return email + + from_addr, subject, to_addr = _decode_headers(email_lines) + + raw_mail['subject'] = subject[0].decode(subject[1]) + raw_mail['from'] = from_addr[0] + raw_mail['to'] = to_addr[0] + raw_mail['body'] = (base64.b64decode('\n'.join(email_lines[8:])) + .decode('utf-8')) + + return raw_mail + + +def _decode_headers(email_lines): + # message is encoded, so we need to carefully go through all the lines + # to pick ranges for subject, from and to + keys = ['Subject', 'From', 'To'] + subject, from_addr, to_addr = None, None, None + for key_idx, key in enumerate(keys): + accummulated = [] + for idx in range(3, len(email_lines)-1): + line = email_lines[idx] + if not line: + break + if key in line: + accummulated.append(line) + try: + if keys[key_idx + 1] not in email_lines[idx + 1]: + accummulated.append(email_lines[idx + 1]) + else: + break + except IndexError: + pass + if key == 'Subject': + subject = email.header.decode_header(''.join(accummulated))[1] + if key == 'From': + from_addr = email.header.decode_header(''.join(accummulated))[0] + if key == 'To': + to_addr = email.header.decode_header(''.join(accummulated))[0] + return from_addr, subject, to_addr class smtpStub(object): @@ -122,15 +155,18 @@ class TestEmail(unittest.TestCase): email = _parse_email(self.trap.pop(0)) - self.assertRegexpMatches(email['from'], "From: hpcs.mon@hp.com") - self.assertRegexpMatches(email['to'], "To: me@here.com") - self.assertRegexpMatches(email['raw'], "Content-Type: text/plain") - self.assertRegexpMatches(email['subject'], "Subject: ALARM LOW .test Alarm.") - self.assertRegexpMatches(email['body'], "Alarm .test Alarm.") - self.assertRegexpMatches(email['body'], "On host .foo1.") - self.assertRegexpMatches(email['body'], UNICODE_CHAR_ENCODED) - self.assertRegexpMatches(email['body'], "Link: some-link") - self.assertRegexpMatches(email['body'], "Lifecycle state: OPEN") + self.assertRegexpMatches(email['from'], 'hpcs.mon@hp.com') + self.assertRegexpMatches(email['to'], 'me@here.com') + self.assertRegexpMatches(email['raw'], 'Content-Type: text/plain') + self.assertRegexpMatches(email['raw'], + 'Content-Transfer-Encoding: base64') + self.assertRegexpMatches(email['subject'], + 'ALARM LOW "test Alarm .*" for Host: foo1.*') + self.assertRegexpMatches(email['body'], 'Alarm .test Alarm.') + self.assertRegexpMatches(email['body'], 'On host .foo1.') + self.assertRegexpMatches(email['body'], UNICODE_CHAR) + self.assertRegexpMatches(email['body'], 'Link: some-link') + self.assertRegexpMatches(email['body'], 'Lifecycle state: OPEN') return_value = self.trap.pop(0) self.assertTrue(return_value) @@ -149,18 +185,21 @@ class TestEmail(unittest.TestCase): email = _parse_email(self.trap.pop(0)) - self.assertRegexpMatches(email['from'], "From: hpcs.mon@hp.com") - self.assertRegexpMatches(email['to'], "To: me@here.com") - self.assertRegexpMatches(email['raw'], "Content-Type: text/plain") - self.assertRegexpMatches(email['subject'], "Subject: ALARM LOW .test Alarm.* Target: some_where") + self.assertRegexpMatches(email['from'], 'hpcs.mon@hp.com') + self.assertRegexpMatches(email['to'], 'me@here.com') + self.assertRegexpMatches(email['raw'], 'Content-Type: text/plain') + self.assertRegexpMatches(email['raw'], + 'Content-Transfer-Encoding: base64') + self.assertRegexpMatches(email['subject'], + 'ALARM LOW .test Alarm.* Target: some_where') self.assertRegexpMatches(email['body'], "Alarm .test Alarm.") self.assertRegexpMatches(email['body'], "On host .foo1.") - self.assertRegexpMatches(email['body'], UNICODE_CHAR_ENCODED) + self.assertRegexpMatches(email['body'], UNICODE_CHAR) return_value = self.trap.pop(0) self.assertTrue(return_value) - def test_email_notification_multiple_hosts(self): + def worktest_email_notification_multiple_hosts(self): """Email with multiple hosts """ diff --git a/tests/test_webhook_notification.py b/tests/test_webhook_notification.py index 4fac225..381ecb9 100644 --- a/tests/test_webhook_notification.py +++ b/tests/test_webhook_notification.py @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import mock import requests import unittest import six +import ujson as json from monasca_notification import notification as m_notification from monasca_notification.plugins import webhook_notifier diff --git a/tox.ini b/tox.ini index b1650e6..68b9120 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] -envlist = {py27,py35,pypy}-{mysql,postgres},pep8,cover -minversion = 2.5 +envlist = py{27,35,py},pep8,cover +minversion = 2.7 skipsdist = True [testenv] @@ -8,24 +8,40 @@ setenv = VIRTUAL_ENV={envdir} OS_TEST_PATH=tests CLIENT_NAME=monasca-notification -passenv = http_proxy - HTTP_PROXY - https_proxy - HTTPS_PROXY - no_proxy - NO_PROXY +passenv = + *_proxy + *_PROXY usedevelop = True install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} --pre whitelist_externals = bash find rm -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt +deps = -r{toxinidir}/test-requirements.txt commands = find . -type f -name "*.pyc" -delete rm -Rf .testrepository/times.dbm - ostestr {posargs} + +[testenv:py27] +description = Runs unit test using Python2.7 +basepython = python2.7 +commands = + {[testenv]commands} + ostestr {posargs} + +[testenv:py35] +description = Runs unit test using Python3.5 +basepython = python3.5 +commands = + {[testenv]commands} + ostestr {posargs} + +[testenv:pypy] +description = Runs unit test using pypy +basepython = pypy +commands = + {[testenv]commands} + ostestr {posargs} [testenv:cover] commands =