Merge "Remove redundant volume check in nova_volume_detach"
diff --git a/releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yaml b/releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yaml
new file mode 100644
index 0000000..76b395d
--- /dev/null
+++ b/releasenotes/notes/add-reset-group-snapshot-status-api-to-v3-group-snapshots-client-248d41827daf2a0c.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add reset group snapshot status API to v3 group_snapshots_client library,
+ min_microversion of this API is 3.19. This feature enables the possibility
+ to reset group snapshot status.
diff --git a/releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yaml b/releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yaml
new file mode 100644
index 0000000..a39c23b
--- /dev/null
+++ b/releasenotes/notes/add-reset-group-status-api-to-v3-groups-client-9aa048617c66756a.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add reset group status API to v3 groups_client library, min_microversion
+ of this API is 3.20. This feature enables the possibility to reset group
+ status.
diff --git a/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
new file mode 100644
index 0000000..775a383
--- /dev/null
+++ b/releasenotes/notes/fix-list-group-snapshots-api-969d9321002c566c.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+ - |
+ Fix list_group_snapshots API in v3 group_snapshots_client: Bug#1715786.
+ The url path for list group snapshots with details API is changed from
+ ``?detail=True`` to ``/detail``.
diff --git a/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml b/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml
new file mode 100644
index 0000000..920bc5d
--- /dev/null
+++ b/releasenotes/notes/remove-deprecated-skip-decorators-f8b42d812d20b537.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+ - |
+ Remove two deprecated skip decorators in ``config`` module:
+ ``skip_unless_config`` and ``skip_if_config``.
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index 5894e80..c2bdf7e 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -17,6 +17,7 @@
from testtools import matchers
from tempest.api.compute import base
+from tempest.common import identity
from tempest.common import tempest_fixtures as fixtures
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -93,10 +94,11 @@
# Verify that GET shows the updated quota set of project
project_name = data_utils.rand_name('cpu_quota_project')
project_desc = project_name + '-desc'
- project = self.identity_utils.create_project(name=project_name,
- description=project_desc)
+ project = identity.identity_utils(self.os_admin).create_project(
+ name=project_name, description=project_desc)
project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
self.adm_client.update_quota_set(project_id, ram='5120')
quota_set = self.adm_client.show_quota_set(project_id)['quota_set']
@@ -106,12 +108,12 @@
user_name = data_utils.rand_name('cpu_quota_user')
password = data_utils.rand_password()
email = user_name + '@testmail.tm'
- user = self.identity_utils.create_user(username=user_name,
- password=password,
- project=project,
- email=email)
+ user = identity.identity_utils(self.os_admin).create_user(
+ username=user_name, password=password, project=project,
+ email=email)
user_id = user['id']
- self.addCleanup(self.identity_utils.delete_user, user_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_user,
+ user_id)
self.adm_client.update_quota_set(project_id,
user_id=user_id,
@@ -125,10 +127,11 @@
# Admin can delete the resource quota set for a project
project_name = data_utils.rand_name('ram_quota_project')
project_desc = project_name + '-desc'
- project = self.identity_utils.create_project(name=project_name,
- description=project_desc)
+ project = identity.identity_utils(self.os_admin).create_project(
+ name=project_name, description=project_desc)
project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
quota_set_default = (self.adm_client.show_quota_set(project_id)
['quota_set'])
ram_default = quota_set_default['ram']
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 683d3e9..5c4767c 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -262,7 +262,11 @@
image = cls.compute_images_client.create_image(server_id, name=name,
**kwargs)
- image_id = data_utils.parse_image_id(image.response['location'])
+ if api_version_utils.compare_version_header_to_response(
+ "OpenStack-API-Version", "compute 2.45", image.response, "lt"):
+ image_id = image['image_id']
+ else:
+ image_id = data_utils.parse_image_id(image.response['location'])
cls.addClassResourceCleanup(test_utils.call_and_ignore_notfound_exc,
cls.compute_images_client.delete_image,
image_id)
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index f69d7c5..cf4236d 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.network import base
+from tempest.common import identity
from tempest.common import utils
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
@@ -46,10 +47,11 @@
# Add a project to conduct the test
project = data_utils.rand_name('test_project_')
description = data_utils.rand_name('desc_')
- project = self.identity_utils.create_project(name=project,
- description=description)
+ project = identity.identity_utils(self.os_admin).create_project(
+ name=project, description=description)
project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
# Change quotas for project
quota_set = self.admin_quotas_client.update_quotas(
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index f745f9c..8cdb41e 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -16,6 +16,7 @@
import testtools
from tempest.api.network import base
+from tempest.common import identity
from tempest.common import utils
from tempest import config
from tempest.lib.common.utils import data_utils
@@ -53,10 +54,11 @@
# Test creating router from admin user setting project_id.
project = data_utils.rand_name('test_tenant_')
description = data_utils.rand_name('desc_')
- project = self.identity_utils.create_project(name=project,
- description=description)
+ project = identity.identity_utils(self.os_admin).create_project(
+ name=project, description=description)
project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
name = data_utils.rand_name('router-')
create_body = self.admin_routers_client.create_router(
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 4c49b2a..24c9c24 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -43,7 +43,7 @@
for cont in containers:
try:
params = {'limit': 9999, 'format': 'json'}
- _, objlist = container_client.list_container_contents(cont, params)
+ _, objlist = container_client.list_container_objects(cont, params)
# delete every object in the container
for obj in objlist:
test_utils.call_and_ignore_notfound_exc(
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 9abd59e..6599e43 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -96,7 +96,7 @@
self.assertIn(container_name, [b['name'] for b in body])
param = {'format': 'json'}
- resp, contents_list = self.container_client.list_container_contents(
+ resp, contents_list = self.container_client.list_container_objects(
container_name, param)
self.assertHeaders(resp, 'Container', 'GET')
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 4b66ebf..765bc6d 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -41,10 +41,11 @@
tenant_name = self.os_roles_operator_alt.credentials.tenant_name
username = self.os_roles_operator_alt.credentials.username
cont_headers = {'X-Container-Read': tenant_name + ':' + username}
+ container_client = self.os_roles_operator.container_client
resp_meta, _ = (
- self.os_roles_operator.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix=''))
+ container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
@@ -68,10 +69,11 @@
tenant_name = self.os_roles_operator_alt.credentials.tenant_name
username = self.os_roles_operator_alt.credentials.username
cont_headers = {'X-Container-Write': tenant_name + ':' + username}
+ container_client = self.os_roles_operator.container_client
resp_meta, _ = (
- self.os_roles_operator.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix=''))
+ container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# set alternative authentication data; cannot simply use the
# other object client.
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index e064753..03a5879 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -133,9 +133,10 @@
# attempt to read object using non-authorized user
# update X-Container-Read metadata ACL
cont_headers = {'X-Container-Read': 'badtenant:baduser'}
- resp_meta, _ = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
@@ -157,9 +158,10 @@
# attempt to write object using non-authorized user
# update X-Container-Write metadata ACL
cont_headers = {'X-Container-Write': 'badtenant:baduser'}
- resp_meta, _ = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# Trying to write the object without rights
self.object_client.auth_provider.set_alt_auth_data(
@@ -182,9 +184,10 @@
cont_headers = {'X-Container-Read':
tenant_name + ':' + username,
'X-Container-Write': ''}
- resp_meta, _ = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# Trying to write the object without write rights
self.object_client.auth_provider.set_alt_auth_data(
@@ -207,9 +210,10 @@
cont_headers = {'X-Container-Read':
tenant_name + ':' + username,
'X-Container-Write': ''}
- resp_meta, _ = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index c87bed5..982c4a1 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -40,8 +40,8 @@
self.container_name = self.create_container()
metadata = {"quota-bytes": str(QUOTA_BYTES),
"quota-count": str(QUOTA_COUNT), }
- self.container_client.update_container_metadata(
- self.container_name, metadata)
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=metadata)
def tearDown(self):
"""Cleans the container of any object after each test."""
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 76fe8d4..c6f21ec 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -130,7 +130,7 @@
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name)
self.assertHeaders(resp, 'Container', 'GET')
self.assertEqual([object_name], object_list)
@@ -140,7 +140,7 @@
# get empty container contents list
container_name = self.create_container()
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name)
self.assertHeaders(resp, 'Container', 'GET')
self.assertEmpty(object_list)
@@ -153,7 +153,7 @@
self.create_object(container_name, object_name)
params = {'delimiter': '/'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -166,7 +166,7 @@
object_name, _ = self.create_object(container_name)
params = {'end_marker': object_name + 'zzzz'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -179,7 +179,7 @@
self.create_object(container_name)
params = {'format': 'json'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -198,7 +198,7 @@
self.create_object(container_name)
params = {'format': 'xml'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -222,7 +222,7 @@
object_name, _ = self.create_object(container_name)
params = {'limit': data_utils.rand_int_id(1, 10000)}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -235,7 +235,7 @@
object_name, _ = self.create_object(container_name)
params = {'marker': 'AaaaObject1234567890'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -250,7 +250,7 @@
self.create_object(container_name, object_name)
params = {'path': 'Swift'}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -264,7 +264,7 @@
prefix_key = object_name[0:8]
params = {'prefix': prefix_key}
- resp, object_list = self.container_client.list_container_contents(
+ resp, object_list = self.container_client.list_container_objects(
container_name,
params=params)
self.assertHeaders(resp, 'Container', 'GET')
@@ -277,9 +277,9 @@
container_name = self.create_container()
metadata = {'name': 'Pictures'}
- self.container_client.update_container_metadata(
+ self.container_client.create_update_or_delete_container_metadata(
container_name,
- metadata=metadata)
+ create_update_metadata=metadata)
resp, _ = self.container_client.list_container_metadata(
container_name)
@@ -307,10 +307,11 @@
self.containers.append(container_name)
metadata_2 = {'test-container-meta2': 'Meta2'}
- resp, _ = self.container_client.update_container_metadata(
- container_name,
- metadata=metadata_2,
- remove_metadata=metadata_1)
+ resp, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name,
+ create_update_metadata=metadata_2,
+ delete_metadata=metadata_1))
self.assertHeaders(resp, 'Container', 'POST')
resp, _ = self.container_client.list_container_metadata(
@@ -326,9 +327,10 @@
container_name = self.create_container()
metadata = {'test-container-meta1': 'Meta1'}
- resp, _ = self.container_client.update_container_metadata(
- container_name,
- metadata=metadata)
+ resp, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name,
+ create_update_metadata=metadata))
self.assertHeaders(resp, 'Container', 'POST')
resp, _ = self.container_client.list_container_metadata(
@@ -346,9 +348,10 @@
metadata=metadata)
self.containers.append(container_name)
- resp, _ = self.container_client.delete_container_metadata(
- container_name,
- metadata=metadata)
+ resp, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name,
+ delete_metadata=metadata))
self.assertHeaders(resp, 'Container', 'POST')
resp, _ = self.container_client.list_container_metadata(
@@ -361,9 +364,10 @@
container_name = self.create_container()
metadata = {'test-container-meta1': ''}
- resp, _ = self.container_client.update_container_metadata(
- container_name,
- metadata=metadata)
+ resp, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name,
+ create_update_metadata=metadata))
self.assertHeaders(resp, 'Container', 'POST')
resp, _ = self.container_client.list_container_metadata(
@@ -380,9 +384,10 @@
self.containers.append(container_name)
metadata = {'test-container-meta1': ''}
- resp, _ = self.container_client.delete_container_metadata(
- container_name,
- metadata=metadata)
+ resp, _ = (
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name,
+ delete_metadata=metadata))
self.assertHeaders(resp, 'Container', 'POST')
resp, _ = self.container_client.list_container_metadata(container_name)
diff --git a/tempest/api/object_storage/test_container_services_negative.py b/tempest/api/object_storage/test_container_services_negative.py
index 387b7b6..707c016 100644
--- a/tempest/api/object_storage/test_container_services_negative.py
+++ b/tempest/api/object_storage/test_container_services_negative.py
@@ -120,9 +120,10 @@
# Attempts to update metadata using a nonexistent container name.
metadata = {'animal': 'penguin'}
- self.assertRaises(exceptions.NotFound,
- self.container_client.update_container_metadata,
- 'nonexistent_container_name', metadata)
+ self.assertRaises(
+ exceptions.NotFound,
+ self.container_client.create_update_or_delete_container_metadata,
+ 'nonexistent_container_name', create_update_metadata=metadata)
@decorators.attr(type=["negative"])
@decorators.idempotent_id('65387dbf-a0e2-4aac-9ddc-16eb3f1f69ba')
@@ -130,9 +131,10 @@
# Attempts to delete metadata using a nonexistent container name.
metadata = {'animal': 'penguin'}
- self.assertRaises(exceptions.NotFound,
- self.container_client.delete_container_metadata,
- 'nonexistent_container_name', metadata)
+ self.assertRaises(
+ exceptions.NotFound,
+ self.container_client.create_update_or_delete_container_metadata,
+ 'nonexistent_container_name', delete_metadata=metadata)
@decorators.attr(type=["negative"])
@decorators.idempotent_id('14331d21-1e81-420a-beea-19cb5e5207f5')
@@ -141,7 +143,7 @@
# that doesn't exist.
params = {'limit': 9999, 'format': 'json'}
self.assertRaises(exceptions.NotFound,
- self.container_client.list_container_contents,
+ self.container_client.list_container_objects,
'nonexistent_container_name', params)
@decorators.attr(type=["negative"])
@@ -155,7 +157,7 @@
self.assertHeaders(resp, 'Container', 'DELETE')
params = {'limit': 9999, 'format': 'json'}
self.assertRaises(exceptions.NotFound,
- self.container_client.list_container_contents,
+ self.container_client.list_container_objects,
container_name, params)
@decorators.attr(type=["negative"])
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 92fa690..1243b83 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -34,10 +34,10 @@
cls.object_name, cls.object_data = cls.create_object(
cls.container_name)
- cls.container_client.update_container_metadata(
+ cls.container_client.create_update_or_delete_container_metadata(
cls.container_name,
- metadata=headers_public_read_acl,
- metadata_prefix="X-Container-")
+ create_update_metadata=headers_public_read_acl,
+ create_update_metadata_prefix="X-Container-")
@classmethod
def resource_cleanup(cls):
@@ -49,8 +49,8 @@
def test_web_index(self):
headers = {'web-index': self.object_name}
- self.container_client.update_container_metadata(
- self.container_name, metadata=headers)
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=headers)
# Maintain original headers, no auth added
self.account_client.auth_provider.set_alt_auth_data(
@@ -68,8 +68,9 @@
self.assertEqual(body, self.object_data)
# clean up before exiting
- self.container_client.update_container_metadata(self.container_name,
- {'web-index': ""})
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name,
+ create_update_metadata={'web-index': ""})
_, body = self.container_client.list_container_metadata(
self.container_name)
@@ -80,8 +81,8 @@
def test_web_listing(self):
headers = {'web-listings': 'true'}
- self.container_client.update_container_metadata(
- self.container_name, metadata=headers)
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=headers)
# test GET on http://account_url/container_name
# we should retrieve a listing of objects
@@ -100,9 +101,9 @@
self.assertIn(self.object_name, body.decode())
# clean up before exiting
- self.container_client.update_container_metadata(self.container_name,
- {'web-listings': ""})
-
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name,
+ create_update_metadata={'web-listings': ""})
_, body = self.container_client.list_container_metadata(
self.container_name)
self.assertNotIn('x-container-meta-web-listings', body)
@@ -113,8 +114,8 @@
headers = {'web-listings': 'true',
'web-listings-css': 'listings.css'}
- self.container_client.update_container_metadata(
- self.container_name, metadata=headers)
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=headers)
# Maintain original headers, no auth added
self.account_client.auth_provider.set_alt_auth_data(
@@ -136,8 +137,8 @@
headers = {'web-listings': 'true',
'web-error': self.object_name}
- self.container_client.update_container_metadata(
- self.container_name, metadata=headers)
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=headers)
# Create object to return when requested object not found
object_name_404 = "404" + self.object_name
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 7665b48..042d288 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -102,7 +102,7 @@
while self.attempts > 0:
object_lists = []
for c_client, cont in zip(cont_client, self.containers):
- resp, object_list = c_client.list_container_contents(
+ resp, object_list = c_client.list_container_objects(
cont, params=params)
object_lists.append(dict(
(obj['name'], obj) for obj in object_list))
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index d3cdb72..836a875 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -990,8 +990,11 @@
# update container metadata to make it publicly readable
cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers, metadata_prefix='')
+ resp_meta, body = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name,
+ create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
@@ -1025,9 +1028,10 @@
# make container public-readable and access an object in it using
# another user's credentials
cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
+ resp_meta, body = (
+ self.container_client.create_update_or_delete_container_metadata(
+ self.container_name, create_update_metadata=cont_headers,
+ create_update_metadata_prefix=''))
self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 65da63d..c66776e 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -172,6 +172,6 @@
# Check only the format of common headers with custom matcher
self.assertThat(resp, custom_matchers.AreAllWellFormatted())
- resp, body = self.container_client.list_container_contents(
+ resp, body = self.container_client.list_container_objects(
self.container_name)
self.assertEqual(int(resp['x-container-object-count']), 0)
diff --git a/tempest/api/volume/admin/test_groups.py b/tempest/api/volume/admin/test_groups.py
index baea37b..68d355c 100644
--- a/tempest/api/volume/admin/test_groups.py
+++ b/tempest/api/volume/admin/test_groups.py
@@ -23,10 +23,7 @@
CONF = config.CONF
-class GroupsTest(base.BaseVolumeAdminTest):
- _api_version = 3
- min_microversion = '3.14'
- max_microversion = 'latest'
+class BaseGroupsTest(base.BaseVolumeAdminTest):
def _delete_group(self, grp_id, delete_volumes=True):
self.groups_client.delete_group(grp_id, delete_volumes)
@@ -37,8 +34,7 @@
self.groups_client.wait_for_resource_deletion(grp_id)
def _delete_group_snapshot(self, group_snapshot_id, grp_id):
- self.group_snapshots_client.delete_group_snapshot(
- group_snapshot_id)
+ self.group_snapshots_client.delete_group_snapshot(group_snapshot_id)
vols = self.volumes_client.list_volumes(detail=True)['volumes']
snapshots = self.snapshots_client.list_snapshots(
detail=True)['snapshots']
@@ -65,6 +61,12 @@
self.assertEqual(grp_name, grp['name'])
return grp
+
+class GroupsTest(BaseGroupsTest):
+ _api_version = 3
+ min_microversion = '3.14'
+ max_microversion = 'latest'
+
@decorators.idempotent_id('4b111d28-b73d-4908-9bd2-03dc2992e4d4')
def test_group_create_show_list_delete(self):
# Create volume type
@@ -126,8 +128,7 @@
self._delete_group(grp1_id)
# grp2 is empty so delete_volumes flag can be set to False
self._delete_group(grp2_id, delete_volumes=False)
- grps = self.groups_client.list_groups(
- detail=True)['groups']
+ grps = self.groups_client.list_groups(detail=True)['groups']
self.assertEmpty(grps)
@decorators.idempotent_id('1298e537-f1f0-47a3-a1dd-8adec8168897')
@@ -151,6 +152,9 @@
self.group_snapshots_client.create_group_snapshot(
group_id=grp['id'],
name=group_snapshot_name)['group_snapshot'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self._delete_group_snapshot,
+ group_snapshot['id'], grp['id'])
snapshots = self.snapshots_client.list_snapshots(
detail=True)['snapshots']
for snap in snapshots:
@@ -167,18 +171,20 @@
group_snapshot['id'])['group_snapshot']
self.assertEqual(group_snapshot_name, group_snapshot['name'])
- # Get all group snapshots with detail
- group_snapshots = (
- self.group_snapshots_client.list_group_snapshots(
- detail=True)['group_snapshots'])
+ # Get all group snapshots with details, check some detail-specific
+ # elements, and look for the created group snapshot
+ group_snapshots = (self.group_snapshots_client.list_group_snapshots(
+ detail=True)['group_snapshots'])
+ for grp_snapshot in group_snapshots:
+ self.assertIn('created_at', grp_snapshot)
+ self.assertIn('group_id', grp_snapshot)
self.assertIn((group_snapshot['name'], group_snapshot['id']),
[(m['name'], m['id']) for m in group_snapshots])
# Delete group snapshot
self._delete_group_snapshot(group_snapshot['id'], grp['id'])
- group_snapshots = (
- self.group_snapshots_client.list_group_snapshots(
- detail=True)['group_snapshots'])
+ group_snapshots = (self.group_snapshots_client.list_group_snapshots()
+ ['group_snapshots'])
self.assertEmpty(group_snapshots)
@decorators.idempotent_id('eff52c70-efc7-45ed-b47a-4ad675d09b81')
@@ -212,14 +218,12 @@
waiters.wait_for_volume_resource_status(
self.snapshots_client, snap['id'], 'available')
waiters.wait_for_volume_resource_status(
- self.group_snapshots_client,
- group_snapshot['id'], 'available')
+ self.group_snapshots_client, group_snapshot['id'], 'available')
# Create Group from Group snapshot
grp_name2 = data_utils.rand_name('Group_from_snap')
grp2 = self.groups_client.create_group_from_source(
- group_snapshot_id=group_snapshot['id'],
- name=grp_name2)['group']
+ group_snapshot_id=group_snapshot['id'], name=grp_name2)['group']
self.addCleanup(self._delete_group, grp2['id'])
self.assertEqual(grp_name2, grp2['name'])
vols = self.volumes_client.list_volumes(detail=True)['volumes']
@@ -250,8 +254,7 @@
source_group_id=grp['id'], name=grp_name2)['group']
self.addCleanup(self._delete_group, grp2['id'])
self.assertEqual(grp_name2, grp2['name'])
- vols = self.volumes_client.list_volumes(
- detail=True)['volumes']
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
for vol in vols:
if vol['group_id'] == grp2['id']:
waiters.wait_for_volume_resource_status(
@@ -298,10 +301,7 @@
# Get volumes in the group
vols = self.volumes_client.list_volumes(
detail=True)['volumes']
- grp_vols = []
- for vol in vols:
- if vol['group_id'] == grp['id']:
- grp_vols.append(vol)
+ grp_vols = [v for v in vols if v['group_id'] == grp['id']]
self.assertEqual(1, len(grp_vols))
# Add a volume to the group
@@ -313,10 +313,82 @@
self.groups_client, grp['id'], 'available')
# Get volumes in the group
- vols = self.volumes_client.list_volumes(
- detail=True)['volumes']
- grp_vols = []
- for vol in vols:
- if vol['group_id'] == grp['id']:
- grp_vols.append(vol)
+ vols = self.volumes_client.list_volumes(detail=True)['volumes']
+ grp_vols = [v for v in vols if v['group_id'] == grp['id']]
self.assertEqual(2, len(grp_vols))
+
+
+class GroupsV319Test(BaseGroupsTest):
+ _api_version = 3
+ min_microversion = '3.19'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('3b42c9b9-c984-4444-816e-ca2e1ed30b40')
+ def test_reset_group_snapshot_status(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ group = self._create_group(group_type, volume_type)
+
+ # Create volume
+ volume = self.create_volume(volume_type=volume_type['id'],
+ group_id=group['id'])
+
+ # Create group snapshot
+ group_snapshot_name = data_utils.rand_name('group_snapshot')
+ group_snapshot = (self.group_snapshots_client.create_group_snapshot(
+ group_id=group['id'], name=group_snapshot_name)['group_snapshot'])
+ self.addCleanup(self._delete_group_snapshot,
+ group_snapshot['id'], group['id'])
+ snapshots = self.snapshots_client.list_snapshots(
+ detail=True)['snapshots']
+ for snap in snapshots:
+ if volume['id'] == snap['volume_id']:
+ waiters.wait_for_volume_resource_status(
+ self.snapshots_client, snap['id'], 'available')
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client, group_snapshot['id'], 'available')
+
+ # Reset group snapshot status
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.group_snapshots_client,
+ group_snapshot['id'], 'available')
+ self.addCleanup(
+ self.admin_group_snapshots_client.reset_group_snapshot_status,
+ group_snapshot['id'], 'available')
+ for status in ['creating', 'available', 'error']:
+ self.admin_group_snapshots_client.reset_group_snapshot_status(
+ group_snapshot['id'], status)
+ waiters.wait_for_volume_resource_status(
+ self.group_snapshots_client, group_snapshot['id'], status)
+
+
+class GroupsV320Test(BaseGroupsTest):
+ _api_version = 3
+ min_microversion = '3.20'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('b20c696b-0cbc-49a5-8b3a-b1fb9338f45c')
+ def test_reset_group_status(self):
+ # Create volume type
+ volume_type = self.create_volume_type()
+
+ # Create group type
+ group_type = self.create_group_type()
+
+ # Create group
+ group = self._create_group(group_type, volume_type)
+
+ # Reset group status
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.groups_client, group['id'], 'available')
+ self.addCleanup(self.admin_groups_client.reset_group_status,
+ group['id'], 'available')
+ for status in ['creating', 'available', 'error']:
+ self.admin_groups_client.reset_group_status(group['id'], status)
+ waiters.wait_for_volume_resource_status(
+ self.groups_client, group['id'], status)
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
index f551575..75dca41 100644
--- a/tempest/api/volume/admin/test_volume_quota_classes.py
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -19,6 +19,7 @@
from testtools import matchers
from tempest.api.volume import base
+from tempest.common import identity
from tempest.common import tempest_fixtures as fixtures
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -92,9 +93,10 @@
# Verify a new project's default quotas.
project_name = data_utils.rand_name('quota_class_tenant')
description = data_utils.rand_name('desc_')
- project_id = self.identity_utils.create_project(
+ project_id = identity.identity_utils(self.os_admin).create_project(
name=project_name, description=description)['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
default_quotas = self.admin_quotas_client.show_default_quota_set(
project_id)['quota_set']
self.assertThat(default_quotas.items(),
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 754104e..d56f1de 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,6 +13,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import identity
from tempest.common import tempest_fixtures as fixtures
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
@@ -100,7 +101,7 @@
volume = self.create_volume()
self.addCleanup(self.delete_volume,
- self.admin_volume_client, volume['id'])
+ self.volumes_client, volume['id'])
new_quota_usage = self.admin_quotas_client.show_quota_set(
self.demo_tenant_id, params={'usage': True})['quota_set']
@@ -117,10 +118,11 @@
# Admin can delete the resource quota set for a project
project_name = data_utils.rand_name('quota_tenant')
description = data_utils.rand_name('desc_')
- project = self.identity_utils.create_project(project_name,
- description=description)
+ project = identity.identity_utils(self.os_admin).create_project(
+ project_name, description=description)
project_id = project['id']
- self.addCleanup(self.identity_utils.delete_project, project_id)
+ self.addCleanup(identity.identity_utils(self.os_admin).delete_project,
+ project_id)
quota_set_default = self.admin_quotas_client.show_default_quota_set(
project_id)['quota_set']
volume_default = quota_set_default['volumes']
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index d0a87db..0b6ee38 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -20,14 +20,10 @@
class AvailabilityZoneTestJSON(base.BaseVolumeTest):
"""Tests Availability Zone API List"""
- @classmethod
- def setup_clients(cls):
- super(AvailabilityZoneTestJSON, cls).setup_clients()
- cls.client = cls.availability_zone_client
-
@decorators.idempotent_id('01f1ae88-eba9-4c6b-a011-6f7ace06b725')
def test_get_availability_zone_list(self):
# List of availability zone
- availability_zone = (self.client.list_availability_zones()
- ['availabilityZoneInfo'])
+ availability_zone = (
+ self.availability_zone_client.list_availability_zones()
+ ['availabilityZoneInfo'])
self.assertNotEmpty(availability_zone)
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index e71032a..f07f197 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -47,6 +47,12 @@
You can also use the **--list-tests** option in conjunction with selection
arguments to list which tests will be run.
+You can also use the **--load-list** option that lets you pass a filepath to
+tempest run with the file format being in a non-regex format, similar to the
+tests generated by the **--list-tests** option. You can specify target tests
+by removing unnecessary tests from a list file which is generated from
+**--list-tests** option.
+
Test Execution
==============
There are several options to control how the tests are executed. By default
@@ -267,6 +273,12 @@
help='Path to a blacklist file, this file '
'contains a separate regex exclude on '
'each newline')
+ list_selector.add_argument('--load-list', '--load_list',
+ help='Path to a non-regex whitelist file, '
+ 'this file contains a seperate test '
+ 'on each newline. This command'
+ 'supports files created by the tempest'
+ 'run ``--list-tests`` command')
# list only args
parser.add_argument('--list-tests', '-l', action='store_true',
help='List tests',
@@ -318,6 +330,8 @@
options.append("--parallel")
if parsed_args.concurrency:
options.append("--concurrency=%s" % parsed_args.concurrency)
+ if parsed_args.load_list:
+ options.append("--load-list=%s" % parsed_args.load_list)
return options
def _run(self, regex, options):
diff --git a/tempest/common/identity.py b/tempest/common/identity.py
index 469defe..6e496d3 100644
--- a/tempest/common/identity.py
+++ b/tempest/common/identity.py
@@ -13,8 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest import config
+from tempest.lib.common import cred_client
from tempest.lib import exceptions as lib_exc
+CONF = config.CONF
+
def get_tenant_by_name(client, tenant_name):
tenants = client.list_tenants()['tenants']
@@ -30,3 +34,37 @@
if user['name'] == username:
return user
raise lib_exc.NotFound('No such user(%s) in %s' % (username, users))
+
+
+def identity_utils(clients):
+ """A client that abstracts v2 and v3 identity operations.
+
+ This can be used for creating and tearing down projects in tests. It
+ should not be used for testing identity features.
+
+ :param clients: a client manager.
+ :return
+ """
+ if CONF.identity.auth_version == 'v2':
+ client = clients.identity_client
+ users_client = clients.users_client
+ project_client = clients.tenants_client
+ roles_client = clients.roles_client
+ domains_client = None
+ else:
+ client = clients.identity_v3_client
+ users_client = clients.users_v3_client
+ project_client = clients.projects_client
+ roles_client = clients.roles_v3_client
+ domains_client = clients.domains_client
+
+ try:
+ domain = client.auth_provider.credentials.project_domain_name
+ except AttributeError:
+ domain = CONF.auth.default_credentials_domain_name
+
+ return cred_client.get_creds_client(client, project_client,
+ users_client,
+ roles_client,
+ domains_client,
+ project_domain_name=domain)
diff --git a/tempest/config.py b/tempest/config.py
index e78a07f..4d0839a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,15 +15,12 @@
from __future__ import print_function
-import functools
import os
import tempfile
-import debtcollector.removals
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
-import testtools
from tempest.lib import exceptions
from tempest.lib.services import clients
@@ -1284,79 +1281,6 @@
CONF = TempestConfigProxy()
-@debtcollector.removals.remove(
- message='use testtools.skipUnless instead', removal_version='Queens')
-def skip_unless_config(*args):
- """Decorator to raise a skip if a config opt doesn't exist or is False
-
- :param str group: The first arg, the option group to check
- :param str name: The second arg, the option name to check
- :param str msg: Optional third arg, the skip msg to use if a skip is raised
- :raises testtools.TestCaseskipException: If the specified config option
- doesn't exist or it exists and evaluates to False
- """
- def decorator(f):
- group = args[0]
- name = args[1]
-
- @functools.wraps(f)
- def wrapper(self, *func_args, **func_kwargs):
- if not hasattr(CONF, group):
- msg = "Config group %s doesn't exist" % group
- raise testtools.TestCase.skipException(msg)
-
- conf_group = getattr(CONF, group)
- if not hasattr(conf_group, name):
- msg = "Config option %s.%s doesn't exist" % (group,
- name)
- raise testtools.TestCase.skipException(msg)
-
- value = getattr(conf_group, name)
- if not value:
- if len(args) == 3:
- msg = args[2]
- else:
- msg = "Config option %s.%s is false" % (group,
- name)
- raise testtools.TestCase.skipException(msg)
- return f(self, *func_args, **func_kwargs)
- return wrapper
- return decorator
-
-
-@debtcollector.removals.remove(
- message='use testtools.skipIf instead', removal_version='Queens')
-def skip_if_config(*args):
- """Raise a skipException if a config exists and is True
-
- :param str group: The first arg, the option group to check
- :param str name: The second arg, the option name to check
- :param str msg: Optional third arg, the skip msg to use if a skip is raised
- :raises testtools.TestCase.skipException: If the specified config option
- exists and evaluates to True
- """
- def decorator(f):
- group = args[0]
- name = args[1]
-
- @functools.wraps(f)
- def wrapper(self, *func_args, **func_kwargs):
- if hasattr(CONF, group):
- conf_group = getattr(CONF, group)
- if hasattr(conf_group, name):
- value = getattr(conf_group, name)
- if value:
- if len(args) == 3:
- msg = args[2]
- else:
- msg = "Config option %s.%s is false" % (group,
- name)
- raise testtools.TestCase.skipException(msg)
- return f(self, *func_args, **func_kwargs)
- return wrapper
- return decorator
-
-
def service_client_config(service_client_name=None):
"""Return a dict with the parameters to init service clients
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index a8a6ff0..a430d5d 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -52,9 +52,5 @@
"the configured network")
-class RFCViolation(exceptions.RestClientException):
- message = "RFC Violation"
-
-
class InvalidServiceTag(exceptions.TempestException):
message = "Invalid service tag"
diff --git a/tempest/lib/common/api_version_utils.py b/tempest/lib/common/api_version_utils.py
index 98f174d..bcb076b 100644
--- a/tempest/lib/common/api_version_utils.py
+++ b/tempest/lib/common/api_version_utils.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
import testtools
from tempest.lib.common import api_version_request
@@ -20,7 +19,6 @@
LATEST_MICROVERSION = 'latest'
-LOG = logging.getLogger(__name__)
class BaseMicroversionTest(object):
@@ -166,7 +164,6 @@
if op is None:
msg = ("Operation %s is invalid. Valid options include: lt, eq, gt, "
"le, ne, ge." % operation)
- LOG.debug(msg)
raise exceptions.InvalidParam(invalid_param=msg)
# Remove "volume" from "volume <microversion>", for example, so that the
diff --git a/tempest/lib/services/volume/v3/group_snapshots_client.py b/tempest/lib/services/volume/v3/group_snapshots_client.py
index e644f02..6e53e3e 100644
--- a/tempest/lib/services/volume/v3/group_snapshots_client.py
+++ b/tempest/lib/services/volume/v3/group_snapshots_client.py
@@ -60,7 +60,7 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
- def list_group_snapshots(self, **params):
+ def list_group_snapshots(self, detail=False, **params):
"""Information for all the tenant's group snapshots.
For more information, please refer to the official API reference:
@@ -68,6 +68,8 @@
https://developer.openstack.org/api-ref/block-storage/v3/#list-group-snapshots-with-details
"""
url = "group_snapshots"
+ if detail:
+ url += "/detail"
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
@@ -75,6 +77,18 @@
self.expected_success(200, resp.status)
return rest_client.ResponseBody(resp, body)
+ def reset_group_snapshot_status(self, group_snapshot_id, status_to_set):
+ """Resets group snapshot status.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#reset-group-snapshot-status
+ """
+ post_body = json.dumps({'reset_status': {'status': status_to_set}})
+ resp, body = self.post('group_snapshots/%s/action' % group_snapshot_id,
+ post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def is_resource_deleted(self, id):
try:
self.show_group_snapshot(id)
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index b463fdf..e2e477d 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -109,6 +109,17 @@
self.expected_success(202, resp.status)
return rest_client.ResponseBody(resp, body)
+ def reset_group_status(self, group_id, status_to_set):
+ """Resets group status.
+
+ For more information, please refer to the official API reference:
+ https://developer.openstack.org/api-ref/block-storage/v3/#reset-group-status
+ """
+ post_body = json.dumps({'reset_status': {'status': status_to_set}})
+ resp, body = self.post('groups/%s/action' % group_id, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def is_resource_deleted(self, id):
try:
self.show_group(id)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index ef2dd3a..122d3a3 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -271,10 +271,8 @@
if backend_name:
extra_specs = {"volume_backend_name": backend_name}
- body = client.create_volume_type(name=randomized_name,
- extra_specs=extra_specs)
- volume_type = body['volume_type']
- self.assertIn('id', volume_type)
+ volume_type = client.create_volume_type(
+ name=randomized_name, extra_specs=extra_specs)['volume_type']
self.addCleanup(client.delete_volume_type, volume_type['id'])
return volume_type
@@ -855,22 +853,6 @@
floating_ip['id'])
return floating_ip
- def _associate_floating_ip(self, floating_ip, server):
- port_id, _ = self._get_server_port_id_and_ip4(server)
- kwargs = dict(port_id=port_id)
- floating_ip = self.floating_ips_client.update_floatingip(
- floating_ip['id'], **kwargs)['floatingip']
- self.assertEqual(port_id, floating_ip['port_id'])
- return floating_ip
-
- def _disassociate_floating_ip(self, floating_ip):
- """:param floating_ip: floating_ips_client.create_floatingip"""
- kwargs = dict(port_id=None)
- floating_ip = self.floating_ips_client.update_floatingip(
- floating_ip['id'], **kwargs)['floatingip']
- self.assertIsNone(floating_ip['port_id'])
- return floating_ip
-
def check_floating_ip_status(self, floating_ip, status):
"""Verifies floatingip reaches the given status
@@ -1180,12 +1162,6 @@
router['id'])
return router
- def _update_router_admin_state(self, router, admin_state_up):
- kwargs = dict(admin_state_up=admin_state_up)
- router = self.routers_client.update_router(
- router['id'], **kwargs)['router']
- self.assertEqual(admin_state_up, router['admin_state_up'])
-
def create_networks(self, networks_client=None,
routers_client=None, subnets_client=None,
tenant_id=None, dns_nameservers=None,
@@ -1352,7 +1328,7 @@
present_obj = []
if not_present_obj is None:
not_present_obj = []
- _, object_list = self.container_client.list_container_contents(
+ _, object_list = self.container_client.list_container_objects(
container_name)
if present_obj:
for obj in present_obj:
@@ -1364,8 +1340,8 @@
def change_container_acl(self, container_name, acl):
metadata_param = {'metadata_prefix': 'x-container-',
'metadata': {'read': acl}}
- self.container_client.update_container_metadata(container_name,
- **metadata_param)
+ self.container_client.create_update_or_delete_container_metadata(
+ container_name, create_update_metadata=metadata_param)
resp, _ = self.container_client.list_container_metadata(container_name)
self.assertEqual(resp['x-container-read'], acl)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 2d38b06..0c3bf23 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -213,17 +213,20 @@
def _disassociate_floating_ips(self):
floating_ip, _ = self.floating_ip_tuple
- self._disassociate_floating_ip(floating_ip)
- self.floating_ip_tuple = Floating_IP_tuple(
- floating_ip, None)
+ floating_ip = self.floating_ips_client.update_floatingip(
+ floating_ip['id'], port_id=None)['floatingip']
+ self.assertIsNone(floating_ip['port_id'])
+ self.floating_ip_tuple = Floating_IP_tuple(floating_ip, None)
def _reassociate_floating_ips(self):
floating_ip, server = self.floating_ip_tuple
# create a new server for the floating ip
server = self._create_server(self.network)
- self._associate_floating_ip(floating_ip, server)
- self.floating_ip_tuple = Floating_IP_tuple(
- floating_ip, server)
+ port_id, _ = self._get_server_port_id_and_ip4(server)
+ floating_ip = self.floating_ips_client.update_floatingip(
+ floating_ip['id'], port_id=port_id)['floatingip']
+ self.assertEqual(port_id, floating_ip['port_id'])
+ self.floating_ip_tuple = Floating_IP_tuple(floating_ip, server)
def _create_new_network(self, create_gateway=False):
self.new_net = self._create_network()
@@ -355,6 +358,12 @@
self.check_remote_connectivity(ssh_source, remote_ip,
should_connect)
+ def _update_router_admin_state(self, router, admin_state_up):
+ kwargs = dict(admin_state_up=admin_state_up)
+ router = self.routers_client.update_router(
+ router['id'], **kwargs)['router']
+ self.assertEqual(admin_state_up, router['admin_state_up'])
+
@decorators.attr(type='smoke')
@decorators.idempotent_id('f323b3ba-82f8-4db7-8ea6-6a895869ec49')
@utils.services('compute', 'network')
diff --git a/tempest/services/object_storage/container_client.py b/tempest/services/object_storage/container_client.py
index afedd36..a253599 100644
--- a/tempest/services/object_storage/container_client.py
+++ b/tempest/services/object_storage/container_client.py
@@ -15,6 +15,7 @@
from xml.etree import ElementTree as etree
+import debtcollector.moves
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
@@ -54,83 +55,57 @@
self.expected_success(204, resp.status)
return resp, body
- def update_container_metadata(
+ def create_update_or_delete_container_metadata(
self, container_name,
- metadata=None,
- remove_metadata=None,
- metadata_prefix='X-Container-Meta-',
- remove_metadata_prefix='X-Remove-Container-Meta-'):
- """Updates arbitrary metadata on container."""
+ create_update_metadata=None,
+ delete_metadata=None,
+ create_update_metadata_prefix='X-Container-Meta-',
+ delete_metadata_prefix='X-Remove-Container-Meta-'):
+ """Creates, Updates or deletes an containter metadata entry.
+
+ Container Metadata can be created, updated or deleted based on
+ metadata header or value. For detailed info, please refer to the
+ official API reference:
+ https://developer.openstack.org/api-ref/object-store/#create-update-or-delete-container-metadata
+ """
url = str(container_name)
headers = {}
+ if create_update_metadata:
+ for key in create_update_metadata:
+ metadata_header_name = create_update_metadata_prefix + key
+ headers[metadata_header_name] = create_update_metadata[key]
+ if delete_metadata:
+ for key in delete_metadata:
+ headers[delete_metadata_prefix + key] = delete_metadata[key]
- if metadata is not None:
- for key in metadata:
- headers[metadata_prefix + key] = metadata[key]
- if remove_metadata is not None:
- for key in remove_metadata:
- headers[remove_metadata_prefix + key] = remove_metadata[key]
-
- resp, body = self.post(url, body=None, headers=headers)
+ resp, body = self.post(url, headers=headers, body=None)
self.expected_success(204, resp.status)
return resp, body
- def delete_container_metadata(self, container_name, metadata,
- metadata_prefix='X-Remove-Container-Meta-'):
- """Deletes arbitrary metadata on container."""
- url = str(container_name)
- headers = {}
-
- if metadata is not None:
- for item in metadata:
- headers[metadata_prefix + item] = metadata[item]
-
- resp, body = self.post(url, body=None, headers=headers)
- self.expected_success(204, resp.status)
- return resp, body
+ update_container_metadata = debtcollector.moves.moved_function(
+ create_update_or_delete_container_metadata,
+ 'update_container_metadata', __name__,
+ version='Queens', removal_version='Rocky')
def list_container_metadata(self, container_name):
- """Retrieves container metadata headers"""
+ """List all container metadata."""
url = str(container_name)
resp, body = self.head(url)
self.expected_success(204, resp.status)
return resp, body
- def list_container_contents(self, container, params=None):
+ def list_container_objects(self, container_name, params=None):
"""List the objects in a container, given the container name
- Returns the container object listing as a plain text list, or as
- xml or json if that option is specified via the 'format' argument.
+ Returns the container object listing as a plain text list, or as
+ xml or json if that option is specified via the 'format' argument.
- Optional Arguments:
- limit = integer
- For an integer value n, limits the number of results to at most
- n values.
-
- marker = 'string'
- Given a string value x, return object names greater in value
- than the specified marker.
-
- prefix = 'string'
- For a string value x, causes the results to be limited to names
- beginning with the substring x.
-
- format = 'json' or 'xml'
- Specify either json or xml to return the respective serialized
- response.
- If json, returns a list of json objects
- if xml, returns a string of xml
-
- path = 'string'
- For a string value x, return the object names nested in the
- pseudo path (assuming preconditions are met - see below).
-
- delimiter = 'character'
- For a character c, return all the object names nested in the
- container (without the need for the directory marker objects).
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/object-storage/?expanded=show-container-details-and-list-objects-detail
"""
- url = str(container)
+ url = str(container_name)
if params:
url += '?'
url += '&%s' % urllib.urlencode(params)
@@ -148,3 +123,7 @@
self.expected_success([200, 204], resp.status)
return resp, body
+
+ list_container_contents = debtcollector.moves.moved_function(
+ list_container_objects, 'list_container_contents', __name__,
+ version='Queens', removal_version='Rocky')
diff --git a/tempest/test.py b/tempest/test.py
index 7d95bcf..9da85d5 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -27,7 +27,6 @@
from tempest.common import credentials_factory as credentials
from tempest.common import utils
from tempest import config
-from tempest.lib.common import cred_client
from tempest.lib.common import fixed_network
from tempest.lib.common import validation_resources as vr
from tempest.lib import decorators
@@ -98,14 +97,15 @@
- resource_cleanup
"""
- setUpClassCalled = False
-
# NOTE(andreaf) credentials holds a list of the credentials to be allocated
# at class setup time. Credential types can be 'primary', 'alt', 'admin' or
# a list of roles - the first element of the list being a label, and the
# rest the actual roles
credentials = []
+ # Track if setUpClass was invoked
+ __setupclass_called = False
+
# Network resources to be provisioned for the requested test credentials.
# Only used with the dynamic credentials provider.
_network_resources = {}
@@ -131,20 +131,21 @@
@classmethod
def _reset_class(cls):
cls.__setup_credentials_called = False
- cls.__resource_cleaup_called = False
+ cls.__resource_cleanup_called = False
cls.__skip_checks_called = False
+ # Stack of callable to be invoked in reverse order
cls._class_cleanups = []
+ # Stack of (name, callable) to be invoked in reverse order at teardown
+ cls._teardowns = []
@classmethod
def setUpClass(cls):
+ cls.__setupclass_called = True
# Reset state
cls._reset_class()
# It should never be overridden by descendants
if hasattr(super(BaseTestCase, cls), 'setUpClass'):
super(BaseTestCase, cls).setUpClass()
- cls.setUpClassCalled = True
- # Stack of (name, callable) to be invoked in reverse order at teardown
- cls.teardowns = []
# All the configuration checks that may generate a skip
cls.skip_checks()
if not cls.__skip_checks_called:
@@ -152,7 +153,7 @@
"skip_checks" % cls.__name__)
try:
# Allocation of all required credentials and client managers
- cls.teardowns.append(('credentials', cls.clear_credentials))
+ cls._teardowns.append(('credentials', cls.clear_credentials))
cls.setup_credentials()
if not cls.__setup_credentials_called:
raise RuntimeError("setup_credentials for %s did not call the "
@@ -160,7 +161,7 @@
# Shortcuts to clients
cls.setup_clients()
# Additional class-wide test resources
- cls.teardowns.append(('resources', cls.resource_cleanup))
+ cls._teardowns.append(('resources', cls.resource_cleanup))
cls.resource_setup()
except Exception:
etype, value, trace = sys.exc_info()
@@ -187,14 +188,14 @@
# If there was no exception during setup we shall re-raise the first
# exception in teardown
re_raise = (etype is None)
- while cls.teardowns:
- name, teardown = cls.teardowns.pop()
+ while cls._teardowns:
+ name, teardown = cls._teardowns.pop()
# Catch any exception in tearDown so we can re-raise the original
# exception at the end
try:
teardown()
if name == 'resources':
- if not cls.__resource_cleaup_called:
+ if not cls.__resource_cleanup_called:
raise RuntimeError(
"resource_cleanup for %s did not call the "
"super's resource_cleanup" % cls.__name__)
@@ -300,12 +301,65 @@
def setup_credentials(cls):
"""Allocate credentials and create the client managers from them.
- For every element of credentials param function creates tenant/user,
- Then it creates client manager for that credential.
+ `setup_credentials` looks for the content of the `credentials`
+ attribute in the test class. If the value is a non-empty collection,
+ a credentials provider is setup, and credentials are provisioned or
+ allocated based on the content of the collection. Every set of
+ credentials is associated to an object of type `cls.client_manager`.
+ The client manager is accessible by tests via class attribute
+ `os_[type]`:
- Network related tests must override this function with
- set_network_resources() method, otherwise it will create
- network resources(network resources are created in a later step).
+ Valid values in `credentials` are:
+ - 'primary':
+ A normal user is provisioned.
+ It can be used only once. Multiple entries will be ignored.
+ Clients are available at os_primary.
+ - 'alt':
+ A normal user other than 'primary' is provisioned.
+ It can be used only once. Multiple entries will be ignored.
+ Clients are available at os_alt.
+ - 'admin':
+ An admin user is provisioned.
+ It can be used only once. Multiple entries will be ignored.
+ Clients are available at os_admin.
+ - A list in the format ['any_label', 'role1', ... , 'roleN']:
+ A client with roles <list>[1:] is provisioned.
+ It can be used multiple times, with unique labels.
+ Clients are available at os_roles_<list>[0].
+
+ By default network resources are allocated (in case of dynamic
+ credentials). Tests that do not need network or that require a
+ custom network setup must specify which network resources shall
+ be provisioned using the `set_network_resources()` method (note
+ that it must be invoked before the `setup_credentials` is
+ invoked on super).
+
+ Example::
+
+ class TestWithCredentials(test.BaseTestCase):
+
+ credentials = ['primary', 'admin',
+ ['special', 'special_role1']]
+
+ @classmethod
+ def setup_credentials(cls):
+ # set_network_resources must be called first
+ cls.set_network_resources(network=True)
+ super(TestWithCredentials, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ cls.servers = cls.os_primary.compute.ServersClient()
+ cls.admin_servers = cls.os_admin.compute.ServersClient()
+ # certain API calls may require a user with a specific
+ # role assigned. In this example `special_role1` is
+ # assigned to the user in `cls.os_roles_special`.
+ cls.special_servers = (
+ cls.os_roles_special.compute.ServersClient())
+
+ def test_special_servers(self):
+ # Do something with servers
+ pass
"""
cls.__setup_credentials_called = True
for credentials_type in cls.credentials:
@@ -349,10 +403,53 @@
@classmethod
def setup_clients(cls):
- """Create links to the clients into the test object."""
- # TODO(andreaf) There is a fair amount of code that could me moved from
- # base / test classes in here. Ideally tests should be able to only
- # specify which client is `client` and nothing else.
+ """Create aliases to the clients in the client managers.
+
+ `setup_clients` is invoked after the credential provisioning step.
+ Client manager objects are available to tests already. The purpose
+ of this helper is to setup shortcuts to specific clients that are
+ useful for the tests implemented in the test class.
+
+ Its purpose is mostly for code readability, however it should be used
+ carefully to avoid doing exactly the opposite, i.e. making the code
+ unreadable and hard to debug. If aliases are defined in a super class
+ it won't be obvious what they refer to, so it's good practice to define
+ all aliases used in the class. Aliases are meant to be shortcuts to
+ be used in tests, not shortcuts to avoid helper method attributes.
+ If an helper method starts relying on a client alias and a subclass
+ overrides that alias, it will become rather difficult to understand
+ what the helper method actually does.
+
+ Example::
+
+ class TestDoneItRight(test.BaseTestCase):
+
+ credentials = ['primary', 'alt']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestDoneItRight, cls).setup_clients()
+ cls.servers = cls.os_primary.ServersClient()
+ cls.servers_alt = cls.os_alt.ServersClient()
+
+ def _a_good_helper(self, clients):
+ # Some complex logic we're going to use many times
+ servers = clients.ServersClient()
+ vm = servers.create_server(...)
+
+ def delete_server():
+ test_utils.call_and_ignore_notfound_exc(
+ servers.delete_server, vm['id'])
+
+ self.addCleanup(self.delete_server)
+ return vm
+
+ def test_with_servers(self):
+ vm = self._a_good_helper(os.primary)
+ vm_alt = self._a_good_helper(os.alt)
+ cls.servers.show_server(vm['id'])
+ cls.servers_alt.show_server(vm_alt['id'])
+ """
pass
@classmethod
@@ -453,7 +550,7 @@
# At this point test credentials are still available but
# anything from the cleanup stack has been already deleted.
"""
- cls.__resource_cleaup_called = True
+ cls.__resource_cleanup_called = True
cleanup_errors = []
while cls._class_cleanups:
try:
@@ -483,7 +580,7 @@
def setUp(self):
super(BaseTestCase, self).setUp()
- if not self.setUpClassCalled:
+ if not self.__setupclass_called:
raise RuntimeError("setUpClass does not calls the super's"
"setUpClass in the "
+ self.__class__.__name__)
@@ -514,37 +611,6 @@
def credentials_provider(self):
return self._get_credentials_provider()
- @property
- def identity_utils(self):
- """A client that abstracts v2 and v3 identity operations.
-
- This can be used for creating and tearing down projects in tests. It
- should not be used for testing identity features.
- """
- if CONF.identity.auth_version == 'v2':
- client = self.os_admin.identity_client
- users_client = self.os_admin.users_client
- project_client = self.os_admin.tenants_client
- roles_client = self.os_admin.roles_client
- domains_client = None
- else:
- client = self.os_admin.identity_v3_client
- users_client = self.os_admin.users_v3_client
- project_client = self.os_admin.projects_client
- roles_client = self.os_admin.roles_v3_client
- domains_client = self.os_admin.domains_client
-
- try:
- domain = client.auth_provider.credentials.project_domain_name
- except AttributeError:
- domain = 'Default'
-
- return cred_client.get_creds_client(client, project_client,
- users_client,
- roles_client,
- domains_client,
- project_domain_name=domain)
-
@classmethod
def get_identity_version(cls):
"""Returns the identity version used by the test class"""
@@ -710,6 +776,9 @@
specific network resources to be provisioned - none if no parameter
is specified.
+ This method is designed so that only the network resources set on the
+ leaf class are honoured.
+
Credentials are provisioned as part of the class setup fixture,
during the `setup_credentials` step. For this to be effective this
helper must be invoked before super's `setup_credentials` is executed.
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index 6e1250f..0485e14 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -40,6 +40,7 @@
setattr(args, "subunit", True)
setattr(args, "parallel", False)
setattr(args, "concurrency", 10)
+ setattr(args, "load_list", '')
options = self.run_cmd._build_options(args)
self.assertEqual(['--subunit',
'--concurrency=10'],
diff --git a/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py b/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py
index 5ac5c08..c2784b2 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_snapshots_client.py
@@ -93,7 +93,8 @@
bytes_body,
group_snapshot_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
- def _test_list_group_snapshots(self, bytes_body=False, detail=False):
+ def _test_list_group_snapshots(self, detail=False, bytes_body=False,
+ mock_args='group_snapshots', **params):
resp_body = []
if detail:
resp_body = self.FAKE_LIST_GROUP_SNAPSHOTS
@@ -111,8 +112,10 @@
self.client.list_group_snapshots,
'tempest.lib.common.rest_client.RestClient.get',
resp_body,
- bytes_body,
- detail=detail)
+ to_utf=bytes_body,
+ mock_args=[mock_args],
+ detail=detail,
+ **params)
def test_create_group_snapshot_with_str_body(self):
self._test_create_group_snapshot()
@@ -132,6 +135,25 @@
def test_list_group_snapshots_with_bytes_body(self):
self._test_list_group_snapshots(bytes_body=True)
+ def test_list_group_snapshots_with_detail_with_str_body(self):
+ mock_args = "group_snapshots/detail"
+ self._test_list_group_snapshots(detail=True, mock_args=mock_args)
+
+ def test_list_group_snapshots_with_detail_with_bytes_body(self):
+ mock_args = "group_snapshots/detail"
+ self._test_list_group_snapshots(detail=True, bytes_body=True,
+ mock_args=mock_args)
+
+ def test_list_group_snapshots_with_params(self):
+ # Run the test separately for each param, to avoid assertion error
+ # resulting from randomized params order.
+ mock_args = 'group_snapshots?sort_key=name'
+ self._test_list_group_snapshots(mock_args=mock_args, sort_key='name')
+
+ mock_args = 'group_snapshots/detail?limit=10'
+ self._test_list_group_snapshots(detail=True, bytes_body=True,
+ mock_args=mock_args, limit=10)
+
def test_delete_group_snapshot(self):
self.check_service_client_function(
self.client.delete_group_snapshot,
@@ -139,3 +161,12 @@
{},
group_snapshot_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
status=202)
+
+ def test_reset_group_snapshot_status(self):
+ self.check_service_client_function(
+ self.client.reset_group_snapshot_status,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ group_snapshot_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
+ status_to_set='error')
diff --git a/tempest/tests/lib/services/volume/v3/test_groups_client.py b/tempest/tests/lib/services/volume/v3/test_groups_client.py
index 0884e5a..918e958 100644
--- a/tempest/tests/lib/services/volume/v3/test_groups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_groups_client.py
@@ -184,3 +184,12 @@
group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
status=202,
**self.FAKE_UPDATE_GROUP['group'])
+
+ def test_reset_group_status(self):
+ self.check_service_client_function(
+ self.client.reset_group_status,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ status=202,
+ group_id='0e701ab8-1bec-4b9f-b026-a7ba4af13578',
+ status_to_set='error')
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index bf04280..6018441 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -176,96 +176,3 @@
self._test_requires_ext_helper,
extension='enabled_ext',
service='bad_service')
-
-
-class TestConfigDecorators(BaseDecoratorsTest):
- def setUp(self):
- super(TestConfigDecorators, self).setUp()
- cfg.CONF.set_default('nova', True, 'service_available')
- cfg.CONF.set_default('glance', False, 'service_available')
-
- def _assert_skip_message(self, func, skip_msg):
- try:
- func()
- self.fail()
- except testtools.TestCase.skipException as skip_exc:
- self.assertEqual(skip_exc.args[0], skip_msg)
-
- def _test_skip_unless_config(self, expected_to_skip=True, *decorator_args):
-
- class TestFoo(test.BaseTestCase):
- @config.skip_unless_config(*decorator_args)
- def test_bar(self):
- return 0
-
- t = TestFoo('test_bar')
- if expected_to_skip:
- self.assertRaises(testtools.TestCase.skipException, t.test_bar)
- if (len(decorator_args) >= 3):
- # decorator_args[2]: skip message specified
- self._assert_skip_message(t.test_bar, decorator_args[2])
- else:
- try:
- self.assertEqual(t.test_bar(), 0)
- except testtools.TestCase.skipException:
- # We caught a skipException but we didn't expect to skip
- # this test so raise a hard test failure instead.
- raise testtools.TestCase.failureException(
- "Not supposed to skip")
-
- def _test_skip_if_config(self, expected_to_skip=True,
- *decorator_args):
-
- class TestFoo(test.BaseTestCase):
- @config.skip_if_config(*decorator_args)
- def test_bar(self):
- return 0
-
- t = TestFoo('test_bar')
- if expected_to_skip:
- self.assertRaises(testtools.TestCase.skipException, t.test_bar)
- if (len(decorator_args) >= 3):
- # decorator_args[2]: skip message specified
- self._assert_skip_message(t.test_bar, decorator_args[2])
- else:
- try:
- self.assertEqual(t.test_bar(), 0)
- except testtools.TestCase.skipException:
- # We caught a skipException but we didn't expect to skip
- # this test so raise a hard test failure instead.
- raise testtools.TestCase.failureException(
- "Not supposed to skip")
-
- def test_skip_unless_no_group(self):
- self._test_skip_unless_config(True, 'fake_group', 'an_option')
-
- def test_skip_unless_no_option(self):
- self._test_skip_unless_config(True, 'service_available',
- 'not_an_option')
-
- def test_skip_unless_false_option(self):
- self._test_skip_unless_config(True, 'service_available', 'glance')
-
- def test_skip_unless_false_option_msg(self):
- self._test_skip_unless_config(True, 'service_available', 'glance',
- 'skip message')
-
- def test_skip_unless_true_option(self):
- self._test_skip_unless_config(False,
- 'service_available', 'nova')
-
- def test_skip_if_no_group(self):
- self._test_skip_if_config(False, 'fake_group', 'an_option')
-
- def test_skip_if_no_option(self):
- self._test_skip_if_config(False, 'service_available', 'not_an_option')
-
- def test_skip_if_false_option(self):
- self._test_skip_if_config(False, 'service_available', 'glance')
-
- def test_skip_if_true_option(self):
- self._test_skip_if_config(True, 'service_available', 'nova')
-
- def test_skip_if_true_option_msg(self):
- self._test_skip_if_config(True, 'service_available', 'nova',
- 'skip message')
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index ead0bd8..fc50736 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
import sys
import mock
@@ -426,3 +427,200 @@
with testtools.ExpectedException(testtools.testcase.TestSkipped):
NeedV3().skip_checks()
+
+ def test_setup_credentials_all(self):
+ expected_creds = ['string', ['list', 'role1', 'role2']]
+
+ class AllCredentials(self.parent_test):
+ credentials = expected_creds
+
+ expected_clients = 'clients'
+ with mock.patch.object(
+ AllCredentials,
+ 'get_client_manager') as mock_get_client_manager:
+ mock_get_client_manager.return_value = expected_clients
+ all_creds = AllCredentials()
+ all_creds.setup_credentials()
+ self.assertTrue(hasattr(all_creds, 'os_string'))
+ self.assertEqual(expected_clients, all_creds.os_string)
+ self.assertTrue(hasattr(all_creds, 'os_roles_list'))
+ self.assertEqual(expected_clients, all_creds.os_roles_list)
+ self.assertEqual(2, mock_get_client_manager.call_count)
+ self.assertEqual(
+ expected_creds[0],
+ mock_get_client_manager.mock_calls[0][2]['credential_type'])
+ self.assertEqual(
+ expected_creds[1][1:],
+ mock_get_client_manager.mock_calls[1][2]['roles'])
+
+ def test_setup_class_overwritten(self):
+
+ class OverridesSetup(self.parent_test):
+
+ @classmethod
+ def setUpClass(cls): # noqa
+ pass
+
+ overrides_setup = OverridesSetup()
+ suite = unittest.TestSuite((overrides_setup,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # Record 0, test (error holder). The error generates during test run.
+ self.assertIn('runTest', str(log[0][0]))
+ # Record 0, traceback
+ self.assertRegex(
+ str(log[0][2]['traceback']).replace('\n', ' '),
+ RuntimeError.__name__ + ': .* ' + OverridesSetup.__name__)
+
+
+class TestTempestBaseTestClassFixtures(base.TestCase):
+
+ SETUP_FIXTURES = [test.BaseTestCase.setUpClass.__name__,
+ test.BaseTestCase.skip_checks.__name__,
+ test.BaseTestCase.setup_credentials.__name__,
+ test.BaseTestCase.setup_clients.__name__,
+ test.BaseTestCase.resource_setup.__name__]
+ TEARDOWN_FIXTURES = [test.BaseTestCase.tearDownClass.__name__,
+ test.BaseTestCase.resource_cleanup.__name__,
+ test.BaseTestCase.clear_credentials.__name__]
+
+ def setUp(self):
+ super(TestTempestBaseTestClassFixtures, self).setUp()
+ self.mocks = {}
+ for fix in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES:
+ self.mocks[fix] = mock.Mock()
+
+ def tracker_builder(name):
+
+ def tracker(cls):
+ # Track that the fixture was invoked
+ cls.fixtures_invoked.append(name)
+ # Run the fixture
+ getattr(super(TestWithClassFixtures, cls), name)()
+ # Run a mock we can use for side effects
+ self.mocks[name]()
+
+ return tracker
+
+ class TestWithClassFixtures(test.BaseTestCase):
+
+ credentials = []
+ fixtures_invoked = []
+
+ def runTest(_self):
+ pass
+
+ # Decorate all test class fixtures with tracker_builder
+ for method_name in self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES:
+ setattr(TestWithClassFixtures, method_name,
+ classmethod(tracker_builder(method_name)))
+
+ self.test = TestWithClassFixtures()
+
+ def test_no_error_flow(self):
+ # If all setup fixtures are executed, all cleanup fixtures are
+ # executed too
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+ self.test.fixtures_invoked)
+
+ def test_skip_only(self):
+ # If a skip condition is hit in the test, no credentials or resource
+ # is provisioned / cleaned-up
+ self.mocks['skip_checks'].side_effect = (
+ testtools.testcase.TestSkipped())
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If we trigger a skip condition, teardown is not invoked at all
+ self.assertEqual(self.SETUP_FIXTURES[:2],
+ self.test.fixtures_invoked)
+
+ def test_skip_credentials_fails(self):
+ expected_exc = 'sc exploded'
+ self.mocks['setup_credentials'].side_effect = Exception(expected_exc)
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If setup_credentials explodes, we invoked teardown class and
+ # clear credentials, and re-raise
+ self.assertEqual((self.SETUP_FIXTURES[:3] +
+ [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+ self.test.fixtures_invoked)
+ found_exc = log[0][1][1]
+ self.assertIn(expected_exc, str(found_exc))
+
+ def test_skip_credentials_fails_clear_fails(self):
+ # If cleanup fails on failure, we log the exception and do not
+ # re-raise it. Note that since the exception happens outside of
+ # the Tempest test setUp, logging is not captured on the Tempest
+ # test side, it will be captured by the unit test instead.
+ expected_exc = 'sc exploded'
+ clear_exc = 'clear exploded'
+ self.mocks['setup_credentials'].side_effect = Exception(expected_exc)
+ self.mocks['clear_credentials'].side_effect = Exception(clear_exc)
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If setup_credentials explodes, we invoked teardown class and
+ # clear credentials, and re-raise
+ self.assertEqual((self.SETUP_FIXTURES[:3] +
+ [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+ self.test.fixtures_invoked)
+ found_exc = log[0][1][1]
+ self.assertIn(expected_exc, str(found_exc))
+ # Since log capture depends on OS_LOG_CAPTURE, we can only assert if
+ # logging was captured
+ if os.environ.get('OS_LOG_CAPTURE'):
+ self.assertIn(clear_exc, self.log_fixture.logger.output)
+
+ def test_skip_credentials_clients_resources_credentials_clear_fails(self):
+ # If cleanup fails with no previous failure, we re-raise the exception.
+ expected_exc = 'clear exploded'
+ self.mocks['clear_credentials'].side_effect = Exception(expected_exc)
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If setup_credentials explodes, we invoked teardown class and
+ # clear credentials, and re-raise
+ self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+ self.test.fixtures_invoked)
+ found_exc = log[0][1][1]
+ self.assertIn(expected_exc, str(found_exc))
+
+ def test_skip_credentials_clients_fails(self):
+ expected_exc = 'clients exploded'
+ self.mocks['setup_clients'].side_effect = Exception(expected_exc)
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If setup_clients explodes, we invoked teardown class and
+ # clear credentials, and re-raise
+ self.assertEqual((self.SETUP_FIXTURES[:4] +
+ [self.TEARDOWN_FIXTURES[i] for i in (0, 2)]),
+ self.test.fixtures_invoked)
+ found_exc = log[0][1][1]
+ self.assertIn(expected_exc, str(found_exc))
+
+ def test_skip_credentials_clients_resources_fails(self):
+ expected_exc = 'resource setup exploded'
+ self.mocks['resource_setup'].side_effect = Exception(expected_exc)
+ suite = unittest.TestSuite((self.test,))
+ log = []
+ result = LoggingTestResult(log)
+ suite.run(result)
+ # If resource_setup explodes, we invoked teardown class and
+ # clear credentials and resource cleanup, and re-raise
+ self.assertEqual(self.SETUP_FIXTURES + self.TEARDOWN_FIXTURES,
+ self.test.fixtures_invoked)
+ found_exc = log[0][1][1]
+ self.assertIn(expected_exc, str(found_exc))