Merge "Include previous state in alarm notification"
This commit is contained in:
commit
9b7b8627c9
@ -25,11 +25,12 @@ class AlarmNotifier(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
@abc.abstractmethod
|
||||
def notify(self, action, alarm, state, reason):
|
||||
def notify(self, action, alarm, previous, current, reason):
|
||||
"""Notify that an alarm has been triggered.
|
||||
|
||||
:param action: The action that is being attended, as a parsed URL.
|
||||
:param alarm: The triggered alarm.
|
||||
:param state: The state the alarm is now in.
|
||||
:param previous: The previous state of the alarm.
|
||||
:param current: The current state of the alarm.
|
||||
:param reason: The reason the alarm changed its state.
|
||||
"""
|
||||
|
@ -27,6 +27,6 @@ class LogAlarmNotifier(notifier.AlarmNotifier):
|
||||
"Log alarm notifier."""
|
||||
|
||||
@staticmethod
|
||||
def notify(action, alarm, state, reason):
|
||||
LOG.info("Notifying alarm %s in state %s with action %s because %s",
|
||||
alarm, state, action, reason)
|
||||
def notify(action, alarm, previous, current, reason):
|
||||
LOG.info("Notifying alarm %s from %s to %s with action %s because %s",
|
||||
alarm, previous, current, action, reason)
|
||||
|
@ -53,10 +53,11 @@ class RestAlarmNotifier(notifier.AlarmNotifier):
|
||||
"""Rest alarm notifier."""
|
||||
|
||||
@staticmethod
|
||||
def notify(action, alarm, state, reason):
|
||||
LOG.info("Notifying alarm %s in state %s with action %s because %s",
|
||||
alarm, state, action, reason)
|
||||
body = {'alarm': alarm, 'state': state, 'reason': reason}
|
||||
def notify(action, alarm, previous, current, reason):
|
||||
LOG.info("Notifying alarm %s from %s to %s with action %s because %s",
|
||||
alarm, previous, current, action, reason)
|
||||
body = {'alarm': alarm, 'previous': previous,
|
||||
'current': current, 'reason': reason}
|
||||
kwargs = {'data': jsonutils.dumps(body)}
|
||||
|
||||
if action.scheme == 'https':
|
||||
|
@ -26,5 +26,5 @@ class TestAlarmNotifier(notifier.AlarmNotifier):
|
||||
def __init__(self):
|
||||
self.notifications = []
|
||||
|
||||
def notify(self, action, alarm, state, reason):
|
||||
self.notifications.append((action, alarm, state, reason))
|
||||
def notify(self, action, alarm, previous, current, reason):
|
||||
self.notifications.append((action, alarm, previous, current, reason))
|
||||
|
@ -37,11 +37,12 @@ class RPCAlarmNotifier(rpc_proxy.RpcProxy):
|
||||
default_version='1.0',
|
||||
topic=cfg.CONF.alarm.notifier_rpc_topic)
|
||||
|
||||
def notify(self, alarm, state, reason):
|
||||
def notify(self, alarm, previous, reason):
|
||||
actions = getattr(alarm, Alarm.ALARM_ACTIONS_MAP[alarm.state])
|
||||
msg = self.make_msg('notify_alarm', data={
|
||||
'actions': actions,
|
||||
'alarm': alarm.alarm_id,
|
||||
'state': state,
|
||||
'previous': previous,
|
||||
'current': alarm.state,
|
||||
'reason': reason})
|
||||
self.cast(context.get_admin_context(), msg)
|
||||
|
@ -128,7 +128,7 @@ class AlarmNotifierService(rpc_service.Service):
|
||||
'ceilometer.alarm.' + cfg.CONF.alarm.notifier_rpc_topic,
|
||||
)
|
||||
|
||||
def _handle_action(self, action, alarm, state, reason):
|
||||
def _handle_action(self, action, alarm, previous, current, reason):
|
||||
try:
|
||||
action = network_utils.urlsplit(action)
|
||||
except Exception:
|
||||
@ -150,7 +150,7 @@ class AlarmNotifierService(rpc_service.Service):
|
||||
try:
|
||||
LOG.debug("Notifying alarm %s with action %s",
|
||||
alarm, action)
|
||||
notifier.notify(action, alarm, state, reason)
|
||||
notifier.notify(action, alarm, previous, current, reason)
|
||||
except Exception:
|
||||
LOG.exception(_("Unable to notify alarm %s"), alarm)
|
||||
return
|
||||
@ -162,7 +162,8 @@ class AlarmNotifierService(rpc_service.Service):
|
||||
- actions, the URL of the action to run;
|
||||
this is a mapped to extensions automatically
|
||||
- alarm, the alarm that has been triggered
|
||||
- state, the new state the alarm transitionned to
|
||||
- previous, the previous state of the alarm
|
||||
- current, the new state the alarm has transitioned to
|
||||
- reason, the reason the alarm changed its state
|
||||
|
||||
:param context: Request context.
|
||||
@ -176,7 +177,8 @@ class AlarmNotifierService(rpc_service.Service):
|
||||
for action in actions:
|
||||
self._handle_action(action,
|
||||
data.get('alarm'),
|
||||
data.get('state'),
|
||||
data.get('previous'),
|
||||
data.get('current'),
|
||||
data.get('reason'))
|
||||
|
||||
|
||||
|
@ -127,7 +127,8 @@ class Evaluator(object):
|
||||
def _refresh(self, alarm, state, reason):
|
||||
"""Refresh alarm state."""
|
||||
try:
|
||||
if alarm.state != state:
|
||||
previous = alarm.state
|
||||
if previous != state:
|
||||
LOG.info(_('alarm %(id)s transitioning to %(state)s because '
|
||||
'%(reason)s') % {'id': alarm.alarm_id,
|
||||
'state': state,
|
||||
@ -136,7 +137,7 @@ class Evaluator(object):
|
||||
self._client.alarms.update(alarm.alarm_id, **dict(state=state))
|
||||
alarm.state = state
|
||||
if self.notifier:
|
||||
self.notifier.notify(alarm, state, reason)
|
||||
self.notifier.notify(alarm, previous, reason)
|
||||
except Exception:
|
||||
# retry will occur naturally on the next evaluation
|
||||
# cycle (unless alarm state reverts in the meantime)
|
||||
|
@ -26,6 +26,15 @@ from ceilometer.openstack.common import context
|
||||
from ceilometer.tests import base
|
||||
|
||||
|
||||
DATA_JSON = ('{"current": "ALARM", "alarm": "foobar",'
|
||||
' "reason": "what ?", "previous": "OK"}')
|
||||
NOTIFICATION = dict(alarm='foobar',
|
||||
condition=dict(threshold=42),
|
||||
reason='what ?',
|
||||
previous='OK',
|
||||
current='ALARM')
|
||||
|
||||
|
||||
class TestAlarmNotifier(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -44,7 +53,8 @@ class TestAlarmNotifier(base.TestCase):
|
||||
data = {
|
||||
'actions': ['test://'],
|
||||
'alarm': 'foobar',
|
||||
'state': 'ALARM',
|
||||
'previous': 'OK',
|
||||
'current': 'ALARM',
|
||||
'reason': 'Everything is on fire',
|
||||
}
|
||||
self.service.notify_alarm(context.get_admin_context(), data)
|
||||
@ -53,7 +63,8 @@ class TestAlarmNotifier(base.TestCase):
|
||||
self.assertEqual(notifications[0], (
|
||||
urlparse.urlsplit(data['actions'][0]),
|
||||
data['alarm'],
|
||||
data['state'],
|
||||
data['previous'],
|
||||
data['current'],
|
||||
data['reason']))
|
||||
|
||||
def test_notify_alarm_no_action(self):
|
||||
@ -71,26 +82,25 @@ class TestAlarmNotifier(base.TestCase):
|
||||
def _fake_spawn_n(func, *args, **kwargs):
|
||||
func(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _notification(action):
|
||||
notification = {}
|
||||
notification.update(NOTIFICATION)
|
||||
notification['actions'] = [action]
|
||||
return notification
|
||||
|
||||
def test_notify_alarm_rest_action_ok(self):
|
||||
action = 'http://host/action'
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json)
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON)
|
||||
|
||||
def test_notify_alarm_rest_action_with_ssl_client_cert(self):
|
||||
action = 'https://host/action'
|
||||
certificate = "/etc/ssl/cert/whatever.pem"
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
cfg.CONF.set_override("rest_notifier_certificate_file", certificate,
|
||||
group='alarm')
|
||||
@ -98,21 +108,14 @@ class TestAlarmNotifier(base.TestCase):
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json,
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON,
|
||||
cert=certificate, verify=True)
|
||||
|
||||
def test_notify_alarm_rest_action_with_ssl_client_cert_and_key(self):
|
||||
action = 'https://host/action'
|
||||
certificate = "/etc/ssl/cert/whatever.pem"
|
||||
key = "/etc/ssl/cert/whatever.key"
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
cfg.CONF.set_override("rest_notifier_certificate_file", certificate,
|
||||
group='alarm')
|
||||
@ -122,19 +125,12 @@ class TestAlarmNotifier(base.TestCase):
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json,
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON,
|
||||
cert=(certificate, key), verify=True)
|
||||
|
||||
def test_notify_alarm_rest_action_with_ssl_verify_disable_by_cfg(self):
|
||||
action = 'https://host/action'
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
cfg.CONF.set_override("rest_notifier_ssl_verify", False,
|
||||
group='alarm')
|
||||
@ -142,36 +138,22 @@ class TestAlarmNotifier(base.TestCase):
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json,
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON,
|
||||
verify=False)
|
||||
|
||||
def test_notify_alarm_rest_action_with_ssl_verify_disable(self):
|
||||
action = 'https://host/action?ceilometer-alarm-ssl-verify=0'
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json,
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON,
|
||||
verify=False)
|
||||
|
||||
def test_notify_alarm_rest_action_with_ssl_verify_enable_by_user(self):
|
||||
action = 'https://host/action?ceilometer-alarm-ssl-verify=1'
|
||||
data_json = '{"reason": "what ?", "alarm": "foobar", "state": "ALARM"}'
|
||||
|
||||
cfg.CONF.set_override("rest_notifier_ssl_verify", False,
|
||||
group='alarm')
|
||||
@ -179,14 +161,8 @@ class TestAlarmNotifier(base.TestCase):
|
||||
with mock.patch('eventlet.spawn_n', self._fake_spawn_n):
|
||||
with mock.patch.object(requests, 'post') as poster:
|
||||
self.service.notify_alarm(context.get_admin_context(),
|
||||
{
|
||||
'actions': [action],
|
||||
'alarm': 'foobar',
|
||||
'condition': {'threshold': 42},
|
||||
'reason': 'what ?',
|
||||
'state': 'ALARM',
|
||||
})
|
||||
poster.assert_called_with(action, data=data_json,
|
||||
self._notification(action))
|
||||
poster.assert_called_with(action, data=DATA_JSON,
|
||||
verify=True)
|
||||
|
||||
@staticmethod
|
||||
|
@ -73,8 +73,9 @@ class TestRPCAlarmNotifier(base.TestCase):
|
||||
]
|
||||
|
||||
def test_notify_alarm(self):
|
||||
previous = ['alarm', 'ok']
|
||||
for i, a in enumerate(self.alarms):
|
||||
self.notifier.notify(a, "ok", "what? %d" % i)
|
||||
self.notifier.notify(a, previous[i], "what? %d" % i)
|
||||
self.assertEqual(len(self.notified), 2)
|
||||
for i, a in enumerate(self.alarms):
|
||||
actions = getattr(a, AlarmModel.ALARM_ACTIONS_MAP[a.state])
|
||||
@ -84,7 +85,9 @@ class TestRPCAlarmNotifier(base.TestCase):
|
||||
self.alarms[i].alarm_id)
|
||||
self.assertEqual(self.notified[i][1]["args"]["data"]["actions"],
|
||||
actions)
|
||||
self.assertEqual(self.notified[i][1]["args"]["data"]["state"],
|
||||
"ok")
|
||||
self.assertEqual(self.notified[i][1]["args"]["data"]["previous"],
|
||||
previous[i])
|
||||
self.assertEqual(self.notified[i][1]["args"]["data"]["current"],
|
||||
self.alarms[i].state)
|
||||
self.assertEqual(self.notified[i][1]["args"]["data"]["reason"],
|
||||
"what? %d" % i)
|
||||
|
@ -102,7 +102,7 @@ class TestEvaluate(base.TestCase):
|
||||
update_calls = self.api_client.alarms.update.call_args_list
|
||||
self.assertEqual(update_calls, expected)
|
||||
expected = [mock.call(alarm,
|
||||
'insufficient data',
|
||||
'ok',
|
||||
('%d datapoints are unknown' %
|
||||
alarm.evaluation_periods))
|
||||
for alarm in self.alarms]
|
||||
@ -123,7 +123,7 @@ class TestEvaluate(base.TestCase):
|
||||
)
|
||||
self.notifier.notify.assert_called_once_with(
|
||||
self.alarms[0],
|
||||
'insufficient data',
|
||||
'ok',
|
||||
mock.ANY
|
||||
)
|
||||
|
||||
@ -146,7 +146,7 @@ class TestEvaluate(base.TestCase):
|
||||
' threshold, most recent: 85.0',
|
||||
'Transition to alarm due to 4 samples outside'
|
||||
' threshold, most recent: 7.0']
|
||||
expected = [mock.call(alarm, 'alarm', reason)
|
||||
expected = [mock.call(alarm, 'ok', reason)
|
||||
for alarm, reason in zip(self.alarms, reasons)]
|
||||
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
||||
|
||||
@ -169,7 +169,7 @@ class TestEvaluate(base.TestCase):
|
||||
' threshold, most recent: 76.0',
|
||||
'Transition to ok due to 4 samples inside'
|
||||
' threshold, most recent: 14.0']
|
||||
expected = [mock.call(alarm, 'ok', reason)
|
||||
expected = [mock.call(alarm, 'alarm', reason)
|
||||
for alarm, reason in zip(self.alarms, reasons)]
|
||||
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
||||
|
||||
@ -247,7 +247,7 @@ class TestEvaluate(base.TestCase):
|
||||
' threshold, most recent: 85.0',
|
||||
'Transition to alarm due to 4 samples outside'
|
||||
' threshold, most recent: 7.0']
|
||||
expected = [mock.call(alarm, 'alarm', reason)
|
||||
expected = [mock.call(alarm, 'ok', reason)
|
||||
for alarm, reason in zip(self.alarms, reasons)]
|
||||
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
||||
|
||||
@ -270,6 +270,6 @@ class TestEvaluate(base.TestCase):
|
||||
' threshold, most recent: 85.0',
|
||||
'Transition to alarm due to 4 samples outside'
|
||||
' threshold, most recent: 7.0']
|
||||
expected = [mock.call(alarm, 'alarm', reason)
|
||||
expected = [mock.call(alarm, 'insufficient data', reason)
|
||||
for alarm, reason in zip(self.alarms, reasons)]
|
||||
self.assertEqual(self.notifier.notify.call_args_list, expected)
|
||||
|
Loading…
x
Reference in New Issue
Block a user