Add metrics notifier to Pecan
This adds the standard 'object.(create|update|delete).(start|end)' notifications to the Pecan notification hook and adds unit tests to exercise them. This patch also corrects the on_error handler for untranslated exceptions which was incorrectly raising the exception rather than returning it. This was resulting in the other hooks not getting the correct status code on an untranslated exception. Closes-Bug: #1552979 Change-Id: I400f8d3988db204caed25e7c848a415b45d47172
This commit is contained in:
parent
718434bf9b
commit
e433c2870a
@ -20,6 +20,7 @@ from pecan import hooks
|
||||
|
||||
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
|
||||
from neutron.common import constants
|
||||
from neutron.common import rpc as n_rpc
|
||||
from neutron import manager
|
||||
from neutron.pecan_wsgi import constants as pecan_constants
|
||||
|
||||
@ -29,7 +30,11 @@ LOG = log.getLogger(__name__)
|
||||
class NotifierHook(hooks.PecanHook):
|
||||
priority = 135
|
||||
|
||||
# TODO(kevinbenton): implement ceilo notifier
|
||||
@property
|
||||
def _notifier(self):
|
||||
if not hasattr(self, '_notifier_inst'):
|
||||
self._notifier_inst = n_rpc.get_notifier('network')
|
||||
return self._notifier_inst
|
||||
|
||||
def _nova_notify(self, action, resource, *args):
|
||||
action_resource = '%s_%s' % (action, resource)
|
||||
@ -55,6 +60,26 @@ class NotifierHook(hooks.PecanHook):
|
||||
LOG.debug("Sending DHCP agent notification for: %s", item)
|
||||
dhcp_agent_notifier.notify(context, item, notifier_method)
|
||||
|
||||
def before(self, state):
|
||||
if state.request.method not in ('POST', 'PUT', 'DELETE'):
|
||||
return
|
||||
resource = state.request.context.get('resource')
|
||||
if not resource:
|
||||
return
|
||||
action = pecan_constants.ACTION_MAP.get(state.request.method)
|
||||
event = '%s.%s.start' % (resource, action)
|
||||
if action in ('create', 'update'):
|
||||
# notifier just gets plain old body without any treatment other
|
||||
# than the population of the object ID being operated on
|
||||
payload = state.request.json.copy()
|
||||
if action == 'update':
|
||||
payload['id'] = state.request.context.get('resource_id')
|
||||
elif action == 'delete':
|
||||
resource_id = state.request.context.get('resource_id')
|
||||
payload = {resource + '_id': resource_id}
|
||||
self._notifier.info(state.request.context.get('neutron_context'),
|
||||
event, payload)
|
||||
|
||||
def after(self, state):
|
||||
# if the after hook is executed the request completed successfully and
|
||||
# therefore notifications must be sent
|
||||
@ -109,3 +134,24 @@ class NotifierHook(hooks.PecanHook):
|
||||
for resource in resources:
|
||||
self._nova_notify(action, resource_name, orig,
|
||||
{resource_name: resource})
|
||||
|
||||
event = '%s.%s.end' % (resource_name, action)
|
||||
if action == 'delete':
|
||||
if state.response.status_int > 300:
|
||||
# don't notify when unsuccessful
|
||||
# NOTE(kevinbenton): we may want to be more strict with the
|
||||
# response codes
|
||||
return
|
||||
resource_id = state.request.context.get('resource_id')
|
||||
payload = {resource_name + '_id': resource_id}
|
||||
elif action in ('create', 'update'):
|
||||
if not resources:
|
||||
# create/update did not complete so no notification
|
||||
return
|
||||
if len(resources) > 1:
|
||||
payload = {collection_name: resources}
|
||||
else:
|
||||
payload = {resource_name: resources[0]}
|
||||
else:
|
||||
return
|
||||
self._notifier.info(neutron_context, event, payload)
|
||||
|
@ -35,5 +35,5 @@ class ExceptionTranslationHook(hooks.PecanHook):
|
||||
# leaked unexpected exception, convert to boring old 500 error and
|
||||
# hide message from user in case it contained sensitive details
|
||||
LOG.exception(_LE("An unexpected exception was caught: %s"), e)
|
||||
raise webob.exc.HTTPInternalServerError(
|
||||
return webob.exc.HTTPInternalServerError(
|
||||
_("An unexpected internal error occurred."))
|
||||
|
@ -361,3 +361,122 @@ class TestNovaNotifierHook(test_functional.PecanFunctionalTest):
|
||||
[mock.call('create', 'network', {}, {'network': item_1}),
|
||||
mock.call('create', 'network', {}, {'network': item_2})],
|
||||
self.mock_notifier.mock_calls)
|
||||
|
||||
|
||||
class TestMetricsNotifierHook(test_functional.PecanFunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
patcher = mock.patch('neutron.pecan_wsgi.hooks.notifier.NotifierHook.'
|
||||
'_notifier')
|
||||
self.mock_notifier = patcher.start().info
|
||||
super(TestMetricsNotifierHook, self).setUp()
|
||||
|
||||
def test_post_put_delete_triggers_notification(self):
|
||||
req_headers = {'X-Project-Id': 'tenid', 'X-Roles': 'admin'}
|
||||
payload = {'network': {'name': 'meh'}}
|
||||
response = self.app.post_json(
|
||||
'/v2.0/networks.json',
|
||||
params=payload, headers=req_headers)
|
||||
self.assertEqual(201, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.create.start', payload),
|
||||
mock.call(mock.ANY, 'network.create.end', json_body)],
|
||||
self.mock_notifier.mock_calls)
|
||||
self.mock_notifier.reset_mock()
|
||||
network_id = json_body['network']['id']
|
||||
|
||||
payload = {'network': {'name': 'meh-2'}}
|
||||
response = self.app.put_json(
|
||||
'/v2.0/networks/%s.json' % network_id,
|
||||
params=payload, headers=req_headers)
|
||||
self.assertEqual(200, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
# id should be in payload sent to notifier
|
||||
payload['id'] = network_id
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.update.start', payload),
|
||||
mock.call(mock.ANY, 'network.update.end', json_body)],
|
||||
self.mock_notifier.mock_calls)
|
||||
self.mock_notifier.reset_mock()
|
||||
|
||||
response = self.app.delete(
|
||||
'/v2.0/networks/%s.json' % network_id, headers=req_headers)
|
||||
self.assertEqual(204, response.status_int)
|
||||
payload = {'network_id': network_id}
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.delete.start', payload),
|
||||
mock.call(mock.ANY, 'network.delete.end', payload)],
|
||||
self.mock_notifier.mock_calls)
|
||||
|
||||
def test_bulk_create_triggers_notification(self):
|
||||
req_headers = {'X-Project-Id': 'tenid', 'X-Roles': 'admin'}
|
||||
payload = {'networks': [{'name': 'meh_1'}, {'name': 'meh_2'}]}
|
||||
response = self.app.post_json(
|
||||
'/v2.0/networks.json',
|
||||
params=payload,
|
||||
headers=req_headers)
|
||||
self.assertEqual(201, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self.assertEqual(2, self.mock_notifier.call_count)
|
||||
self.mock_notifier.assert_has_calls(
|
||||
[mock.call(mock.ANY, 'network.create.start', payload),
|
||||
mock.call(mock.ANY, 'network.create.end', json_body)])
|
||||
|
||||
def test_bad_create_doesnt_emit_end(self):
|
||||
req_headers = {'X-Project-Id': 'tenid', 'X-Roles': 'admin'}
|
||||
payload = {'network': {'name': 'meh'}}
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
with mock.patch.object(plugin, 'create_network',
|
||||
side_effect=ValueError):
|
||||
response = self.app.post_json(
|
||||
'/v2.0/networks.json',
|
||||
params=payload, headers=req_headers,
|
||||
expect_errors=True)
|
||||
self.assertEqual(500, response.status_int)
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.create.start', mock.ANY)],
|
||||
self.mock_notifier.mock_calls)
|
||||
|
||||
def test_bad_update_doesnt_emit_end(self):
|
||||
req_headers = {'X-Project-Id': 'tenid', 'X-Roles': 'admin'}
|
||||
payload = {'network': {'name': 'meh'}}
|
||||
response = self.app.post_json(
|
||||
'/v2.0/networks.json',
|
||||
params=payload, headers=req_headers,
|
||||
expect_errors=True)
|
||||
self.assertEqual(201, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self.mock_notifier.reset_mock()
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
with mock.patch.object(plugin, 'update_network',
|
||||
side_effect=ValueError):
|
||||
response = self.app.put_json(
|
||||
'/v2.0/networks/%s.json' % json_body['network']['id'],
|
||||
params=payload, headers=req_headers,
|
||||
expect_errors=True)
|
||||
self.assertEqual(500, response.status_int)
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.update.start', mock.ANY)],
|
||||
self.mock_notifier.mock_calls)
|
||||
|
||||
def test_bad_delete_doesnt_emit_end(self):
|
||||
req_headers = {'X-Project-Id': 'tenid', 'X-Roles': 'admin'}
|
||||
payload = {'network': {'name': 'meh'}}
|
||||
response = self.app.post_json(
|
||||
'/v2.0/networks.json',
|
||||
params=payload, headers=req_headers,
|
||||
expect_errors=True)
|
||||
self.assertEqual(201, response.status_int)
|
||||
json_body = jsonutils.loads(response.body)
|
||||
self.mock_notifier.reset_mock()
|
||||
plugin = manager.NeutronManager.get_plugin()
|
||||
with mock.patch.object(plugin, 'delete_network',
|
||||
side_effect=ValueError):
|
||||
response = self.app.delete(
|
||||
'/v2.0/networks/%s.json' % json_body['network']['id'],
|
||||
headers=req_headers, expect_errors=True)
|
||||
self.assertEqual(500, response.status_int)
|
||||
self.assertEqual(
|
||||
[mock.call(mock.ANY, 'network.delete.start', mock.ANY)],
|
||||
self.mock_notifier.mock_calls)
|
||||
|
Loading…
Reference in New Issue
Block a user