Add network tags-ext API tests

Adds network API tests for the tags-ext network extension. These
tests validates creating, listing, deleting, checking the existence
of, deleting all, and updating all tags for the following resources:
subnets, ports, routers and subnetpools.

The tests have been designed such that adding a new entry to
`SUPPORTED_RESOURCES` at the top of TagsExtTest and creating
the new resource in resource_setup should work -- resulting
in lower code maintenance.

Note that API tests for the networks resource has already been added
in this commit Icfff444ee7638a3220d228330f9162044673636c -- which is
part of the tag extension. This commit uses a separate class which
checks for the tag-ext extension, which all the other aforementioned
resources rely on.

Change-Id: I0df5aa7e3177f70a75fd76d811bed8553fb26c25
diff --git a/tempest/api/network/test_tags.py b/tempest/api/network/test_tags.py
index 1f3a7c4..567a462 100644
--- a/tempest/api/network/test_tags.py
+++ b/tempest/api/network/test_tags.py
@@ -14,11 +14,14 @@
 # under the License.
 
 from tempest.api.network import base
+from tempest import config
 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
 
+CONF = config.CONF
+
 
 class TagsTest(base.BaseNetworkTest):
     """Tests the following operations in the tags API:
@@ -88,3 +91,112 @@
             self.assertRaises(lib_exc.NotFound,
                               self.tags_client.check_tag_existence, 'networks',
                               self.network['id'], replace_tags[i])
+
+
+class TagsExtTest(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-ext extension allows users to
+    set tags on the following resources: subnets, ports, routers and
+    subnetpools.
+    """
+
+    # NOTE(felipemonteiro): The supported resource names are plural. Use
+    # the singular case for the corresponding class resource object.
+    SUPPORTED_RESOURCES = ['subnets', 'ports', 'routers', 'subnetpools']
+
+    @classmethod
+    def skip_checks(cls):
+        super(TagsExtTest, cls).skip_checks()
+        if not test.is_extension_enabled('tag-ext', 'network'):
+            msg = "tag-ext extension not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def resource_setup(cls):
+        super(TagsExtTest, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        cls.port = cls.create_port(cls.network)
+        cls.router = cls.create_router()
+
+        subnetpool_name = data_utils.rand_name(cls.__name__ + '-Subnetpool')
+        prefix = CONF.network.default_network
+        cls.subnetpool = cls.subnetpools_client.create_subnetpool(
+            name=subnetpool_name, prefixes=prefix)['subnetpool']
+
+    @classmethod
+    def resource_cleanup(cls):
+        cls.subnetpools_client.delete_subnetpool(cls.subnetpool['id'])
+        super(TagsExtTest, cls).resource_cleanup()
+
+    def _create_tags_for_each_resource(self):
+        # Create a tag for each resource in `SUPPORTED_RESOURCES` and return
+        # the list of tag names.
+        tag_names = []
+
+        for resource in self.SUPPORTED_RESOURCES:
+            tag_name = data_utils.rand_name(self.__class__.__name__ + '-Tag')
+            tag_names.append(tag_name)
+            resource_object = getattr(self, resource[:-1])
+
+            self.tags_client.create_tag(resource, resource_object['id'],
+                                        tag_name)
+            self.addCleanup(self.tags_client.delete_all_tags, resource,
+                            resource_object['id'])
+
+        return tag_names
+
+    @decorators.idempotent_id('c6231efa-9a89-4adf-b050-2a3156b8a1d9')
+    def test_create_check_list_and_delete_tags(self):
+        tag_names = self._create_tags_for_each_resource()
+
+        for i, resource in enumerate(self.SUPPORTED_RESOURCES):
+            # Ensure that a tag was created for each resource.
+            resource_object = getattr(self, resource[:-1])
+            retrieved_tags = self.tags_client.list_tags(
+                resource, resource_object['id'])['tags']
+            self.assertEqual(1, len(retrieved_tags))
+            self.assertEqual(tag_names[i], retrieved_tags[0])
+
+            # Check that the expected tag exists for each resource.
+            self.tags_client.check_tag_existence(
+                resource, resource_object['id'], tag_names[i])
+
+            # Delete the tag and ensure it was deleted.
+            self.tags_client.delete_tag(
+                resource, resource_object['id'], tag_names[i])
+            retrieved_tags = self.tags_client.list_tags(
+                resource, resource_object['id'])['tags']
+            self.assertEmpty(retrieved_tags)
+
+    @decorators.idempotent_id('663a90f5-f334-4b44-afe0-c5fc1d408791')
+    def test_update_and_delete_all_tags(self):
+        self._create_tags_for_each_resource()
+
+        for resource in self.SUPPORTED_RESOURCES:
+            # 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
+            # current resource has the 3 new tags.
+            resource_object = getattr(self, resource[:-1])
+            updated_tags = self.tags_client.update_all_tags(
+                resource, resource_object['id'], replace_tags)['tags']
+            self.assertEqual(3, len(updated_tags))
+            self.assertEqual(set(replace_tags), set(updated_tags))
+
+            # Delete all the tags and check that they have been removed.
+            self.tags_client.delete_all_tags(resource, resource_object['id'])
+            for i in range(3):
+                self.assertRaises(
+                    lib_exc.NotFound, self.tags_client.check_tag_existence,
+                    resource, resource_object['id'], replace_tags[i])