Files
neutron/neutron/pecan_wsgi/hooks/notifier.py
Kevin Benton e433c2870a 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
2016-03-11 15:47:11 -08:00

158 lines
6.7 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
# All Rights Reserved.
#
# 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.
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils
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
LOG = log.getLogger(__name__)
class NotifierHook(hooks.PecanHook):
priority = 135
@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)
if not hasattr(self, '_nova_notifier'):
# this is scoped to avoid a dependency on nova client when nova
# notifications aren't enabled
from neutron.notifiers import nova
self._nova_notifier = nova.Notifier()
self._nova_notifier.send_network_change(action_resource, *args)
def _notify_dhcp_agent(self, context, resource_name, action, resources):
plugin = manager.NeutronManager.get_plugin_for_resource(resource_name)
notifier_method = '%s.%s.end' % (resource_name, action)
# use plugin's dhcp notifier, if this is already instantiated
agent_notifiers = getattr(plugin, 'agent_notifiers', {})
dhcp_agent_notifier = (
agent_notifiers.get(constants.AGENT_TYPE_DHCP) or
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
# The DHCP Agent does not accept bulk notifications
for resource in resources:
item = {resource_name: resource}
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
resource_name = state.request.context.get('resource')
collection_name = state.request.context.get('collection')
neutron_context = state.request.context.get('neutron_context')
if not resource_name:
LOG.debug("Skipping NotifierHook processing as there was no "
"resource associated with the request")
return
action = pecan_constants.ACTION_MAP.get(state.request.method)
if not action or action == 'get':
LOG.debug("No notification will be sent for action: %s", action)
return
if action == 'delete':
# The object has been deleted, so we must notify the agent with the
# data of the original object
data = {collection_name:
state.request.context.get('original_resources', [])}
else:
try:
data = jsonutils.loads(state.response.body)
except ValueError:
if not state.response.body:
data = {}
resources = []
if data:
if resource_name in data:
resources = [data[resource_name]]
elif collection_name in data:
# This was a bulk request
resources = data[collection_name]
# Send a notification only if a resource can be identified in the
# response. This means that for operations such as add_router_interface
# no notification will be sent
if cfg.CONF.dhcp_agent_notification and data:
self._notify_dhcp_agent(
neutron_context, resource_name,
action, resources)
if cfg.CONF.notify_nova_on_port_data_changes:
orig = {}
if action == 'update':
orig = state.request.context.get('original_resources')[0]
elif action == 'delete':
# NOTE(kevinbenton): the nova notifier is a bit strange because
# it expects the original to be in the last argument on a
# delete rather than in the 'original_obj' position
resources = (
state.request.context.get('original_resources') or [])
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)