460 lines
18 KiB
Python
460 lines
18 KiB
Python
#
|
|
# Copyright 2013-2015 eNovance
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
import fixtures
|
|
import json
|
|
import time
|
|
|
|
import mock
|
|
from oslo_config import fixture as fixture_config
|
|
import oslo_messaging
|
|
import requests
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
from aodh import notifier
|
|
from aodh import service
|
|
from aodh.tests import base as tests_base
|
|
|
|
|
|
DATA_JSON = json.loads(
|
|
'{"current": "ALARM", "alarm_id": "foobar", "alarm_name": "testalarm",'
|
|
' "severity": "critical", "reason": "what ?",'
|
|
' "reason_data": {"test": "test"}, "previous": "OK"}'
|
|
)
|
|
NOTIFICATION = dict(alarm_id='foobar',
|
|
alarm_name='testalarm',
|
|
severity='critical',
|
|
condition=dict(threshold=42),
|
|
reason='what ?',
|
|
reason_data={'test': 'test'},
|
|
previous='OK',
|
|
current='ALARM')
|
|
|
|
|
|
class TestAlarmNotifierService(tests_base.BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestAlarmNotifierService, self).setUp()
|
|
conf = service.prepare_service(argv=[], config_files=[])
|
|
self.CONF = self.useFixture(fixture_config.Config(conf)).conf
|
|
self.setup_messaging(self.CONF)
|
|
|
|
def test_init_host_queue(self):
|
|
self.service = notifier.AlarmNotifierService(0, self.CONF)
|
|
self.service.terminate()
|
|
|
|
|
|
class TestAlarmNotifier(tests_base.BaseTestCase):
|
|
def setUp(self):
|
|
super(TestAlarmNotifier, self).setUp()
|
|
conf = service.prepare_service(argv=[], config_files=[])
|
|
self.CONF = self.useFixture(fixture_config.Config(conf)).conf
|
|
self.setup_messaging(self.CONF)
|
|
self._msg_notifier = oslo_messaging.Notifier(
|
|
self.transport, topics=['alarming'], driver='messaging',
|
|
publisher_id='testpublisher')
|
|
self.zaqar = FakeZaqarClient(self)
|
|
self.useFixture(fixtures.MockPatch(
|
|
'aodh.notifier.zaqar.ZaqarAlarmNotifier.get_zaqar_client',
|
|
return_value=self.zaqar))
|
|
self.service = notifier.AlarmNotifierService(0, self.CONF)
|
|
self.addCleanup(self.service.terminate)
|
|
|
|
def test_notify_alarm(self):
|
|
data = {
|
|
'actions': ['test://'],
|
|
'alarm_id': 'foobar',
|
|
'alarm_name': 'testalarm',
|
|
'severity': 'critical',
|
|
'previous': 'OK',
|
|
'current': 'ALARM',
|
|
'reason': 'Everything is on fire',
|
|
'reason_data': {'fire': 'everywhere'}
|
|
}
|
|
self._msg_notifier.sample({}, 'alarm.update', data)
|
|
time.sleep(1)
|
|
notifications = self.service.notifiers['test'].obj.notifications
|
|
self.assertEqual(1, len(notifications))
|
|
self.assertEqual((urlparse.urlsplit(data['actions'][0]),
|
|
data['alarm_id'],
|
|
data['alarm_name'],
|
|
data['severity'],
|
|
data['previous'],
|
|
data['current'],
|
|
data['reason'],
|
|
data['reason_data']),
|
|
notifications[0])
|
|
|
|
@mock.patch('aodh.notifier.LOG.debug')
|
|
def test_notify_alarm_with_batch_listener(self, logger):
|
|
data1 = {
|
|
'actions': ['test://'],
|
|
'alarm_id': 'foobar',
|
|
'alarm_name': 'testalarm',
|
|
'severity': 'critical',
|
|
'previous': 'OK',
|
|
'current': 'ALARM',
|
|
'reason': 'Everything is on fire',
|
|
'reason_data': {'fire': 'everywhere'}
|
|
}
|
|
data2 = {
|
|
'actions': ['test://'],
|
|
'alarm_id': 'foobar2',
|
|
'alarm_name': 'testalarm2',
|
|
'severity': 'low',
|
|
'previous': 'ALARM',
|
|
'current': 'OK',
|
|
'reason': 'Everything is fine',
|
|
'reason_data': {'fine': 'fine'}
|
|
}
|
|
self.service.terminate()
|
|
self.CONF.set_override("batch_size", 2, 'notifier')
|
|
# Init a new service with new configuration
|
|
self.svc = notifier.AlarmNotifierService(0, self.CONF)
|
|
self.addCleanup(self.svc.terminate)
|
|
self._msg_notifier.sample({}, 'alarm.update', data1)
|
|
self._msg_notifier.sample({}, 'alarm.update', data2)
|
|
time.sleep(1)
|
|
notifications = self.svc.notifiers['test'].obj.notifications
|
|
self.assertEqual(2, len(notifications))
|
|
self.assertEqual((urlparse.urlsplit(data1['actions'][0]),
|
|
data1['alarm_id'],
|
|
data1['alarm_name'],
|
|
data1['severity'],
|
|
data1['previous'],
|
|
data1['current'],
|
|
data1['reason'],
|
|
data1['reason_data']),
|
|
notifications[0])
|
|
self.assertEqual((urlparse.urlsplit(data2['actions'][0]),
|
|
data2['alarm_id'],
|
|
data2['alarm_name'],
|
|
data2['severity'],
|
|
data2['previous'],
|
|
data2['current'],
|
|
data2['reason'],
|
|
data2['reason_data']),
|
|
notifications[1])
|
|
self.assertEqual(mock.call('Received %s messages in batch.', 2),
|
|
logger.call_args_list[0])
|
|
|
|
@staticmethod
|
|
def _notification(action):
|
|
notification = {}
|
|
notification.update(NOTIFICATION)
|
|
notification['actions'] = [action]
|
|
return notification
|
|
|
|
@mock.patch('aodh.notifier.rest.LOG')
|
|
def test_notify_alarm_rest_action_ok(self, m_log):
|
|
action = 'http://host/action'
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
self.assertEqual(2, len(m_log.info.call_args_list))
|
|
expected = mock.call('Notifying alarm <%(id)s> gets response: '
|
|
'%(status_code)s %(reason)s.',
|
|
mock.ANY)
|
|
self.assertEqual(expected, m_log.info.call_args_list[1])
|
|
|
|
def test_notify_alarm_rest_action_with_ssl_client_cert(self):
|
|
action = 'https://host/action'
|
|
certificate = "/etc/ssl/cert/whatever.pem"
|
|
|
|
self.CONF.set_override("rest_notifier_certificate_file", certificate)
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
cert=certificate, verify=True)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
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"
|
|
|
|
self.CONF.set_override("rest_notifier_certificate_file", certificate)
|
|
self.CONF.set_override("rest_notifier_certificate_key", key)
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
cert=(certificate, key), verify=True)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
def test_notify_alarm_rest_action_with_ssl_verify_disable_by_cfg(self):
|
|
action = 'https://host/action'
|
|
|
|
self.CONF.set_override("rest_notifier_ssl_verify", False)
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
verify=False)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
def test_notify_alarm_rest_action_with_ssl_server_verify_enable(self):
|
|
action = 'https://host/action'
|
|
ca_bundle = "/path/to/custom_cert.pem"
|
|
|
|
self.CONF.set_override("rest_notifier_ca_bundle_certificate_path",
|
|
ca_bundle)
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
verify=ca_bundle)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
def test_notify_alarm_rest_action_with_ssl_verify_disable(self):
|
|
action = 'https://host/action?aodh-alarm-ssl-verify=0'
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
verify=False)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
def test_notify_alarm_rest_action_with_ssl_verify_enable_by_user(self):
|
|
action = 'https://host/action?aodh-alarm-ssl-verify=1'
|
|
|
|
self.CONF.set_override("rest_notifier_ssl_verify", False)
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({},
|
|
'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(action, data=mock.ANY,
|
|
headers=mock.ANY,
|
|
verify=True)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
@staticmethod
|
|
def _fake_urlsplit(*args, **kwargs):
|
|
raise Exception("Evil urlsplit!")
|
|
|
|
def test_notify_alarm_invalid_url(self):
|
|
with mock.patch('oslo_utils.netutils.urlsplit',
|
|
self._fake_urlsplit):
|
|
LOG = mock.MagicMock()
|
|
with mock.patch('aodh.notifier.LOG', LOG):
|
|
self._msg_notifier.sample(
|
|
{}, 'alarm.update',
|
|
{
|
|
'actions': ['no-such-action-i-am-sure'],
|
|
'alarm_id': 'foobar',
|
|
'condition': {'threshold': 42},
|
|
})
|
|
time.sleep(1)
|
|
self.assertTrue(LOG.error.called)
|
|
|
|
def test_notify_alarm_invalid_action(self):
|
|
LOG = mock.MagicMock()
|
|
with mock.patch('aodh.notifier.LOG', LOG):
|
|
self._msg_notifier.sample(
|
|
{}, 'alarm.update',
|
|
{
|
|
'actions': ['no-such-action-i-am-sure://'],
|
|
'alarm_id': 'foobar',
|
|
'condition': {'threshold': 42},
|
|
})
|
|
time.sleep(1)
|
|
self.assertTrue(LOG.error.called)
|
|
|
|
def test_notify_alarm_trust_action(self):
|
|
action = 'trust+http://trust-1234@host/action'
|
|
url = 'http://host/action'
|
|
|
|
client = mock.MagicMock()
|
|
client.session.auth.get_access.return_value.auth_token = 'token_1234'
|
|
|
|
self.useFixture(
|
|
fixtures.MockPatch('aodh.keystone_client.get_trusted_client',
|
|
lambda *args: client))
|
|
|
|
with mock.patch.object(requests.Session, 'post') as poster:
|
|
self._msg_notifier.sample({}, 'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
poster.assert_called_with(
|
|
url, data=mock.ANY, headers=mock.ANY)
|
|
args, kwargs = poster.call_args
|
|
self.assertEqual(
|
|
{
|
|
'X-Auth-Token': 'token_1234',
|
|
'x-openstack-request-id':
|
|
kwargs['headers']['x-openstack-request-id'],
|
|
'content-type': 'application/json'
|
|
},
|
|
kwargs['headers'])
|
|
|
|
self.assertEqual(DATA_JSON, json.loads(kwargs['data']))
|
|
|
|
def test_zaqar_notifier_action(self):
|
|
with mock.patch.object(notifier.zaqar.ZaqarAlarmNotifier,
|
|
'_get_client_conf') as get_conf:
|
|
action = ('zaqar://?topic=critical'
|
|
'&subscriber=http://example.com/data'
|
|
'&subscriber=mailto:foo@example.com&ttl=7200')
|
|
self._msg_notifier.sample({}, 'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
get_conf.assert_called()
|
|
self.assertEqual(self.zaqar,
|
|
self.service.notifiers['zaqar'].obj._zclient)
|
|
self.assertEqual(2, self.zaqar.subscriptions)
|
|
self.assertEqual(1, self.zaqar.posts)
|
|
|
|
def test_presigned_zaqar_notifier_action(self):
|
|
action = ('zaqar://?'
|
|
'subscriber=http://example.com/data&ttl=7200'
|
|
'&signature=mysignature&expires=2016-06-29T01:49:56'
|
|
'&paths=/v2/queues/beijing/messages'
|
|
'&methods=GET,PATCH,POST,PUT&queue_name=foobar-critical'
|
|
'&project_id=my_project_id')
|
|
self._msg_notifier.sample({}, 'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
self.assertEqual(1, self.zaqar.subscriptions)
|
|
self.assertEqual(1, self.zaqar.posts)
|
|
|
|
def test_trust_zaqar_notifier_action(self):
|
|
client = mock.MagicMock()
|
|
client.session.auth.get_access.return_value.auth_token = 'token_1234'
|
|
|
|
self.useFixture(
|
|
fixtures.MockPatch('aodh.keystone_client.get_trusted_client',
|
|
lambda *args: client))
|
|
|
|
action = 'trust+zaqar://trust-1234:delete@?queue_name=foobar-critical'
|
|
self._msg_notifier.sample({}, 'alarm.update',
|
|
self._notification(action))
|
|
time.sleep(1)
|
|
self.assertEqual(0, self.zaqar.subscriptions)
|
|
self.assertEqual(1, self.zaqar.posts)
|
|
|
|
|
|
class FakeZaqarClient(object):
|
|
|
|
def __init__(self, testcase):
|
|
self.testcase = testcase
|
|
self.subscriptions = 0
|
|
self.posts = 0
|
|
|
|
def queue(self, queue_name, **kwargs):
|
|
self.testcase.assertEqual('foobar-critical', queue_name)
|
|
self.testcase.assertEqual({}, kwargs)
|
|
return FakeZaqarQueue(self)
|
|
|
|
def subscription(self, queue_name, **kwargs):
|
|
self.testcase.assertEqual('foobar-critical', queue_name)
|
|
subscribers = ['http://example.com/data', 'mailto:foo@example.com']
|
|
self.testcase.assertIn(kwargs['subscriber'], subscribers)
|
|
self.testcase.assertEqual(7200, kwargs['ttl'])
|
|
self.subscriptions += 1
|
|
|
|
|
|
class FakeZaqarQueue(object):
|
|
|
|
def __init__(self, client):
|
|
self.client = client
|
|
self.testcase = client.testcase
|
|
|
|
def post(self, message):
|
|
expected_message = {'body': {'alarm_name': 'testalarm',
|
|
'reason_data': {'test': 'test'},
|
|
'current': 'ALARM',
|
|
'alarm_id': 'foobar',
|
|
'reason': 'what ?',
|
|
'severity': 'critical',
|
|
'previous': 'OK'}}
|
|
self.testcase.assertEqual(expected_message, message)
|
|
self.client.posts += 1
|