Merge "Add test case for reset group status"
diff --git a/HACKING.rst b/HACKING.rst
index 79ebc4d..8407734 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -103,10 +103,10 @@
 Service Tagging
 ---------------
 Service tagging is used to specify which services are exercised by a particular
-test method. You specify the services with the ``tempest.test.services``
+test method. You specify the services with the ``tempest.common.utils.services``
 decorator. For example:
 
-@services('compute', 'image')
+@utils.services('compute', 'image')
 
 Valid service tag names are the same as the list of directories in tempest.api
 that have tests.
diff --git a/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml b/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml
new file mode 100644
index 0000000..920bc5d
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    Remove two deprecated skip decorators in ``config`` module:
+    ``skip_unless_config`` and ``skip_if_config``.
diff --git a/requirements.txt b/requirements.txt
index 16223d6..911f0e5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,4 +22,4 @@
 os-testr>=1.0.0 # Apache-2.0
 urllib3>=1.21.1 # MIT
 debtcollector>=1.2.0 # Apache-2.0
-unittest2 # BSD
+unittest2>=1.1.0 # BSD
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index d4e7bd5..4d27a22 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -53,12 +53,11 @@
             ephemeral=ephemeral,
             swap=swap,
             rxtx_factor=rxtx)['flavor']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.admin_flavors_client.delete_flavor(cls.flavor['id'])
-        cls.admin_flavors_client.wait_for_resource_deletion(cls.flavor['id'])
-        super(FlavorsExtraSpecsTestJSON, cls).resource_cleanup()
+        cls.addClassResourceCleanup(
+            cls.admin_flavors_client.wait_for_resource_deletion,
+            cls.flavor['id'])
+        cls.addClassResourceCleanup(cls.admin_flavors_client.delete_flavor,
+                                    cls.flavor['id'])
 
     @decorators.idempotent_id('0b2f9d4b-1ca2-4b99-bb40-165d4bb94208')
     def test_flavor_set_get_update_show_unset_keys(self):
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index bc32346..5cde39e 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -55,12 +55,11 @@
             ephemeral=ephemeral,
             swap=swap,
             rxtx_factor=rxtx)['flavor']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.admin_flavors_client.delete_flavor(cls.flavor['id'])
-        cls.admin_flavors_client.wait_for_resource_deletion(cls.flavor['id'])
-        super(FlavorsExtraSpecsNegativeTestJSON, cls).resource_cleanup()
+        cls.addClassResourceCleanup(
+            cls.admin_flavors_client.wait_for_resource_deletion,
+            cls.flavor['id'])
+        cls.addClassResourceCleanup(cls.admin_flavors_client.delete_flavor,
+                                    cls.flavor['id'])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('a00a3b81-5641-45a8-ab2b-4a8ec41e1d7d')
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 5894e80..c2bdf7e 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -17,6 +17,7 @@
 from testtools import matchers
 
 from tempest.api.compute import base
+from tempest.common import identity
 from tempest.common import tempest_fixtures as fixtures
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -93,10 +94,11 @@
         # Verify that GET shows the updated quota set of project
         project_name = data_utils.rand_name('cpu_quota_project')
         project_desc = project_name + '-desc'
-        project = self.identity_utils.create_project(name=project_name,
-                                                     description=project_desc)
+        project = identity.identity_utils(self.os_admin).create_project(
+            name=project_name, description=project_desc)
         project_id = project['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
 
         self.adm_client.update_quota_set(project_id, ram='5120')
         quota_set = self.adm_client.show_quota_set(project_id)['quota_set']
@@ -106,12 +108,12 @@
         user_name = data_utils.rand_name('cpu_quota_user')
         password = data_utils.rand_password()
         email = user_name + '@testmail.tm'
-        user = self.identity_utils.create_user(username=user_name,
-                                               password=password,
-                                               project=project,
-                                               email=email)
+        user = identity.identity_utils(self.os_admin).create_user(
+            username=user_name, password=password, project=project,
+            email=email)
         user_id = user['id']
-        self.addCleanup(self.identity_utils.delete_user, user_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_user,
+                        user_id)
 
         self.adm_client.update_quota_set(project_id,
                                          user_id=user_id,
@@ -125,10 +127,11 @@
         # Admin can delete the resource quota set for a project
         project_name = data_utils.rand_name('ram_quota_project')
         project_desc = project_name + '-desc'
-        project = self.identity_utils.create_project(name=project_name,
-                                                     description=project_desc)
+        project = identity.identity_utils(self.os_admin).create_project(
+            name=project_name, description=project_desc)
         project_id = project['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
         quota_set_default = (self.adm_client.show_quota_set(project_id)
                              ['quota_set'])
         ram_default = quota_set_default['ram']
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 3656770..f720b84 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -61,7 +61,7 @@
         flavor_ref = self.create_flavor(ram=ram, vcpus=vcpus, disk=disk)
         self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
                           self.client.resize_server,
-                          self.servers[0]['id'],
+                          self.s1_id,
                           flavor_ref['id'])
 
     @decorators.idempotent_id('7368a427-2f26-4ad9-9ba9-911a0ec2b0db')
@@ -83,7 +83,7 @@
         flavor_ref = self.create_flavor(ram=ram, vcpus=vcpus, disk=disk)
         self.assertRaises((lib_exc.Forbidden, lib_exc.OverLimit),
                           self.client.resize_server,
-                          self.servers[0]['id'],
+                          self.s1_id,
                           flavor_ref['id'])
 
     @decorators.attr(type=['negative'])
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 1a31723..683d3e9 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -116,39 +116,6 @@
         cls.ssh_user = CONF.validation.image_ssh_user
         cls.image_ssh_user = CONF.validation.image_ssh_user
         cls.image_ssh_password = CONF.validation.image_ssh_password
-        cls.servers = []
-        cls.security_groups = []
-        cls.server_groups = []
-        cls.volumes = []
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.clear_servers()
-        cls.clear_resources('security groups', cls.security_groups,
-                            cls.security_groups_client.delete_security_group)
-        cls.clear_resources('server groups', cls.server_groups,
-                            cls.server_groups_client.delete_server_group)
-        cls.clear_volumes()
-        super(BaseV2ComputeTest, cls).resource_cleanup()
-
-    @classmethod
-    def clear_servers(cls):
-        LOG.debug('Clearing servers: %s', ','.join(
-            server['id'] for server in cls.servers))
-        for server in cls.servers:
-            try:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.servers_client.delete_server, server['id'])
-            except Exception:
-                LOG.exception('Deleting server %s failed', server['id'])
-
-        for server in cls.servers:
-            try:
-                waiters.wait_for_server_termination(cls.servers_client,
-                                                    server['id'])
-            except Exception:
-                LOG.exception('Waiting for deletion of server %s failed',
-                              server['id'])
 
     @classmethod
     def server_check_teardown(cls):
@@ -222,7 +189,15 @@
             volume_backed=volume_backed,
             **kwargs)
 
-        cls.servers.extend(servers)
+        # For each server schedule wait and delete, so we first delete all
+        # and then wait for all
+        for server in servers:
+            cls.addClassResourceCleanup(waiters.wait_for_server_termination,
+                                        cls.servers_client, server['id'])
+        for server in servers:
+            cls.addClassResourceCleanup(
+                test_utils.call_and_ignore_notfound_exc,
+                cls.servers_client.delete_server, server['id'])
 
         return body
 
@@ -234,7 +209,10 @@
             description = data_utils.rand_name('description')
         body = cls.security_groups_client.create_security_group(
             name=name, description=description)['security_group']
-        cls.security_groups.append(body['id'])
+        cls.addClassResourceCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.security_groups_client.delete_security_group,
+            body['id'])
 
         return body
 
@@ -246,7 +224,10 @@
             policy = ['affinity']
         body = cls.server_groups_client.create_server_group(
             name=name, policies=policy)['server_group']
-        cls.server_groups.append(body['id'])
+        cls.addClassResourceCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            cls.server_groups_client.delete_server_group,
+            body['id'])
         return body
 
     def wait_for(self, condition):
@@ -264,18 +245,6 @@
                 return
             time.sleep(self.build_interval)
 
-    @staticmethod
-    def _delete_volume(volumes_client, volume_id):
-        """Deletes the given volume and waits for it to be gone."""
-        try:
-            volumes_client.delete_volume(volume_id)
-            # TODO(mriedem): We should move the wait_for_resource_deletion
-            # into the delete_volume method as a convenience to the caller.
-            volumes_client.wait_for_resource_deletion(volume_id)
-        except lib_exc.NotFound:
-            LOG.warning("Unable to delete volume '%s' since it was not found. "
-                        "Maybe it was already deleted?", volume_id)
-
     @classmethod
     def prepare_instance_network(cls):
         if (CONF.validation.auth_method != 'disabled' and
@@ -383,7 +352,14 @@
     @classmethod
     def delete_volume(cls, volume_id):
         """Deletes the given volume and waits for it to be gone."""
-        cls._delete_volume(cls.volumes_client, volume_id)
+        try:
+            cls.volumes_client.delete_volume(volume_id)
+            # TODO(mriedem): We should move the wait_for_resource_deletion
+            # into the delete_volume method as a convenience to the caller.
+            cls.volumes_client.wait_for_resource_deletion(volume_id)
+        except lib_exc.NotFound:
+            LOG.warning("Unable to delete volume '%s' since it was not found. "
+                        "Maybe it was already deleted?", volume_id)
 
     @classmethod
     def get_server_ip(cls, server, validation_resources=None):
@@ -433,29 +409,15 @@
         if image_ref is not None:
             kwargs['imageRef'] = image_ref
         volume = cls.volumes_client.create_volume(**kwargs)['volume']
-        cls.volumes.append(volume)
+        cls.addClassResourceCleanup(
+            cls.volumes_client.wait_for_resource_deletion, volume['id'])
+        cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
+                                    cls.volumes_client.delete_volume,
+                                    volume['id'])
         waiters.wait_for_volume_resource_status(cls.volumes_client,
                                                 volume['id'], 'available')
         return volume
 
-    @classmethod
-    def clear_volumes(cls):
-        LOG.debug('Clearing volumes: %s', ','.join(
-            volume['id'] for volume in cls.volumes))
-        for volume in cls.volumes:
-            try:
-                test_utils.call_and_ignore_notfound_exc(
-                    cls.volumes_client.delete_volume, volume['id'])
-            except Exception:
-                LOG.exception('Deleting volume %s failed', volume['id'])
-
-        for volume in cls.volumes:
-            try:
-                cls.volumes_client.wait_for_resource_deletion(volume['id'])
-            except Exception:
-                LOG.exception('Waiting for deletion of volume %s failed',
-                              volume['id'])
-
     def attach_volume(self, server, volume, device=None, check_reserved=False):
         """Attaches volume to server and waits for 'in-use' volume status.
 
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 9ee0ac9..86e244b 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -45,7 +45,6 @@
     @classmethod
     def resource_setup(cls):
         super(FloatingIPsTestJSON, cls).resource_setup()
-        cls.floating_ip_id = None
 
         # Server creation
         server = cls.create_test_server(wait_until='ACTIVE')
@@ -53,16 +52,10 @@
         # Floating IP creation
         body = cls.client.create_floating_ip(
             pool=CONF.network.floating_network_name)['floating_ip']
+        cls.addClassResourceCleanup(cls.client.delete_floating_ip, body['id'])
         cls.floating_ip_id = body['id']
         cls.floating_ip = body['ip']
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Deleting the floating IP which is created in this method
-        if cls.floating_ip_id:
-            cls.client.delete_floating_ip(cls.floating_ip_id)
-        super(FloatingIPsTestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('f7bfb946-297e-41b8-9e8c-aba8e9bb5194')
     def test_allocate_floating_ip(self):
         # Positive test:Allocation of a new floating IP to a project
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 2314433..516c544 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -41,18 +41,12 @@
     def resource_setup(cls):
         super(FloatingIPDetailsTestJSON, cls).resource_setup()
         cls.floating_ip = []
-        cls.floating_ip_id = []
         for _ in range(3):
             body = cls.client.create_floating_ip(
                 pool=CONF.network.floating_network_name)['floating_ip']
+            cls.addClassResourceCleanup(cls.client.delete_floating_ip,
+                                        body['id'])
             cls.floating_ip.append(body)
-            cls.floating_ip_id.append(body['id'])
-
-    @classmethod
-    def resource_cleanup(cls):
-        for f_id in cls.floating_ip_id:
-            cls.client.delete_floating_ip(f_id)
-        super(FloatingIPDetailsTestJSON, cls).resource_cleanup()
 
     @decorators.idempotent_id('16db31c3-fb85-40c9-bbe2-8cf7b67ff99f')
     def test_list_floating_ips(self):
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 7fd1dd1..c9ee671 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -19,6 +19,7 @@
 from tempest.common import waiters
 from tempest import config
 from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -31,10 +32,6 @@
         super(ServersTestJSON, cls).setup_clients()
         cls.client = cls.servers_client
 
-    def tearDown(self):
-        self.clear_servers()
-        super(ServersTestJSON, self).tearDown()
-
     @decorators.idempotent_id('b92d5ec7-b1dd-44a2-87e4-45e888c46ef0')
     @testtools.skipUnless(CONF.compute_feature_enabled.
                           enable_instance_password,
@@ -43,6 +40,11 @@
         # If an admin password is provided on server creation, the server's
         # root password should be set to that password.
         server = self.create_test_server(adminPass='testpassword')
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, server['id'])
 
         # Verify the password is set correctly in the response
         self.assertEqual('testpassword', server['adminPass'])
@@ -57,9 +59,19 @@
         server = self.create_test_server(name=server_name,
                                          wait_until='ACTIVE')
         id1 = server['id']
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, id1)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, id1)
         server = self.create_test_server(name=server_name,
                                          wait_until='ACTIVE')
         id2 = server['id']
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, id2)
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, id2)
         self.assertNotEqual(id1, id2, "Did not create a new server")
         server = self.client.show_server(id1)['server']
         name1 = server['name']
@@ -76,6 +88,11 @@
         self.addCleanup(self.keypairs_client.delete_keypair, key_name)
         self.keypairs_client.list_keypairs()
         server = self.create_test_server(key_name=key_name)
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, server['id'])
         waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
         server = self.client.show_server(server['id'])['server']
         self.assertEqual(key_name, server['key_name'])
@@ -98,6 +115,11 @@
     def test_update_server_name(self):
         # The server name should be changed to the provided value
         server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, server['id'])
         # Update instance name with non-ASCII characters
         prefix_name = u'\u00CD\u00F1st\u00E1\u00F1c\u00E9'
         self._update_server_name(server['id'], 'ACTIVE', prefix_name)
@@ -115,6 +137,11 @@
     def test_update_access_server_address(self):
         # The server's access addresses should reflect the provided values
         server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, server['id'])
 
         # Update the IPv4 and IPv6 access addresses
         self.client.update_server(server['id'],
@@ -131,6 +158,11 @@
     def test_create_server_with_ipv6_addr_only(self):
         # Create a server without an IPv4 address(only IPv6 address).
         server = self.create_test_server(accessIPv6='2001:2001::3')
+        self.addCleanup(waiters.wait_for_server_termination,
+                        self.servers_client, server['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.servers_client.delete_server, server['id'])
         waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('2001:2001::3', server['accessIPv6'])
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index f69d7c5..cf4236d 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 from tempest.api.network import base
+from tempest.common import identity
 from tempest.common import utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
@@ -46,10 +47,11 @@
         # Add a project to conduct the test
         project = data_utils.rand_name('test_project_')
         description = data_utils.rand_name('desc_')
-        project = self.identity_utils.create_project(name=project,
-                                                     description=description)
+        project = identity.identity_utils(self.os_admin).create_project(
+            name=project, description=description)
         project_id = project['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
 
         # Change quotas for project
         quota_set = self.admin_quotas_client.update_quotas(
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index f745f9c..8cdb41e 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -16,6 +16,7 @@
 import testtools
 
 from tempest.api.network import base
+from tempest.common import identity
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -53,10 +54,11 @@
         # Test creating router from admin user setting project_id.
         project = data_utils.rand_name('test_tenant_')
         description = data_utils.rand_name('desc_')
-        project = self.identity_utils.create_project(name=project,
-                                                     description=description)
+        project = identity.identity_utils(self.os_admin).create_project(
+            name=project, description=description)
         project_id = project['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
 
         name = data_utils.rand_name('router-')
         create_body = self.admin_routers_client.create_router(
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
index f551575..75dca41 100644
--- a/tempest/api/volume/admin/test_volume_quota_classes.py
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -19,6 +19,7 @@
 from testtools import matchers
 
 from tempest.api.volume import base
+from tempest.common import identity
 from tempest.common import tempest_fixtures as fixtures
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -92,9 +93,10 @@
         # Verify a new project's default quotas.
         project_name = data_utils.rand_name('quota_class_tenant')
         description = data_utils.rand_name('desc_')
-        project_id = self.identity_utils.create_project(
+        project_id = identity.identity_utils(self.os_admin).create_project(
             name=project_name, description=description)['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
         default_quotas = self.admin_quotas_client.show_default_quota_set(
             project_id)['quota_set']
         self.assertThat(default_quotas.items(),
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 754104e..c4d3a40 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,6 +13,7 @@
 #    under the License.
 
 from tempest.api.volume import base
+from tempest.common import identity
 from tempest.common import tempest_fixtures as fixtures
 from tempest.common import waiters
 from tempest.lib.common.utils import data_utils
@@ -117,10 +118,11 @@
         # Admin can delete the resource quota set for a project
         project_name = data_utils.rand_name('quota_tenant')
         description = data_utils.rand_name('desc_')
-        project = self.identity_utils.create_project(project_name,
-                                                     description=description)
+        project = identity.identity_utils(self.os_admin).create_project(
+            project_name, description=description)
         project_id = project['id']
-        self.addCleanup(self.identity_utils.delete_project, project_id)
+        self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+                        project_id)
         quota_set_default = self.admin_quotas_client.show_default_quota_set(
             project_id)['quota_set']
         volume_default = quota_set_default['volumes']
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index 469defe..6e496d3 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -13,8 +13,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest import config
+from tempest.lib.common import cred_client
 from tempest.lib import exceptions as lib_exc
 
+CONF = config.CONF
+
 
 def get_tenant_by_name(client, tenant_name):
     tenants = client.list_tenants()['tenants']
@@ -30,3 +34,37 @@
         if user['name'] == username:
             return user
     raise lib_exc.NotFound('No such user(%s) in %s' % (username, users))
+
+
+def identity_utils(clients):
+    """A client that abstracts v2 and v3 identity operations.
+
+    This can be used for creating and tearing down projects in tests. It
+    should not be used for testing identity features.
+
+    :param clients: a client manager.
+    :return
+    """
+    if CONF.identity.auth_version == 'v2':
+        client = clients.identity_client
+        users_client = clients.users_client
+        project_client = clients.tenants_client
+        roles_client = clients.roles_client
+        domains_client = None
+    else:
+        client = clients.identity_v3_client
+        users_client = clients.users_v3_client
+        project_client = clients.projects_client
+        roles_client = clients.roles_v3_client
+        domains_client = clients.domains_client
+
+    try:
+        domain = client.auth_provider.credentials.project_domain_name
+    except AttributeError:
+        domain = CONF.auth.default_credentials_domain_name
+
+    return cred_client.get_creds_client(client, project_client,
+                                        users_client,
+                                        roles_client,
+                                        domains_client,
+                                        project_domain_name=domain)
diff --git a/tempest/config.py b/tempest/config.py
index e78a07f..4d0839a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,15 +15,12 @@
 
 from __future__ import print_function
 
-import functools
 import os
 import tempfile
 
-import debtcollector.removals
 from oslo_concurrency import lockutils
 from oslo_config import cfg
 from oslo_log import log as logging
-import testtools
 
 from tempest.lib import exceptions
 from tempest.lib.services import clients
@@ -1284,79 +1281,6 @@
 CONF = TempestConfigProxy()
 
 
-@debtcollector.removals.remove(
-    message='use testtools.skipUnless instead', removal_version='Queens')
-def skip_unless_config(*args):
-    """Decorator to raise a skip if a config opt doesn't exist or is False
-
-    :param str group: The first arg, the option group to check
-    :param str name: The second arg, the option name to check
-    :param str msg: Optional third arg, the skip msg to use if a skip is raised
-    :raises testtools.TestCaseskipException: If the specified config option
-        doesn't exist or it exists and evaluates to False
-    """
-    def decorator(f):
-        group = args[0]
-        name = args[1]
-
-        @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
-            if not hasattr(CONF, group):
-                msg = "Config group %s doesn't exist" % group
-                raise testtools.TestCase.skipException(msg)
-
-            conf_group = getattr(CONF, group)
-            if not hasattr(conf_group, name):
-                msg = "Config option %s.%s doesn't exist" % (group,
-                                                             name)
-                raise testtools.TestCase.skipException(msg)
-
-            value = getattr(conf_group, name)
-            if not value:
-                if len(args) == 3:
-                    msg = args[2]
-                else:
-                    msg = "Config option %s.%s is false" % (group,
-                                                            name)
-                raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
-        return wrapper
-    return decorator
-
-
-@debtcollector.removals.remove(
-    message='use testtools.skipIf instead', removal_version='Queens')
-def skip_if_config(*args):
-    """Raise a skipException if a config exists and is True
-
-    :param str group: The first arg, the option group to check
-    :param str name: The second arg, the option name to check
-    :param str msg: Optional third arg, the skip msg to use if a skip is raised
-    :raises testtools.TestCase.skipException: If the specified config option
-        exists and evaluates to True
-    """
-    def decorator(f):
-        group = args[0]
-        name = args[1]
-
-        @functools.wraps(f)
-        def wrapper(self, *func_args, **func_kwargs):
-            if hasattr(CONF, group):
-                conf_group = getattr(CONF, group)
-                if hasattr(conf_group, name):
-                    value = getattr(conf_group, name)
-                    if value:
-                        if len(args) == 3:
-                            msg = args[2]
-                        else:
-                            msg = "Config option %s.%s is false" % (group,
-                                                                    name)
-                        raise testtools.TestCase.skipException(msg)
-            return f(self, *func_args, **func_kwargs)
-        return wrapper
-    return decorator
-
-
 def service_client_config(service_client_name=None):
     """Return a dict with the parameters to init service clients
 
diff --git a/tempest/test.py b/tempest/test.py
index 7d95bcf..9da85d5 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -27,7 +27,6 @@
 from tempest.common import credentials_factory as credentials
 from tempest.common import utils
 from tempest import config
-from tempest.lib.common import cred_client
 from tempest.lib.common import fixed_network
 from tempest.lib.common import validation_resources as vr
 from tempest.lib import decorators
@@ -98,14 +97,15 @@
     - resource_cleanup
     """
 
-    setUpClassCalled = False
-
     # NOTE(andreaf) credentials holds a list of the credentials to be allocated
     # at class setup time. Credential types can be 'primary', 'alt', 'admin' or
     # a list of roles - the first element of the list being a label, and the
     # rest the actual roles
     credentials = []
 
+    # Track if setUpClass was invoked
+    __setupclass_called = False
+
     # Network resources to be provisioned for the requested test credentials.
     # Only used with the dynamic credentials provider.
     _network_resources = {}
@@ -131,20 +131,21 @@
     @classmethod
     def _reset_class(cls):
         cls.__setup_credentials_called = False
-        cls.__resource_cleaup_called = False
+        cls.__resource_cleanup_called = False
         cls.__skip_checks_called = False
+        # Stack of callable to be invoked in reverse order
         cls._class_cleanups = []
+        # Stack of (name, callable) to be invoked in reverse order at teardown
+        cls._teardowns = []
 
     @classmethod
     def setUpClass(cls):
+        cls.__setupclass_called = True
         # Reset state
         cls._reset_class()
         # It should never be overridden by descendants
         if hasattr(super(BaseTestCase, cls), 'setUpClass'):
             super(BaseTestCase, cls).setUpClass()
-        cls.setUpClassCalled = True
-        # Stack of (name, callable) to be invoked in reverse order at teardown
-        cls.teardowns = []
         # All the configuration checks that may generate a skip
         cls.skip_checks()
         if not cls.__skip_checks_called:
@@ -152,7 +153,7 @@
                                "skip_checks" % cls.__name__)
         try:
             # Allocation of all required credentials and client managers
-            cls.teardowns.append(('credentials', cls.clear_credentials))
+            cls._teardowns.append(('credentials', cls.clear_credentials))
             cls.setup_credentials()
             if not cls.__setup_credentials_called:
                 raise RuntimeError("setup_credentials for %s did not call the "
@@ -160,7 +161,7 @@
             # Shortcuts to clients
             cls.setup_clients()
             # Additional class-wide test resources
-            cls.teardowns.append(('resources', cls.resource_cleanup))
+            cls._teardowns.append(('resources', cls.resource_cleanup))
             cls.resource_setup()
         except Exception:
             etype, value, trace = sys.exc_info()
@@ -187,14 +188,14 @@
         # If there was no exception during setup we shall re-raise the first
         # exception in teardown
         re_raise = (etype is None)
-        while cls.teardowns:
-            name, teardown = cls.teardowns.pop()
+        while cls._teardowns:
+            name, teardown = cls._teardowns.pop()
             # Catch any exception in tearDown so we can re-raise the original
             # exception at the end
             try:
                 teardown()
                 if name == 'resources':
-                    if not cls.__resource_cleaup_called:
+                    if not cls.__resource_cleanup_called:
                         raise RuntimeError(
                             "resource_cleanup for %s did not call the "
                             "super's resource_cleanup" % cls.__name__)
@@ -300,12 +301,65 @@
     def setup_credentials(cls):
         """Allocate credentials and create the client managers from them.
 
-        For every element of credentials param function creates tenant/user,
-        Then it creates client manager for that credential.
+        `setup_credentials` looks for the content of the `credentials`
+        attribute in the test class. If the value is a non-empty collection,
+        a credentials provider is setup, and credentials are provisioned or
+        allocated based on the content of the collection. Every set of
+        credentials is associated to an object of type `cls.client_manager`.
+        The client manager is accessible by tests via class attribute
+        `os_[type]`:
 
-        Network related tests must override this function with
-        set_network_resources() method, otherwise it will create
-        network resources(network resources are created in a later step).
+        Valid values in `credentials` are:
+        - 'primary':
+            A normal user is provisioned.
+            It can be used only once. Multiple entries will be ignored.
+            Clients are available at os_primary.
+        - 'alt':
+            A normal user other than 'primary' is provisioned.
+            It can be used only once. Multiple entries will be ignored.
+            Clients are available at os_alt.
+        - 'admin':
+            An admin user is provisioned.
+            It can be used only once. Multiple entries will be ignored.
+            Clients are available at os_admin.
+        - A list in the format ['any_label', 'role1', ... , 'roleN']:
+            A client with roles <list>[1:] is provisioned.
+            It can be used multiple times, with unique labels.
+            Clients are available at os_roles_<list>[0].
+
+        By default network resources are allocated (in case of dynamic
+        credentials). Tests that do not need network or that require a
+        custom network setup must specify which network resources shall
+        be provisioned using the `set_network_resources()` method (note
+        that it must be invoked before the `setup_credentials` is
+        invoked on super).
+
+        Example::
+
+            class TestWithCredentials(test.BaseTestCase):
+
+                credentials = ['primary', 'admin',
+                               ['special', 'special_role1']]
+
+                @classmethod
+                def setup_credentials(cls):
+                    # set_network_resources must be called first
+                    cls.set_network_resources(network=True)
+                    super(TestWithCredentials, cls).setup_credentials()
+
+                @classmethod
+                def setup_clients(cls):
+                    cls.servers = cls.os_primary.compute.ServersClient()
+                    cls.admin_servers = cls.os_admin.compute.ServersClient()
+                    # certain API calls may require a user with a specific
+                    # role assigned. In this example `special_role1` is
+                    # assigned to the user in `cls.os_roles_special`.
+                    cls.special_servers = (
+                        cls.os_roles_special.compute.ServersClient())
+
+                def test_special_servers(self):
+                    # Do something with servers
+                    pass
         """
         cls.__setup_credentials_called = True
         for credentials_type in cls.credentials:
@@ -349,10 +403,53 @@
 
     @classmethod
     def setup_clients(cls):
-        """Create links to the clients into the test object."""
-        # TODO(andreaf) There is a fair amount of code that could me moved from
-        # base / test classes in here. Ideally tests should be able to only
-        # specify which client is `client` and nothing else.
+        """Create aliases to the clients in the client managers.
+
+        `setup_clients` is invoked after the credential provisioning step.
+        Client manager objects are available to tests already. The purpose
+        of this helper is to setup shortcuts to specific clients that are
+        useful for the tests implemented in the test class.
+
+        Its purpose is mostly for code readability, however it should be used
+        carefully to avoid doing exactly the opposite, i.e. making the code
+        unreadable and hard to debug. If aliases are defined in a super class
+        it won't be obvious what they refer to, so it's good practice to define
+        all aliases used in the class. Aliases are meant to be shortcuts to
+        be used in tests, not shortcuts to avoid helper method attributes.
+        If an helper method starts relying on a client alias and a subclass
+        overrides that alias, it will become rather difficult to understand
+        what the helper method actually does.
+
+        Example::
+
+            class TestDoneItRight(test.BaseTestCase):
+
+                credentials = ['primary', 'alt']
+
+                @classmethod
+                def setup_clients(cls):
+                    super(TestDoneItRight, cls).setup_clients()
+                    cls.servers = cls.os_primary.ServersClient()
+                    cls.servers_alt = cls.os_alt.ServersClient()
+
+                def _a_good_helper(self, clients):
+                    # Some complex logic we're going to use many times
+                    servers = clients.ServersClient()
+                    vm = servers.create_server(...)
+
+                    def delete_server():
+                        test_utils.call_and_ignore_notfound_exc(
+                            servers.delete_server, vm['id'])
+
+                    self.addCleanup(self.delete_server)
+                    return vm
+
+                def test_with_servers(self):
+                    vm = self._a_good_helper(os.primary)
+                    vm_alt = self._a_good_helper(os.alt)
+                    cls.servers.show_server(vm['id'])
+                    cls.servers_alt.show_server(vm_alt['id'])
+        """
         pass
 
     @classmethod
@@ -453,7 +550,7 @@
                     # At this point test credentials are still available but
                     # anything from the cleanup stack has been already deleted.
         """
-        cls.__resource_cleaup_called = True
+        cls.__resource_cleanup_called = True
         cleanup_errors = []
         while cls._class_cleanups:
             try:
@@ -483,7 +580,7 @@
 
     def setUp(self):
         super(BaseTestCase, self).setUp()
-        if not self.setUpClassCalled:
+        if not self.__setupclass_called:
             raise RuntimeError("setUpClass does not calls the super's"
                                "setUpClass in the "
                                + self.__class__.__name__)
@@ -514,37 +611,6 @@
     def credentials_provider(self):
         return self._get_credentials_provider()
 
-    @property
-    def identity_utils(self):
-        """A client that abstracts v2 and v3 identity operations.
-
-        This can be used for creating and tearing down projects in tests. It
-        should not be used for testing identity features.
-        """
-        if CONF.identity.auth_version == 'v2':
-            client = self.os_admin.identity_client
-            users_client = self.os_admin.users_client
-            project_client = self.os_admin.tenants_client
-            roles_client = self.os_admin.roles_client
-            domains_client = None
-        else:
-            client = self.os_admin.identity_v3_client
-            users_client = self.os_admin.users_v3_client
-            project_client = self.os_admin.projects_client
-            roles_client = self.os_admin.roles_v3_client
-            domains_client = self.os_admin.domains_client
-
-        try:
-            domain = client.auth_provider.credentials.project_domain_name
-        except AttributeError:
-            domain = 'Default'
-
-        return cred_client.get_creds_client(client, project_client,
-                                            users_client,
-                                            roles_client,
-                                            domains_client,
-                                            project_domain_name=domain)
-
     @classmethod
     def get_identity_version(cls):
         """Returns the identity version used by the test class"""
@@ -710,6 +776,9 @@
         specific network resources to be provisioned - none if no parameter
         is specified.
 
+        This method is designed so that only the network resources set on the
+        leaf class are honoured.
+
         Credentials are provisioned as part of the class setup fixture,
         during the `setup_credentials` step. For this to be effective this
         helper must be invoked before super's `setup_credentials` is executed.
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index bf04280..6018441 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -176,96 +176,3 @@
                           self._test_requires_ext_helper,
                           extension='enabled_ext',
                           service='bad_service')
-
-
-class TestConfigDecorators(BaseDecoratorsTest):
-    def setUp(self):
-        super(TestConfigDecorators, self).setUp()
-        cfg.CONF.set_default('nova', True, 'service_available')
-        cfg.CONF.set_default('glance', False, 'service_available')
-
-    def _assert_skip_message(self, func, skip_msg):
-        try:
-            func()
-            self.fail()
-        except testtools.TestCase.skipException as skip_exc:
-            self.assertEqual(skip_exc.args[0], skip_msg)
-
-    def _test_skip_unless_config(self, expected_to_skip=True, *decorator_args):
-
-        class TestFoo(test.BaseTestCase):
-            @config.skip_unless_config(*decorator_args)
-            def test_bar(self):
-                return 0
-
-        t = TestFoo('test_bar')
-        if expected_to_skip:
-            self.assertRaises(testtools.TestCase.skipException, t.test_bar)
-            if (len(decorator_args) >= 3):
-                # decorator_args[2]: skip message specified
-                self._assert_skip_message(t.test_bar, decorator_args[2])
-        else:
-            try:
-                self.assertEqual(t.test_bar(), 0)
-            except testtools.TestCase.skipException:
-                # We caught a skipException but we didn't expect to skip
-                # this test so raise a hard test failure instead.
-                raise testtools.TestCase.failureException(
-                    "Not supposed to skip")
-
-    def _test_skip_if_config(self, expected_to_skip=True,
-                             *decorator_args):
-
-        class TestFoo(test.BaseTestCase):
-            @config.skip_if_config(*decorator_args)
-            def test_bar(self):
-                return 0
-
-        t = TestFoo('test_bar')
-        if expected_to_skip:
-            self.assertRaises(testtools.TestCase.skipException, t.test_bar)
-            if (len(decorator_args) >= 3):
-                # decorator_args[2]: skip message specified
-                self._assert_skip_message(t.test_bar, decorator_args[2])
-        else:
-            try:
-                self.assertEqual(t.test_bar(), 0)
-            except testtools.TestCase.skipException:
-                # We caught a skipException but we didn't expect to skip
-                # this test so raise a hard test failure instead.
-                raise testtools.TestCase.failureException(
-                    "Not supposed to skip")
-
-    def test_skip_unless_no_group(self):
-        self._test_skip_unless_config(True, 'fake_group', 'an_option')
-
-    def test_skip_unless_no_option(self):
-        self._test_skip_unless_config(True, 'service_available',
-                                      'not_an_option')
-
-    def test_skip_unless_false_option(self):
-        self._test_skip_unless_config(True, 'service_available', 'glance')
-
-    def test_skip_unless_false_option_msg(self):
-        self._test_skip_unless_config(True, 'service_available', 'glance',
-                                      'skip message')
-
-    def test_skip_unless_true_option(self):
-        self._test_skip_unless_config(False,
-                                      'service_available', 'nova')
-
-    def test_skip_if_no_group(self):
-        self._test_skip_if_config(False, 'fake_group', 'an_option')
-
-    def test_skip_if_no_option(self):
-        self._test_skip_if_config(False, 'service_available', 'not_an_option')
-
-    def test_skip_if_false_option(self):
-        self._test_skip_if_config(False, 'service_available', 'glance')
-
-    def test_skip_if_true_option(self):
-        self._test_skip_if_config(True, 'service_available', 'nova')
-
-    def test_skip_if_true_option_msg(self):
-        self._test_skip_if_config(True, 'service_available', 'nova',
-                                  'skip message')
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index ead0bd8..fc50736 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
 import sys
 
 import mock
@@ -426,3 +427,200 @@
 
         with testtools.ExpectedException(testtools.testcase.TestSkipped):
             NeedV3().skip_checks()
+
+    def test_setup_credentials_all(self):
+        expected_creds = ['string', ['list', 'role1', 'role2']]
+
+        class AllCredentials(self.parent_test):
+            credentials = expected_creds
+
+        expected_clients = 'clients'
+        with mock.patch.object(
+                AllCredentials,
+                'get_client_manager') as mock_get_client_manager:
+            mock_get_client_manager.return_value = expected_clients
+            all_creds = AllCredentials()
+            all_creds.setup_credentials()
+        self.assertTrue(hasattr(all_creds, 'os_string'))
+        self.assertEqual(expected_clients, all_creds.os_string)
+        self.assertTrue(hasattr(all_creds, 'os_roles_list'))
+        self.assertEqual(expected_clients, all_creds.os_roles_list)
+        self.assertEqual(2, mock_get_client_manager.call_count)
+        self.assertEqual(
+            expected_creds[0],
+            mock_get_client_manager.mock_calls[0][2]['credential_type'])
+        self.assertEqual(
+            expected_creds[1][1:],
+            mock_get_client_manager.mock_calls[1][2]['roles'])
+
+    def test_setup_class_overwritten(self):
+
+        class OverridesSetup(self.parent_test):
+
+            @classmethod
+            def setUpClass(cls):  # noqa
+                pass
+
+        overrides_setup = OverridesSetup()
+        suite = unittest.TestSuite((overrides_setup,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # Record 0, test (error holder). The error generates during test run.
+        self.assertIn('runTest', str(log[0][0]))
+        # Record 0, traceback
+        self.assertRegex(
+            str(log[0][2]['traceback']).replace('\n', ' '),
+            RuntimeError.__name__ + ': .* ' + OverridesSetup.__name__)
+
+
+class TestTempestBaseTestClassFixtures(base.TestCase):
+
+    SETUP_FIXTURES = [test.BaseTestCase.setUpClass.__name__,
+                      test.BaseTestCase.skip_checks.__name__,
+                      test.BaseTestCase.setup_credentials.__name__,
+                      test.BaseTestCase.setup_clients.__name__,
+                      test.BaseTestCase.resource_setup.__name__]
+    TEARDOWN_FIXTURES = [test.BaseTestCase.tearDownClass.__name__,
+                         test.BaseTestCase.resource_cleanup.__name__,
+                         test.BaseTestCase.clear_credentials.__name__]
+
+    def setUp(self):
+        super(TestTempestBaseTestClassFixtures, self).setUp()
+        self.mocks = {}
+        for fix in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES:
+            self.mocks[fix] = mock.Mock()
+
+        def tracker_builder(name):
+
+            def tracker(cls):
+                # Track that the fixture was invoked
+                cls.fixtures_invoked.append(name)
+                # Run the fixture
+                getattr(super(TestWithClassFixtures, cls), name)()
+                # Run a mock we can use for side effects
+                self.mocks[name]()
+
+            return tracker
+
+        class TestWithClassFixtures(test.BaseTestCase):
+
+            credentials = []
+            fixtures_invoked = []
+
+            def runTest(_self):
+                pass
+
+        # Decorate all test class fixtures with tracker_builder
+        for method_name in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES:
+            setattr(TestWithClassFixtures, method_name,
+                    classmethod(tracker_builder(method_name)))
+
+        self.test = TestWithClassFixtures()
+
+    def test_no_error_flow(self):
+        # If all setup fixtures are executed, all cleanup fixtures are
+        # executed too
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+                         self.test.fixtures_invoked)
+
+    def test_skip_only(self):
+        # If a skip condition is hit in the test, no credentials or resource
+        # is provisioned / cleaned-up
+        self.mocks['skip_checks'].side_effect = (
+            testtools.testcase.TestSkipped())
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If we trigger a skip condition, teardown is not invoked at all
+        self.assertEqual(self.SETUP_FIXTURES[:2],
+                         self.test.fixtures_invoked)
+
+    def test_skip_credentials_fails(self):
+        expected_exc = 'sc exploded'
+        self.mocks['setup_credentials'].side_effect = Exception(expected_exc)
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If setup_credentials explodes, we invoked teardown class and
+        # clear credentials, and re-raise
+        self.assertEqual((self.SETUP_FIXTURES[:3] +
+                          [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+                         self.test.fixtures_invoked)
+        found_exc = log[0][1][1]
+        self.assertIn(expected_exc, str(found_exc))
+
+    def test_skip_credentials_fails_clear_fails(self):
+        # If cleanup fails on failure, we log the exception and do not
+        # re-raise it. Note that since the exception happens outside of
+        # the Tempest test setUp, logging is not captured on the Tempest
+        # test side, it will be captured by the unit test instead.
+        expected_exc = 'sc exploded'
+        clear_exc = 'clear exploded'
+        self.mocks['setup_credentials'].side_effect = Exception(expected_exc)
+        self.mocks['clear_credentials'].side_effect = Exception(clear_exc)
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If setup_credentials explodes, we invoked teardown class and
+        # clear credentials, and re-raise
+        self.assertEqual((self.SETUP_FIXTURES[:3] +
+                          [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+                         self.test.fixtures_invoked)
+        found_exc = log[0][1][1]
+        self.assertIn(expected_exc, str(found_exc))
+        # Since log capture depends on OS_LOG_CAPTURE, we can only assert if
+        # logging was captured
+        if os.environ.get('OS_LOG_CAPTURE'):
+            self.assertIn(clear_exc, self.log_fixture.logger.output)
+
+    def test_skip_credentials_clients_resources_credentials_clear_fails(self):
+        # If cleanup fails with no previous failure, we re-raise the exception.
+        expected_exc = 'clear exploded'
+        self.mocks['clear_credentials'].side_effect = Exception(expected_exc)
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If setup_credentials explodes, we invoked teardown class and
+        # clear credentials, and re-raise
+        self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+                         self.test.fixtures_invoked)
+        found_exc = log[0][1][1]
+        self.assertIn(expected_exc, str(found_exc))
+
+    def test_skip_credentials_clients_fails(self):
+        expected_exc = 'clients exploded'
+        self.mocks['setup_clients'].side_effect = Exception(expected_exc)
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If setup_clients explodes, we invoked teardown class and
+        # clear credentials, and re-raise
+        self.assertEqual((self.SETUP_FIXTURES[:4] +
+                          [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+                         self.test.fixtures_invoked)
+        found_exc = log[0][1][1]
+        self.assertIn(expected_exc, str(found_exc))
+
+    def test_skip_credentials_clients_resources_fails(self):
+        expected_exc = 'resource setup exploded'
+        self.mocks['resource_setup'].side_effect = Exception(expected_exc)
+        suite = unittest.TestSuite((self.test,))
+        log = []
+        result = LoggingTestResult(log)
+        suite.run(result)
+        # If resource_setup explodes, we invoked teardown class and
+        # clear credentials and resource cleanup, and re-raise
+        self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+                         self.test.fixtures_invoked)
+        found_exc = log[0][1][1]
+        self.assertIn(expected_exc, str(found_exc))