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