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
158 lines
6.7 KiB
Python
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)
|