Merge "Add token related API to Keystone v3-ext/OS-OAUTH1 client"
diff --git a/tempest/api/identity/admin/v3/test_services.py b/tempest/api/identity/admin/v3/test_services.py
index ad4e549..20c8a44 100644
--- a/tempest/api/identity/admin/v3/test_services.py
+++ b/tempest/api/identity/admin/v3/test_services.py
@@ -77,17 +77,26 @@
     def test_list_services(self):
         # Create, List, Verify and Delete Services
         service_ids = list()
+        service_types = list()
         for _ in range(3):
-            name = data_utils.rand_name('service')
-            serv_type = data_utils.rand_name('type')
+            name = data_utils.rand_name(self.__class__.__name__ + '-Service')
+            serv_type = data_utils.rand_name(self.__class__.__name__ + '-Type')
             create_service = self.services_client.create_service(
                 type=serv_type, name=name)['service']
             self.addCleanup(self.services_client.delete_service,
                             create_service['id'])
             service_ids.append(create_service['id'])
+            service_types.append(serv_type)
 
         # List and Verify Services
         services = self.services_client.list_services()['services']
         fetched_ids = [service['id'] for service in services]
         found = [s for s in fetched_ids if s in service_ids]
         self.assertEqual(len(found), len(service_ids))
+
+        # Check that filtering by service type works.
+        for serv_type in service_types:
+            fetched_services = self.services_client.list_services(
+                type=serv_type)['services']
+            self.assertEqual(1, len(fetched_services))
+            self.assertEqual(serv_type, fetched_services[0]['type'])
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])
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index a33962b..5e63c0d 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -26,12 +26,8 @@
 import json
 import re
 
-try:
-    # For Python 3.0 and later
-    import urllib
-except ImportError:
-    # Fall back to Python 2's urllib2
-    import urllib2 as urllib
+from six.moves import urllib
+
 
 url = 'https://review.openstack.org/projects/'
 
@@ -55,18 +51,18 @@
 
 def has_tempest_plugin(proj):
     try:
-        r = urllib.urlopen("https://git.openstack.org/cgit/%s/plain/setup.cfg"
-                           % proj)
-    except urllib.HTTPError as err:
+        r = urllib.request.urlopen(
+            "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+    except urllib.error.HTTPError as err:
         if err.code == 404:
             return False
     p = re.compile('^tempest\.test_plugins', re.M)
-    if p.findall(r.read()):
+    if p.findall(r.read().decode('utf-8')):
         return True
     else:
         False
 
-r = urllib.urlopen(url)
+r = urllib.request.urlopen(url)
 # Gerrit prepends 4 garbage octets to the JSON, in order to counter
 # cross-site scripting attacks.  Therefore we must discard it so the
 # json library won't choke.