Updated email notification

Change-Id: I7e8482b8c1f908ebe505a2f85eeca8397a12b9bf
This commit is contained in:
Steven Travis 2014-10-25 13:01:59 -06:00
parent 634965a6dc
commit ebc3c7fc89
5 changed files with 68 additions and 22 deletions

View File

@ -17,7 +17,8 @@ import json
class Notification(object):
"""An abstract base class used to define the notification interface and common functions
"""An abstract base class used to define the notification interface
and common functions
"""
__slots__ = (
'address',
@ -31,12 +32,14 @@ class Notification(object):
'src_offset',
'state',
'tenant_id',
'type'
'type',
'metrics'
)
def __init__(self, ntype, src_partition, src_offset, name, address, alarm):
"""Setup the notification object
The src_partition and src_offset allow the notification to be linked to the alarm that it came from
The src_partition and src_offset allow the notification
to be linked to the alarm that it came from.
ntype - The notification type
name - Name used in sending
address - to send the notification to
@ -55,8 +58,10 @@ class Notification(object):
self.message = alarm['stateChangeReason']
self.state = alarm['newState']
self.tenant_id = alarm['tenantId']
self.metrics = alarm['metrics']
self.notification_timestamp = None # to be updated on actual notification send time
# to be updated on actual notification send time
self.notification_timestamp = None
def __eq__(self, other):
if not isinstance(other, Notification):
@ -82,5 +87,6 @@ class Notification(object):
'state',
'tenant_id'
]
notification_data = {name: getattr(self, name) for name in notification_fields}
notification_data = {name: getattr(self, name)
for name in notification_fields}
return json.dumps(notification_data)

View File

@ -43,20 +43,57 @@ class NotificationProcessor(BaseProcessor):
self.monascastatsd = mstatsd.Client(name='monasca',
dimensions=BaseProcessor.dimensions)
def _create_msg(self, hostname, notification):
"""Create two kind of messages:
1. Notifications that include metrics with a hostname as a dimension. There may be more than one hostname.
We will only report the hostname if there is only one.
2. Notifications that do not include metrics and therefore no hostname. Example: API initiated changes.
* A third notification type which include metrics but do not include a hostname will
be treated as type #2.
"""
if len(hostname) == 1: # Type 1
msg = email.mime.text.MIMEText("On host \"%s\" %s\n\nAlarm \"%s\" transitioned to the %s state at %s UTC"
% (hostname[0],
notification.message.lower(),
notification.alarm_name,
notification.state,
time.asctime(time.gmtime(notification.alarm_timestamp))) +
"\nalarm_id: %s" % notification.alarm_id)
msg['Subject'] = "%s \"%s\" for Host: %s" % (notification.state, notification.alarm_name, hostname[0])
else: # Type 2
msg = email.mime.text.MIMEText("%s\n\nAlarm \"%s\" transitioned to the %s state at %s UTC\nAlarm_id: %s"
% (notification.message,
notification.alarm_name,
notification.state,
time.asctime(time.gmtime(notification.alarm_timestamp)),
notification.alarm_id))
msg['Subject'] = "%s \"%s\" " % (notification.state, notification.alarm_name)
msg['From'] = self.email_config['from_addr']
msg['To'] = notification.address
return msg
def _send_email(self, notification):
"""Send the notification via email
Returns the notification upon success, None upon failure
"""
msg = email.mime.text.MIMEText("%s\nAlarm %s transitioned to the %s state at %s UTC\nFull Data:\n%s"
% (notification.message,
notification.alarm_name,
notification.state,
time.asctime(time.gmtime(notification.alarm_timestamp)),
notification.to_json()))
msg['Subject'] = '%s: %s' % (notification.state, notification.alarm_name)
msg['From'] = self.email_config['from_addr']
msg['To'] = notification.address
# Get the "hostname" from the notification metrics if there is one
hostname = []
for metric in notification.metrics:
for dimension in metric['dimensions']:
if 'hostname' in dimension:
if not metric['dimensions']['%s' % dimension] in hostname[:]:
hostname.append(metric['dimensions']['%s' % dimension])
# Generate the message
msg = self._create_msg(hostname, notification)
# email the notification
try:
self.smtp.sendmail(self.email_config['from_addr'], notification.address, msg.as_string())
log.debug('Sent email to %s, notification %s' % (notification.address, notification.to_json()))

8
tests/test_alarm_processor.py Normal file → Executable file
View File

@ -89,7 +89,7 @@ class TestAlarmProcessor(unittest.TestCase):
"""Should cause the alarm_ttl to fire log a warning and push to finished queue."""
alarm_dict = {"tenantId": "0", "alarmDefinitionId": "0", "alarmId": "1", "alarmName": "test Alarm",
"oldState": "OK", "newState": "ALARM", "stateChangeReason": "I am alarming!",
"timestamp": 1375346830, "actionsEnabled": 1}
"timestamp": 1375346830, "actionsEnabled": 1, "metrics": "cpu_util"}
self.alarm_queue.put(self._create_raw_alarm(0, 2, alarm_dict))
finished, log_msg = self._run_alarm_processor(self.finished_queue, None)
@ -101,7 +101,7 @@ class TestAlarmProcessor(unittest.TestCase):
"""
alarm_dict = {"tenantId": "0", "alarmDefinitionId": "0", "alarmId": "1", "alarmName": "test Alarm",
"oldState": "OK", "newState": "ALARM", "stateChangeReason": "I am alarming!",
"timestamp": time.time(), "actionsEnabled": 1}
"timestamp": time.time(), "actionsEnabled": 1, "metrics": "cpu_util"}
self.alarm_queue.put(self._create_raw_alarm(0, 3, alarm_dict))
finished, log_msg = self._run_alarm_processor(self.finished_queue, None)
@ -112,7 +112,7 @@ class TestAlarmProcessor(unittest.TestCase):
"""
alarm_dict = {"tenantId": "0", "alarmDefinitionId": "0", "alarmId": "1", "alarmName": "test Alarm",
"oldState": "OK", "newState": "ALARM", "stateChangeReason": "I am alarming!",
"timestamp": time.time(), "actionsEnabled": 1}
"timestamp": time.time(), "actionsEnabled": 1, "metrics": "cpu_util"}
self.alarm_queue.put(self._create_raw_alarm(0, 4, alarm_dict))
sql_response = [['test notification', 'EMAIL', 'me@here.com']]
finished, log_msg = self._run_alarm_processor(self.notification_queue, sql_response)
@ -124,7 +124,7 @@ class TestAlarmProcessor(unittest.TestCase):
def test_two_valid_notifications(self):
alarm_dict = {"tenantId": "0", "alarmDefinitionId": "0", "alarmId": "1", "alarmName": "test Alarm",
"oldState": "OK", "newState": "ALARM", "stateChangeReason": "I am alarming!",
"timestamp": time.time(), "actionsEnabled": 1}
"timestamp": time.time(), "actionsEnabled": 1, "metrics": "cpu_util"}
self.alarm_queue.put(self._create_raw_alarm(0, 5, alarm_dict))
sql_response = [['test notification', 'EMAIL', 'me@here.com'], ['test notification2', 'EMAIL', 'me@here.com']]
finished, log_msg = self._run_alarm_processor(self.notification_queue, sql_response)

View File

@ -27,7 +27,8 @@ def test_json():
'timestamp': 'timestamp',
'stateChangeReason': 'stateChangeReason',
'newState': 'newState',
'tenantId': 'tenantId'}
'tenantId': 'tenantId',
'metrics': 'cpu_util'}
test_notification = notification.Notification('ntype', 'src_partition', 'src_offset', 'name', 'address', alarm)
expected_dict = {u'name': u'name',
@ -49,7 +50,8 @@ def test_equal():
'timestamp': 'timestamp',
'stateChangeReason': 'stateChangeReason',
'newState': 'newState',
'tenantId': 'tenantId'}
'tenantId': 'tenantId',
'metrics': 'cpu_util'}
test_notification = notification.Notification('ntype', 'src_partition', 'src_offset', 'name', 'address', alarm)
test_notification2 = notification.Notification('ntype', 'src_partition', 'src_offset', 'name', 'address', alarm)
@ -62,7 +64,8 @@ def test_unequal():
'timestamp': 'timestamp',
'stateChangeReason': 'stateChangeReason',
'newState': 'newState',
'tenantId': 'tenantId'}
'tenantId': 'tenantId',
'metrics': 'cpu_util'}
test_notification = notification.Notification('ntype', 'src_partition', 'src_offset', 'name', 'address', alarm)
test_notification2 = notification.Notification('ntype', 'src_partition', 'src_offset', 'name', 'address', alarm)
test_notification2.alarm_id = None

View File

@ -60,7 +60,7 @@ class TestStateTracker(unittest.TestCase):
"""Verify invalid notification type is rejected.
"""
alarm_dict = {"tenantId": "0", "alarmId": "0", "alarmName": "test Alarm", "oldState": "OK", "newState": "ALARM",
"stateChangeReason": "I am alarming!", "timestamp": time.time()}
"stateChangeReason": "I am alarming!", "timestamp": time.time(), "metrics": "cpu_util"}
invalid_notification = Notification('invalid', 0, 1, 'test notification', 'me@here.com', alarm_dict)
self.notification_queue.put([invalid_notification])