# # 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 abc import copy from neutron_lib.api.definitions import port from neutron_lib.api import extensions as api_extensions from neutron_lib.api import faults from neutron_lib.api import validators from neutron_lib import exceptions from neutron_lib.plugins import directory from neutron_lib import rpc as n_rpc from neutron_lib.services import base as service_base import six import webob.exc from neutron._i18n import _ from neutron.api import extensions from neutron.api.v2 import resource as api_resource from neutron.db import standard_attr TAG = 'tag' TAGS = TAG + 's' TAGS_ANY = TAGS + '-any' NOT_TAGS = 'not-' + TAGS NOT_TAGS_ANY = NOT_TAGS + '-any' MAX_TAG_LEN = 60 TAG_PLUGIN_TYPE = 'TAG' TAG_SUPPORTED_RESOURCES = standard_attr.get_tag_resource_parent_map() TAG_ATTRIBUTE_MAP = { TAGS: {'allow_post': False, 'allow_put': False, 'is_visible': True, 'is_filter': True}, TAGS_ANY: {'allow_post': False, 'allow_put': False, 'is_visible': False, 'is_filter': True}, NOT_TAGS: {'allow_post': False, 'allow_put': False, 'is_visible': False, 'is_filter': True}, NOT_TAGS_ANY: {'allow_post': False, 'allow_put': False, 'is_visible': False, 'is_filter': True}, } TAG_ATTRIBUTE_MAP_PORTS = copy.deepcopy(TAG_ATTRIBUTE_MAP) TAG_ATTRIBUTE_MAP_PORTS[TAGS] = { 'allow_post': True, 'allow_put': False, 'validate': {'type:list_of_unique_strings': MAX_TAG_LEN}, 'default': [], 'is_visible': True, 'is_filter': True } class TagResourceNotFound(exceptions.NotFound): message = _("Resource %(resource)s %(resource_id)s could not be found.") class TagNotFound(exceptions.NotFound): message = _("Tag %(tag)s could not be found.") def validate_tag(tag): msg = validators.validate_string(tag, MAX_TAG_LEN) if msg: raise exceptions.InvalidInput(error_message=msg) def validate_tags(body): if not isinstance(body, dict) or 'tags' not in body: raise exceptions.InvalidInput(error_message=_("Invalid tags body")) msg = validators.validate_list_of_unique_strings(body['tags'], MAX_TAG_LEN) if msg: raise exceptions.InvalidInput(error_message=msg) def notify_tag_action(context, action, parent, parent_id, tags=None): notifier = n_rpc.get_notifier('network') tag_event = 'tag.%s' % action # TODO(hichihara): Add 'updated_at' into payload payload = {'parent_resource': parent, 'parent_resource_id': parent_id} if tags is not None: payload['tags'] = tags notifier.info(context, tag_event, payload) class TaggingController(object): def __init__(self): self.plugin = directory.get_plugin(TAG_PLUGIN_TYPE) self.supported_resources = TAG_SUPPORTED_RESOURCES def _get_parent_resource_and_id(self, kwargs): for key in kwargs: for resource in self.supported_resources: if key == self.supported_resources[resource] + '_id': return resource, kwargs[key] return None, None def index(self, request, **kwargs): # GET /v2.0/networks/{network_id}/tags parent, parent_id = self._get_parent_resource_and_id(kwargs) return self.plugin.get_tags(request.context, parent, parent_id) def show(self, request, id, **kwargs): # GET /v2.0/networks/{network_id}/tags/{tag} # id == tag validate_tag(id) parent, parent_id = self._get_parent_resource_and_id(kwargs) return self.plugin.get_tag(request.context, parent, parent_id, id) def create(self, request, **kwargs): # not supported # POST /v2.0/networks/{network_id}/tags raise webob.exc.HTTPNotFound("not supported") def update(self, request, id, **kwargs): # PUT /v2.0/networks/{network_id}/tags/{tag} # id == tag validate_tag(id) parent, parent_id = self._get_parent_resource_and_id(kwargs) notify_tag_action(request.context, 'create.start', parent, parent_id, [id]) result = self.plugin.update_tag(request.context, parent, parent_id, id) notify_tag_action(request.context, 'create.end', parent, parent_id, [id]) return result def update_all(self, request, body, **kwargs): # PUT /v2.0/networks/{network_id}/tags # body: {"tags": ["aaa", "bbb"]} validate_tags(body) parent, parent_id = self._get_parent_resource_and_id(kwargs) notify_tag_action(request.context, 'update.start', parent, parent_id, body['tags']) result = self.plugin.update_tags(request.context, parent, parent_id, body) notify_tag_action(request.context, 'update.end', parent, parent_id, body['tags']) return result def delete(self, request, id, **kwargs): # DELETE /v2.0/networks/{network_id}/tags/{tag} # id == tag validate_tag(id) parent, parent_id = self._get_parent_resource_and_id(kwargs) notify_tag_action(request.context, 'delete.start', parent, parent_id, [id]) result = self.plugin.delete_tag(request.context, parent, parent_id, id) notify_tag_action(request.context, 'delete.end', parent, parent_id, [id]) return result def delete_all(self, request, **kwargs): # DELETE /v2.0/networks/{network_id}/tags parent, parent_id = self._get_parent_resource_and_id(kwargs) notify_tag_action(request.context, 'delete_all.start', parent, parent_id) result = self.plugin.delete_tags(request.context, parent, parent_id) notify_tag_action(request.context, 'delete_all.end', parent, parent_id) return result class Tagging(api_extensions.ExtensionDescriptor): """Extension class supporting tags.""" @classmethod def get_name(cls): return ("Tag support for resources with standard attribute: %s" % ', '.join(TAG_SUPPORTED_RESOURCES.values())) @classmethod def get_alias(cls): return "standard-attr-tag" @classmethod def get_description(cls): return "Enables to set tag on resources with standard attribute." @classmethod def get_updated(cls): return "2017-01-01T00:00:00-00:00" def get_required_extensions(self): return [] @classmethod def get_resources(cls): """Returns Ext Resources.""" exts = [] action_status = {'index': 200, 'show': 204, 'update': 201, 'update_all': 200, 'delete': 204, 'delete_all': 204} controller = api_resource.Resource(TaggingController(), faults.FAULT_MAP, action_status=action_status) collection_methods = {"delete_all": "DELETE", "update_all": "PUT"} exts = [] for collection_name, member_name in TAG_SUPPORTED_RESOURCES.items(): if 'security_group' in collection_name: collection_name = collection_name.replace('_', '-') parent = {'member_name': member_name, 'collection_name': collection_name} exts.append(extensions.ResourceExtension( TAGS, controller, parent, collection_methods=collection_methods)) return exts def get_extended_resources(self, version): if version != "2.0": return {} EXTENDED_ATTRIBUTES_2_0 = {} for collection_name in TAG_SUPPORTED_RESOURCES: if collection_name == port.COLLECTION_NAME: EXTENDED_ATTRIBUTES_2_0[collection_name] = ( TAG_ATTRIBUTE_MAP_PORTS) else: EXTENDED_ATTRIBUTES_2_0[collection_name] = TAG_ATTRIBUTE_MAP return EXTENDED_ATTRIBUTES_2_0 @six.add_metaclass(abc.ABCMeta) class TagPluginBase(service_base.ServicePluginBase): """REST API to operate the Tag.""" def get_plugin_description(self): return "Tag support" @classmethod def get_plugin_type(cls): return TAG_PLUGIN_TYPE @abc.abstractmethod def get_tags(self, context, resource, resource_id): pass @abc.abstractmethod def get_tag(self, context, resource, resource_id, tag): pass @abc.abstractmethod def update_tags(self, context, resource, resource_id, body): pass @abc.abstractmethod def update_tag(self, context, resource, resource_id, tag): pass @abc.abstractmethod def delete_tags(self, context, resource, resource_id): pass @abc.abstractmethod def delete_tag(self, context, resource, resource_id, tag): pass