Merge "Add network tags client"
diff --git a/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml
new file mode 100644
index 0000000..9af57b1
--- /dev/null
+++ b/releasenotes/notes/network-tag-client-f4614029af7927f0.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Define v2.0 ``tags_client`` for the network service as a library
+ interface, allowing other projects to use this module as a stable
+ library without maintenance changes.
+
+ * tags_client(v2.0)
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 8775495..6bec0d7 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -83,6 +83,7 @@
cls.os_primary.security_group_rules_client)
cls.network_versions_client = cls.os_primary.network_versions_client
cls.service_providers_client = cls.os_primary.service_providers_client
+ cls.tags_client = cls.os_primary.tags_client
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
new file mode 100644
index 0000000..1f3a7c4
--- /dev/null
+++ b/tempest/api/network/test_tags.py
@@ -0,0 +1,90 @@
+# Copyright 2017 AT&T Corporation.
+# 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 tempest.api.network import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+
+class TagsTest(base.BaseNetworkTest):
+ """Tests the following operations in the tags API:
+
+ Update all tags.
+ Delete all tags.
+ Check tag existence.
+ Create a tag.
+ List tags.
+ Remove a tag.
+
+ v2.0 of the Neutron API is assumed. The tag extension allows users to set
+ tags on their networks. The extension supports networks only.
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(TagsTest, cls).skip_checks()
+ if not test.is_extension_enabled('tag', 'network'):
+ msg = "tag extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagsTest, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ @decorators.idempotent_id('ee76bfaf-ac94-4d74-9ecc-4bbd4c583cb1')
+ def test_create_list_show_update_delete_tags(self):
+ # Validate that creating a tag on a network resource works.
+ tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
+ self.tags_client.create_tag('networks', self.network['id'], tag_name)
+ self.addCleanup(self.tags_client.delete_all_tags, 'networks',
+ self.network['id'])
+ self.tags_client.check_tag_existence('networks', self.network['id'],
+ tag_name)
+
+ # Validate that listing tags on a network resource works.
+ retrieved_tags = self.tags_client.list_tags(
+ 'networks', self.network['id'])['tags']
+ self.assertEqual([tag_name], retrieved_tags)
+
+ # Generate 3 new tag names.
+ replace_tags = [data_utils.rand_name(
+ self.__class__.__name__ + '-Tag') for _ in range(3)]
+
+ # Replace the current tag with the 3 new tags and validate that the
+ # network resource has the 3 new tags.
+ updated_tags = self.tags_client.update_all_tags(
+ 'networks', self.network['id'], replace_tags)['tags']
+ self.assertEqual(3, len(updated_tags))
+ self.assertEqual(set(replace_tags), set(updated_tags))
+
+ # Delete the first tag and check that it has been removed.
+ self.tags_client.delete_tag(
+ 'networks', self.network['id'], replace_tags[0])
+ self.assertRaises(lib_exc.NotFound,
+ self.tags_client.check_tag_existence, 'networks',
+ self.network['id'], replace_tags[0])
+ for i in range(1, 3):
+ self.tags_client.check_tag_existence(
+ 'networks', self.network['id'], replace_tags[i])
+
+ # Delete all the remaining tags and check that they have been removed.
+ self.tags_client.delete_all_tags('networks', self.network['id'])
+ for i in range(1, 3):
+ self.assertRaises(lib_exc.NotFound,
+ self.tags_client.check_tag_existence, 'networks',
+ self.network['id'], replace_tags[i])
diff --git a/tempest/clients.py b/tempest/clients.py
index 7b6cc19..a941301 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -79,6 +79,7 @@
self.security_groups_client = self.network.SecurityGroupsClient()
self.network_versions_client = self.network.NetworkVersionsClient()
self.service_providers_client = self.network.ServiceProvidersClient()
+ self.tags_client = self.network.TagsClient()
def _set_image_clients(self):
if CONF.service_available.glance:
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index 19e5463..419e593 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -31,11 +31,12 @@
ServiceProvidersClient
from tempest.lib.services.network.subnetpools_client import SubnetpoolsClient
from tempest.lib.services.network.subnets_client import SubnetsClient
+from tempest.lib.services.network.tags_client import TagsClient
from tempest.lib.services.network.versions_client import NetworkVersionsClient
__all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
'MeteringLabelRulesClient', 'MeteringLabelsClient',
- 'NetworksClient', 'PortsClient', 'QuotasClient', 'RoutersClient',
- 'SecurityGroupRulesClient', 'SecurityGroupsClient',
- 'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient',
- 'NetworkVersionsClient']
+ 'NetworksClient', 'NetworkVersionsClient', 'PortsClient',
+ 'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient',
+ 'SecurityGroupsClient', 'ServiceProvidersClient',
+ 'SubnetpoolsClient', 'SubnetsClient', 'TagsClient']
diff --git a/tempest/lib/services/network/tags_client.py b/tempest/lib/services/network/tags_client.py
new file mode 100644
index 0000000..20c2c11
--- /dev/null
+++ b/tempest/lib/services/network/tags_client.py
@@ -0,0 +1,88 @@
+# Copyright 2017 AT&T Corporation.
+# 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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+from tempest.lib.services.network import base
+
+
+class TagsClient(base.BaseNetworkClient):
+
+ def create_tag(self, resource_type, resource_id, tag):
+ """Adds a tag on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#add-a-tag
+ """
+ # NOTE(felipemonteiro): Cannot use ``update_resource`` method because
+ # this API requires self.put but returns 201 instead of 200 expected
+ # by ``update_resource``.
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, _ = self.put(uri, json.dumps({}))
+ self.expected_success(201, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def check_tag_existence(self, resource_type, resource_id, tag):
+ """Confirm that a given tag is set on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#confirm-a-tag
+ """
+ # TODO(felipemonteiro): Use the "check_resource" method in
+ # ``BaseNetworkClient`` once it has been implemented.
+ uri = '%s/%s/%s/tags/%s' % (
+ self.uri_prefix, resource_type, resource_id, tag)
+ resp, _ = self.get(uri)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp)
+
+ def update_all_tags(self, resource_type, resource_id, tags):
+ """Replace all tags on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#replace-all-tags
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ put_body = {"tags": tags}
+ return self.update_resource(uri, put_body)
+
+ def delete_tag(self, resource_type, resource_id, tag):
+ """Removes a tag on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#remove-a-tag
+ """
+ uri = '/%s/%s/tags/%s' % (resource_type, resource_id, tag)
+ return self.delete_resource(uri)
+
+ def delete_all_tags(self, resource_type, resource_id):
+ """Removes all tags on the resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#remove-all-tags
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ return self.delete_resource(uri)
+
+ def list_tags(self, resource_type, resource_id):
+ """Retrieves the tags for a resource.
+
+ For more information, please refer to the official API reference:
+ http://developer.openstack.org/api-ref/networking/v2/index.html#obtain-tag-list
+ """
+ uri = '/%s/%s/tags' % (resource_type, resource_id)
+ return self.list_resources(uri)
diff --git a/tempest/tests/lib/services/network/test_tags_client.py b/tempest/tests/lib/services/network/test_tags_client.py
new file mode 100644
index 0000000..dbe50a0
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_tags_client.py
@@ -0,0 +1,123 @@
+# Copyright 2017 AT&T Corporation.
+# 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 tempest.lib.services.network import tags_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTagsClient(base.BaseServiceTest):
+
+ FAKE_TAGS = {
+ "tags": [
+ "red",
+ "blue"
+ ]
+ }
+
+ FAKE_RESOURCE_TYPE = 'network'
+
+ FAKE_RESOURCE_ID = '7a8f904b-c1ed-4446-a87d-60440c02934b'
+
+ def setUp(self):
+ super(TestTagsClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = tags_client.TagsClient(
+ fake_auth, 'network', 'regionOne')
+
+ def _test_update_all_tags(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.update_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ self.FAKE_TAGS,
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tags=self.FAKE_TAGS)
+
+ def _test_check_tag_existence(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_tag_existence,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ {},
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=204)
+
+ def _test_create_tag(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.create_tag,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=201)
+
+ def _test_list_tags(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_tags,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_TAGS,
+ bytes_body,
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID)
+
+ def test_update_all_tags_with_str_body(self):
+ self._test_update_all_tags()
+
+ def test_update_all_tags_with_bytes_body(self):
+ self._test_update_all_tags(bytes_body=True)
+
+ def test_delete_all_tags(self):
+ self.check_service_client_function(
+ self.client.delete_all_tags,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ status=204)
+
+ def test_check_tag_existence_with_str_body(self):
+ self._test_check_tag_existence()
+
+ def test_check_tag_existence_with_bytes_body(self):
+ self._test_check_tag_existence(bytes_body=True)
+
+ def test_create_tag_with_str_body(self):
+ self._test_create_tag()
+
+ def test_create_tag_with_bytes_body(self):
+ self._test_create_tag(bytes_body=True)
+
+ def test_list_tags_with_str_body(self):
+ self._test_list_tags()
+
+ def test_list_tags_with_bytes_body(self):
+ self._test_list_tags(bytes_body=True)
+
+ def test_delete_tag(self):
+ self.check_service_client_function(
+ self.client.delete_tag,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ resource_type=self.FAKE_RESOURCE_TYPE,
+ resource_id=self.FAKE_RESOURCE_ID,
+ tag=self.FAKE_TAGS['tags'][0],
+ status=204)