Merge "Need to have stable implementation of nova_volume_detach() and add missing docstrings"
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 80f790f..687fe57 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -21,6 +21,7 @@
class BulkTest(base.BaseObjectTest):
+ """Test bulk operation of archived file"""
def setUp(self):
super(BulkTest, self).setUp()
@@ -70,7 +71,7 @@
@decorators.idempotent_id('a407de51-1983-47cc-9f14-47c2b059413c')
@utils.requires_ext(extension='bulk_upload', service='object')
def test_extract_archive(self):
- # Test bulk operation of file upload with an archived file
+ """Test bulk operation of file upload with an archived file"""
filepath, container_name, object_name = self._create_archive()
resp = self._upload_archive(filepath)
self.containers.append(container_name)
@@ -95,7 +96,7 @@
@decorators.idempotent_id('c075e682-0d2a-43b2-808d-4116200d736d')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
@@ -110,7 +111,7 @@
@decorators.idempotent_id('dbea2bcb-efbb-4674-ac8a-a5a0e33d1d79')
@utils.requires_ext(extension='bulk_delete', service='object')
def test_bulk_delete_by_POST(self):
- # Test bulk operation of deleting multiple files
+ """Test bulk operation of deleting multiple files by HTTP POST"""
filepath, container_name, object_name = self._create_archive()
self._upload_archive(filepath)
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 48f42ec..6854bbe 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -22,6 +22,7 @@
class AccountQuotasTest(base.BaseObjectTest):
+ """Test account quotas"""
credentials = [['operator', CONF.object_storage.operator_role],
['reseller', CONF.object_storage.reseller_admin_role]]
@@ -79,6 +80,7 @@
@decorators.idempotent_id('a22ef352-a342-4587-8f47-3bbdb5b039c4')
@utils.requires_ext(extension='account_quotas', service='object')
def test_upload_valid_object(self):
+ """Test uploading valid object"""
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index 3e664d7..8d2a501 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -21,6 +21,7 @@
class AccountNegativeTest(base.BaseObjectTest):
+ """Negative tests of account"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -33,7 +34,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('070e6aca-6152-4867-868d-1118d68fb38c')
def test_list_containers_with_non_authorized_user(self):
- # list containers using non-authorized user
+ """Test listing containers using non-authorized user"""
test_auth_provider = self.os_operator.auth_provider
# Get auth for the test user
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index e9ca0b1..c8731fe 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -22,6 +22,7 @@
class ObjectTestACLs(base.BaseObjectTest):
+ """Test object ACLs"""
credentials = [['operator', CONF.object_storage.operator_role],
['operator_alt', CONF.object_storage.operator_role]]
@@ -36,7 +37,7 @@
@decorators.idempotent_id('a3270f3f-7640-4944-8448-c7ea783ea5b6')
def test_read_object_with_rights(self):
- # attempt to read object using authorized user
+ """Test reading object using authorized user"""
# update X-Container-Read metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
@@ -64,7 +65,7 @@
@decorators.idempotent_id('aa58bfa5-40d9-4bc3-82b4-d07f4a9e392a')
def test_write_object_with_rights(self):
- # attempt to write object using authorized user
+ """Test writing object using authorized user"""
# update X-Container-Write metadata ACL
tenant_id = self.os_roles_operator_alt.credentials.tenant_id
user_id = self.os_roles_operator_alt.credentials.user_id
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index cdc420e..7ad6f6f 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -19,6 +19,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test containers"""
+
def tearDown(self):
self.delete_containers()
super(ContainerTest, self).tearDown()
@@ -26,6 +28,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
def test_create_container(self):
+ """Test creating container"""
container_name = data_utils.rand_name(name='TestContainer')
resp, _ = self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -33,7 +36,7 @@
@decorators.idempotent_id('49f866ed-d6af-4395-93e7-4187eb56d322')
def test_create_container_overwrite(self):
- # overwrite container with the same name
+ """Test overwriting container with the same name"""
container_name = data_utils.rand_name(name='TestContainer')
self.container_client.update_container(container_name)
self.containers.append(container_name)
@@ -43,7 +46,7 @@
@decorators.idempotent_id('c2ac4d59-d0f5-40d5-ba19-0635056d48cd')
def test_create_container_with_metadata_key(self):
- # create container with the blank value of metadata
+ """Test creating container with the blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': ''}
resp, _ = self.container_client.update_container(
@@ -60,7 +63,7 @@
@decorators.idempotent_id('e1e8df32-7b22-44e1-aa08-ccfd8d446b58')
def test_create_container_with_metadata_value(self):
- # create container with metadata value
+ """Test creating container with metadata value"""
container_name = data_utils.rand_name(name='TestContainer')
# metadata name using underscores should be converted to hyphens
@@ -79,7 +82,7 @@
@decorators.idempotent_id('24d16451-1c0c-4e4f-b59c-9840a3aba40e')
def test_create_container_with_remove_metadata_key(self):
- # create container with the blank value of remove metadata
+ """Test creating container with the blank value of remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -97,7 +100,7 @@
@decorators.idempotent_id('8a21ebad-a5c7-4e29-b428-384edc8cd156')
def test_create_container_with_remove_metadata_value(self):
- # create container with remove metadata
+ """Test creating container with remove metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta': 'Meta1'}
self.container_client.update_container(container_name, **headers)
@@ -114,6 +117,7 @@
@decorators.idempotent_id('95d3a249-b702-4082-a2c4-14bb860cf06a')
def test_delete_container(self):
+ """Test deleting container"""
# create a container
container_name = self.create_container()
# delete container, success asserted within
@@ -123,7 +127,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('312ff6bd-5290-497f-bda1-7c5fec6697ab')
def test_list_container_contents(self):
- # get container contents list
+ """Test getting container contents list"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -134,7 +138,7 @@
@decorators.idempotent_id('4646ac2d-9bfb-4c7d-a3c5-0f527402b3df')
def test_list_container_contents_with_no_object(self):
- # get empty container contents list
+ """Test getting empty container contents list"""
container_name = self.create_container()
resp, object_list = self.container_client.list_container_objects(
@@ -144,7 +148,7 @@
@decorators.idempotent_id('fe323a32-57b9-4704-a996-2e68f83b09bc')
def test_list_container_contents_with_delimiter(self):
- # get container contents list using delimiter param
+ """Test getting container contents list using delimiter param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject/')
self.create_object(container_name, object_name)
@@ -158,7 +162,7 @@
@decorators.idempotent_id('55b4fa5c-e12e-4ca9-8fcf-a79afe118522')
def test_list_container_contents_with_end_marker(self):
- # get container contents list using end_marker param
+ """Test getting container contents list using end_marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -171,7 +175,7 @@
@decorators.idempotent_id('196f5034-6ab0-4032-9da9-a937bbb9fba9')
def test_list_container_contents_with_format_json(self):
- # get container contents list using format_json param
+ """Test getting container contents list using format_json param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -190,7 +194,7 @@
@decorators.idempotent_id('655a53ca-4d15-408c-a377-f4c6dbd0a1fa')
def test_list_container_contents_with_format_xml(self):
- # get container contents list using format_xml param
+ """Test getting container contents list using format_xml param"""
container_name = self.create_container()
self.create_object(container_name)
@@ -214,7 +218,7 @@
@decorators.idempotent_id('297ec38b-2b61-4ff4-bcd1-7fa055e97b61')
def test_list_container_contents_with_limit(self):
- # get container contents list using limit param
+ """Test getting container contents list using limit param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -227,7 +231,7 @@
@decorators.idempotent_id('c31ddc63-2a58-4f6b-b25c-94d2937e6867')
def test_list_container_contents_with_marker(self):
- # get container contents list using marker param
+ """Test getting container contents list using marker param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -240,7 +244,7 @@
@decorators.idempotent_id('58ca6cc9-6af0-408d-aaec-2a6a7b2f0df9')
def test_list_container_contents_with_path(self):
- # get container contents list using path param
+ """Test getting container contents list using path param"""
container_name = self.create_container()
object_name = data_utils.rand_name(name='TestObject')
object_name = 'Swift/' + object_name
@@ -255,7 +259,7 @@
@decorators.idempotent_id('77e742c7-caf2-4ec9-8aa4-f7d509a3344c')
def test_list_container_contents_with_prefix(self):
- # get container contents list using prefix param
+ """Test getting container contents list using prefix param"""
container_name = self.create_container()
object_name, _ = self.create_object(container_name)
@@ -270,7 +274,7 @@
@decorators.attr(type='smoke')
@decorators.idempotent_id('96e68f0e-19ec-4aa2-86f3-adc6a45e14dd')
def test_list_container_metadata(self):
- # List container metadata
+ """Test listing container metadata"""
container_name = self.create_container()
metadata = {'name': 'Pictures'}
@@ -286,7 +290,7 @@
@decorators.idempotent_id('a2faf936-6b13-4f8d-92a2-c2278355821e')
def test_list_no_container_metadata(self):
- # HEAD container without metadata
+ """Test listing container without metadata"""
container_name = self.create_container()
resp, _ = self.container_client.list_container_metadata(
@@ -296,7 +300,10 @@
@decorators.idempotent_id('cf19bc0b-7e16-4a5a-aaed-cb0c2fe8deef')
def test_update_container_metadata_with_create_and_delete_metadata(self):
- # Send one request of adding and deleting metadata
+ """Test updating container with adding and deleting metadata
+
+ Send one request of adding and deleting metadata.
+ """
container_name = data_utils.rand_name(name='TestContainer')
metadata_1 = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata_1)
@@ -319,7 +326,7 @@
@decorators.idempotent_id('2ae5f295-4bf1-4e04-bfad-21e54b62cec5')
def test_update_container_metadata_with_create_metadata(self):
- # update container metadata using add metadata
+ """Test updating container metadata using add metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': 'Meta1'}
@@ -337,7 +344,7 @@
@decorators.idempotent_id('3a5ce7d4-6e4b-47d0-9d87-7cd42c325094')
def test_update_container_metadata_with_delete_metadata(self):
- # update container metadata using delete metadata
+ """Test updating container metadata using delete metadata"""
container_name = data_utils.rand_name(name='TestContainer')
metadata = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **metadata)
@@ -355,7 +362,7 @@
@decorators.idempotent_id('31f40a5f-6a52-4314-8794-cd89baed3040')
def test_update_container_metadata_with_create_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = self.create_container()
metadata = {'test-container-meta1': ''}
@@ -371,7 +378,7 @@
@decorators.idempotent_id('a2e36378-6f1f-43f4-840a-ffd9cfd61914')
def test_update_container_metadata_with_delete_metadata_key(self):
- # update container metadata with a blank value of metadata
+ """Test updating container metadata with a blank value of metadata"""
container_name = data_utils.rand_name(name='TestContainer')
headers = {'X-Container-Meta-test-container-meta1': 'Meta1'}
self.container_client.update_container(container_name, **headers)
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index 8e9e406..f5e2443 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -19,12 +19,14 @@
class HealthcheckTest(base.BaseObjectTest):
+ """Test healthcheck"""
def setUp(self):
super(HealthcheckTest, self).setUp()
@decorators.idempotent_id('db5723b1-f25c-49a9-bfeb-7b5640caf337')
def test_get_healthcheck(self):
+ """Test getting healthcheck"""
url = self.account_client._get_base_version_url() + "healthcheck"
resp, body = self.account_client.raw_request(url, "GET")
self.account_client._error_checker(resp, body)
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index 75111b6..b64b172 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -24,6 +24,8 @@
class ContainerTest(base.BaseObjectTest):
+ """Test versioned container"""
+
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
self.assertHeaders(resp, 'Container', 'HEAD')
@@ -39,6 +41,15 @@
not CONF.object_storage_feature_enabled.object_versioning,
'Object-versioning is disabled')
def test_versioned_container(self):
+ """Test versioned container
+
+ 1. create container1
+ 2. create container2, with container1 as 'X-versions-Location' header
+ 3. create object1 in container1
+ 4. create 2nd version of object1
+ 5. delete object version 2
+ 6. delete object version 1
+ """
# create container
vers_container_name = data_utils.rand_name(name='TestVersionContainer')
resp, _ = self.container_client.update_container(vers_container_name)
diff --git a/tempest/api/volume/admin/test_volume_services.py b/tempest/api/volume/admin/test_volume_services.py
index 293af81..1d12a73 100644
--- a/tempest/api/volume/admin/test_volume_services.py
+++ b/tempest/api/volume/admin/test_volume_services.py
@@ -39,12 +39,14 @@
@decorators.idempotent_id('e0218299-0a59-4f43-8b2b-f1c035b3d26d')
def test_list_services(self):
+ """Test listing volume services"""
services = (self.admin_volume_services_client.list_services()
['services'])
self.assertNotEmpty(services)
@decorators.idempotent_id('63a3e1ca-37ee-4983-826d-83276a370d25')
def test_get_service_by_service_binary_name(self):
+ """Test getting volume service by binary name"""
services = (self.admin_volume_services_client.list_services(
binary=self.binary_name)['services'])
self.assertNotEmpty(services)
@@ -53,6 +55,7 @@
@decorators.idempotent_id('178710e4-7596-4e08-9333-745cb8bc4f8d')
def test_get_service_by_host_name(self):
+ """Test getting volume service by service host name"""
services_on_host = [service for service in self.services if
_get_host(service['host']) == self.host_name]
@@ -69,6 +72,7 @@
@decorators.idempotent_id('67ec6902-f91d-4dec-91fa-338523208bbc')
def test_get_service_by_volume_host_name(self):
+ """Test getting volume service by volume host name"""
volume_id = self.create_volume()['id']
volume = self.admin_volume_client.show_volume(volume_id)['volume']
hostname = _get_host(volume['os-vol-host-attr:host'])
@@ -83,7 +87,7 @@
@decorators.idempotent_id('ffa6167c-4497-4944-a464-226bbdb53908')
def test_get_service_by_service_and_host_name(self):
-
+ """Test getting volume service by binary name and host name"""
services = (self.admin_volume_services_client.list_services(
host=self.host_name, binary=self.binary_name))['services']
diff --git a/tempest/api/volume/admin/test_volume_type_access.py b/tempest/api/volume/admin/test_volume_type_access.py
index b64face..55ec428 100644
--- a/tempest/api/volume/admin/test_volume_type_access.py
+++ b/tempest/api/volume/admin/test_volume_type_access.py
@@ -24,11 +24,13 @@
class VolumeTypesAccessTest(base.BaseVolumeAdminTest):
+ """Test volume type access"""
credentials = ['primary', 'alt', 'admin']
@decorators.idempotent_id('d4dd0027-835f-4554-a6e5-50903fb79184')
def test_volume_type_access_add(self):
+ """Test adding volume type access for non-admin project"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
@@ -52,6 +54,7 @@
@decorators.idempotent_id('5220eb28-a435-43ce-baaf-ed46f0e95159')
def test_volume_type_access_list(self):
+ """Test listing volume type access"""
# Creating a NON public volume type
params = {'os-volume-type-access:is_public': False}
volume_type = self.create_volume_type(**params)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 730acdf..852aa93 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -19,6 +19,7 @@
class VolumeTypesExtraSpecsTest(base.BaseVolumeAdminTest):
+ """Test volume type extra specs"""
@classmethod
def resource_setup(cls):
@@ -27,7 +28,7 @@
@decorators.idempotent_id('b42923e9-0452-4945-be5b-d362ae533e60')
def test_volume_type_extra_specs_list(self):
- # List Volume types extra specs.
+ """Test listing volume type extra specs"""
extra_specs = {"spec1": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -40,7 +41,7 @@
@decorators.idempotent_id('0806db36-b4a0-47a1-b6f3-c2e7f194d017')
def test_volume_type_extra_specs_update(self):
- # Update volume type extra specs
+ """Test updating volume type extra specs"""
extra_specs = {"spec2": "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
self.volume_type['id'], extra_specs)['extra_specs']
@@ -74,7 +75,7 @@
@decorators.idempotent_id('d4772798-601f-408a-b2a5-29e8a59d1220')
def test_volume_type_extra_spec_create_get_delete(self):
- # Create/Get/Delete volume type extra spec.
+ """Test Create/Get/Delete volume type extra specs"""
spec_key = "spec3"
extra_specs = {spec_key: "val1"}
body = self.admin_volume_types_client.create_volume_type_extra_specs(
diff --git a/tempest/api/volume/admin/test_volumes_backup.py b/tempest/api/volume/admin/test_volumes_backup.py
index 45060d0..bd4b3fa 100644
--- a/tempest/api/volume/admin/test_volumes_backup.py
+++ b/tempest/api/volume/admin/test_volumes_backup.py
@@ -67,11 +67,8 @@
# Export Backup
export_backup = (self.admin_backups_client.export_backup(backup['id'])
['backup-record'])
- self.assertIn('backup_service', export_backup)
- self.assertIn('backup_url', export_backup)
self.assertTrue(export_backup['backup_service'].startswith(
'cinder.backup.drivers'))
- self.assertIsNotNone(export_backup['backup_url'])
# NOTE(geguileo): Backups are imported with the same backup id
# (important for incremental backups among other things), so we cannot
@@ -92,7 +89,6 @@
# deletions will delete data from the backup back-end because they
# were both pointing to the same backend data.
self.addCleanup(self._delete_backup, new_id)
- self.assertIn("id", import_backup)
self.assertEqual(new_id, import_backup['id'])
waiters.wait_for_volume_resource_status(self.admin_backups_client,
import_backup['id'],
diff --git a/tempest/api/volume/admin/test_volumes_list.py b/tempest/api/volume/admin/test_volumes_list.py
index 6ce4a85..c3229f0 100644
--- a/tempest/api/volume/admin/test_volumes_list.py
+++ b/tempest/api/volume/admin/test_volumes_list.py
@@ -24,6 +24,7 @@
class VolumesListAdminTestJSON(base.BaseVolumeAdminTest):
+ """Test listing volumes with admin privilege"""
@classmethod
def resource_setup(cls):
@@ -41,7 +42,7 @@
@decorators.idempotent_id('5866286f-3290-4cfd-a414-088aa6cdc469')
def test_volume_list_param_tenant(self):
- # Test to list volumes from single tenant
+ """Test admin can list volumes belonging to specified project"""
# Create a volume in admin tenant
adm_vol = self.admin_volume_client.create_volume(
size=CONF.volume.volume_size)['volume']
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 0b6ee38..39369be 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -22,7 +22,7 @@
@decorators.idempotent_id('01f1ae88-eba9-4c6b-a011-6f7ace06b725')
def test_get_availability_zone_list(self):
- # List of availability zone
+ """Test listing volume available zones"""
availability_zone = (
self.availability_zone_client.list_availability_zones()
['availabilityZoneInfo'])
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index 39ce00c..acd9ca2 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -26,10 +26,11 @@
class ExtensionsTestJSON(base.BaseVolumeTest):
+ """Test volume extensions"""
@decorators.idempotent_id('94607eb0-43a5-47ca-82aa-736b41bd2e2c')
def test_list_extensions(self):
- # List of all extensions
+ """Test listing volume extensions"""
extensions = (self.volumes_extension_client.list_extensions()
['extensions'])
if not CONF.volume_feature_enabled.api_extensions:
diff --git a/tempest/api/volume/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
index 53b3acc..8f9bbd2 100644
--- a/tempest/api/volume/test_image_metadata.py
+++ b/tempest/api/volume/test_image_metadata.py
@@ -24,6 +24,7 @@
class VolumesImageMetadata(base.BaseVolumeTest):
+ """Test volume image metadata"""
@classmethod
def skip_checks(cls):
@@ -41,6 +42,7 @@
@decorators.idempotent_id('03efff0b-5c75-4822-8f10-8789ac15b13e')
@utils.services('image')
def test_update_show_delete_image_metadata(self):
+ """Test update/show/delete volume's image metadata"""
# Update image metadata
image_metadata = {'image_id': '5137a025-3c5f-43c1-bc64-5f41270040a5',
'image_name': 'image',
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
index e6fe25d..ee1b5e5 100644
--- a/tempest/api/volume/test_snapshot_metadata.py
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -23,6 +23,8 @@
class SnapshotMetadataTestJSON(base.BaseVolumeTest):
+ """Test snapshot metadata"""
+
@classmethod
def skip_checks(cls):
super(SnapshotMetadataTestJSON, cls).skip_checks()
@@ -45,6 +47,7 @@
@decorators.idempotent_id('a2f20f99-e363-4584-be97-bc33afb1a56c')
def test_crud_snapshot_metadata(self):
+ """Test create/get/update/delete snapshot metadata"""
# Create metadata for the snapshot
metadata = {"key1": "value1",
"key2": "value2",
@@ -82,7 +85,7 @@
@decorators.idempotent_id('e8ff85c5-8f97-477f-806a-3ac364a949ed')
def test_update_show_snapshot_metadata_item(self):
- # Update metadata item for the snapshot
+ """Test update/show snapshot metadata item"""
metadata = {"key1": "value1",
"key2": "value2",
"key3": "value3"}
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index e42ea40..5b50bfa 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -25,6 +25,8 @@
class VolumesActionsTest(base.BaseVolumeTest):
+ """Test volume actions"""
+
create_default_network = True
@classmethod
@@ -38,6 +40,7 @@
@decorators.attr(type='smoke')
@utils.services('compute')
def test_attach_detach_volume_to_instance(self):
+ """Test attaching and detaching volume to instance"""
# Create a server
server = self.create_server()
# Volume is attached and detached successfully from an instance
@@ -53,7 +56,7 @@
@decorators.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
def test_volume_bootable(self):
- # Verify that a volume bootable flag is retrieved
+ """Test setting and retrieving bootable flag of a volume"""
for bool_bootable in [True, False]:
self.volumes_client.set_bootable_volume(self.volume['id'],
bootable=bool_bootable)
@@ -69,6 +72,11 @@
@decorators.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371')
@utils.services('compute')
def test_get_volume_attachment(self):
+ """Test getting volume attachments
+
+ Attach a volume to a server, and then retrieve volume's attachments
+ info.
+ """
# Create a server
server = self.create_server()
# Verify that a volume's attachment information is retrieved
@@ -96,6 +104,7 @@
@decorators.idempotent_id('d8f1ca95-3d5b-44a3-b8ca-909691c9532d')
@utils.services('image')
def test_volume_upload(self):
+ """Test uploading volume to create an image"""
# NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
@@ -118,6 +127,7 @@
@decorators.idempotent_id('92c4ef64-51b2-40c0-9f7e-4749fbaaba33')
def test_reserve_unreserve_volume(self):
+ """Test reserving and unreserving volume"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# To get the volume info
@@ -131,6 +141,7 @@
@decorators.idempotent_id('fff74e1e-5bd3-4b33-9ea9-24c103bc3f59')
def test_volume_readonly_update(self):
+ """Test updating and retrieve volume's readonly flag"""
for readonly in [True, False]:
# Update volume readonly
self.volumes_client.update_volume_readonly(self.volume['id'],
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index c178272..e3b8800 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -80,11 +80,7 @@
self.assertEqual('container', backup['container'])
# Get all backups with detail
- backups = self.backups_client.list_backups(
- detail=True)['backups']
- for backup_info in backups:
- self.assertIn('created_at', backup_info)
- self.assertIn('links', backup_info)
+ backups = self.backups_client.list_backups(detail=True)['backups']
self.assertIn((backup['name'], backup['id']),
[(m['name'], m['id']) for m in backups])
@@ -176,7 +172,6 @@
backup['id'], **update_kwargs)['backup']
self.assertEqual(backup['id'], update_backup['id'])
self.assertEqual(update_kwargs['name'], update_backup['name'])
- self.assertIn('links', update_backup)
# Assert response body for show_backup method
retrieved_backup = self.backups_client.show_backup(
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 866bd87..76c22f0 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -28,6 +28,7 @@
class VolumesNegativeTest(base.BaseVolumeTest):
+ """Negative tests of volumes"""
@classmethod
def resource_setup(cls):
@@ -58,50 +59,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('f131c586-9448-44a4-a8b0-54ca838aa43e')
def test_volume_get_nonexistent_volume_id(self):
- # Should not be able to get a non-existent volume
+ """Test getting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('555efa6e-efcd-44ef-8a3b-4a7ca4837a29')
def test_volume_delete_nonexistent_volume_id(self):
- # Should not be able to delete a non-existent Volume
+ """Test deleting non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1ed83a8a-682d-4dfb-a30e-ee63ffd6c049')
def test_create_volume_with_invalid_size(self):
- # Should not be able to create volume with invalid size in request
+ """Test creating volume with invalid size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='#$%')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9387686f-334f-4d31-a439-33494b9e2683')
def test_create_volume_without_passing_size(self):
- # Should not be able to create volume without passing size
- # in request
+ """Test creating volume with empty size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('41331caa-eaf4-4001-869d-bc18c1869360')
def test_create_volume_with_size_zero(self):
- # Should not be able to create volume with size zero
+ """Test creating volume with zero size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='0')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8b472729-9eba-446e-a83b-916bdb34bef7')
def test_create_volume_with_size_negative(self):
- # Should not be able to create volume with size negative
+ """Test creating volume with negative size should fail"""
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.create_volume, size='-1')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('10254ed8-3849-454e-862e-3ab8e6aa01d2')
def test_create_volume_with_nonexistent_volume_type(self):
- # Should not be able to create volume with non-existent volume type
+ """Test creating volume with non existent volume type should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
volume_type=data_utils.rand_uuid())
@@ -109,7 +109,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0c36f6ae-4604-4017-b0a9-34fdc63096f9')
def test_create_volume_with_nonexistent_snapshot_id(self):
- # Should not be able to create volume with non-existent snapshot
+ """Test creating volume with non existent snapshot should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
snapshot_id=data_utils.rand_uuid())
@@ -117,7 +117,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('47c73e08-4be8-45bb-bfdf-0c4e79b88344')
def test_create_volume_with_nonexistent_source_volid(self):
- # Should not be able to create volume with non-existent source volume
+ """Test creating volume with non existent source volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.create_volume,
size=CONF.volume.volume_size,
source_volid=data_utils.rand_uuid())
@@ -125,46 +125,49 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0186422c-999a-480e-a026-6a665744c30c')
def test_update_volume_with_nonexistent_volume_id(self):
+ """Test updating non existent volume should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_uuid())
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e66e40d6-65e6-4e75-bdc7-636792fa152d')
def test_update_volume_with_invalid_volume_id(self):
+ """Test updating volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id=data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('72aeca85-57a5-4c1f-9057-f320f9ea575b')
def test_update_volume_with_empty_volume_id(self):
+ """Test updating volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.update_volume,
volume_id='')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('30799cfd-7ee4-446c-b66c-45b383ed211b')
def test_get_invalid_volume_id(self):
- # Should not be able to get volume with invalid id
+ """Test getting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.show_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('c6c3db06-29ad-4e91-beb0-2ab195fe49e3')
def test_get_volume_without_passing_volume_id(self):
- # Should not be able to get volume when empty ID is passed
+ """Test getting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.show_volume, '')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('1f035827-7c32-4019-9240-b4ec2dbd9dfd')
def test_delete_invalid_volume_id(self):
- # Should not be able to delete volume when invalid ID is passed
+ """Test deleting volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound, self.volumes_client.delete_volume,
data_utils.rand_name('invalid'))
@decorators.attr(type=['negative'])
@decorators.idempotent_id('441a1550-5d44-4b30-af0f-a6d402f52026')
def test_delete_volume_without_passing_volume_id(self):
- # Should not be able to delete volume when empty ID is passed
+ """Test deleting volume with empty volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.delete_volume, '')
@@ -172,6 +175,7 @@
@decorators.idempotent_id('f5e56b0a-5d02-43c1-a2a7-c9b792c2e3f6')
@utils.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
+ """Test attaching non existent volume to server should fail"""
server = self.create_server()
self.assertRaises(lib_exc.NotFound,
@@ -183,6 +187,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9f9c24e4-011d-46b5-b992-952140ce237a')
def test_detach_volumes_with_invalid_volume_id(self):
+ """Test detaching volume with invalid volume id should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.detach_volume,
'xxx')
@@ -190,7 +195,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('e0c75c74-ee34-41a9-9288-2a2051452854')
def test_volume_extend_with_size_smaller_than_original_size(self):
- # Extend volume with smaller size than original size.
+ """Test extending volume with decreasing size should fail"""
extend_size = 0
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -199,7 +204,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('5d0b480d-e833-439f-8a5a-96ad2ed6f22f')
def test_volume_extend_with_non_number_size(self):
- # Extend volume when size is non number.
+ """Test extending volume with non-integer size should fail"""
extend_size = 'abc'
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -208,7 +213,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('355218f1-8991-400a-a6bb-971239287d92')
def test_volume_extend_with_None_size(self):
- # Extend volume with None size.
+ """Test extending volume with none size should fail"""
extend_size = None
self.assertRaises(lib_exc.BadRequest,
self.volumes_client.extend_volume,
@@ -217,7 +222,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('8f05a943-013c-4063-ac71-7baf561e82eb')
def test_volume_extend_with_nonexistent_volume_id(self):
- # Extend volume size when volume is nonexistent.
+ """Test extending non existent volume should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
data_utils.rand_uuid(), new_size=extend_size)
@@ -225,7 +230,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('aff8ba64-6d6f-4f2e-bc33-41a08ee9f115')
def test_volume_extend_without_passing_volume_id(self):
- # Extend volume size when passing volume id is None.
+ """Test extending volume without passing volume id should fail"""
extend_size = self.volume['size'] + 1
self.assertRaises(lib_exc.NotFound, self.volumes_client.extend_volume,
None, new_size=extend_size)
@@ -233,6 +238,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ac6084c0-0546-45f9-b284-38a367e0e0e2')
def test_reserve_volume_with_nonexistent_volume_id(self):
+ """Test reserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.reserve_volume,
data_utils.rand_uuid())
@@ -240,6 +246,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('eb467654-3dc1-4a72-9b46-47c29d22654c')
def test_unreserve_volume_with_nonexistent_volume_id(self):
+ """Test unreserving non existent volume should fail"""
self.assertRaises(lib_exc.NotFound,
self.volumes_client.unreserve_volume,
data_utils.rand_uuid())
@@ -247,6 +254,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
def test_reserve_volume_with_negative_volume_status(self):
+ """Test reserving already reserved volume should fail"""
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
@@ -259,6 +267,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('0f4aa809-8c7b-418f-8fb3-84c7a5dfc52f')
def test_list_volumes_with_nonexistent_name(self):
+ """Test listing volumes with non existent name should get nothing"""
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = self.volumes_client.list_volumes(
@@ -268,6 +277,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('9ca17820-a0e7-4cbd-a7fa-f4468735e359')
def test_list_volumes_detail_with_nonexistent_name(self):
+ """Test listing volume details with non existent name
+
+ Listing volume details with non existent name should get nothing.
+ """
v_name = data_utils.rand_name(self.__class__.__name__ + '-Volume')
params = {'name': v_name}
fetched_volume = \
@@ -278,6 +291,7 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('143b279b-7522-466b-81be-34a87d564a7c')
def test_list_volumes_with_invalid_status(self):
+ """Test listing volumes with invalid status should get nothing"""
params = {'status': 'null'}
fetched_volume = self.volumes_client.list_volumes(
params=params)['volumes']
@@ -286,6 +300,10 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('ba94b27b-be3f-496c-a00e-0283b373fa75')
def test_list_volumes_detail_with_invalid_status(self):
+ """Test listing volume details with invalid status
+
+ Listing volume details with invalid status should get nothing
+ """
params = {'status': 'null'}
fetched_volume = \
self.volumes_client.list_volumes(detail=True,
@@ -296,6 +314,7 @@
@decorators.idempotent_id('5b810c91-0ad1-47ce-aee8-615f789be78f')
@utils.services('image')
def test_create_volume_from_image_with_decreasing_size(self):
+ """Test creating volume from image with decreasing size should fail"""
# Create image
image = self.create_image()
@@ -311,6 +330,7 @@
@decorators.idempotent_id('d15e7f35-2cfc-48c8-9418-c8223a89bcbb')
@utils.services('image')
def test_create_volume_from_deactivated_image(self):
+ """Test creating volume from deactivated image should fail"""
# Create image
image = self.create_image()
diff --git a/tempest/lib/api_schema/response/volume/backups.py b/tempest/lib/api_schema/response/volume/backups.py
new file mode 100644
index 0000000..9e85f5f
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/backups.py
@@ -0,0 +1,229 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# 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.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+common_show_backup = {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'object_count': {'type': 'integer'},
+ 'container': {'type': ['string', 'null']},
+ 'description': {'type': ['string', 'null']},
+ 'links': parameter_types.links,
+ 'availability_zone': {'type': ['string', 'null']},
+ 'created_at': parameter_types.date_time,
+ 'updated_at': parameter_types.date_time_or_null,
+ 'name': {'type': ['string', 'null']},
+ 'has_dependent_backups': {'type': 'boolean'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'fail_reason': {'type': ['string', 'null']},
+ 'size': {'type': 'integer'},
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'is_incremental': {'type': 'boolean'},
+ 'data_timestamp': parameter_types.date_time_or_null,
+ 'snapshot_id': {'type': ['string', 'null']},
+ # TODO(zhufl): os-backup-project-attr:project_id is added
+ # in 3.18, we should move it to the 3.18 schema file when
+ # microversion is supported in volume interfaces.
+ 'os-backup-project-attr:project_id': {
+ 'type': 'string', 'format': 'uuid'},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ # TODO(zhufl): user_id is added in 3.56, we should move it
+ # to the 3.56 schema file when microversion is supported
+ # in volume interfaces.
+ 'user_id': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'object_count', 'fail_reason', 'links',
+ 'created_at', 'updated_at', 'name', 'volume_id', 'size', 'id',
+ 'data_timestamp']
+}
+
+create_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'},
+ # TODO(zhufl): metadata is added in 3.43, we should move it
+ # to the 3.43 schema file when microversion is supported
+ # in volume interfaces.
+ 'metadata': {'^.+$': {'type': 'string'}},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+update_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': 'string'},
+ 'metadata': {'^.+$': {'type': 'string'}}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+restore_backup = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'restore': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_id': {'type': 'string', 'format': 'uuid'},
+ 'volume_name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_id', 'volume_id', 'volume_name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['restore']
+ }
+}
+
+delete_backup = {'status_code': [202]}
+
+show_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': common_show_backup
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+list_backups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'links': parameter_types.links,
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': ['string', 'null']},
+ # TODO(zhufl): count is added in 3.45, we should move
+ # it to the 3.45 schema file when microversion is
+ # supported in volume interfaces
+ 'count': {'type': 'integer'}
+ },
+ 'additionalProperties': False,
+ 'required': ['links', 'id', 'name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+list_backups_detail = copy.deepcopy(common_show_backup)
+# TODO(zhufl): count is added in 3.45, we should move it to the 3.45 schema
+# file when microversion is supported in volume interfaces
+list_backups_detail['properties'].update({'count': {'type': 'integer'}})
+list_backups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backups': {
+ 'type': 'array',
+ 'items': list_backups_detail
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backups'],
+ }
+}
+
+export_backup = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup-record': {
+ 'type': 'object',
+ 'properties': {
+ 'backup_service': {'type': 'string'},
+ 'backup_url': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['backup_service', 'backup_url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup-record']
+ }
+}
+
+import_backup = {
+ 'status_code': [201],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'backup': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'links': parameter_types.links,
+ 'name': {'type': ['string', 'null']},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'links', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['backup']
+ }
+}
+
+reset_backup_status = {'status_code': [202]}
diff --git a/tempest/lib/api_schema/response/volume/groups.py b/tempest/lib/api_schema/response/volume/groups.py
new file mode 100644
index 0000000..cb31269
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/groups.py
@@ -0,0 +1,164 @@
+# Copyright 2015 NEC Corporation. All rights reserved.
+#
+# 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.api_schema.response.compute.v2_1 import parameter_types
+
+create_group = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+delete_group = {'status_code': [202]}
+
+show_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should move it
+ # to the 3.25 schema file when microversion is supported
+ # in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'replication_status': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+
+list_groups_no_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'}
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name'],
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+list_groups_with_detail = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'groups': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'status': {'type': 'string'},
+ 'description': {'type': ['string', 'null']},
+ 'availability_zone': {'type': 'string'},
+ 'created_at': parameter_types.date_time,
+ 'group_type': {'type': 'string', 'format': 'uuid'},
+ 'group_snapshot_id': {'type': ['string', 'null']},
+ 'source_group_id': {'type': ['string', 'null']},
+ 'volume_types': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ # TODO(zhufl): volumes is added in 3.25, we should
+ # move it to the 3.25 schema file when microversion
+ # is supported in volume interfaces
+ 'volumes': {
+ 'type': 'array',
+ 'items': {'type': 'string', 'format': 'uuid'}
+ },
+ },
+ 'additionalProperties': False,
+ 'required': ['status', 'description', 'created_at',
+ 'group_type', 'volume_types', 'id', 'name']
+ }
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['groups'],
+ }
+}
+
+create_group_from_source = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'group': {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string', 'format': 'uuid'},
+ 'name': {'type': 'string'},
+ },
+ 'additionalProperties': False,
+ 'required': ['id', 'name']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['group']
+ }
+}
+update_group = {'status_code': [202]}
+reset_group_status = {'status_code': [202]}
diff --git a/tempest/lib/services/volume/v3/backups_client.py b/tempest/lib/services/volume/v3/backups_client.py
index 970471e..1df45fa 100644
--- a/tempest/lib/services/volume/v3/backups_client.py
+++ b/tempest/lib/services/volume/v3/backups_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import backups as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -34,7 +35,7 @@
post_body = json.dumps({'backup': kwargs})
resp, body = self.post('backups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def update_backup(self, backup_id, **kwargs):
@@ -47,7 +48,7 @@
put_body = json.dumps({'backup': kwargs})
resp, body = self.put('backups/%s' % backup_id, put_body)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.update_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def restore_backup(self, backup_id, **kwargs):
@@ -60,13 +61,13 @@
post_body = json.dumps({'restore': kwargs})
resp, body = self.post('backups/%s/restore' % (backup_id), post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.restore_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_backup(self, backup_id):
"""Delete a backup of volume."""
resp, body = self.delete('backups/%s' % backup_id)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def show_backup(self, backup_id):
@@ -74,7 +75,7 @@
url = "backups/%s" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def list_backups(self, detail=False, **params):
@@ -86,13 +87,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/index.html#list-backups-with-detail
"""
url = "backups"
+ list_backups_schema = schema.list_backups_no_detail
if detail:
url += "/detail"
+ list_backups_schema = schema.list_backups_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(list_backups_schema, resp, body)
return rest_client.ResponseBody(resp, body)
def export_backup(self, backup_id):
@@ -100,7 +103,7 @@
url = "backups/%s/export_record" % backup_id
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.export_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def import_backup(self, **kwargs):
@@ -113,14 +116,14 @@
post_body = json.dumps({'backup-record': kwargs})
resp, body = self.post("backups/import_record", post_body)
body = json.loads(body)
- self.expected_success(201, resp.status)
+ self.validate_response(schema.import_backup, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_backup_status(self, backup_id, status):
"""Reset the specified backup's status."""
post_body = json.dumps({'os-reset_status': {"status": status}})
resp, body = self.post('backups/%s/action' % backup_id, post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.reset_backup_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/groups_client.py b/tempest/lib/services/volume/v3/groups_client.py
index ffae232..3d8523d 100644
--- a/tempest/lib/services/volume/v3/groups_client.py
+++ b/tempest/lib/services/volume/v3/groups_client.py
@@ -16,6 +16,7 @@
from oslo_serialization import jsonutils as json
from six.moves.urllib import parse as urllib
+from tempest.lib.api_schema.response.volume import groups as schema
from tempest.lib.common import rest_client
from tempest.lib import exceptions as lib_exc
from tempest.lib.services.volume import base_client
@@ -23,6 +24,7 @@
class GroupsClient(base_client.BaseClient):
"""Client class to send CRUD Volume Group API requests"""
+ api_version = 'v3'
def create_group(self, **kwargs):
"""Creates a group.
@@ -35,7 +37,7 @@
post_body = json.dumps({'group': kwargs})
resp, body = self.post('groups', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group, resp, body)
return rest_client.ResponseBody(resp, body)
def delete_group(self, group_id, delete_volumes=True):
@@ -49,7 +51,7 @@
post_body = json.dumps({'delete': post_body})
resp, body = self.post('groups/%s/action' % group_id,
post_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.delete_group, resp, body)
return rest_client.ResponseBody(resp, body)
def show_group(self, group_id):
@@ -62,7 +64,7 @@
url = "groups/%s" % str(group_id)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema.show_group, resp, body)
return rest_client.ResponseBody(resp, body)
def list_groups(self, detail=False, **params):
@@ -74,13 +76,15 @@
https://docs.openstack.org/api-ref/block-storage/v3/#list-groups-with-details
"""
url = "groups"
+ schema_list_groups = schema.list_groups_no_detail
if detail:
url += "/detail"
+ schema_list_groups = schema.list_groups_with_detail
if params:
url += '?%s' % urllib.urlencode(params)
resp, body = self.get(url)
body = json.loads(body)
- self.expected_success(200, resp.status)
+ self.validate_response(schema_list_groups, resp, body)
return rest_client.ResponseBody(resp, body)
def create_group_from_source(self, **kwargs):
@@ -93,7 +97,7 @@
post_body = json.dumps({'create-from-src': kwargs})
resp, body = self.post('groups/action', post_body)
body = json.loads(body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.create_group_from_source, resp, body)
return rest_client.ResponseBody(resp, body)
def update_group(self, group_id, **kwargs):
@@ -105,7 +109,7 @@
"""
put_body = json.dumps({'group': kwargs})
resp, body = self.put('groups/%s' % group_id, put_body)
- self.expected_success(202, resp.status)
+ self.validate_response(schema.update_group, resp, body)
return rest_client.ResponseBody(resp, body)
def reset_group_status(self, group_id, status_to_set):
@@ -116,7 +120,7 @@
"""
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)
+ self.validate_response(schema.reset_group_status, resp, body)
return rest_client.ResponseBody(resp, body)
def is_resource_deleted(self, id):
diff --git a/tempest/tests/lib/services/volume/v3/test_backups_client.py b/tempest/tests/lib/services/volume/v3/test_backups_client.py
index 97e1132..ca7918a 100644
--- a/tempest/tests/lib/services/volume/v3/test_backups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_backups_client.py
@@ -45,6 +45,8 @@
"availability_zone": "az1",
"container": "volumebackups",
"created_at": "2013-04-02T10:35:27.000000",
+ "updated_at": "2013-04-02T10:39:27.000000",
+ "data_timestamp": "2013-04-02T10:35:27.000000",
"description": None,
"fail_reason": None,
"id": "2ef47aee-8844-490c-804d-2a8efe561c65",
@@ -64,7 +66,6 @@
"user_id": "515ba0dd59f84f25a6a084a45d8d93b2",
"size": 1,
"status": "available",
- "updated_at": "2013-04-02T10:35:27.000000",
"volume_id": "e5185058-943a-4cb4-96d9-72c184c337d6",
"is_incremental": True,
"has_dependent_backups": False