Пример #1
0
    def after(self, state):
        resource_name = state.request.context.get('resource')
        collection_name = state.request.context.get('collection')
        neutron_context = state.request.context.get('neutron_context')
        action = pecan_constants.ACTION_MAP.get(state.request.method)
        if not action or action not in ('create', 'update', 'delete'):
            return
        if utils.is_member_action(utils.get_controller(state)):
            return
        if not resource_name:
            LOG.debug("Skipping NotifierHook processing as there was no "
                      "resource associated with the request")
            return
        if state.response.status_int > 300:
            LOG.debug(
                "No notification will be sent due to unsuccessful "
                "status code: %s", state.response.status_int)
            return

        original = {}
        if (action in ('delete', 'update')
                and state.request.context.get('original_resources', [])):
            # We only need the original resource for updates and deletes
            original = state.request.context.get('original_resources')[0]
        if action == 'delete':
            # The object has been deleted, so we must notify the agent with the
            # data of the original object as the payload, but we do not need
            # to pass it in as the original
            result = {resource_name: original}
            original = {}
        else:
            if not state.response.body:
                result = {}
            else:
                result = state.response.json

        notifier_method = '%s.%s.end' % (resource_name, action)
        notifier_action = utils.get_controller(state).plugin_handlers[action]
        registry.publish(resource_name,
                         events.BEFORE_RESPONSE,
                         self,
                         payload=events.APIEventPayload(
                             neutron_context,
                             notifier_method,
                             notifier_action,
                             request_body=state.request.body,
                             states=(
                                 original,
                                 result,
                             ),
                             collection_name=collection_name))

        if action == 'delete':
            resource_id = state.request.context.get('resource_id')
            result[resource_name + '_id'] = resource_id

        self._notifier.info(neutron_context, notifier_method, result)
Пример #2
0
    def after(self, state):
        resource_name = state.request.context.get('resource')
        collection_name = state.request.context.get('collection')
        neutron_context = state.request.context.get('neutron_context')
        action = pecan_constants.ACTION_MAP.get(state.request.method)
        if not action or action not in ('create', 'update', 'delete'):
            return
        if utils.is_member_action(utils.get_controller(state)):
            return
        if not resource_name:
            LOG.debug("Skipping NotifierHook processing as there was no "
                      "resource associated with the request")
            return
        if state.response.status_int > 300:
            LOG.debug("No notification will be sent due to unsuccessful "
                      "status code: %s", state.response.status_int)
            return

        original = {}
        if (action in ('delete', 'update') and
                state.request.context.get('original_resources', [])):
            # We only need the original resource for updates and deletes
            original = state.request.context.get('original_resources')[0]
        if action == 'delete':
            # The object has been deleted, so we must notify the agent with the
            # data of the original object as the payload, but we do not need
            # to pass it in as the original
            result = {resource_name: original}
            original = {}
        else:
            if not state.response.body:
                result = {}
            else:
                result = state.response.json

        notifier_method = '%s.%s.end' % (resource_name, action)
        notifier_action = utils.get_controller(state).plugin_handlers[action]
        registry.publish(resource_name, events.BEFORE_RESPONSE, self,
                         payload=events.APIEventPayload(
                             neutron_context, notifier_method, notifier_action,
                             request_body=state.request.body,
                             states=(original, result,),
                             collection_name=collection_name))

        if action == 'delete':
            resource_id = state.request.context.get('resource_id')
            result[resource_name + '_id'] = resource_id

        self._notifier.info(neutron_context, notifier_method, result)
Пример #3
0
 def before(self, state):
     if state.request.method not in ('POST', 'PUT', 'DELETE'):
         return
     resource = state.request.context.get('resource')
     if not resource:
         return
     if utils.is_member_action(utils.get_controller(state)):
         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
         try:
             payload = state.request.json.copy()
             if not payload:
                 return
         except ValueError:
             return
         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)
Пример #4
0
    def after(self, state):
        neutron_context = state.request.context.get('neutron_context')
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        controller = utils.get_controller(state)
        if not resource:
            # can't filter a resource we don't recognize
            return
        # NOTE(kevinbenton): extension listing isn't controlled by policy
        if resource == 'extension':
            return
        try:
            data = state.response.json
        except ValueError:
            return
        if state.request.method not in pecan_constants.ACTION_MAP:
            return
        if not data or (resource not in data and collection not in data):
            return
        policy.init()
        is_single = resource in data
        action_type = pecan_constants.ACTION_MAP[state.request.method]
        if action_type == 'get':
            action = controller.plugin_handlers[controller.SHOW]
        else:
            action = controller.plugin_handlers[action_type]
        key = resource if is_single else collection
        to_process = [data[resource]] if is_single else data[collection]
        # in the single case, we enforce which raises on violation
        # in the plural case, we just check so violating items are hidden
        policy_method = policy.enforce if is_single else policy.check
        plugin = manager.NeutronManager.get_plugin_for_resource(collection)
        try:
            resp = [
                self._get_filtered_item(state.request, controller, resource,
                                        collection, item)
                for item in to_process
                if (state.request.method != 'GET'
                    or policy_method(neutron_context,
                                     action,
                                     item,
                                     plugin=plugin,
                                     pluralized=collection))
            ]
        except oslo_policy.PolicyNotAuthorized:
            # This exception must be explicitly caught as the exception
            # translation hook won't be called if an error occurs in the
            # 'after' handler.  Instead of raising an HTTPForbidden exception,
            # we have to set the status_code here to prevent the catch_errors
            # middleware from turning this into a 500.
            state.response.status_code = 403
            return

        if is_single:
            resp = resp[0]
        state.response.json = {key: resp}
Пример #5
0
    def after(self, state):
        neutron_context = state.request.context.get('neutron_context')
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        controller = utils.get_controller(state)
        if not resource:
            # can't filter a resource we don't recognize
            return
        # NOTE(kevinbenton): extension listing isn't controlled by policy
        if resource == 'extension':
            return
        try:
            data = state.response.json
        except ValueError:
            return
        if state.request.method not in pecan_constants.ACTION_MAP:
            return
        if not data or (resource not in data and collection not in data):
            return
        policy.init()
        is_single = resource in data
        action_type = pecan_constants.ACTION_MAP[state.request.method]
        if action_type == 'get':
            action = controller.plugin_handlers[controller.SHOW]
        else:
            action = controller.plugin_handlers[action_type]
        key = resource if is_single else collection
        to_process = [data[resource]] if is_single else data[collection]
        # in the single case, we enforce which raises on violation
        # in the plural case, we just check so violating items are hidden
        policy_method = policy.enforce if is_single else policy.check
        plugin = manager.NeutronManager.get_plugin_for_resource(collection)
        try:
            resp = [self._get_filtered_item(state.request, controller,
                                            resource, collection, item)
                    for item in to_process
                    if (state.request.method != 'GET' or
                        policy_method(neutron_context, action, item,
                                      plugin=plugin,
                                      pluralized=collection))]
        except oslo_policy.PolicyNotAuthorized:
            # This exception must be explicitly caught as the exception
            # translation hook won't be called if an error occurs in the
            # 'after' handler.  Instead of raising an HTTPForbidden exception,
            # we have to set the status_code here to prevent the catch_errors
            # middleware from turning this into a 500.
            state.response.status_code = 403
            return

        if is_single:
            resp = resp[0]
        state.response.json = {key: resp}
Пример #6
0
    def before(self, state):
        if state.request.method not in ('POST', 'PUT'):
            return
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        neutron_context = state.request.context['neutron_context']
        is_create = state.request.method == 'POST'
        if not resource:
            return

        if not state.request.body:
            return
        try:
            json_data = jsonutils.loads(state.request.body)
            if not isinstance(json_data, dict):
                raise ValueError()
        except ValueError:
            msg = _("Body contains invalid data")
            raise webob.exc.HTTPBadRequest(msg)
        # Raw data are consumed by member actions such as add_router_interface
        state.request.context['request_data'] = json_data
        if not (resource in json_data or collection in json_data):
            # there is no resource in the request. This can happen when a
            # member action is being processed or on agent scheduler operations
            return
        # Prepare data to be passed to the plugin from request body
        controller = utils.get_controller(state)
        try:
            data = v2_base.Controller.prepare_request_body(
                neutron_context,
                json_data,
                is_create,
                resource,
                controller.resource_info,
                allow_bulk=is_create)
        except Exception as e:
            LOG.warning(
                "An exception happened while processing the request "
                "body. The exception message is [%s].", e)
            raise e

        if collection in data:
            state.request.context['resources'] = [
                item[resource] for item in data[collection]
            ]
            state.request.context['is_bulk'] = True
        else:
            state.request.context['resources'] = [data[resource]]
            state.request.context['is_bulk'] = False
Пример #7
0
    def after(self, state):
        neutron_context = state.request.context.get('neutron_context')
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        controller = utils.get_controller(state)
        if not resource:
            # can't filter a resource we don't recognize
            return
        # NOTE(kevinbenton): extension listing isn't controlled by policy
        if resource == 'extension':
            return
        try:
            data = state.response.json
        except ValueError:
            return
        if state.request.method not in pecan_constants.ACTION_MAP:
            return
        action = '%s_%s' % (pecan_constants.ACTION_MAP[state.request.method],
                            resource)
        if not data or (resource not in data and collection not in data):
            return
        is_single = resource in data
        key = resource if is_single else collection
        to_process = [data[resource]] if is_single else data[collection]
        # in the single case, we enforce which raises on violation
        # in the plural case, we just check so violating items are hidden
        policy_method = policy.enforce if is_single else policy.check
        plugin = manager.NeutronManager.get_plugin_for_resource(resource)
        try:
            resp = [
                self._get_filtered_item(state.request, controller, resource,
                                        collection, item)
                for item in to_process
                if (state.request.method != 'GET'
                    or policy_method(neutron_context,
                                     action,
                                     item,
                                     plugin=plugin,
                                     pluralized=collection))
            ]
        except oslo_policy.PolicyNotAuthorized as e:
            # This exception must be explicitly caught as the exception
            # translation hook won't be called if an error occurs in the
            # 'after' handler.
            raise webob.exc.HTTPForbidden(str(e))

        if is_single:
            resp = resp[0]
        state.response.json = {key: resp}
Пример #8
0
    def after(self, state):
        neutron_context = state.request.context.get('neutron_context')
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        controller = utils.get_controller(state)
        if not resource:
            # can't filter a resource we don't recognize
            return
        # NOTE(kevinbenton): extension listing isn't controlled by policy
        if resource == 'extension':
            return
        try:
            data = state.response.json
        except ValueError:
            return
        if state.request.method not in pecan_constants.ACTION_MAP:
            return
        action = '%s_%s' % (pecan_constants.ACTION_MAP[state.request.method],
                            resource)
        if not data or (resource not in data and collection not in data):
            return
        is_single = resource in data
        key = resource if is_single else collection
        to_process = [data[resource]] if is_single else data[collection]
        # in the single case, we enforce which raises on violation
        # in the plural case, we just check so violating items are hidden
        policy_method = policy.enforce if is_single else policy.check
        plugin = manager.NeutronManager.get_plugin_for_resource(collection)
        try:
            resp = [self._get_filtered_item(state.request, controller,
                                            resource, collection, item)
                    for item in to_process
                    if (state.request.method != 'GET' or
                        policy_method(neutron_context, action, item,
                                      plugin=plugin,
                                      pluralized=collection))]
        except oslo_policy.PolicyNotAuthorized as e:
            # This exception must be explicitly caught as the exception
            # translation hook won't be called if an error occurs in the
            # 'after' handler.
            raise webob.exc.HTTPForbidden(str(e))

        if is_single:
            resp = resp[0]
        state.response.json = {key: resp}
Пример #9
0
    def before(self, state):
        if state.request.method not in ('POST', 'PUT'):
            return
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        neutron_context = state.request.context['neutron_context']
        is_create = state.request.method == 'POST'
        if not resource:
            return

        try:
            json_data = jsonutils.loads(state.request.body)
        except ValueError:
            LOG.debug("No JSON Data in %(method)s request for %(collection)s",
                      {
                          'method': state.request.method,
                          'collections': collection
                      })
            return
        # Raw data are consumed by member actions such as add_router_interface
        state.request.context['request_data'] = json_data
        if not (resource in json_data or collection in json_data):
            # there is no resource in the request. This can happen when a
            # member action is being processed or on agent scheduler operations
            return
        # Prepare data to be passed to the plugin from request body
        controller = utils.get_controller(state)
        data = v2_base.Controller.prepare_request_body(
            neutron_context,
            json_data,
            is_create,
            resource,
            controller.resource_info,
            allow_bulk=is_create)
        if collection in data:
            state.request.context['resources'] = [
                item[resource] for item in data[collection]
            ]
            state.request.context['is_bulk'] = True
        else:
            state.request.context['resources'] = [data[resource]]
            state.request.context['is_bulk'] = False
Пример #10
0
 def before(self, state):
     state.request.context['query_params'] = {}
     if state.request.method != 'GET':
         return
     collection = state.request.context.get('collection')
     if not collection:
         return
     controller = utils.get_controller(state)
     combined_fields, added_fields = _set_fields(state, controller)
     filters = _set_filters(state, controller)
     query_params = {'fields': combined_fields, 'filters': filters}
     pagination_helper = _get_pagination_helper(state.request, controller)
     sorting_helper = _get_sorting_helper(state.request, controller)
     sorting_helper.update_args(query_params)
     sorting_helper.update_fields(query_params.get('fields', []),
                                  added_fields)
     pagination_helper.update_args(query_params)
     pagination_helper.update_fields(query_params.get('fields', []),
                                     added_fields)
     state.request.context['query_params'] = query_params
Пример #11
0
 def before(self, state):
     state.request.context['query_params'] = {}
     if state.request.method != 'GET':
         return
     collection = state.request.context.get('collection')
     if not collection:
         return
     controller = utils.get_controller(state)
     combined_fields, added_fields = _set_fields(state, controller)
     filters = _set_filters(state, controller)
     query_params = {'fields': combined_fields, 'filters': filters}
     pagination_helper = _get_pagination_helper(state.request, controller)
     sorting_helper = _get_sorting_helper(state.request, controller)
     sorting_helper.update_args(query_params)
     sorting_helper.update_fields(query_params.get('fields', []),
                                  added_fields)
     pagination_helper.update_args(query_params)
     pagination_helper.update_fields(query_params.get('fields', []),
                                     added_fields)
     state.request.context['query_params'] = query_params
Пример #12
0
    def before(self, state):
        if state.request.method not in ('POST', 'PUT'):
            return
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        neutron_context = state.request.context['neutron_context']
        is_create = state.request.method == 'POST'
        if not resource:
            return

        if not state.request.body:
            return
        try:
            json_data = jsonutils.loads(state.request.body)
            if not isinstance(json_data, dict):
                raise ValueError()
        except ValueError:
            msg = _("Body contains invalid data")
            raise webob.exc.HTTPBadRequest(msg)
        # Raw data are consumed by member actions such as add_router_interface
        state.request.context['request_data'] = json_data
        if not (resource in json_data or collection in json_data):
            # there is no resource in the request. This can happen when a
            # member action is being processed or on agent scheduler operations
            return
        # Prepare data to be passed to the plugin from request body
        controller = utils.get_controller(state)
        data = v2_base.Controller.prepare_request_body(
            neutron_context,
            json_data,
            is_create,
            resource,
            controller.resource_info,
            allow_bulk=is_create)
        if collection in data:
            state.request.context['resources'] = [item[resource] for item in
                                                  data[collection]]
            state.request.context['is_bulk'] = True
        else:
            state.request.context['resources'] = [data[resource]]
            state.request.context['is_bulk'] = False
Пример #13
0
    def before(self, state):
        if state.request.method not in ('POST', 'PUT'):
            return
        resource = state.request.context.get('resource')
        collection = state.request.context.get('collection')
        neutron_context = state.request.context['neutron_context']
        is_create = state.request.method == 'POST'
        if not resource:
            return

        try:
            json_data = jsonutils.loads(state.request.body)
        except ValueError:
            LOG.debug("No JSON Data in %(method)s request for %(collection)s",
                      {'method': state.request.method,
                       'collections': collection})
            return
        # Raw data are consumed by member actions such as add_router_interface
        state.request.context['request_data'] = json_data
        if not (resource in json_data or collection in json_data):
            # there is no resource in the request. This can happen when a
            # member action is being processed or on agent scheduler operations
            return
        # Prepare data to be passed to the plugin from request body
        controller = utils.get_controller(state)
        data = v2_base.Controller.prepare_request_body(
            neutron_context,
            json_data,
            is_create,
            resource,
            controller.resource_info,
            allow_bulk=is_create)
        if collection in data:
            state.request.context['resources'] = [item[resource] for item in
                                                  data[collection]]
            state.request.context['is_bulk'] = True
        else:
            state.request.context['resources'] = [data[resource]]
            state.request.context['is_bulk'] = False
Пример #14
0
    def before(self, state):
        # This hook should be run only for PUT,POST and DELETE methods and for
        # requests targeting a neutron resource
        resources = state.request.context.get('resources', [])
        if state.request.method not in ('POST', 'PUT', 'DELETE'):
            return
        # As this routine will likely alter the resources, do a shallow copy
        resources_copy = resources[:]
        neutron_context = state.request.context.get('neutron_context')
        resource = state.request.context.get('resource')
        # If there is no resource for this request, don't bother running authZ
        # policies
        if not resource:
            return
        controller = utils.get_controller(state)
        if not controller or utils.is_member_action(controller):
            return
        collection = state.request.context.get('collection')
        needs_prefetch = (state.request.method == 'PUT' or
                          state.request.method == 'DELETE')
        policy.init()

        action = controller.plugin_handlers[
            pecan_constants.ACTION_MAP[state.request.method]]

        # NOTE(salv-orlando): As bulk updates are not supported, in case of PUT
        # requests there will be only a single item to process, and its
        # identifier would have been already retrieved by the lookup process;
        # in the case of DELETE requests there won't be any item to process in
        # the request body
        original_resources = []
        if needs_prefetch:
            try:
                item = resources_copy.pop()
            except IndexError:
                # Ops... this was a delete after all!
                item = {}
            resource_id = state.request.context.get('resource_id')
            parent_id = state.request.context.get('parent_id')
            method = state.request.method
            resource_obj = fetch_resource(method, neutron_context, controller,
                                          collection, resource, resource_id,
                                          parent_id=parent_id)
            if resource_obj:
                original_resources.append(resource_obj)
                obj = copy.copy(resource_obj)
                obj.update(item)
                obj[const.ATTRIBUTES_TO_UPDATE] = item.keys()
                # Put back the item in the list so that policies could be
                # enforced
                resources_copy.append(obj)
        # TODO(salv-orlando): as other hooks might need to prefetch resources,
        # store them in the request context. However, this should be done in a
        # separate hook which is conveniently called before all other hooks
        state.request.context['original_resources'] = original_resources
        for item in resources_copy:
            try:
                policy.enforce(
                    neutron_context, action, item,
                    pluralized=collection)
            except oslo_policy.PolicyNotAuthorized:
                with excutils.save_and_reraise_exception() as ctxt:
                    # If a tenant is modifying it's own object, it's safe to
                    # return a 403. Otherwise, pretend that it doesn't exist
                    # to avoid giving away information.
                    orig_item_tenant_id = item.get('tenant_id')
                    if (needs_prefetch and
                        (neutron_context.tenant_id != orig_item_tenant_id or
                         orig_item_tenant_id is None)):
                        ctxt.reraise = False
                msg = _('The resource could not be found.')
                raise webob.exc.HTTPNotFound(msg)
Пример #15
0
    def after(self, state):
        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 utils.is_member_action(utils.get_controller(state)):
            return
        if state.response.status_int > 300:
            LOG.debug("No notification will be sent due to unsuccessful "
                      "status code: %s", state.response.status_int)
            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':
            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)
Пример #16
0
    def after(self, state):
        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 utils.is_member_action(utils.get_controller(state)):
            return
        if state.response.status_int > 300:
            LOG.debug(
                "No notification will be sent due to unsuccessful "
                "status code: %s", state.response.status_int)
            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':
            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)