Enhance tag mechanism

This patch enhances the tag mechanism for subnet, port, subnetpool,
router resources. The tag-ext as new extension is added so that
tag supports their resources.

APIImpact: Adds tag support to subnet, port, subnetpool, router
DocImpact: allow users to set tags on some resources

Change-Id: I3ab8c2f47f283bee7219f39f20b07361b8e0c5f1
Closes-Bug: #1661608
This commit is contained in:
Hirofumi Ichihara 2017-01-19 13:52:39 +09:00
parent eb97599218
commit b56f008f3a
9 changed files with 416 additions and 143 deletions

View File

@ -84,6 +84,7 @@ class RouterInterfaceAttachmentConflict(nexception.Conflict):
message = _("Error %(reason)s while attempting the operation.") message = _("Error %(reason)s while attempting the operation.")
ROUTER = 'router'
ROUTERS = 'routers' ROUTERS = 'routers'
FLOATINGIP = 'floatingip' FLOATINGIP = 'floatingip'
FLOATINGIPS = '%ss' % FLOATINGIP FLOATINGIPS = '%ss' % FLOATINGIP

View File

@ -35,8 +35,9 @@ MAX_TAG_LEN = 60
TAG_PLUGIN_TYPE = 'TAG' TAG_PLUGIN_TYPE = 'TAG'
TAG_SUPPORTED_RESOURCES = { TAG_SUPPORTED_RESOURCES = {
# We shouldn't add new resources here. If more resources need to be tagged,
# we must add them in new extension.
attributes.NETWORKS: attributes.NETWORK, attributes.NETWORKS: attributes.NETWORK,
# other resources can be added
} }
TAG_ATTRIBUTE_MAP = { TAG_ATTRIBUTE_MAP = {
@ -52,14 +53,6 @@ class TagNotFound(exceptions.NotFound):
message = _("Tag %(tag)s could not be found.") message = _("Tag %(tag)s could not be found.")
def get_parent_resource_and_id(kwargs):
for key in kwargs:
for resource in TAG_SUPPORTED_RESOURCES:
if key == TAG_SUPPORTED_RESOURCES[resource] + '_id':
return resource, kwargs[key]
return None, None
def validate_tag(tag): def validate_tag(tag):
msg = validators.validate_string(tag, MAX_TAG_LEN) msg = validators.validate_string(tag, MAX_TAG_LEN)
if msg: if msg:
@ -88,17 +81,25 @@ def notify_tag_action(context, action, parent, parent_id, tags=None):
class TagController(object): class TagController(object):
def __init__(self): def __init__(self):
self.plugin = directory.get_plugin(TAG_PLUGIN_TYPE) 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): def index(self, request, **kwargs):
# GET /v2.0/networks/{network_id}/tags # GET /v2.0/networks/{network_id}/tags
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
return self.plugin.get_tags(request.context, parent, parent_id) return self.plugin.get_tags(request.context, parent, parent_id)
def show(self, request, id, **kwargs): def show(self, request, id, **kwargs):
# GET /v2.0/networks/{network_id}/tags/{tag} # GET /v2.0/networks/{network_id}/tags/{tag}
# id == tag # id == tag
validate_tag(id) validate_tag(id)
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
return self.plugin.get_tag(request.context, parent, parent_id, id) return self.plugin.get_tag(request.context, parent, parent_id, id)
def create(self, request, **kwargs): def create(self, request, **kwargs):
@ -110,7 +111,7 @@ class TagController(object):
# PUT /v2.0/networks/{network_id}/tags/{tag} # PUT /v2.0/networks/{network_id}/tags/{tag}
# id == tag # id == tag
validate_tag(id) validate_tag(id)
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
notify_tag_action(request.context, 'create.start', notify_tag_action(request.context, 'create.start',
parent, parent_id, [id]) parent, parent_id, [id])
result = self.plugin.update_tag(request.context, parent, parent_id, id) result = self.plugin.update_tag(request.context, parent, parent_id, id)
@ -122,7 +123,7 @@ class TagController(object):
# PUT /v2.0/networks/{network_id}/tags # PUT /v2.0/networks/{network_id}/tags
# body: {"tags": ["aaa", "bbb"]} # body: {"tags": ["aaa", "bbb"]}
validate_tags(body) validate_tags(body)
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
notify_tag_action(request.context, 'update.start', notify_tag_action(request.context, 'update.start',
parent, parent_id, body['tags']) parent, parent_id, body['tags'])
result = self.plugin.update_tags(request.context, parent, result = self.plugin.update_tags(request.context, parent,
@ -135,7 +136,7 @@ class TagController(object):
# DELETE /v2.0/networks/{network_id}/tags/{tag} # DELETE /v2.0/networks/{network_id}/tags/{tag}
# id == tag # id == tag
validate_tag(id) validate_tag(id)
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
notify_tag_action(request.context, 'delete.start', notify_tag_action(request.context, 'delete.start',
parent, parent_id, [id]) parent, parent_id, [id])
result = self.plugin.delete_tag(request.context, parent, parent_id, id) result = self.plugin.delete_tag(request.context, parent, parent_id, id)
@ -145,7 +146,7 @@ class TagController(object):
def delete_all(self, request, **kwargs): def delete_all(self, request, **kwargs):
# DELETE /v2.0/networks/{network_id}/tags # DELETE /v2.0/networks/{network_id}/tags
parent, parent_id = get_parent_resource_and_id(kwargs) parent, parent_id = self._get_parent_resource_and_id(kwargs)
notify_tag_action(request.context, 'delete_all.start', notify_tag_action(request.context, 'delete_all.start',
parent, parent_id) parent, parent_id)
result = self.plugin.delete_tags(request.context, parent, parent_id) result = self.plugin.delete_tags(request.context, parent, parent_id)

View File

@ -0,0 +1,90 @@
#
# 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 neutron_lib.api import extensions as api_extensions
from neutron_lib.plugins import directory
from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.api.v2 import base
from neutron.api.v2 import resource as api_resource
from neutron.extensions import l3
from neutron.extensions import tag as tag_base
TAG_SUPPORTED_RESOURCES = {
# We shouldn't add new resources here. If more resources need to be tagged,
# we must add them in new extension.
attributes.SUBNETS: attributes.SUBNET,
attributes.PORTS: attributes.PORT,
attributes.SUBNETPOOLS: attributes.SUBNETPOOL,
l3.ROUTERS: l3.ROUTER,
}
class TagExtController(tag_base.TagController):
def __init__(self):
self.plugin = directory.get_plugin(tag_base.TAG_PLUGIN_TYPE)
self.supported_resources = TAG_SUPPORTED_RESOURCES
class Tag_ext(api_extensions.ExtensionDescriptor):
"""Extension class supporting tags for ext resources."""
@classmethod
def get_name(cls):
return ("Tag support for resources: %s"
% ', '.join(TAG_SUPPORTED_RESOURCES.values()))
@classmethod
def get_alias(cls):
return "tag-ext"
@classmethod
def get_description(cls):
return "Extends tag support to more L2 and L3 resources."
@classmethod
def get_updated(cls):
return "2017-01-01T00:00:00-00:00"
@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(TagExtController(),
base.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():
parent = {'member_name': member_name,
'collection_name': collection_name}
exts.append(extensions.ResourceExtension(
tag_base.TAGS, controller, parent,
collection_methods=collection_methods))
return exts
def get_optional_extensions(self):
return ['router']
def get_extended_resources(self, version):
if version != "2.0":
return {}
EXTENDED_ATTRIBUTES_2_0 = {}
for collection_name in TAG_SUPPORTED_RESOURCES:
EXTENDED_ATTRIBUTES_2_0[collection_name] = (
tag_base.TAG_ATTRIBUTE_MAP)
return EXTENDED_ATTRIBUTES_2_0

View File

@ -14,6 +14,7 @@
import functools import functools
from neutron_lib.plugins import directory
from oslo_db import api as oslo_db_api from oslo_db import api as oslo_db_api
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import helpers as log_helpers from oslo_log import helpers as log_helpers
@ -22,19 +23,28 @@ from sqlalchemy.orm import exc
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.db import api as db_api from neutron.db import api as db_api
from neutron.db import common_db_mixin from neutron.db import common_db_mixin
from neutron.db.models import l3 as l3_model
from neutron.db.models import tag as tag_model from neutron.db.models import tag as tag_model
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import tag_db as tag_methods from neutron.db import tag_db as tag_methods
from neutron.extensions import l3 as l3_ext
from neutron.extensions import tag as tag_ext from neutron.extensions import tag as tag_ext
resource_model_map = { resource_model_map = {
# When we'll add other resources, we must add new extension for them
# if we don't have better discovery mechanism instead of it.
attributes.NETWORKS: models_v2.Network, attributes.NETWORKS: models_v2.Network,
# other resources can be added attributes.SUBNETS: models_v2.Subnet,
attributes.PORTS: models_v2.Port,
attributes.SUBNETPOOLS: models_v2.SubnetPool,
l3_ext.ROUTERS: l3_model.Router,
} }
def _extend_tags_dict(plugin, response_data, db_data): def _extend_tags_dict(plugin, response_data, db_data):
if not directory.get_plugin(tag_ext.TAG_PLUGIN_TYPE):
return
tags = [tag_db.tag for tag_db in db_data.standard_attr.tags] tags = [tag_db.tag for tag_db in db_data.standard_attr.tags]
response_data['tags'] = tags response_data['tags'] = tags
@ -42,7 +52,7 @@ def _extend_tags_dict(plugin, response_data, db_data):
class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase): class TagPlugin(common_db_mixin.CommonDbMixin, tag_ext.TagPluginBase):
"""Implementation of the Neutron Tag Service Plugin.""" """Implementation of the Neutron Tag Service Plugin."""
supported_extension_aliases = ['tag'] supported_extension_aliases = ['tag', 'tag-ext']
def _get_resource(self, context, resource, resource_id): def _get_resource(self, context, resource, resource_id):
model = resource_model_map[resource] model = resource_model_map[resource]

View File

@ -39,6 +39,7 @@ NETWORK_API_EXTENSIONS="
standard-attr-timestamp, \ standard-attr-timestamp, \
subnet_allocation, \ subnet_allocation, \
tag, \ tag, \
tag-ext, \
trunk, \ trunk, \
trunk-details" trunk-details"
NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')" NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')"

View File

@ -38,6 +38,7 @@ NETWORK_API_EXTENSIONS="
standard-attr-timestamp, \ standard-attr-timestamp, \
subnet_allocation, \ subnet_allocation, \
tag, \ tag, \
tag-ext, \
trunk, \ trunk, \
trunk-details" trunk-details"
NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')" NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')"

View File

@ -83,9 +83,72 @@ class TagNetworkTestJSON(TagTestJSON):
self._test_tag_operations() self._test_tag_operations()
class TagSubnetTestJSON(TagTestJSON):
resource = 'subnets'
@classmethod
def _create_resource(cls):
network = cls.create_network()
subnet = cls.create_subnet(network)
return subnet['id']
@test.attr(type='smoke')
@test.idempotent_id('2805aabf-a94c-4e70-a0b2-9814f06beb03')
@test.requires_ext(extension="tag-ext", service="network")
def test_subnet_tags(self):
self._test_tag_operations()
class TagPortTestJSON(TagTestJSON):
resource = 'ports'
@classmethod
def _create_resource(cls):
network = cls.create_network()
port = cls.create_port(network)
return port['id']
@test.attr(type='smoke')
@test.idempotent_id('c7c44f2c-edb0-4ebd-a386-d37cec155c34')
@test.requires_ext(extension="tag-ext", service="network")
def test_port_tags(self):
self._test_tag_operations()
class TagSubnetPoolTestJSON(TagTestJSON):
resource = 'subnetpools'
@classmethod
def _create_resource(cls):
subnetpool = cls.create_subnetpool('subnetpool', default_prefixlen=24,
prefixes=['10.0.0.0/8'])
return subnetpool['id']
@test.attr(type='smoke')
@test.idempotent_id('bdc1c24b-c0b5-4835-953c-8f67dc11edfe')
@test.requires_ext(extension="tag-ext", service="network")
def test_subnetpool_tags(self):
self._test_tag_operations()
class TagRouterTestJSON(TagTestJSON):
resource = 'routers'
@classmethod
def _create_resource(cls):
router = cls.create_router(router_name='test')
return router['id']
@test.attr(type='smoke')
@test.idempotent_id('b898ff92-dc33-4232-8ab9-2c6158c80d28')
@test.requires_ext(extension="router", service="network")
@test.requires_ext(extension="tag-ext", service="network")
def test_router_tags(self):
self._test_tag_operations()
class TagFilterTestJSON(base.BaseAdminNetworkTest): class TagFilterTestJSON(base.BaseAdminNetworkTest):
credentials = ['primary', 'alt', 'admin'] credentials = ['primary', 'alt', 'admin']
resource = 'networks'
@classmethod @classmethod
@test.requires_ext(extension="tag", service="network") @test.requires_ext(extension="tag", service="network")
@ -166,9 +229,88 @@ class TagFilterNetworkTestJSON(TagFilterTestJSON):
def _list_resource(self, filters): def _list_resource(self, filters):
res = self.client.list_networks(**filters) res = self.client.list_networks(**filters)
return res['networks'] return res[self.resource]
@test.attr(type='smoke') @test.attr(type='smoke')
@test.idempotent_id('a66b5cca-7db2-40f5-a33d-8ac9f864e53e') @test.idempotent_id('a66b5cca-7db2-40f5-a33d-8ac9f864e53e')
def test_filter_network_tags(self): def test_filter_network_tags(self):
self._test_filter_tags() self._test_filter_tags()
class TagFilterSubnetTestJSON(TagFilterTestJSON):
resource = 'subnets'
@classmethod
def _create_resource(cls, name):
network = cls.create_network()
res = cls.create_subnet(network, name=name)
return res['id']
def _list_resource(self, filters):
res = self.client.list_subnets(**filters)
return res[self.resource]
@test.attr(type='smoke')
@test.idempotent_id('dd8f9ba7-bcf6-496f-bead-714bd3daac10')
@test.requires_ext(extension="tag-ext", service="network")
def test_filter_subnet_tags(self):
self._test_filter_tags()
class TagFilterPortTestJSON(TagFilterTestJSON):
resource = 'ports'
@classmethod
def _create_resource(cls, name):
network = cls.create_network()
res = cls.create_port(network, name=name)
return res['id']
def _list_resource(self, filters):
res = self.client.list_ports(**filters)
return res[self.resource]
@test.attr(type='smoke')
@test.idempotent_id('09c036b8-c8d0-4bee-b776-7f4601512898')
@test.requires_ext(extension="tag-ext", service="network")
def test_filter_port_tags(self):
self._test_filter_tags()
class TagFilterSubnetpoolTestJSON(TagFilterTestJSON):
resource = 'subnetpools'
@classmethod
def _create_resource(cls, name):
res = cls.create_subnetpool(name, default_prefixlen=24,
prefixes=['10.0.0.0/8'])
return res['id']
def _list_resource(self, filters):
res = self.client.list_subnetpools(**filters)
return res[self.resource]
@test.attr(type='smoke')
@test.idempotent_id('16ae7ad2-55c2-4821-9195-bfd04ab245b7')
@test.requires_ext(extension="tag-ext", service="network")
def test_filter_subnetpool_tags(self):
self._test_filter_tags()
class TagFilterRouterTestJSON(TagFilterTestJSON):
resource = 'routers'
@classmethod
def _create_resource(cls, name):
res = cls.create_router(router_name=name)
return res['id']
def _list_resource(self, filters):
res = self.client.list_routers(**filters)
return res[self.resource]
@test.attr(type='smoke')
@test.idempotent_id('cdd3f3ea-073d-4435-a6cb-826a4064193d')
@test.requires_ext(extension="tag-ext", service="network")
def test_filter_router_tags(self):
self._test_filter_tags()

View File

@ -10,56 +10,100 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import testscenarios
from neutron.api import extensions from neutron.api import extensions
from neutron.api.v2 import attributes
from neutron.common import config from neutron.common import config
import neutron.extensions import neutron.extensions
from neutron.services.tag import tag_plugin from neutron.services.tag import tag_plugin
from neutron.tests import fake_notifier from neutron.tests import fake_notifier
from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.db import test_db_base_plugin_v2
from neutron.tests.unit.extensions import test_l3
load_tests = testscenarios.load_tests_apply_scenarios
extensions_path = ':'.join(neutron.extensions.__path__) extensions_path = ':'.join(neutron.extensions.__path__)
class TestTagApiBase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase): class TestTagApiBase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase,
test_l3.L3NatTestCaseMixin):
scenarios = [
('Network Tag Test',
dict(resource='networks',
member='network')),
('Subnet Tag Test',
dict(resource='subnets',
member='subnet')),
('Port Tag Test',
dict(resource='ports',
member='port')),
('Subnetpool Tag Test',
dict(resource='subnetpools',
member='subnetpool')),
('Router Tag Test',
dict(resource='routers',
member='router')),
]
def setUp(self): def setUp(self):
service_plugins = {'TAG': "neutron.services.tag.tag_plugin.TagPlugin"} service_plugins = {
'TAG': "neutron.services.tag.tag_plugin.TagPlugin",
'router':
"neutron.tests.unit.extensions.test_l3.TestL3NatServicePlugin"}
super(TestTagApiBase, self).setUp(service_plugins=service_plugins) super(TestTagApiBase, self).setUp(service_plugins=service_plugins)
plugin = tag_plugin.TagPlugin() plugin = tag_plugin.TagPlugin()
l3_plugin = test_l3.TestL3NatServicePlugin()
ext_mgr = extensions.PluginAwareExtensionManager( ext_mgr = extensions.PluginAwareExtensionManager(
extensions_path, {'TAG': plugin} extensions_path, {'router': l3_plugin, 'TAG': plugin}
) )
ext_mgr.extend_resources("2.0", attributes.RESOURCE_ATTRIBUTE_MAP)
app = config.load_paste_app('extensions_test_app') app = config.load_paste_app('extensions_test_app')
self.ext_api = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr) self.ext_api = extensions.ExtensionMiddleware(app, ext_mgr=ext_mgr)
def _get_resource_tags(self, resource_id): def _make_resource(self):
res = self._show(self.resource, resource_id) if self.resource == "networks":
res = self._make_network(self.fmt, 'net1', True)
elif self.resource == "subnets":
net = self._make_network(self.fmt, 'net1', True)
res = self._make_subnet(self.fmt, net, '10.0.0.1', '10.0.0.0/24')
elif self.resource == "ports":
net = self._make_network(self.fmt, 'net1', True)
res = self._make_port(self.fmt, net['network']['id'])
elif self.resource == "subnetpools":
res = self._make_subnetpool(self.fmt, ['10.0.0.0/8'],
name='my pool', tenant_id="tenant")
elif self.resource == "routers":
res = self._make_router(self.fmt, None)
return res[self.member]['id']
def _get_resource_tags(self):
res = self._show(self.resource, self.resource_id)
return res[self.member]['tags'] return res[self.member]['tags']
def _put_tag(self, resource_id, tag): def _put_tag(self, tag):
req = self._req('PUT', self.resource, id=resource_id, req = self._req('PUT', self.resource, id=self.resource_id,
subresource='tags', sub_id=tag) subresource='tags', sub_id=tag)
return req.get_response(self.ext_api) return req.get_response(self.ext_api)
def _put_tags(self, resource_id, tags): def _put_tags(self, tags):
body = {'tags': tags} body = {'tags': tags}
req = self._req('PUT', self.resource, data=body, id=resource_id, req = self._req('PUT', self.resource, data=body, id=self.resource_id,
subresource='tags') subresource='tags')
return req.get_response(self.ext_api) return req.get_response(self.ext_api)
def _get_tag(self, resource_id, tag): def _get_tag(self, tag):
req = self._req('GET', self.resource, id=resource_id, req = self._req('GET', self.resource, id=self.resource_id,
subresource='tags', sub_id=tag) subresource='tags', sub_id=tag)
return req.get_response(self.ext_api) return req.get_response(self.ext_api)
def _delete_tag(self, resource_id, tag): def _delete_tag(self, tag):
req = self._req('DELETE', self.resource, id=resource_id, req = self._req('DELETE', self.resource, id=self.resource_id,
subresource='tags', sub_id=tag) subresource='tags', sub_id=tag)
return req.get_response(self.ext_api) return req.get_response(self.ext_api)
def _delete_tags(self, resource_id): def _delete_tags(self):
req = self._req('DELETE', self.resource, id=resource_id, req = self._req('DELETE', self.resource, id=self.resource_id,
subresource='tags') subresource='tags')
return req.get_response(self.ext_api) return req.get_response(self.ext_api)
@ -83,9 +127,7 @@ class TestTagApiBase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
not_tags=None, not_tags_any=None): not_tags=None, not_tags_any=None):
params = self._make_query_string(tags, tags_any, not_tags, params = self._make_query_string(tags, tags_any, not_tags,
not_tags_any) not_tags_any)
req = self._req('GET', self.resource, params=params) res = self._list(self.resource, query_params=params)
res = req.get_response(self.api)
res = self.deserialize(self.fmt, res)
return res[self.resource] return res[self.resource]
def _test_notification_report(self, expect_notify): def _test_notification_report(self, expect_notify):
@ -96,168 +138,149 @@ class TestTagApiBase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
fake_notifier.reset() fake_notifier.reset()
class TestNetworkTagApi(TestTagApiBase): class TestResourceTagApi(TestTagApiBase):
resource = 'networks'
member = 'network' def setUp(self):
super(TestResourceTagApi, self).setUp()
self.resource_id = self._make_resource()
def test_put_tag(self): def test_put_tag(self):
expect_notify = set(['tag.create.start', expect_notify = set(['tag.create.start',
'tag.create.end']) 'tag.create.end'])
with self.network() as net: res = self._put_tag('red')
net_id = net['network']['id']
res = self._put_tag(net_id, 'red')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['red'], tags) self._assertEqualTags(['red'], tags)
self._test_notification_report(expect_notify) self._test_notification_report(expect_notify)
res = self._put_tag(net_id, 'blue') res = self._put_tag('blue')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['red', 'blue'], tags) self._assertEqualTags(['red', 'blue'], tags)
self._test_notification_report(expect_notify) self._test_notification_report(expect_notify)
def test_put_tag_exists(self): def test_put_tag_exists(self):
with self.network() as net: res = self._put_tag('blue')
net_id = net['network']['id']
res = self._put_tag(net_id, 'blue')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
res = self._put_tag(net_id, 'blue') res = self._put_tag('blue')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
def test_put_tags(self): def test_put_tags(self):
expect_notify = set(['tag.update.start', expect_notify = set(['tag.update.start',
'tag.update.end']) 'tag.update.end'])
with self.network() as net: res = self._put_tags(['red', 'green'])
net_id = net['network']['id']
res = self._put_tags(net_id, ['red', 'green'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['red', 'green'], tags) self._assertEqualTags(['red', 'green'], tags)
self._test_notification_report(expect_notify) self._test_notification_report(expect_notify)
def test_put_tags_replace(self): def test_put_tags_replace(self):
with self.network() as net: res = self._put_tags(['red', 'green'])
net_id = net['network']['id']
res = self._put_tags(net_id, ['red', 'green'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['red', 'green'], tags) self._assertEqualTags(['red', 'green'], tags)
res = self._put_tags(net_id, ['blue', 'red']) res = self._put_tags(['blue', 'red'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['blue', 'red'], tags) self._assertEqualTags(['blue', 'red'], tags)
def test_get_tag(self): def test_get_tag(self):
with self.network() as net: res = self._put_tag('red')
net_id = net['network']['id']
res = self._put_tag(net_id, 'red')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
res = self._get_tag(net_id, 'red') res = self._get_tag('red')
self.assertEqual(204, res.status_int) self.assertEqual(204, res.status_int)
def test_get_tag_notfound(self): def test_get_tag_notfound(self):
with self.network() as net: res = self._put_tag('red')
net_id = net['network']['id']
res = self._put_tag(net_id, 'red')
self.assertEqual(201, res.status_int) self.assertEqual(201, res.status_int)
res = self._get_tag(net_id, 'green') res = self._get_tag('green')
self.assertEqual(404, res.status_int) self.assertEqual(404, res.status_int)
def test_delete_tag(self): def test_delete_tag(self):
expect_notify = set(['tag.delete.start', expect_notify = set(['tag.delete.start',
'tag.delete.end']) 'tag.delete.end'])
with self.network() as net: res = self._put_tags(['red', 'green'])
net_id = net['network']['id']
res = self._put_tags(net_id, ['red', 'green'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
res = self._delete_tag(net_id, 'red') res = self._delete_tag('red')
self.assertEqual(204, res.status_int) self.assertEqual(204, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags(['green'], tags) self._assertEqualTags(['green'], tags)
self._test_notification_report(expect_notify) self._test_notification_report(expect_notify)
def test_delete_tag_notfound(self): def test_delete_tag_notfound(self):
with self.network() as net: res = self._put_tags(['red', 'green'])
net_id = net['network']['id']
res = self._put_tags(net_id, ['red', 'green'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
res = self._delete_tag(net_id, 'blue') res = self._delete_tag('blue')
self.assertEqual(404, res.status_int) self.assertEqual(404, res.status_int)
def test_delete_tags(self): def test_delete_tags(self):
expect_notify = set(['tag.delete_all.start', expect_notify = set(['tag.delete_all.start',
'tag.delete_all.end']) 'tag.delete_all.end'])
with self.network() as net: res = self._put_tags(['red', 'green'])
net_id = net['network']['id']
res = self._put_tags(net_id, ['red', 'green'])
self.assertEqual(200, res.status_int) self.assertEqual(200, res.status_int)
res = self._delete_tags(net_id) res = self._delete_tags()
self.assertEqual(204, res.status_int) self.assertEqual(204, res.status_int)
tags = self._get_resource_tags(net_id) tags = self._get_resource_tags()
self._assertEqualTags([], tags) self._assertEqualTags([], tags)
self._test_notification_report(expect_notify) self._test_notification_report(expect_notify)
class TestNetworkTagFilter(TestTagApiBase): class TestResourceTagFilter(TestTagApiBase):
resource = 'networks'
member = 'network'
def setUp(self): def setUp(self):
super(TestNetworkTagFilter, self).setUp() super(TestResourceTagFilter, self).setUp()
self._prepare_network_tags() self._prepare_resource_tags()
def _prepare_network_tags(self): def _make_tags(self, resource_id, tags):
res = self._make_network(self.fmt, 'net1', True) body = {'tags': tags}
net1_id = res['network']['id'] req = self._req('PUT', self.resource, data=body, id=resource_id,
res = self._make_network(self.fmt, 'net2', True) subresource='tags')
net2_id = res['network']['id'] return req.get_response(self.ext_api)
res = self._make_network(self.fmt, 'net3', True)
net3_id = res['network']['id']
res = self._make_network(self.fmt, 'net4', True)
net4_id = res['network']['id']
res = self._make_network(self.fmt, 'net5', True)
net5_id = res['network']['id']
self._put_tags(net1_id, ['red']) def _prepare_resource_tags(self):
self._put_tags(net2_id, ['red', 'blue']) self.res1 = self._make_resource()
self._put_tags(net3_id, ['red', 'blue', 'green']) self.res2 = self._make_resource()
self._put_tags(net4_id, ['green']) self.res3 = self._make_resource()
# net5: no tags self.res4 = self._make_resource()
tags = self._get_resource_tags(net5_id) self.res5 = self._make_resource()
self._assertEqualTags([], tags)
self._make_tags(self.res1, ['red'])
self._make_tags(self.res2, ['red', 'blue'])
self._make_tags(self.res3, ['red', 'blue', 'green'])
self._make_tags(self.res4, ['green'])
# res5: no tags
def _assertEqualResources(self, expected, res): def _assertEqualResources(self, expected, res):
actual = [n['name'] for n in res] actual = [n['id'] for n in res]
self.assertEqual(set(expected), set(actual)) self.assertEqual(set(expected), set(actual))
def test_filter_tags_single(self): def test_filter_tags_single(self):
res = self._get_tags_filter_resources(tags=['red']) res = self._get_tags_filter_resources(tags=['red'])
self._assertEqualResources(['net1', 'net2', 'net3'], res) self._assertEqualResources([self.res1, self.res2, self.res3], res)
def test_filter_tags_multi(self): def test_filter_tags_multi(self):
res = self._get_tags_filter_resources(tags=['red', 'blue']) res = self._get_tags_filter_resources(tags=['red', 'blue'])
self._assertEqualResources(['net2', 'net3'], res) self._assertEqualResources([self.res2, self.res3], res)
def test_filter_tags_any_single(self): def test_filter_tags_any_single(self):
res = self._get_tags_filter_resources(tags_any=['blue']) res = self._get_tags_filter_resources(tags_any=['blue'])
self._assertEqualResources(['net2', 'net3'], res) self._assertEqualResources([self.res2, self.res3], res)
def test_filter_tags_any_multi(self): def test_filter_tags_any_multi(self):
res = self._get_tags_filter_resources(tags_any=['red', 'blue']) res = self._get_tags_filter_resources(tags_any=['red', 'blue'])
self._assertEqualResources(['net1', 'net2', 'net3'], res) self._assertEqualResources([self.res1, self.res2, self.res3], res)
def test_filter_not_tags_single(self): def test_filter_not_tags_single(self):
res = self._get_tags_filter_resources(not_tags=['red']) res = self._get_tags_filter_resources(not_tags=['red'])
self._assertEqualResources(['net4', 'net5'], res) self._assertEqualResources([self.res4, self.res5], res)
def test_filter_not_tags_multi(self): def test_filter_not_tags_multi(self):
res = self._get_tags_filter_resources(not_tags=['red', 'blue']) res = self._get_tags_filter_resources(not_tags=['red', 'blue'])
self._assertEqualResources(['net1', 'net4', 'net5'], res) self._assertEqualResources([self.res1, self.res4, self.res5], res)
def test_filter_not_tags_any_single(self): def test_filter_not_tags_any_single(self):
res = self._get_tags_filter_resources(not_tags_any=['blue']) res = self._get_tags_filter_resources(not_tags_any=['blue'])
self._assertEqualResources(['net1', 'net4', 'net5'], res) self._assertEqualResources([self.res1, self.res4, self.res5], res)
def test_filter_not_tags_any_multi(self): def test_filter_not_tags_any_multi(self):
res = self._get_tags_filter_resources(not_tags_any=['red', 'blue']) res = self._get_tags_filter_resources(not_tags_any=['red', 'blue'])
self._assertEqualResources(['net4', 'net5'], res) self._assertEqualResources([self.res4, self.res5], res)

View File

@ -0,0 +1,4 @@
---
features:
- Resource tag mechanism now supports subnet, port, subnetpool and
router resources.