neutron/neutron/extensions/tagging.py
Harald Jensås 35c7999ebd Allow 255 character strings in tags
In TripleO we are moving to a model where heat is not
used to manage neutron resources. This work relies on
using tags, and we are hitting the 60 characther limit.

This change bumps the tag elements to 255 characters.

Closes-Bug: #1921713
Change-Id: Ie69526acb94b62fd5d8db1dbddc1f24072df7a5e
2021-03-30 10:44:39 +02:00

261 lines
9.2 KiB
Python

#
# 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 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 = 255
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
class TagPluginBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta):
"""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