diff --git a/ceilometer/api/controllers/v2.py b/ceilometer/api/controllers/v2.py index 3fb7aef86..c7fbb2110 100644 --- a/ceilometer/api/controllers/v2.py +++ b/ceilometer/api/controllers/v2.py @@ -41,6 +41,7 @@ from wsme import types as wtypes from ceilometer.openstack.common import context from ceilometer.openstack.common.gettextutils import _ from ceilometer.openstack.common import log +from ceilometer.openstack.common.notifier import api as notify from ceilometer.openstack.common import strutils from ceilometer.openstack.common import timeutils from ceilometer import sample @@ -449,6 +450,13 @@ def _make_link(rel_name, url, type, type_arg, query=None): rel=rel_name) +def _send_notification(event, payload): + notification = event.replace(" ", "_") + notification = "alarm.%s" % notification + notify.notify(None, notify.publisher_id("ceilometer.api"), + notification, notify.INFO, payload) + + class Sample(_Base): """A single measurement for a given meter and resource. """ @@ -1228,22 +1236,29 @@ class AlarmController(rest.RestController): if not cfg.CONF.alarm.record_history: return type = type or storage.models.AlarmChange.RULE_CHANGE - detail = json.dumps(utils.stringify_timestamps(data)) + scrubbed_data = utils.stringify_timestamps(data) + detail = json.dumps(scrubbed_data) user_id = pecan.request.headers.get('X-User-Id') project_id = pecan.request.headers.get('X-Project-Id') on_behalf_of = on_behalf_of or project_id + payload = dict(event_id=str(uuid.uuid4()), + alarm_id=self._id, + type=type, + detail=detail, + user_id=user_id, + project_id=project_id, + on_behalf_of=on_behalf_of, + timestamp=now) + try: - self.conn.record_alarm_change(dict(event_id=str(uuid.uuid4()), - alarm_id=self._id, - type=type, - detail=detail, - user_id=user_id, - project_id=project_id, - on_behalf_of=on_behalf_of, - timestamp=now)) + self.conn.record_alarm_change(payload) except NotImplementedError: pass + # Revert to the pre-json'ed details ... + payload['detail'] = scrubbed_data + _send_notification(type, payload) + @wsme_pecan.wsexpose(Alarm, wtypes.text) def get(self): """Return this alarm.""" @@ -1356,21 +1371,28 @@ class AlarmsController(rest.RestController): if not cfg.CONF.alarm.record_history: return type = storage.models.AlarmChange.CREATION - detail = json.dumps(utils.stringify_timestamps(data)) + scrubbed_data = utils.stringify_timestamps(data) + detail = json.dumps(scrubbed_data) user_id = pecan.request.headers.get('X-User-Id') project_id = pecan.request.headers.get('X-Project-Id') + payload = dict(event_id=str(uuid.uuid4()), + alarm_id=alarm_id, + type=type, + detail=detail, + user_id=user_id, + project_id=project_id, + on_behalf_of=project_id, + timestamp=now) + try: - conn.record_alarm_change(dict(event_id=str(uuid.uuid4()), - alarm_id=alarm_id, - type=type, - detail=detail, - user_id=user_id, - project_id=project_id, - on_behalf_of=project_id, - timestamp=now)) + conn.record_alarm_change(payload) except NotImplementedError: pass + # Revert to the pre-json'ed details ... + payload['detail'] = scrubbed_data + _send_notification(type, payload) + @wsme.validate(Alarm) @wsme_pecan.wsexpose(Alarm, body=Alarm, status_code=201) def post(self, data): diff --git a/tests/api/v2/test_alarm_scenarios.py b/tests/api/v2/test_alarm_scenarios.py index abd8c3bce..c8a558a8d 100644 --- a/tests/api/v2/test_alarm_scenarios.py +++ b/tests/api/v2/test_alarm_scenarios.py @@ -23,6 +23,7 @@ import datetime import json as jsonutils import logging +import mock import uuid import testscenarios @@ -1106,3 +1107,52 @@ class TestAlarms(FunctionalTest, # continued existence of the alarm itself history = self._get_alarm_history(dict(alarm_id='foobar')) self.assertEqual([], history) + + def test_alarms_sends_notification(self): + # Hit the AlarmsController ... + json = { + 'name': 'sent_notification', + 'type': 'threshold', + 'threshold_rule': { + 'meter_name': 'ameter', + 'comparison_operator': 'gt', + 'threshold': 2.0, + 'statistic': 'avg', + } + + } + with mock.patch('ceilometer.openstack.common.notifier.api.notify') \ + as notifier: + self.post_json('/alarms', params=json, headers=self.auth_headers) + + calls = notifier.call_args_list + self.assertEqual(len(calls), 1) + args, _ = calls[0] + context, publisher, event_type, priority, payload = args + self.assertTrue(publisher.startswith('ceilometer.api')) + self.assertEqual(event_type, 'alarm.creation') + self.assertEqual(priority, 'INFO') + self.assertEqual(payload['detail']['name'], 'sent_notification') + self.assertTrue(set(['alarm_id', 'detail', 'event_id', 'on_behalf_of', + 'project_id', 'timestamp', 'type', + 'user_id']).issubset(payload.keys())) + + def test_alarm_sends_notification(self): + # Hit the AlarmController (with alarm_id supplied) ... + data = self.get_json('/alarms') + with mock.patch('ceilometer.openstack.common.notifier.api.notify') \ + as notifier: + self.delete('/alarms/%s' % data[0]['alarm_id'], + headers=self.auth_headers, status=204) + + calls = notifier.call_args_list + self.assertEqual(len(calls), 1) + args, _ = calls[0] + context, publisher, event_type, priority, payload = args + self.assertTrue(publisher.startswith('ceilometer.api')) + self.assertEqual(event_type, 'alarm.deletion') + self.assertEqual(priority, 'INFO') + self.assertEqual(payload['detail']['name'], 'name1') + self.assertTrue(set(['alarm_id', 'detail', 'event_id', 'on_behalf_of', + 'project_id', 'timestamp', 'type', + 'user_id']).issubset(payload.keys()))