Merge "Hacking: enable H904"
diff --git a/.gitignore b/.gitignore
index 9292dbb..287db4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 ChangeLog
 *.pyc
 __pycache__/
+etc/accounts.yaml
 etc/tempest.conf
 etc/tempest.conf.sample
 etc/logging.conf
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 2edaddb..04ddfdf 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
 # Tempest documentation build configuration file, created by
 # sphinx-quickstart on Tue May 21 17:43:32 2013.
 #
diff --git a/releasenotes/notes/add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml b/releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
similarity index 100%
rename from releasenotes/notes/add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
rename to releasenotes/notes/14.0.0-add-cred-provider-abstract-class-to-lib-70ff513221f8a871.yaml
diff --git a/releasenotes/notes/add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml b/releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
similarity index 100%
rename from releasenotes/notes/add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
rename to releasenotes/notes/14.0.0-add-cred_client-to-tempest.lib-4d4af33f969c576f.yaml
diff --git a/releasenotes/notes/add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml b/releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
similarity index 100%
rename from releasenotes/notes/add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
rename to releasenotes/notes/14.0.0-add-error-code-translation-to-versions-clients-acbc78292e24b014.yaml
diff --git a/releasenotes/notes/add-image-clients-af94564fb34ddca6.yaml b/releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml
similarity index 100%
rename from releasenotes/notes/add-image-clients-af94564fb34ddca6.yaml
rename to releasenotes/notes/14.0.0-add-image-clients-af94564fb34ddca6.yaml
diff --git a/releasenotes/notes/add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
similarity index 100%
rename from releasenotes/notes/add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
rename to releasenotes/notes/14.0.0-add-role-assignments-client-as-a-library-d34b4fdf376984ad.yaml
diff --git a/releasenotes/notes/add-service-provider-client-cbba77d424a30dd3.yaml b/releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
similarity index 100%
rename from releasenotes/notes/add-service-provider-client-cbba77d424a30dd3.yaml
rename to releasenotes/notes/14.0.0-add-service-provider-client-cbba77d424a30dd3.yaml
diff --git a/releasenotes/notes/add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml b/releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
similarity index 100%
rename from releasenotes/notes/add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
rename to releasenotes/notes/14.0.0-add-ssh-port-parameter-to-client-6d16c374ac4456c1.yaml
diff --git a/releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml b/releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
similarity index 100%
rename from releasenotes/notes/deprecate-nova-api-extensions-df16b02485dae203.yaml
rename to releasenotes/notes/14.0.0-deprecate-nova-api-extensions-df16b02485dae203.yaml
diff --git a/releasenotes/notes/move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml b/releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
similarity index 100%
rename from releasenotes/notes/move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
rename to releasenotes/notes/14.0.0-move-cinder-v3-to-lib-service-be3ba0c20753b594.yaml
diff --git a/releasenotes/notes/new-volume-limit-client-517c17d9090f4df4.yaml b/releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
similarity index 100%
rename from releasenotes/notes/new-volume-limit-client-517c17d9090f4df4.yaml
rename to releasenotes/notes/14.0.0-new-volume-limit-client-517c17d9090f4df4.yaml
diff --git a/releasenotes/notes/remo-stress-tests-81052b211ad95d2e.yaml b/releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
similarity index 100%
rename from releasenotes/notes/remo-stress-tests-81052b211ad95d2e.yaml
rename to releasenotes/notes/14.0.0-remo-stress-tests-81052b211ad95d2e.yaml
diff --git a/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml b/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
new file mode 100644
index 0000000..ca2635e
--- /dev/null
+++ b/releasenotes/notes/14.0.0-remove-baremetal-tests-65186d9e15d5b8fb.yaml
@@ -0,0 +1,4 @@
+---
+upgrade:
+  - All tests for the Ironic project have been removed from Tempest. Those
+    exist as a Tempest plugin in the Ironic project.
diff --git a/releasenotes/notes/remove-bootable-option-024f8944c056a3e0.yaml b/releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
similarity index 100%
rename from releasenotes/notes/remove-bootable-option-024f8944c056a3e0.yaml
rename to releasenotes/notes/14.0.0-remove-bootable-option-024f8944c056a3e0.yaml
diff --git a/releasenotes/notes/remove-negative-test-generator-1653f4c0f86ccf75.yaml b/releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
similarity index 100%
rename from releasenotes/notes/remove-negative-test-generator-1653f4c0f86ccf75.yaml
rename to releasenotes/notes/14.0.0-remove-negative-test-generator-1653f4c0f86ccf75.yaml
diff --git a/releasenotes/notes/remove-sahara-tests-1532c47c7df80e3a.yaml b/releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
similarity index 100%
rename from releasenotes/notes/remove-sahara-tests-1532c47c7df80e3a.yaml
rename to releasenotes/notes/14.0.0-remove-sahara-tests-1532c47c7df80e3a.yaml
diff --git a/releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml b/releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
similarity index 100%
rename from releasenotes/notes/13.1.0-volume-clients-as-library-309030c7a16e62ab.yaml
rename to releasenotes/notes/14.0.0-volume-clients-as-library-309030c7a16e62ab.yaml
diff --git a/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml b/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
new file mode 100644
index 0000000..1af1939
--- /dev/null
+++ b/releasenotes/notes/add-identity-v3-clients-as-a-library-d34b4fdf376984ad.yaml
@@ -0,0 +1,6 @@
+---
+features:
+    - Define the identity v3 service client domains_client as a library.
+      Add domains_client to the library interface so the other
+      projects can use this module as a stable library without any
+      maintenance changes.
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 23b16e7..206260f 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -72,8 +72,8 @@
             raise self.skipException("ram quota set is -1,"
                                      " cannot test overlimit")
         ram += 1
-        vcpus = 8
-        disk = 10
+        vcpus = 1
+        disk = 5
         flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
                                                        ram=ram, vcpus=vcpus,
                                                        disk=disk,
@@ -93,7 +93,6 @@
         self.useFixture(fixtures.LockFixture('compute_quotas'))
         flavor_name = data_utils.rand_name("flavor")
         flavor_id = self._get_unused_flavor_id()
-        ram = 512
         quota_set = self.quotas_client.show_quota_set(
             self.tenant_id)['quota_set']
         vcpus = int(quota_set['cores'])
@@ -101,7 +100,8 @@
             raise self.skipException("cores quota set is -1,"
                                      " cannot test overlimit")
         vcpus += 1
-        disk = 10
+        ram = 512
+        disk = 5
         flavor_ref = self.flavors_client.create_flavor(name=flavor_name,
                                                        ram=ram, vcpus=vcpus,
                                                        disk=disk,
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index d2e31ad..2dcacb7 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -253,24 +253,18 @@
             self.flavor_ref)['flavor']
 
         def create_flavor_with_ephemeral(ephem_disk):
-            flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
+            flavor_id = data_utils.rand_int_id(start=1000)
+            name = 'flavor_with_ephemeral_%s' % ephem_disk
+            flavor_name = data_utils.rand_name(name)
 
             ram = flavor_base['ram']
             vcpus = flavor_base['vcpus']
             disk = flavor_base['disk']
 
-            if ephem_disk > 0:
-                # Create a flavor with ephemeral disk
-                flavor_name = data_utils.rand_name('eph_flavor')
-                flavor = self.flavor_client.create_flavor(
-                    name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
-                    id=flavor_with_eph_disk_id, ephemeral=ephem_disk)['flavor']
-            else:
-                # Create a flavor without ephemeral disk
-                flavor_name = data_utils.rand_name('no_eph_flavor')
-                flavor = self.flavor_client.create_flavor(
-                    name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
-                    id=flavor_with_eph_disk_id)['flavor']
+            # Create a flavor with ephemeral disk
+            flavor = self.flavor_client.create_flavor(
+                name=flavor_name, ram=ram, vcpus=vcpus, disk=disk,
+                id=flavor_id, ephemeral=ephem_disk)['flavor']
             self.addCleanup(flavor_clean_up, flavor['id'])
 
             return flavor['id']
diff --git a/tempest/api/identity/admin/v3/test_users.py b/tempest/api/identity/admin/v3/test_users.py
index fd2683e..3ec4ff1 100644
--- a/tempest/api/identity/admin/v3/test_users.py
+++ b/tempest/api/identity/admin/v3/test_users.py
@@ -15,11 +15,17 @@
 
 import time
 
+import testtools
+
 from tempest.api.identity import base
 from tempest.common.utils import data_utils
+from tempest import config
 from tempest import test
 
 
+CONF = config.CONF
+
+
 class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
 
     @test.idempotent_id('b537d090-afb9-4519-b95d-270b0708e87e')
@@ -152,3 +158,30 @@
         user = self.setup_test_user()
         fetched_user = self.users_client.show_user(user['id'])['user']
         self.assertEqual(user['id'], fetched_user['id'])
+
+    @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+                          'Security compliance not available.')
+    @test.idempotent_id('568cd46c-ee6c-4ab4-a33a-d3791931979e')
+    def test_password_history_not_enforced_in_admin_reset(self):
+        old_password = self.os.credentials.password
+        user_id = self.os.credentials.user_id
+
+        new_password = data_utils.rand_password()
+        self.users_client.update_user(user_id, password=new_password)
+        # To be safe, we add this cleanup to restore the original password in
+        # case something goes wrong before it is restored later.
+        self.addCleanup(
+            self.users_client.update_user, user_id, password=old_password)
+
+        # Check authorization with new password
+        self.token.auth(user_id=user_id, password=new_password)
+
+        if CONF.identity.user_unique_last_password_count > 1:
+            # The password history is not enforced via the admin reset route.
+            # We can set the same password.
+            self.users_client.update_user(user_id, password=new_password)
+
+        # Restore original password
+        self.users_client.update_user(user_id, password=old_password)
+        # Check authorization with old password
+        self.token.auth(user_id=user_id, password=old_password)
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 33d212c..bafb1f2 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -16,11 +16,15 @@
 import time
 
 from tempest.api.identity import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions
 from tempest import test
 
 
+CONF = config.CONF
+
+
 class IdentityUsersTest(base.BaseIdentityV2Test):
 
     @classmethod
@@ -31,36 +35,10 @@
         cls.password = cls.creds.password
         cls.tenant_name = cls.creds.tenant_name
 
-    @test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
-    def test_user_update_own_password(self):
-
-        def _restore_password(client, user_id, old_pass, new_pass):
-            # Reset auth to get a new token with the new password
-            client.auth_provider.clear_auth()
-            client.auth_provider.credentials.password = new_pass
-            client.update_user_own_password(user_id, password=old_pass,
-                                            original_password=new_pass)
-            # Reset auth again to verify the password restore does work.
-            # Clear auth restores the original credentials and deletes
-            # cached auth data
-            client.auth_provider.clear_auth()
-            # NOTE(lbragstad): Fernet tokens are not subsecond aware and
-            # Keystone should only be precise to the second. Sleep to ensure we
-            # are passing the second boundary before attempting to
-            # authenticate.
-            time.sleep(1)
-            client.auth_provider.set_auth()
-
-        old_pass = self.creds.password
-        new_pass = data_utils.rand_password()
-        user_id = self.creds.user_id
-        # to change password back. important for allow_tenant_isolation = false
-        self.addCleanup(_restore_password, self.non_admin_users_client,
-                        user_id, old_pass=old_pass, new_pass=new_pass)
-
-        # user updates own password
+    def _update_password(self, user_id, original_password, password):
         self.non_admin_users_client.update_user_own_password(
-            user_id, password=new_pass, original_password=old_pass)
+            user_id, password=password, original_password=original_password)
+
         # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
         # Keystone should only be precise to the second. Sleep to ensure
         # we are passing the second boundary.
@@ -68,13 +46,55 @@
 
         # check authorization with new password
         self.non_admin_token_client.auth(self.username,
-                                         new_pass,
+                                         password,
                                          self.tenant_name)
 
+        # Reset auth to get a new token with the new password
+        self.non_admin_users_client.auth_provider.clear_auth()
+        self.non_admin_users_client.auth_provider.credentials.password = (
+            password)
+
+    def _restore_password(self, user_id, old_pass, new_pass):
+        if CONF.identity_feature_enabled.security_compliance:
+            # First we need to clear the password history
+            unique_count = CONF.identity.user_unique_last_password_count
+            for i in range(unique_count):
+                random_pass = data_utils.rand_password()
+                self._update_password(
+                    user_id, original_password=new_pass, password=random_pass)
+                new_pass = random_pass
+
+        self._update_password(
+            user_id, original_password=new_pass, password=old_pass)
+        # Reset auth again to verify the password restore does work.
+        # Clear auth restores the original credentials and deletes
+        # cached auth data
+        self.non_admin_users_client.auth_provider.clear_auth()
+        # NOTE(lbragstad): Fernet tokens are not subsecond aware and
+        # Keystone should only be precise to the second. Sleep to ensure we
+        # are passing the second boundary before attempting to
+        # authenticate.
+        time.sleep(1)
+        self.non_admin_users_client.auth_provider.set_auth()
+
+    @test.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
+    def test_user_update_own_password(self):
+        old_pass = self.creds.password
+        old_token = self.non_admin_users_client.token
+        new_pass = data_utils.rand_password()
+        user_id = self.creds.user_id
+
+        # to change password back. important for allow_tenant_isolation = false
+        self.addCleanup(self._restore_password, user_id, old_pass, new_pass)
+
+        # user updates own password
+        self._update_password(
+            user_id, original_password=old_pass, password=new_pass)
+
         # authorize with old token should lead to Unauthorized
         self.assertRaises(exceptions.Unauthorized,
                           self.non_admin_token_client.auth_token,
-                          self.non_admin_users_client.token)
+                          old_token)
 
         # authorize with old password should lead to Unauthorized
         self.assertRaises(exceptions.Unauthorized,
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 1a38f3a..f5b357c 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -15,12 +15,18 @@
 
 import time
 
+import testtools
+
 from tempest.api.identity import base
+from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import exceptions
 from tempest import test
 
 
+CONF = config.CONF
+
+
 class IdentityV3UsersTest(base.BaseIdentityV3Test):
 
     @classmethod
@@ -31,36 +37,11 @@
         cls.username = cls.creds.username
         cls.password = cls.creds.password
 
-    @test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
-    def test_user_update_own_password(self):
-
-        def _restore_password(client, user_id, old_pass, new_pass):
-            # Reset auth to get a new token with the new password
-            client.auth_provider.clear_auth()
-            client.auth_provider.credentials.password = new_pass
-            client.update_user_password(user_id, password=old_pass,
-                                        original_password=new_pass)
-            # Reset auth again to verify the password restore does work.
-            # Clear auth restores the original credentials and deletes
-            # cached auth data
-            client.auth_provider.clear_auth()
-            # NOTE(lbragstad): Fernet tokens are not subsecond aware and
-            # Keystone should only be precise to the second. Sleep to ensure we
-            # are passing the second boundary before attempting to
-            # authenticate.
-            time.sleep(1)
-            client.auth_provider.set_auth()
-
-        old_pass = self.creds.password
-        new_pass = data_utils.rand_password()
-        user_id = self.creds.user_id
-        # to change password back. important for allow_tenant_isolation = false
-        self.addCleanup(_restore_password, self.non_admin_users_client,
-                        user_id, old_pass=old_pass, new_pass=new_pass)
-
-        # user updates own password
+    def _update_password(self, original_password, password):
         self.non_admin_users_client.update_user_password(
-            user_id, password=new_pass, original_password=old_pass)
+            self.user_id,
+            password=password,
+            original_password=original_password)
 
         # NOTE(morganfainberg): Fernet tokens are not subsecond aware and
         # Keystone should only be precise to the second. Sleep to ensure
@@ -68,15 +49,112 @@
         time.sleep(1)
 
         # check authorization with new password
-        self.non_admin_token.auth(user_id=self.user_id, password=new_pass)
+        self.non_admin_token.auth(user_id=self.user_id, password=password)
+
+        # Reset auth to get a new token with the new password
+        self.non_admin_users_client.auth_provider.clear_auth()
+        self.non_admin_users_client.auth_provider.credentials.password = (
+            password)
+
+    def _restore_password(self, old_pass, new_pass):
+        if CONF.identity_feature_enabled.security_compliance:
+            # First we need to clear the password history
+            unique_count = CONF.identity.user_unique_last_password_count
+            for i in range(unique_count):
+                random_pass = data_utils.rand_password()
+                self._update_password(
+                    original_password=new_pass, password=random_pass)
+                new_pass = random_pass
+
+        self._update_password(original_password=new_pass, password=old_pass)
+        # Reset auth again to verify the password restore does work.
+        # Clear auth restores the original credentials and deletes
+        # cached auth data
+        self.non_admin_users_client.auth_provider.clear_auth()
+        # NOTE(lbragstad): Fernet tokens are not subsecond aware and
+        # Keystone should only be precise to the second. Sleep to ensure we
+        # are passing the second boundary before attempting to
+        # authenticate.
+        time.sleep(1)
+        self.non_admin_users_client.auth_provider.set_auth()
+
+    @test.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
+    def test_user_update_own_password(self):
+        old_pass = self.creds.password
+        old_token = self.non_admin_client.token
+        new_pass = data_utils.rand_password()
+
+        # to change password back. important for allow_tenant_isolation = false
+        self.addCleanup(self._restore_password, old_pass, new_pass)
+
+        # user updates own password
+        self._update_password(original_password=old_pass, password=new_pass)
 
         # authorize with old token should lead to IdentityError (404 code)
         self.assertRaises(exceptions.IdentityError,
                           self.non_admin_token.auth,
-                          token=self.non_admin_client.token)
+                          token=old_token)
 
         # authorize with old password should lead to Unauthorized
         self.assertRaises(exceptions.Unauthorized,
                           self.non_admin_token.auth,
                           user_id=self.user_id,
                           password=old_pass)
+
+    @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+                          'Security compliance not available.')
+    @test.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
+    def test_password_history_check_self_service_api(self):
+        old_pass = self.creds.password
+        new_pass1 = data_utils.rand_password()
+        new_pass2 = data_utils.rand_password()
+
+        self.addCleanup(self._restore_password, old_pass, new_pass2)
+
+        # Update password
+        self._update_password(original_password=old_pass, password=new_pass1)
+
+        if CONF.identity.user_unique_last_password_count > 1:
+            # Can not reuse a previously set password
+            self.assertRaises(exceptions.BadRequest,
+                              self.non_admin_users_client.update_user_password,
+                              self.user_id,
+                              password=new_pass1,
+                              original_password=new_pass1)
+
+            self.assertRaises(exceptions.BadRequest,
+                              self.non_admin_users_client.update_user_password,
+                              self.user_id,
+                              password=old_pass,
+                              original_password=new_pass1)
+
+        # A different password can be set
+        self._update_password(original_password=new_pass1, password=new_pass2)
+
+    @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
+                          'Security compliance not available.')
+    @test.idempotent_id('a7ad8bbf-2cff-4520-8c1d-96332e151658')
+    def test_user_account_lockout(self):
+        password = self.creds.password
+
+        # First, we login using the correct credentials
+        self.non_admin_token.auth(user_id=self.user_id, password=password)
+
+        # Lock user account by using the wrong password to login
+        bad_password = data_utils.rand_password()
+        for i in range(CONF.identity.user_lockout_failure_attempts):
+            self.assertRaises(exceptions.Unauthorized,
+                              self.non_admin_token.auth,
+                              user_id=self.user_id,
+                              password=bad_password)
+
+        # The user account must be locked, so now it is not possible to login
+        # even using the correct password
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_token.auth,
+                          user_id=self.user_id,
+                          password=password)
+
+        # If we wait the required time, the user account will be unlocked
+        time.sleep(CONF.identity.user_lockout_duration + 1)
+        self.token.auth(user_id=self.user_id, password=password)
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index 50f0926..9c211ef 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -14,6 +14,7 @@
 
 
 from tempest.api.image import base
+from tempest.lib import exceptions as lib_exc
 from tempest import test
 
 
@@ -54,3 +55,5 @@
         body = self.image_member_client.list_image_members(image_id)
         members = body['members']
         self.assertEqual(0, len(members), str(members))
+        self.assertRaises(
+            lib_exc.NotFound, self.alt_img_cli.show_image, image_id)
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index 9fbdcd7..0caaa67 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -36,7 +36,7 @@
         msg = ("The container format and the disk format don't match. "
                "Container format: %(container)s, Disk format: %(disk)s." %
                {'container': container_format, 'disk': disk_format})
-        raise exceptions.InvalidConfiguration(message=msg)
+        raise exceptions.InvalidConfiguration(msg)
 
     return container_format, disk_format
 
@@ -54,7 +54,6 @@
                                   disk_format=disk_format,
                                   is_public=False,
                                   properties=properties)
-        self.assertIn('id', image)
         self.assertEqual('New Name', image.get('name'))
         self.assertFalse(image.get('is_public'))
         self.assertEqual('queued', image.get('status'))
@@ -77,7 +76,6 @@
                                  location=CONF.image.http_image,
                                  properties={'key1': 'value1',
                                              'key2': 'value2'})
-        self.assertIn('id', body)
         self.assertEqual('New Remote Image', body.get('name'))
         self.assertFalse(body.get('is_public'))
         self.assertEqual('active', body.get('status'))
@@ -92,7 +90,6 @@
                                   container_format=container_format,
                                   disk_format=disk_format, is_public=False,
                                   copy_from=CONF.image.http_image)
-        self.assertIn('id', image)
         self.assertEqual('New Http Image', image.get('name'))
         self.assertFalse(image.get('is_public'))
         waiters.wait_for_image_status(self.client, image['id'], 'active')
@@ -109,7 +106,6 @@
                                  is_public=False,
                                  min_ram=40,
                                  properties=properties)
-        self.assertIn('id', body)
         self.assertEqual('New_image_with_min_ram', body.get('name'))
         self.assertFalse(body.get('is_public'))
         self.assertEqual('queued', body.get('status'))
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 7b9244b..453bb34 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -49,7 +49,6 @@
                                   disk_format=disk_format,
                                   visibility='private',
                                   ramdisk_id=uuid)
-        self.assertIn('id', image)
         self.assertIn('name', image)
         self.assertEqual(image_name, image['name'])
         self.assertIn('visibility', image)
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index 5b46088..e7153f0 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -108,7 +108,7 @@
         if ((address.version == 4 and address.prefixlen >= 30) or
            (address.version == 6 and address.prefixlen >= 126)):
             msg = ("Subnet %s isn't large enough for the test" % address.cidr)
-            raise exceptions.InvalidConfiguration(message=msg)
+            raise exceptions.InvalidConfiguration(msg)
         allocation_pools = {'allocation_pools': [{'start': str(address[2]),
                                                   'end': str(address[-2])}]}
         subnet = self.create_subnet(network, cidr=address,
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index eb313d2..535137e 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -25,6 +25,38 @@
 CONF = config.CONF
 
 
+def delete_containers(containers, container_client, object_client):
+    """Remove containers and all objects in them.
+
+    The containers should be visible from the container_client given.
+    Will not throw any error if the containers don't exist.
+    Will not check that object and container deletions succeed.
+    After delete all the objects from a container, it will wait 2
+    seconds before delete the container itself, in order to deployments
+    using HA proxy sync the deletion properly, otherwise, the container
+    might fail to be deleted because it's not empty.
+
+    :param containers: List of containers to be deleted
+    :param container_client: Client to be used to delete containers
+    :param object_client: Client to be used to delete objects
+    """
+    for cont in containers:
+        try:
+            params = {'limit': 9999, 'format': 'json'}
+            resp, objlist = container_client.list_container_contents(
+                cont, params)
+            # delete every object in the container
+            for obj in objlist:
+                test_utils.call_and_ignore_notfound_exc(
+                    object_client.delete_object, cont, obj['name'])
+            # sleep 2 seconds to sync the deletion of the objects
+            # in HA deployment
+            time.sleep(2)
+            container_client.delete_container(cont)
+        except lib_exc.NotFound:
+            pass
+
+
 class BaseObjectTest(tempest.test.BaseTestCase):
 
     credentials = [['operator', CONF.object_storage.operator_role]]
@@ -98,42 +130,12 @@
         return object_name, data
 
     @classmethod
-    def delete_containers(cls, container_client=None,
-                          object_client=None):
-        """Remove containers and all objects in them.
-
-        The containers should be visible from the container_client given.
-        Will not throw any error if the containers don't exist.
-        Will not check that object and container deletions succeed.
-        After delete all the objects from a container, it will wait 2
-        seconds before delete the container itself, in order to deployments
-        using HA proxy sync the deletion properly, otherwise, the container
-        might fail to be deleted because it's not empty.
-
-        :param container_client: if None, use cls.container_client, this means
-            that the default testing user will be used (see 'username' in
-            'etc/tempest.conf')
-        :param object_client: if None, use cls.object_client
-        """
+    def delete_containers(cls, container_client=None, object_client=None):
         if container_client is None:
             container_client = cls.container_client
         if object_client is None:
             object_client = cls.object_client
-        for cont in cls.containers:
-            try:
-                params = {'limit': 9999, 'format': 'json'}
-                resp, objlist = container_client.list_container_contents(
-                    cont, params)
-                # delete every object in the container
-                for obj in objlist:
-                    test_utils.call_and_ignore_notfound_exc(
-                        object_client.delete_object, cont, obj['name'])
-                # sleep 2 seconds to sync the deletion of the objects
-                # in HA deployment
-                time.sleep(2)
-                container_client.delete_container(cont)
-            except lib_exc.NotFound:
-                pass
+        delete_containers(cls.containers, container_client, object_client)
 
     def assertHeaders(self, resp, target, method):
         """Check the existence and the format of response headers"""
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index a75ed98..1eda49a 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -27,7 +27,10 @@
         self.containers = []
 
     def tearDown(self):
-        self.delete_containers()
+        # NOTE(andreaf) BulkTests needs to cleanup containers after each
+        # test is executed.
+        base.delete_containers(self.containers, self.container_client,
+                               self.object_client)
         super(BulkTest, self).tearDown()
 
     def _create_archive(self):
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 3098cab..6b2acc6 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -93,7 +93,6 @@
                        "vendor_name": vendor}
         body = self.create_volume_type(description=description, name=name,
                                        extra_specs=extra_specs)
-        self.assertIn('id', body)
         self.assertIn('name', body)
         self.assertEqual(name, body['name'],
                          "The created volume_type name is not equal "
diff --git a/tempest/api/volume/admin/v2/test_volume_pools.py b/tempest/api/volume/admin/v2/test_volume_pools.py
index e460278..8544a6a 100644
--- a/tempest/api/volume/admin/v2/test_volume_pools.py
+++ b/tempest/api/volume/admin/v2/test_volume_pools.py
@@ -25,19 +25,18 @@
         # Create a test shared volume for tests
         cls.volume = cls.create_volume()
 
-    @test.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
-    def test_get_pools_without_details(self):
-        volume_info = self.admin_volume_client. \
-            show_volume(self.volume['id'])['volume']
-        cinder_pools = self.admin_scheduler_stats_client.list_pools()['pools']
+    def _assert_host_volume_in_pools(self, with_detail=False):
+        volume_info = self.admin_volume_client.show_volume(
+            self.volume['id'])['volume']
+        cinder_pools = self.admin_volume_client.show_pools(
+            detail=with_detail)['pools']
         self.assertIn(volume_info['os-vol-host-attr:host'],
                       [pool['name'] for pool in cinder_pools])
 
+    @test.idempotent_id('0248a46c-e226-4933-be10-ad6fca8227e7')
+    def test_get_pools_without_details(self):
+        self._assert_host_volume_in_pools()
+
     @test.idempotent_id('d4bb61f7-762d-4437-b8a4-5785759a0ced')
     def test_get_pools_with_details(self):
-        volume_info = self.admin_volume_client. \
-            show_volume(self.volume['id'])['volume']
-        cinder_pools = self.admin_scheduler_stats_client.\
-            list_pools(detail=True)['pools']
-        self.assertIn(volume_info['os-vol-host-attr:host'],
-                      [pool['name'] for pool in cinder_pools])
+        self._assert_host_volume_in_pools(with_detail=True)
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 9f522bd..90dc7f4 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -51,7 +51,7 @@
                 raise cls.skipException(msg)
         else:
             msg = ("Invalid Cinder API version (%s)" % cls._api_version)
-            raise exceptions.InvalidConfiguration(message=msg)
+            raise exceptions.InvalidConfiguration(msg)
 
     @classmethod
     def setup_credentials(cls):
@@ -200,16 +200,13 @@
     @classmethod
     def clear_snapshots(cls):
         for snapshot in cls.snapshots:
-            try:
-                cls.snapshots_client.delete_snapshot(snapshot['id'])
-            except Exception:
-                pass
+            test_utils.call_and_ignore_notfound_exc(
+                cls.snapshots_client.delete_snapshot, snapshot['id'])
 
         for snapshot in cls.snapshots:
-            try:
-                cls.snapshots_client.wait_for_resource_deletion(snapshot['id'])
-            except Exception:
-                pass
+            test_utils.call_and_ignore_notfound_exc(
+                cls.snapshots_client.wait_for_resource_deletion,
+                snapshot['id'])
 
     def create_server(self, **kwargs):
         name = kwargs.pop(
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index bc7694a..35e0d56 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -23,6 +23,9 @@
 
 class AbsoluteLimitsV2Tests(base.BaseVolumeTest):
 
+    # avoid existing volumes of pre-defined tenant
+    force_tenant_isolation = True
+
     @classmethod
     def resource_setup(cls):
         super(AbsoluteLimitsV2Tests, cls).resource_setup()
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 0091027..70b3c58 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -30,6 +30,22 @@
         if not CONF.volume_feature_enabled.backup:
             raise cls.skipException("Cinder backup feature disabled")
 
+    def restore_backup(self, backup_id):
+        # Restore a backup
+        restored_volume = self.backups_client.restore_backup(
+            backup_id)['restore']
+
+        # Delete backup
+        self.addCleanup(self.volumes_client.delete_volume,
+                        restored_volume['volume_id'])
+        self.assertEqual(backup_id, restored_volume['backup_id'])
+        waiters.wait_for_backup_status(self.backups_client,
+                                       backup_id, 'available')
+        waiters.wait_for_volume_status(self.volumes_client,
+                                       restored_volume['volume_id'],
+                                       'available')
+        return restored_volume
+
     @test.idempotent_id('a66eb488-8ee1-47d4-8e9f-575a095728c6')
     def test_volume_backup_create_get_detailed_list_restore_delete(self):
         # Create backup
@@ -57,18 +73,7 @@
         self.assertIn((backup['name'], backup['id']),
                       [(m['name'], m['id']) for m in backups])
 
-        # Restore backup
-        restore = self.backups_client.restore_backup(
-            backup['id'])['restore']
-
-        # Delete backup
-        self.addCleanup(self.volumes_client.delete_volume,
-                        restore['volume_id'])
-        self.assertEqual(backup['id'], restore['backup_id'])
-        waiters.wait_for_backup_status(self.backups_client,
-                                       backup['id'], 'available')
-        waiters.wait_for_volume_status(self.volumes_client,
-                                       restore['volume_id'], 'available')
+        self.restore_backup(backup['id'])
 
     @test.idempotent_id('07af8f6d-80af-44c9-a5dc-c8427b1b62e6')
     @test.services('compute')
@@ -99,6 +104,28 @@
                                     name=backup_name, force=True)
         self.assertEqual(backup_name, backup['name'])
 
+    @test.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
+    def test_bootable_volume_backup_and_restore(self):
+        # Create volume from image
+        img_uuid = CONF.compute.image_ref
+        volume = self.create_volume(imageRef=img_uuid)
+
+        volume_details = self.volumes_client.show_volume(
+            volume['id'])['volume']
+        self.assertEqual('true', volume_details['bootable'])
+
+        # Create a backup
+        backup = self.create_backup(volume_id=volume['id'])
+
+        # Restore the backup
+        restored_volume_id = self.restore_backup(backup['id'])['volume_id']
+
+        # Verify the restored backup volume is bootable
+        restored_volume_info = self.volumes_client.show_volume(
+            restored_volume_id)['volume']
+
+        self.assertEqual('true', restored_volume_info['bootable'])
+
 
 class VolumesBackupsV1Test(VolumesBackupsV2Test):
     _api_version = 1
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 3c7a2c8..6f85891 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -140,6 +140,14 @@
         # Destination volume bigger than source snapshot
         dst_vol = self.create_volume(snapshot_id=src_snap['id'],
                                      size=src_size + 1)
+        # NOTE(zhufl): dst_vol is created based on snapshot, so dst_vol
+        # should be deleted before deleting snapshot, otherwise deleting
+        # snapshot will end with status 'error-deleting'. This depends on
+        # the implementation mechanism of vendors, generally speaking,
+        # some verdors will use "virtual disk clone" which will promote
+        # disk clone speed, and in this situation the "disk clone"
+        # is just a relationship between volume and snapshot.
+        self.addCleanup(self.delete_volume, self.volumes_client, dst_vol['id'])
 
         volume = self.volumes_client.show_volume(dst_vol['id'])['volume']
         # Should allow
diff --git a/tempest/clients.py b/tempest/clients.py
index 48ee59f..e3466e5 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -13,15 +13,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import copy
-
 from oslo_log import log as logging
 
 from tempest import config
 from tempest.lib import auth
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services import clients
-from tempest.services import identity
+from tempest.lib.services import identity
 from tempest.services import object_storage
 from tempest.services import orchestration
 
@@ -34,6 +32,14 @@
 
     default_params = config.service_client_config()
 
+    # TODO(jordanP): remove this once no Tempest plugin use that class
+    # variable.
+    default_params_with_timeout_values = {
+        'build_interval': CONF.compute.build_interval,
+        'build_timeout': CONF.compute.build_timeout
+    }
+    default_params_with_timeout_values.update(default_params)
+
     def __init__(self, credentials, service=None, scope='project'):
         """Initialization of Manager class.
 
@@ -178,67 +184,52 @@
             **params_volume)
 
     def _set_identity_clients(self):
-        params = self.parameters['identity']
-
         # Clients below use the admin endpoint type of Keystone API v2
-        params_v2_admin = copy.copy(params)
-        params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
-        self.endpoints_client = identity.v2.EndpointsClient(self.auth_provider,
-                                                            **params_v2_admin)
-        self.identity_client = identity.v2.IdentityClient(self.auth_provider,
-                                                          **params_v2_admin)
-        self.tenants_client = identity.v2.TenantsClient(self.auth_provider,
-                                                        **params_v2_admin)
-        self.roles_client = identity.v2.RolesClient(self.auth_provider,
-                                                    **params_v2_admin)
-        self.users_client = identity.v2.UsersClient(self.auth_provider,
-                                                    **params_v2_admin)
-        self.identity_services_client = identity.v2.ServicesClient(
-            self.auth_provider, **params_v2_admin)
+        params_v2_admin = {
+            'endpoint_type': CONF.identity.v2_admin_endpoint_type}
+        self.endpoints_client = self.identity_v2.EndpointsClient(
+            **params_v2_admin)
+        self.identity_client = self.identity_v2.IdentityClient(
+            **params_v2_admin)
+        self.tenants_client = self.identity_v2.TenantsClient(
+            **params_v2_admin)
+        self.roles_client = self.identity_v2.RolesClient(**params_v2_admin)
+        self.users_client = self.identity_v2.UsersClient(**params_v2_admin)
+        self.identity_services_client = self.identity_v2.ServicesClient(
+            **params_v2_admin)
 
         # Clients below use the public endpoint type of Keystone API v2
-        params_v2_public = copy.copy(params)
-        params_v2_public['endpoint_type'] = (
-            CONF.identity.v2_public_endpoint_type)
-        self.identity_public_client = identity.v2.IdentityClient(
-            self.auth_provider, **params_v2_public)
-        self.tenants_public_client = identity.v2.TenantsClient(
-            self.auth_provider, **params_v2_public)
-        self.users_public_client = identity.v2.UsersClient(
-            self.auth_provider, **params_v2_public)
+        params_v2_public = {
+            'endpoint_type': CONF.identity.v2_public_endpoint_type}
+        self.identity_public_client = self.identity_v2.IdentityClient(
+            **params_v2_public)
+        self.tenants_public_client = self.identity_v2.TenantsClient(
+            **params_v2_public)
+        self.users_public_client = self.identity_v2.UsersClient(
+            **params_v2_public)
 
         # Clients below use the endpoint type of Keystone API v3, which is set
         # in endpoint_type
-        params_v3 = copy.copy(params)
-        params_v3['endpoint_type'] = CONF.identity.v3_endpoint_type
-        self.domains_client = identity.v3.DomainsClient(self.auth_provider,
-                                                        **params_v3)
-        self.identity_v3_client = identity.v3.IdentityClient(
-            self.auth_provider, **params_v3)
-        self.trusts_client = identity.v3.TrustsClient(self.auth_provider,
-                                                      **params_v3)
-        self.users_v3_client = identity.v3.UsersClient(self.auth_provider,
-                                                       **params_v3)
-        self.endpoints_v3_client = identity.v3.EndPointsClient(
-            self.auth_provider, **params_v3)
-        self.roles_v3_client = identity.v3.RolesClient(self.auth_provider,
-                                                       **params_v3)
-        self.inherited_roles_client = identity.v3.InheritedRolesClient(
-            self.auth_provider, **params_v3)
-        self.role_assignments_client = identity.v3.RoleAssignmentsClient(
-            self.auth_provider, **params_v3)
-        self.identity_services_v3_client = identity.v3.ServicesClient(
-            self.auth_provider, **params_v3)
-        self.policies_client = identity.v3.PoliciesClient(self.auth_provider,
-                                                          **params_v3)
-        self.projects_client = identity.v3.ProjectsClient(self.auth_provider,
-                                                          **params_v3)
-        self.regions_client = identity.v3.RegionsClient(self.auth_provider,
-                                                        **params_v3)
-        self.credentials_client = identity.v3.CredentialsClient(
-            self.auth_provider, **params_v3)
-        self.groups_client = identity.v3.GroupsClient(self.auth_provider,
-                                                      **params_v3)
+        params_v3 = {'endpoint_type': CONF.identity.v3_endpoint_type}
+        self.domains_client = self.identity_v3.DomainsClient(**params_v3)
+        self.identity_v3_client = self.identity_v3.IdentityClient(**params_v3)
+        self.trusts_client = self.identity_v3.TrustsClient(**params_v3)
+        self.users_v3_client = self.identity_v3.UsersClient(**params_v3)
+        self.endpoints_v3_client = self.identity_v3.EndPointsClient(
+            **params_v3)
+        self.roles_v3_client = self.identity_v3.RolesClient(**params_v3)
+        self.inherited_roles_client = self.identity_v3.InheritedRolesClient(
+            **params_v3)
+        self.role_assignments_client = self.identity_v3.RoleAssignmentsClient(
+            **params_v3)
+        self.identity_services_v3_client = self.identity_v3.ServicesClient(
+            **params_v3)
+        self.policies_client = self.identity_v3.PoliciesClient(**params_v3)
+        self.projects_client = self.identity_v3.ProjectsClient(**params_v3)
+        self.regions_client = self.identity_v3.RegionsClient(**params_v3)
+        self.credentials_client = self.identity_v3.CredentialsClient(
+            **params_v3)
+        self.groups_client = self.identity_v3.GroupsClient(**params_v3)
 
         # Token clients do not use the catalog. They only need default_params.
         # They read auth_url, so they should only be set if the corresponding
diff --git a/tempest/config.py b/tempest/config.py
index a922367..0e45f2e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -171,7 +171,20 @@
     cfg.BoolOpt('admin_domain_scope',
                 default=False,
                 help="Whether keystone identity v3 policy required "
-                     "a domain scoped token to use admin APIs")
+                     "a domain scoped token to use admin APIs"),
+    # Security Compliance (PCI-DSS)
+    cfg.IntOpt('user_lockout_failure_attempts',
+               default=2,
+               help="The number of unsuccessful login attempts the user is "
+                    "allowed before having the account locked."),
+    cfg.IntOpt('user_lockout_duration',
+               default=5,
+               help="The number of seconds a user account will remain "
+                    "locked."),
+    cfg.IntOpt('user_unique_last_password_count',
+               default=2,
+               help="The number of passwords for a user that must be unique "
+                    "before an old password can be reused."),
 ]
 
 service_clients_group = cfg.OptGroup(name='service-clients',
@@ -208,7 +221,11 @@
     # of life.
     cfg.BoolOpt('reseller',
                 default=False,
-                help='Does the environment support reseller?')
+                help='Does the environment support reseller?'),
+    cfg.BoolOpt('security_compliance',
+                default=False,
+                help='Does the environment have the security compliance '
+                     'settings enabled?')
 ]
 
 compute_group = cfg.OptGroup(name='compute',
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index e5497d2..262a894 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -41,6 +41,7 @@
     return {
         'compute': compute,
         'identity.v2': identity.v2,
+        'identity.v3': identity.v3,
         'image.v1': image.v1,
         'image.v2': image.v2,
         'network': network,
@@ -55,7 +56,7 @@
     # NOTE(andreaf) This list will exists only as long the remain clients
     # are migrated to tempest.lib, and it will then be deleted without
     # deprecation or advance notice
-    return set(['identity.v3', 'object-storage'])
+    return set(['object-storage'])
 
 
 def available_modules():
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 24557d8..597e815 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -100,7 +100,7 @@
         any changes.
         :param disk_config: The name is changed to OS-DCF:diskConfig
         """
-        if kwargs.get('disk_config'):
+        if 'disk_config' in kwargs:
             kwargs['OS-DCF:diskConfig'] = kwargs.pop('disk_config')
 
         post_body = json.dumps({'server': kwargs})
diff --git a/tempest/lib/services/identity/__init__.py b/tempest/lib/services/identity/__init__.py
index e69de29..941a10e 100644
--- a/tempest/lib/services/identity/__init__.py
+++ b/tempest/lib/services/identity/__init__.py
@@ -0,0 +1,18 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# 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.identity import v2
+from tempest.lib.services.identity import v3
+
+__all__ = ['v2', 'v3']
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index e69de29..8058d51 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -0,0 +1,38 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+#
+# 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.identity.v3.credentials_client import \
+    CredentialsClient
+from tempest.lib.services.identity.v3.domains_client import DomainsClient
+from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
+from tempest.lib.services.identity.v3.groups_client import GroupsClient
+from tempest.lib.services.identity.v3.identity_client import IdentityClient
+from tempest.lib.services.identity.v3.inherited_roles_client import \
+    InheritedRolesClient
+from tempest.lib.services.identity.v3.policies_client import PoliciesClient
+from tempest.lib.services.identity.v3.projects_client import ProjectsClient
+from tempest.lib.services.identity.v3.regions_client import RegionsClient
+from tempest.lib.services.identity.v3.role_assignments_client import \
+    RoleAssignmentsClient
+from tempest.lib.services.identity.v3.roles_client import RolesClient
+from tempest.lib.services.identity.v3.services_client import ServicesClient
+from tempest.lib.services.identity.v3.token_client import V3TokenClient
+from tempest.lib.services.identity.v3.trusts_client import TrustsClient
+from tempest.lib.services.identity.v3.users_client import UsersClient
+
+__all__ = ['CredentialsClient', 'DomainsClient', 'EndPointsClient',
+           'GroupsClient', 'IdentityClient', 'InheritedRolesClient',
+           'PoliciesClient', 'ProjectsClient', 'RegionsClient',
+           'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
+           'V3TokenClient', 'TrustsClient', 'UsersClient', ]
diff --git a/tempest/services/identity/v3/json/domains_client.py b/tempest/lib/services/identity/v3/domains_client.py
similarity index 100%
rename from tempest/services/identity/v3/json/domains_client.py
rename to tempest/lib/services/identity/v3/domains_client.py
diff --git a/tempest/lib/services/image/v1/images_client.py b/tempest/lib/services/image/v1/images_client.py
index e67a547..03f4c4b 100644
--- a/tempest/lib/services/image/v1/images_client.py
+++ b/tempest/lib/services/image/v1/images_client.py
@@ -115,7 +115,7 @@
         if detail:
             url += '/detail'
 
-        if kwargs.get('changes_since'):
+        if 'changes_since' in kwargs:
             kwargs['changes-since'] = kwargs.pop('changes_since')
 
         if len(kwargs) > 0:
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 0605902..1279484 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -198,12 +198,11 @@
     @test.idempotent_id('a4858f6c-401e-4155-9a49-d5cd053d1a2f')
     @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
                           'Cold migration is not available.')
+    @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+                          'Less than 2 compute nodes, skipping multinode '
+                          'tests.')
     @test.services('compute', 'network')
     def test_server_connectivity_cold_migration(self):
-        if CONF.compute.min_compute_nodes < 2:
-            msg = "Less than 2 compute nodes, skipping multinode tests."
-            raise self.skipException(msg)
-
         keypair = self.create_keypair()
         server = self._setup_server(keypair)
         floating_ip = self._setup_network(server, keypair)
@@ -220,3 +219,28 @@
         dst_host = self._get_host_for_server(server['id'])
 
         self.assertNotEqual(src_host, dst_host)
+
+    @test.idempotent_id('25b188d7-0183-4b1e-a11d-15840c8e2fd6')
+    @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
+                          'Cold migration is not available.')
+    @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
+                          'Less than 2 compute nodes, skipping multinode '
+                          'tests.')
+    @test.services('compute', 'network')
+    def test_server_connectivity_cold_migration_revert(self):
+        keypair = self.create_keypair()
+        server = self._setup_server(keypair)
+        floating_ip = self._setup_network(server, keypair)
+        src_host = self._get_host_for_server(server['id'])
+        self._wait_server_status_and_check_network_connectivity(
+            server, keypair, floating_ip)
+
+        self.admin_servers_client.migrate_server(server['id'])
+        waiters.wait_for_server_status(self.servers_client, server['id'],
+                                       'VERIFY_RESIZE')
+        self.servers_client.revert_resize_server(server['id'])
+        self._wait_server_status_and_check_network_connectivity(
+            server, keypair, floating_ip)
+        dst_host = self._get_host_for_server(server['id'])
+
+        self.assertEqual(src_host, dst_host)
diff --git a/tempest/services/identity/__init__.py b/tempest/services/identity/__init__.py
deleted file mode 100644
index 53c223f..0000000
--- a/tempest/services/identity/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# 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.identity import v2
-from tempest.services.identity import v3
-
-__all__ = ['v2', 'v3']
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
deleted file mode 100644
index 6e64a7d..0000000
--- a/tempest/services/identity/v3/__init__.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
-#
-# 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.identity.v3.credentials_client import \
-    CredentialsClient
-from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
-from tempest.lib.services.identity.v3.groups_client import GroupsClient
-from tempest.lib.services.identity.v3.identity_client import IdentityClient
-from tempest.lib.services.identity.v3.inherited_roles_client import \
-    InheritedRolesClient
-from tempest.lib.services.identity.v3.policies_client import PoliciesClient
-from tempest.lib.services.identity.v3.projects_client import ProjectsClient
-from tempest.lib.services.identity.v3.regions_client import RegionsClient
-from tempest.lib.services.identity.v3.role_assignments_client import \
-    RoleAssignmentsClient
-from tempest.lib.services.identity.v3.roles_client import RolesClient
-from tempest.lib.services.identity.v3.services_client import ServicesClient
-from tempest.lib.services.identity.v3.token_client import V3TokenClient
-from tempest.lib.services.identity.v3.trusts_client import TrustsClient
-from tempest.lib.services.identity.v3.users_client import UsersClient
-from tempest.services.identity.v3.json.domains_client import DomainsClient
-
-__all__ = ['CredentialsClient', 'EndPointsClient', 'GroupsClient',
-           'IdentityClient', 'InheritedRolesClient', 'PoliciesClient',
-           'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
-           'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
-           'UsersClient', 'DomainsClient', ]
diff --git a/tempest/services/identity/v3/json/__init__.py b/tempest/services/identity/v3/json/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/services/identity/v3/json/__init__.py
+++ /dev/null
diff --git a/tempest/tests/cmd/test_account_generator.py b/tempest/tests/cmd/test_account_generator.py
index b08954f..6773b2f 100644
--- a/tempest/tests/cmd/test_account_generator.py
+++ b/tempest/tests/cmd/test_account_generator.py
@@ -75,7 +75,7 @@
         fake_domain_list = {'domains': [{'id': 'fake_domain',
                                          'name': 'Fake_Domain'}]}
         self.useFixture(fixtures.MockPatch(''.join([
-            'tempest.services.identity.v3.json.domains_client.'
+            'tempest.lib.services.identity.v3.domains_client.'
             'DomainsClient.list_domains']),
             return_value=fake_domain_list))
         self.useFixture(fixtures.MockPatch(
@@ -121,7 +121,7 @@
         super(TestAccountGeneratorV3, self).setUp()
         fake_domain_list = {'domains': [{'id': 'fake_domain'}]}
         self.useFixture(fixtures.MockPatch(''.join([
-            'tempest.services.identity.v3.json.domains_client.'
+            'tempest.lib.services.identity.v3.domains_client.'
             'DomainsClient.list_domains']),
             return_value=fake_domain_list))
 
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index a90ca8a..b4fbd50 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -27,6 +27,7 @@
     v2_tenants_client
 from tempest.lib.services.identity.v2 import token_client as v2_token_client
 from tempest.lib.services.identity.v2 import users_client as v2_users_client
+from tempest.lib.services.identity.v3 import domains_client
 from tempest.lib.services.identity.v3 import identity_client as v3_iden_client
 from tempest.lib.services.identity.v3 import projects_client as \
     v3_projects_client
@@ -35,7 +36,6 @@
 from tempest.lib.services.identity.v3 import users_client as \
     v3_users_client
 from tempest.lib.services.network import routers_client
-from tempest.services.identity.v3.json import domains_client
 from tempest.tests import base
 from tempest.tests import fake_config
 from tempest.tests.lib import fake_http
diff --git a/tempest/tests/lib/fake_identity.py b/tempest/tests/lib/fake_identity.py
index 831f8b5..8bae34f 100644
--- a/tempest/tests/lib/fake_identity.py
+++ b/tempest/tests/lib/fake_identity.py
@@ -55,6 +55,7 @@
         },
         "user": {
             "id": "fake_alt_user_id",
+            "password_expires_at": None,
         },
         "serviceCatalog": CATALOG_V2,
     },
@@ -71,6 +72,7 @@
         },
         "user": {
             "id": "fake_user_id",
+            "password_expires_at": None,
         },
         "serviceCatalog": CATALOG_V2,
     },
@@ -83,18 +85,21 @@
             "id": "first_compute_fake_service",
             "interface": "public",
             "region": "NoMatchRegion",
+            "region_id": "NoMatchRegion",
             "url": "http://fake_url/v3/first_endpoint/api"
         },
         {
             "id": "second_fake_service",
             "interface": "public",
             "region": "FakeRegion",
+            "region_id": "FakeRegion",
             "url": "http://fake_url/v3/second_endpoint/api"
         },
         {
             "id": "third_fake_service",
             "interface": "admin",
             "region": "MiddleEarthRegion",
+            "region_id": "MiddleEarthRegion",
             "url": "http://fake_url/v3/third_endpoint/api"
         }
 
@@ -108,6 +113,7 @@
 
 IDENTITY_V3_RESPONSE = {
     "token": {
+        "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
         "methods": [
             "token",
             "password"
@@ -127,7 +133,8 @@
                 "name": "domain_name"
             },
             "id": "fake_user_id",
-            "name": "username"
+            "name": "username",
+            "password_expires_at": None,
         },
         "issued_at": "2013-05-29T16:55:21.468960Z",
         "catalog": CATALOG_V3
@@ -136,6 +143,7 @@
 
 IDENTITY_V3_RESPONSE_DOMAIN_SCOPE = {
     "token": {
+        "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
         "methods": [
             "token",
             "password"
@@ -151,7 +159,8 @@
                 "name": "domain_name"
             },
             "id": "fake_user_id",
-            "name": "username"
+            "name": "username",
+            "password_expires_at": None,
         },
         "issued_at": "2013-05-29T16:55:21.468960Z",
         "catalog": CATALOG_V3
@@ -160,6 +169,7 @@
 
 IDENTITY_V3_RESPONSE_NO_SCOPE = {
     "token": {
+        "audit_ids": ["ny5LA5YXToa_mAVO8Hnupw", "9NPTvsRDSkmsW61abP978Q"],
         "methods": [
             "token",
             "password"
@@ -171,7 +181,8 @@
                 "name": "domain_name"
             },
             "id": "fake_user_id",
-            "name": "username"
+            "name": "username",
+            "password_expires_at": None,
         },
         "issued_at": "2013-05-29T16:55:21.468960Z",
     }
diff --git a/tempest/tests/lib/services/identity/v3/test_domains_client.py b/tempest/tests/lib/services/identity/v3/test_domains_client.py
new file mode 100644
index 0000000..f89ced7
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_domains_client.py
@@ -0,0 +1,138 @@
+# Copyright 2016 Red Hat, Inc.
+#
+# 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.identity.v3 import domains_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestDomainsClient(base.BaseServiceTest):
+    FAKE_CREATE_DOMAIN = {
+        "domain": {
+            "description": "Domain description",
+            "enabled": True,
+            "name": "myDomain"
+        }
+    }
+
+    FAKE_DOMAIN_INFO = {
+        "domain": {
+            "description": "Used for swift functional testing",
+            "enabled": True,
+            "id": "5a75994a3",
+            "links": {
+                "self": "http://example.com/identity/v3/domains/5a75994a3"
+            },
+            "name": "swift_test"
+        }
+    }
+
+    FAKE_LIST_DOMAINS = {
+        "domains": [
+            {
+                "description": "Used for swift functional testing",
+                "enabled": True,
+                "id": "5a75994a3",
+                "links": {
+                    "self": "http://example.com/identity/v3/domains/5a75994a3"
+                },
+                "name": "swift_test"
+            },
+            {
+                "description": "Owns users and tenants available on " +
+                               "Identity API",
+                "enabled": True,
+                "id": "default",
+                "links": {
+                    "self": "http://example.com/identity/v3/domains/default"
+                },
+                "name": "Default"
+            }
+        ],
+        "links": {
+            "next": None,
+            "previous": None,
+            "self": "http://example.com/identity/v3/domains"
+        }
+    }
+
+    def setUp(self):
+        super(TestDomainsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = domains_client.DomainsClient(fake_auth,
+                                                   'identity',
+                                                   'regionOne')
+
+    def _test_create_domain(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_domain,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_CREATE_DOMAIN,
+            bytes_body,
+            status=201)
+
+    def _test_show_domain(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_domain,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_DOMAIN_INFO,
+            bytes_body,
+            domain_id="5a75994a3")
+
+    def _test_list_domains(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_domains,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_DOMAINS,
+            bytes_body)
+
+    def _test_update_domain(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_domain,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_DOMAIN_INFO,
+            bytes_body,
+            domain_id="5a75994a3")
+
+    def test_create_domain_with_str_body(self):
+        self._test_create_domain()
+
+    def test_create_domain_with_bytes_body(self):
+        self._test_create_domain(bytes_body=True)
+
+    def test_show_domain_with_str_body(self):
+        self._test_show_domain()
+
+    def test_show_domain_with_bytes_body(self):
+        self._test_show_domain(bytes_body=True)
+
+    def test_list_domain_with_str_body(self):
+        self._test_list_domains()
+
+    def test_list_domain_with_bytes_body(self):
+        self._test_list_domains(bytes_body=True)
+
+    def test_update_domain_with_str_body(self):
+        self._test_update_domain()
+
+    def test_update_domain_with_bytes_body(self):
+        self._test_update_domain(bytes_body=True)
+
+    def test_delete_domain(self):
+        self.check_service_client_function(
+            self.client.delete_domain,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            domain_id="5a75994a3",
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
index 9f4b4cc..38e8c4a 100644
--- a/tempest/tests/lib/services/identity/v3/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -20,7 +20,7 @@
 from tempest.lib import exceptions
 from tempest.lib.services.identity.v3 import token_client
 from tempest.tests import base
-from tempest.tests.lib import fake_http
+from tempest.tests.lib import fake_identity
 
 
 class TestTokenClientV3(base.TestCase):
@@ -31,10 +31,8 @@
 
     def test_auth(self):
         token_client_v3 = token_client.V3TokenClient('fake_url')
-        response = fake_http.fake_http_response(
-            None, status=201,
-        )
-        body = {'access': {'token': 'fake_token'}}
+        response, body_text = fake_identity._fake_v3_response(None, None)
+        body = json.loads(body_text)
 
         with mock.patch.object(token_client_v3, 'post') as post_mock:
             post_mock.return_value = response, body
@@ -60,10 +58,8 @@
 
     def test_auth_with_project_id_and_domain_id(self):
         token_client_v3 = token_client.V3TokenClient('fake_url')
-        response = fake_http.fake_http_response(
-            None, status=201,
-        )
-        body = {'access': {'token': 'fake_token'}}
+        response, body_text = fake_identity._fake_v3_response(None, None)
+        body = json.loads(body_text)
 
         with mock.patch.object(token_client_v3, 'post') as post_mock:
             post_mock.return_value = response, body
@@ -103,10 +99,8 @@
 
     def test_auth_with_tenant(self):
         token_client_v3 = token_client.V3TokenClient('fake_url')
-        response = fake_http.fake_http_response(
-            None, status=201,
-        )
-        body = {'access': {'token': 'fake_token'}}
+        response, body_text = fake_identity._fake_v3_response(None, None)
+        body = json.loads(body_text)
 
         with mock.patch.object(token_client_v3, 'post') as post_mock:
             post_mock.return_value = response, body
@@ -138,13 +132,10 @@
 
     def test_request_with_str_body(self):
         token_client_v3 = token_client.V3TokenClient('fake_url')
-        response = fake_http.fake_http_response(
-            None, status=200,
-        )
-        body = str('{"access": {"token": "fake_token"}}')
 
         with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
-            mock_raw_r.return_value = response, body
+            mock_raw_r.return_value = (
+                fake_identity._fake_v3_response(None, None))
             resp, body = token_client_v3.request('GET', 'fake_uri')
 
         self.assertIsInstance(body, dict)
@@ -152,10 +143,8 @@
     def test_request_with_bytes_body(self):
         token_client_v3 = token_client.V3TokenClient('fake_url')
 
-        response = fake_http.fake_http_response(
-            None, status=200,
-        )
-        body = b'{"access": {"token": "fake_token"}}'
+        response, body_text = fake_identity._fake_v3_response(None, None)
+        body = body_text.encode('utf-8')
 
         with mock.patch.object(token_client_v3, 'raw_request') as mock_raw_r:
             mock_raw_r.return_value = response, body