Merge "Assert volume deletion in test_image_defined_boot_from_volume"
diff --git a/.zuul.yaml b/.zuul.yaml
index 04d60fe..8dcb935 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -274,6 +274,16 @@
- ^setup.cfg$
- ^tempest/hacking/.*$
- ^tempest/tests/.*$
+ - nova-live-migration:
+ irrelevant-files:
+ - ^(test-|)requirements.txt$
+ - ^.*\.rst$
+ - ^doc/.*$
+ - ^etc/.*$
+ - ^releasenotes/.*$
+ - ^setup.cfg$
+ - ^tempest/hacking/.*$
+ - ^tempest/tests/.*$
periodic-stable:
jobs:
- tempest-full-queens
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 942f969..3bc1d0c 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -306,10 +306,18 @@
.. _2.6: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id5
+ * `2.9`_
+
+ .. _2.9: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id8
+
* `2.10`_
.. _2.10: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id9
+ * `2.19`_
+
+ .. _2.19: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id17
+
* `2.20`_
.. _2.20: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id18
@@ -322,6 +330,10 @@
.. _2.25: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-mitaka
+ * `2.26`_
+
+ .. _2.26: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id23
+
* `2.32`_
.. _2.32: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id29
@@ -342,6 +354,14 @@
.. _2.48: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id43
+ * `2.54`_
+
+ .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id4
+
+ * `2.55`_
+
+ .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+
* `2.60`_
.. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id54
diff --git a/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml b/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml
new file mode 100644
index 0000000..222a99f
--- /dev/null
+++ b/releasenotes/notes/add-update-flavor--api-to-flavors-client-a859542fe54aab7c.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Add update flavor API to compute flavors_client library.
diff --git a/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml b/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml
new file mode 100644
index 0000000..ea69293
--- /dev/null
+++ b/releasenotes/notes/volume-service-testing-default-to-v3-endpoints-20b86895a590925d.yaml
@@ -0,0 +1,8 @@
+---
+upgrade:
+ - |
+ The volume config option ``catalog_type`` default is changed to
+ ``volumev3`` which is v3 API endpoint configured in devstack.
+ With this change Tempest will be testing v3 API as default.
+ User who want to test v2 API can still test by configuring the
+ ``catalog_type`` to v2 endpoint.
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
new file mode 100644
index 0000000..027af25
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -0,0 +1,43 @@
+# Copyright 2018 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.api.compute import base
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+
+class FlavorsV255TestJSON(base.BaseV2ComputeAdminTest):
+ min_microversion = '2.55'
+ max_microversion = 'latest'
+
+ # NOTE(gmann): This class tests the flavors APIs
+ # response schema for the 2.55 microversion.
+
+ @decorators.idempotent_id('61976b25-488d-41dc-9dcb-cb9693a7b075')
+ def test_crud_flavor(self):
+ flavor_id = data_utils.rand_int_id(start=1000)
+ # Checking create API response schema
+ new_flavor_id = self.create_flavor(ram=512,
+ vcpus=1,
+ disk=10,
+ id=flavor_id)['id']
+ # Checking show API response schema
+ self.flavors_client.show_flavor(new_flavor_id)['flavor']
+ # Checking update API response schema
+ self.admin_flavors_client.update_flavor(new_flavor_id,
+ description='new')['flavor']
+ # Checking list details API response schema
+ self.flavors_client.list_flavors(detail=True)['flavors']
+ # Checking list API response schema
+ self.flavors_client.list_flavors()['flavors']
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 2398cf1..bc38144 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -147,6 +147,7 @@
@testtools.skipIf(not CONF.compute_feature_enabled.
block_migrate_cinder_iscsi,
'Block Live migration not configured for iSCSI')
+ @utils.services('volume')
def test_iscsi_volume(self):
server = self.create_test_server(wait_until="ACTIVE")
server_id = server['id']
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index f3eb597..73e191b 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -56,15 +56,3 @@
# sort the lists before comparing, to take out dependency
# on order.
self.assertEqual(sorted(s1), sorted(s2))
-
- @decorators.idempotent_id('39397f6f-37b8-4234-8671-281e44c74025')
- def test_get_service_by_service_and_host_name(self):
- services = self.client.list_services()['services']
- host_name = services[0]['host']
- binary_name = services[0]['binary']
-
- services = self.client.list_services(host=host_name,
- binary=binary_name)['services']
- self.assertEqual(1, len(services))
- self.assertEqual(host_name, services[0]['host'])
- self.assertEqual(binary_name, services[0]['binary'])
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 2904976..543fa1c 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -184,8 +184,28 @@
min_microversion = '2.47'
max_microversion = 'latest'
+ # NOTE(gmann): This test tests the server APIs response schema
+ # Along with 2.47 microversion schema this test class tests the
+ # other microversions 2.9, 2.19 and 2.26 server APIs response schema
+ # also. 2.47 APIs schema are on top of 2.9->2.19->2.26 schema so
+ # below tests cover all of the schema.
+
@decorators.idempotent_id('88b0bdb2-494c-11e7-a919-92ebcb67fe33')
def test_show_server(self):
server = self.create_test_server()
# All fields will be checked by API schema
self.servers_client.show_server(server['id'])
+
+ @decorators.idempotent_id('8de397c2-57d0-4b90-aa30-e5d668f21a8b')
+ def test_update_rebuild_list_server(self):
+ server = self.create_test_server()
+ # Checking update API response schema
+ self.servers_client.update_server(server['id'])
+ waiters.wait_for_server_status(self.servers_client, server['id'],
+ 'ACTIVE')
+ # Checking rebuild API response schema
+ self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'], 'ACTIVE')
+ # Checking list details API response schema
+ self.servers_client.list_servers(detail=True)
diff --git a/tempest/api/compute/servers/test_servers_microversions.py b/tempest/api/compute/servers/test_servers_microversions.py
new file mode 100644
index 0000000..f3863f1
--- /dev/null
+++ b/tempest/api/compute/servers/test_servers_microversions.py
@@ -0,0 +1,51 @@
+# Copyright 2018 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.api.compute import base
+from tempest.common import waiters
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+# NOTE(gmann): This file is to write the tests which mainly
+# tests newly added microversion schema related to servers APIs.
+# As per (https://docs.openstack.org/tempest/latest/microversion_testing.
+# html#tempest-scope-for-microversion-testing),
+# we need to fill the API response schema gaps which gets modified
+# during microversion change. To cover the testing of such schema
+# we need to have operation schema test which just test
+# the microversion schemas.
+# If you are adding server APIs microversion schema file without
+# their integration tests, you can add tests to cover those schema
+# in this file.
+
+
+class ServerShowV254Test(base.BaseV2ComputeTest):
+ min_microversion = '2.54'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('09170a98-4940-4637-add7-1a35121f1a5a')
+ def test_rebuild_server(self):
+ server = self.create_test_server(wait_until='ACTIVE')
+ keypair_name = data_utils.rand_name(
+ self.__class__.__name__ + '-keypair')
+ kwargs = {'name': keypair_name}
+ self.keypairs_client.create_keypair(**kwargs)
+ self.addCleanup(self.keypairs_client.delete_keypair,
+ keypair_name)
+ # Checking rebuild API response schema
+ self.servers_client.rebuild_server(server['id'], self.image_ref_alt,
+ key_name=keypair_name)
+ waiters.wait_for_server_status(self.servers_client,
+ server['id'], 'ACTIVE')
diff --git a/tempest/api/identity/admin/v2/test_tenants.py b/tempest/api/identity/admin/v2/test_tenants.py
index 0f955bf..cda721c 100644
--- a/tempest/api/identity/admin/v2/test_tenants.py
+++ b/tempest/api/identity/admin/v2/test_tenants.py
@@ -59,11 +59,9 @@
# Create a tenant that is enabled
tenant = self.setup_test_tenant(enabled=True)
tenant_id = tenant['id']
- en1 = tenant['enabled']
- self.assertTrue(en1, 'Enable should be True in response')
+ self.assertTrue(tenant['enabled'], 'Enable should be True in response')
body = self.tenants_client.show_tenant(tenant_id)['tenant']
- en2 = body['enabled']
- self.assertTrue(en2, 'Enable should be True in lookup')
+ self.assertTrue(body['enabled'], 'Enable should be True in lookup')
self.tenants_client.delete_tenant(tenant_id)
@decorators.idempotent_id('3be22093-b30f-499d-b772-38340e5e16fb')
@@ -71,12 +69,10 @@
# Create a tenant that is not enabled
tenant = self.setup_test_tenant(enabled=False)
tenant_id = tenant['id']
- en1 = tenant['enabled']
- self.assertEqual('false', str(en1).lower(),
+ self.assertFalse(tenant['enabled'],
'Enable should be False in response')
body = self.tenants_client.show_tenant(tenant_id)['tenant']
- en2 = body['enabled']
- self.assertEqual('false', str(en2).lower(),
+ self.assertFalse(body['enabled'],
'Enable should be False in lookup')
self.tenants_client.delete_tenant(tenant_id)
@@ -143,7 +139,7 @@
resp3_en = body['enabled']
self.assertNotEqual(resp1_en, resp3_en)
- self.assertEqual('false', str(resp1_en).lower())
+ self.assertFalse(tenant['enabled'])
self.assertEqual(resp2_en, resp3_en)
self.tenants_client.delete_tenant(t_id)
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index ac23067..bc94a8e 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -101,22 +101,19 @@
# Create a project that is enabled
project = self.setup_test_project(enabled=True)
project_id = project['id']
- en1 = project['enabled']
- self.assertTrue(en1, 'Enable should be True in response')
+ self.assertTrue(project['enabled'],
+ 'Enable should be True in response')
body = self.projects_client.show_project(project_id)['project']
- en2 = body['enabled']
- self.assertTrue(en2, 'Enable should be True in lookup')
+ self.assertTrue(body['enabled'], 'Enable should be True in lookup')
@decorators.idempotent_id('78f96a9c-e0e0-4ee6-a3ba-fbf6dfd03207')
def test_project_create_not_enabled(self):
# Create a project that is not enabled
project = self.setup_test_project(enabled=False)
- en1 = project['enabled']
- self.assertEqual('false', str(en1).lower(),
+ self.assertFalse(project['enabled'],
'Enable should be False in response')
body = self.projects_client.show_project(project['id'])['project']
- en2 = body['enabled']
- self.assertEqual('false', str(en2).lower(),
+ self.assertFalse(body['enabled'],
'Enable should be False in lookup')
@decorators.idempotent_id('f608f368-048c-496b-ad63-d286c26dab6b')
@@ -178,7 +175,7 @@
resp3_en = body['enabled']
self.assertNotEqual(resp1_en, resp3_en)
- self.assertEqual('false', str(resp1_en).lower())
+ self.assertFalse(project['enabled'])
self.assertEqual(resp2_en, resp3_en)
@decorators.idempotent_id('59398d4a-5dc5-4f86-9a4c-c26cc804d6c6')
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index ce5bd3e..aa57daf 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -71,8 +71,12 @@
self.assertEqual(1024, body.get('size'))
# Now try get image file
+ # NOTE: This Glance API returns different status codes for image
+ # condition. In this non-empty data case, Glance should return 200,
+ # so here should check the status code.
body = self.client.show_image_file(image['id'])
self.assertEqual(file_content, body.data)
+ self.assertEqual(200, body.response.status)
@decorators.attr(type='smoke')
@decorators.idempotent_id('f848bb94-1c6e-45a4-8726-39e3a5b23535')
@@ -111,6 +115,13 @@
visibility='private')
self.assertEqual('queued', image['status'])
+ # NOTE: This Glance API returns different status codes for image
+ # condition. In this empty data case, Glance should return 204,
+ # so here should check the status code.
+ image_file = self.client.show_image_file(image['id'])
+ self.assertEqual(0, len(image_file.data))
+ self.assertEqual(204, image_file.response.status)
+
# Now try uploading an image file
image_file = six.BytesIO(data_utils.random_bytes())
self.client.store_image_file(image['id'], image_file)
diff --git a/tempest/api/volume/admin/test_volume_retype_with_migration.py b/tempest/api/volume/admin/test_volume_retype_with_migration.py
index f0b3a4f..025c1be 100644
--- a/tempest/api/volume/admin/test_volume_retype_with_migration.py
+++ b/tempest/api/volume/admin/test_volume_retype_with_migration.py
@@ -46,13 +46,10 @@
extra_specs_src = {"volume_backend_name": backend_src}
extra_specs_dst = {"volume_backend_name": backend_dst}
- src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
+ cls.src_vol_type = cls.create_volume_type(extra_specs=extra_specs_src)
cls.dst_vol_type = cls.create_volume_type(extra_specs=extra_specs_dst)
- cls.src_vol = cls.create_volume(volume_type=src_vol_type['name'])
-
- @classmethod
- def resource_cleanup(cls):
+ def _wait_for_internal_volume_cleanup(self, vol):
# When retyping a volume, Cinder creates an internal volume in the
# target backend. The volume in the source backend is deleted after
# the migration, so we need to wait for Cinder delete this volume
@@ -60,40 +57,37 @@
# This list should return 2 volumes until the copy and cleanup
# process is finished.
- fetched_list = cls.admin_volume_client.list_volumes(
+ fetched_list = self.admin_volume_client.list_volumes(
params={'all_tenants': True,
- 'display_name': cls.src_vol['name']})['volumes']
+ 'display_name': vol['name']})['volumes']
for fetched_vol in fetched_list:
- if fetched_vol['id'] != cls.src_vol['id']:
+ if fetched_vol['id'] != vol['id']:
# This is the Cinder internal volume
LOG.debug('Waiting for internal volume %s deletion',
fetched_vol['id'])
- cls.admin_volume_client.wait_for_resource_deletion(
+ self.admin_volume_client.wait_for_resource_deletion(
fetched_vol['id'])
break
- super(VolumeRetypeWithMigrationTest, cls).resource_cleanup()
-
- @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
- def test_available_volume_retype_with_migration(self):
-
+ def _retype_volume(self, volume):
keys_with_no_change = ('id', 'size', 'description', 'name', 'user_id',
'os-vol-tenant-attr:tenant_id')
keys_with_change = ('volume_type', 'os-vol-host-attr:host')
volume_source = self.admin_volume_client.show_volume(
- self.src_vol['id'])['volume']
+ volume['id'])['volume']
self.volumes_client.retype_volume(
- self.src_vol['id'],
+ volume['id'],
new_type=self.dst_vol_type['name'],
migration_policy='on-demand')
-
- waiters.wait_for_volume_retype(self.volumes_client, self.src_vol['id'],
+ self.addCleanup(self._wait_for_internal_volume_cleanup, volume)
+ waiters.wait_for_volume_retype(self.volumes_client, volume['id'],
self.dst_vol_type['name'])
+
volume_dest = self.admin_volume_client.show_volume(
- self.src_vol['id'])['volume']
+ volume['id'])['volume']
# Check the volume information after the migration.
self.assertEqual('success',
@@ -105,3 +99,27 @@
for key in keys_with_change:
self.assertNotEqual(volume_source[key], volume_dest[key])
+
+ @decorators.idempotent_id('a1a41f3f-9dad-493e-9f09-3ff197d477cd')
+ def test_available_volume_retype_with_migration(self):
+ src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+ self._retype_volume(src_vol)
+
+ @decorators.idempotent_id('d0d9554f-e7a5-4104-8973-f35b27ccb60d')
+ def test_volume_from_snapshot_retype_with_migration(self):
+ # Create a volume in the first backend
+ src_vol = self.create_volume(volume_type=self.src_vol_type['name'])
+
+ # Create a volume snapshot
+ snapshot = self.create_snapshot(src_vol['id'])
+
+ # Create a volume from the snapshot
+ src_vol = self.create_volume(volume_type=self.src_vol_type['name'],
+ snapshot_id=snapshot['id'])
+
+ # Delete the snapshot
+ self.snapshots_client.delete_snapshot(snapshot['id'])
+ self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+
+ # Migrate the volume from snapshot to the second backend
+ self._retype_volume(src_vol)
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index 552b231..07cfad5 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -128,7 +128,7 @@
volume_details = self.volumes_client.show_volume(
volume['id'])['volume']
- self.assertEqual('true', volume_details['bootable'])
+ self.assertTrue(volume_details['bootable'])
# Create a backup
backup = self.create_backup(volume_id=volume['id'])
@@ -140,7 +140,7 @@
restored_volume_info = self.volumes_client.show_volume(
restored_volume_id)['volume']
- self.assertEqual('true', restored_volume_info['bootable'])
+ self.assertTrue(restored_volume_info['bootable'])
class VolumesBackupsV39Test(base.BaseVolumeTest):
diff --git a/tempest/clients.py b/tempest/clients.py
index 0d16748..707127c 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -271,6 +271,7 @@
# Set default client for users that don't need explicit version
self.volumes_client_latest = self.volumes_v2_client
self.snapshots_client_latest = self.snapshots_v2_client
+ self.backups_client_latest = self.backups_v2_client
if CONF.volume_feature_enabled.api_v3:
self.backups_v3_client = self.volume_v3.BackupsClient()
@@ -286,6 +287,7 @@
# Set default client for users that don't need explicit version
self.volumes_client_latest = self.volumes_v3_client
self.snapshots_client_latest = self.snapshots_v3_client
+ self.backups_client_latest = self.backups_v3_client
def _set_object_storage_clients(self):
self.account_client = self.object_storage.AccountClient()
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 9a85d89..84c8631 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -136,7 +136,7 @@
if not os.path.isdir(local_dir):
LOG.debug('Creating local working dir: %s', local_dir)
os.mkdir(local_dir)
- elif not os.listdir(local_dir) == []:
+ elif os.listdir(local_dir):
raise OSError("Directory you are trying to initialize already "
"exists and is not empty: %s" % local_dir)
diff --git a/tempest/config.py b/tempest/config.py
index 7133b3d..1fb5c8e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -752,7 +752,7 @@
help='Timeout in seconds to wait for a volume to become '
'available.'),
cfg.StrOpt('catalog_type',
- default='volume',
+ default='volumev3',
help="Catalog type of the Volume Service"),
cfg.StrOpt('region',
default='',
@@ -1078,7 +1078,7 @@
return opt_list
-# this should never be called outside of this class
+# This should never be called outside of this module
class TempestConfigPrivate(object):
"""Provides OpenStack configuration information."""
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
deleted file mode 100644
index 0e7e894..0000000
--- a/tempest/hacking/ignored_list_T110.txt
+++ /dev/null
@@ -1 +0,0 @@
-./tempest/services/object_storage/object_client.py
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
index 547d94d..af5e67f 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -86,7 +86,7 @@
'status_code': [200]
}
-create_get_flavor_details = {
+create_update_get_flavor_details = {
'status_code': [200],
'response_body': {
'type': 'object',
diff --git a/tempest/lib/api_schema/response/compute/v2_19/servers.py b/tempest/lib/api_schema/response/compute/v2_19/servers.py
index 05cc32c..fd9e933 100644
--- a/tempest/lib/api_schema/response/compute/v2_19/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_19/servers.py
@@ -14,9 +14,9 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_1 import servers as serversv21
from tempest.lib.api_schema.response.compute.v2_16 import servers \
as serversv216
+from tempest.lib.api_schema.response.compute.v2_9 import servers as serversv29
list_servers = copy.deepcopy(serversv216.list_servers)
@@ -32,20 +32,20 @@
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('description')
-update_server = copy.deepcopy(serversv21.update_server)
+update_server = copy.deepcopy(serversv29.update_server)
update_server['response_body']['properties']['server'][
'properties'].update({'description': {'type': ['string', 'null']}})
update_server['response_body']['properties']['server'][
'required'].append('description')
-rebuild_server = copy.deepcopy(serversv21.rebuild_server)
+rebuild_server = copy.deepcopy(serversv29.rebuild_server)
rebuild_server['response_body']['properties']['server'][
'properties'].update({'description': {'type': ['string', 'null']}})
rebuild_server['response_body']['properties']['server'][
'required'].append('description')
rebuild_server_with_admin_pass = copy.deepcopy(
- serversv21.rebuild_server_with_admin_pass)
+ serversv29.rebuild_server_with_admin_pass)
rebuild_server_with_admin_pass['response_body']['properties']['server'][
'properties'].update({'description': {'type': ['string', 'null']}})
rebuild_server_with_admin_pass['response_body']['properties']['server'][
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index b03bdf6..5c35eab 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -43,6 +43,25 @@
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('tags')
+update_server = copy.deepcopy(servers219.update_server)
+update_server['response_body']['properties']['server'][
+ 'properties'].update({'tags': tag_items})
+update_server['response_body']['properties']['server'][
+ 'required'].append('tags')
+
+rebuild_server = copy.deepcopy(servers219.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'tags': tag_items})
+rebuild_server['response_body']['properties']['server'][
+ 'required'].append('tags')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers219.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'tags': tag_items})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('tags')
+
# list response schema wasn't changed for v2.26 so use v2.1
list_servers = copy.deepcopy(servers21.list_servers)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 37a084f..935be70 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -37,3 +37,19 @@
get_server = copy.deepcopy(servers226.get_server)
get_server['response_body']['properties']['server'][
'properties'].update({'flavor': flavor})
+list_servers_detail = copy.deepcopy(servers226.list_servers_detail)
+list_servers_detail['response_body']['properties']['servers']['items'][
+ 'properties'].update({'flavor': flavor})
+
+update_server = copy.deepcopy(servers226.update_server)
+update_server['response_body']['properties']['server'][
+ 'properties'].update({'flavor': flavor})
+
+rebuild_server = copy.deepcopy(servers226.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'flavor': flavor})
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers226.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'flavor': flavor})
diff --git a/tempest/lib/api_schema/response/compute/v2_54/__init__.py b/tempest/lib/api_schema/response/compute/v2_54/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_54/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_54/servers.py b/tempest/lib/api_schema/response/compute/v2_54/servers.py
new file mode 100644
index 0000000..c084696
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_54/servers.py
@@ -0,0 +1,49 @@
+# 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_47 import servers as servers247
+# ****** Schemas changed in microversion 2.54 *****************
+
+# Note(gmann): This is schema for microversion 2.54 which includes the
+# 'key_name' in the Response body of the following APIs:
+# - ``POST '/servers/{server_id}/action (rebuild)``
+
+key_name = {
+ 'oneOf': [
+ {'type': 'string', 'minLength': 1, 'maxLength': 255},
+ {'type': 'null'},
+ ]
+}
+
+rebuild_server = copy.deepcopy(servers247.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'key_name': key_name})
+rebuild_server['response_body']['properties']['server'][
+ 'required'].append('key_name')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers247.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'key_name': key_name})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('key_name')
+
+# ****** Schemas unchanged in microversion 2.54 since microversion 2.47 ***
+
+# NOTE(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+get_server = copy.deepcopy(servers247.get_server)
+list_servers_detail = copy.deepcopy(servers247.list_servers_detail)
+update_server = copy.deepcopy(servers247.update_server)
diff --git a/tempest/lib/api_schema/response/compute/v2_55/__init__.py b/tempest/lib/api_schema/response/compute/v2_55/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_55/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_55/flavors.py b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
new file mode 100644
index 0000000..823190a
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_55/flavors.py
@@ -0,0 +1,112 @@
+# Copyright 2018 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
+
+# Note(gmann): This is schema for microversion 2.55 which includes the
+# following changes:
+# Add new PUT API
+# Adds a ``description`` field to the following APIs response:
+# - ``GET /flavors``
+# - ``GET /flavors/detail``
+# - ``GET /flavors/{flavor_id}``
+# - ``POST /flavors``
+
+flavor_description = {
+ 'type': ['string', 'null'],
+ 'minLength': 0, 'maxLength': 65535
+}
+
+list_flavors = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'id': {'type': 'string'},
+ 'description': flavor_description
+ },
+ 'additionalProperties': False,
+ 'required': ['name', 'links', 'id', 'description']
+ }
+ },
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'required': ['flavors']
+ }
+}
+
+common_flavor_info = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'links': parameter_types.links,
+ 'ram': {'type': 'integer'},
+ 'vcpus': {'type': 'integer'},
+ # 'swap' attributes comes as integer value but if it is empty
+ # it comes as "". So defining type of as string and integer.
+ 'swap': {'type': ['integer', 'string']},
+ 'disk': {'type': 'integer'},
+ 'id': {'type': 'string'},
+ 'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+ 'os-flavor-access:is_public': {'type': 'boolean'},
+ 'rxtx_factor': {'type': 'number'},
+ 'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
+ 'description': flavor_description
+ },
+ 'additionalProperties': False,
+ # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+ # 'OS-FLV-EXT-DATA' are API extensions. So they are not 'required'.
+ 'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id',
+ 'description']
+}
+
+list_flavors_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavors': {
+ 'type': 'array',
+ 'items': common_flavor_info
+ },
+ # NOTE(gmann): flavors_links attribute is not necessary
+ # to be present always So it is not 'required'.
+ 'flavors_links': parameter_types.links
+ },
+ 'additionalProperties': False,
+ 'required': ['flavors']
+ }
+}
+
+create_update_get_flavor_details = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'flavor': common_flavor_info
+ },
+ 'additionalProperties': False,
+ 'required': ['flavor']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index e260e48..7df02d5 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,6 +14,7 @@
import copy
+from tempest.lib.api_schema.response.compute.v2_1 import servers as servers_21
from tempest.lib.api_schema.response.compute.v2_6 import servers
list_servers = copy.deepcopy(servers.list_servers)
@@ -29,3 +30,22 @@
'properties'].update({'locked': {'type': 'boolean'}})
list_servers_detail['response_body']['properties']['servers']['items'][
'required'].append('locked')
+
+update_server = copy.deepcopy(servers_21.update_server)
+update_server['response_body']['properties']['server'][
+ 'properties'].update({'locked': {'type': 'boolean'}})
+update_server['response_body']['properties']['server'][
+ 'required'].append('locked')
+
+rebuild_server = copy.deepcopy(servers_21.rebuild_server)
+rebuild_server['response_body']['properties']['server'][
+ 'properties'].update({'locked': {'type': 'boolean'}})
+rebuild_server['response_body']['properties']['server'][
+ 'required'].append('locked')
+
+rebuild_server_with_admin_pass = copy.deepcopy(
+ servers_21.rebuild_server_with_admin_pass)
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'properties'].update({'locked': {'type': 'boolean'}})
+rebuild_server_with_admin_pass['response_body']['properties']['server'][
+ 'required'].append('locked')
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 0fb1991..4923d7e 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -21,12 +21,18 @@
as schema_access
from tempest.lib.api_schema.response.compute.v2_1 import flavors_extra_specs \
as schema_extra_specs
+from tempest.lib.api_schema.response.compute.v2_55 import flavors \
+ as schemav255
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
class FlavorsClient(base_compute_client.BaseComputeClient):
+ schema_versions_info = [
+ {'min': None, 'max': '2.54', 'schema': schema},
+ {'min': '2.55', 'max': None, 'schema': schemav255}]
+
def list_flavors(self, detail=False, **params):
"""Lists flavors.
@@ -36,11 +42,12 @@
https://developer.openstack.org/api-ref/compute/#list-flavors-with-details
"""
url = 'flavors'
- _schema = schema.list_flavors
-
+ schema = self.get_schema(self.schema_versions_info)
if detail:
url += '/detail'
_schema = schema.list_flavors_details
+ else:
+ _schema = schema.list_flavors
if params:
url += '?%s' % urllib.urlencode(params)
@@ -58,7 +65,9 @@
"""
resp, body = self.get("flavors/%s" % flavor_id)
body = json.loads(body)
- self.validate_response(schema.create_get_flavor_details, resp, body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.create_update_get_flavor_details,
+ resp, body)
return rest_client.ResponseBody(resp, body)
def create_flavor(self, **kwargs):
@@ -77,7 +86,25 @@
resp, body = self.post('flavors', post_body)
body = json.loads(body)
- self.validate_response(schema.create_get_flavor_details, resp, body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.create_update_get_flavor_details,
+ resp, body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_flavor(self, flavor_id, **kwargs):
+ """Uodate the flavor or instance type.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/compute/#update-flavor-description
+ """
+ put_body = json.dumps({'flavor': kwargs})
+ resp, body = self.put("flavors/%s" % flavor_id, put_body)
+
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.create_update_get_flavor_details,
+ resp, body)
return rest_client.ResponseBody(resp, body)
def delete_flavor(self, flavor_id):
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 09bccab..e75cdb5 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -29,6 +29,7 @@
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
from tempest.lib.api_schema.response.compute.v2_47 import servers as schemav247
from tempest.lib.api_schema.response.compute.v2_48 import servers as schemav248
+from tempest.lib.api_schema.response.compute.v2_54 import servers as schemav254
from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client
@@ -47,7 +48,8 @@
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
{'min': '2.26', 'max': '2.46', 'schema': schemav226},
{'min': '2.47', 'max': '2.47', 'schema': schemav247},
- {'min': '2.48', 'max': None, 'schema': schemav248}]
+ {'min': '2.48', 'max': '2.53', 'schema': schemav248},
+ {'min': '2.54', 'max': None, 'schema': schemav254}]
def __init__(self, auth_provider, service, region,
enable_instance_password=True, **kwargs):
@@ -156,11 +158,11 @@
url = 'servers'
schema = self.get_schema(self.schema_versions_info)
- _schema = schema.list_servers
-
if detail:
url += '/detail'
_schema = schema.list_servers_detail
+ else:
+ _schema = schema.list_servers
if params:
url += '?%s' % urllib.urlencode(params)
diff --git a/tempest/lib/services/volume/v2/availability_zone_client.py b/tempest/lib/services/volume/v2/availability_zone_client.py
index bb4a357..147e4c6 100644
--- a/tempest/lib/services/volume/v2/availability_zone_client.py
+++ b/tempest/lib/services/volume/v2/availability_zone_client.py
@@ -19,7 +19,6 @@
class AvailabilityZoneClient(rest_client.RestClient):
- api_version = "v2"
def list_availability_zones(self):
resp, body = self.get('os-availability-zone')
diff --git a/tempest/lib/services/volume/v2/backups_client.py b/tempest/lib/services/volume/v2/backups_client.py
index adfa6a6..3dbb30c 100644
--- a/tempest/lib/services/volume/v2/backups_client.py
+++ b/tempest/lib/services/volume/v2/backups_client.py
@@ -22,8 +22,7 @@
class BackupsClient(base_client.BaseClient):
- """Volume V2 Backups client"""
- api_version = "v2"
+ """Volume Backups client"""
def create_backup(self, **kwargs):
"""Creates a backup of volume.
diff --git a/tempest/lib/services/volume/v2/capabilities_client.py b/tempest/lib/services/volume/v2/capabilities_client.py
index 240be13..7ebcd69 100644
--- a/tempest/lib/services/volume/v2/capabilities_client.py
+++ b/tempest/lib/services/volume/v2/capabilities_client.py
@@ -19,7 +19,6 @@
class CapabilitiesClient(rest_client.RestClient):
- api_version = "v2"
def show_backend_capabilities(self, host):
"""Shows capabilities for a storage back end.
diff --git a/tempest/lib/services/volume/v2/encryption_types_client.py b/tempest/lib/services/volume/v2/encryption_types_client.py
index b99d1fe..5a7ea12 100644
--- a/tempest/lib/services/volume/v2/encryption_types_client.py
+++ b/tempest/lib/services/volume/v2/encryption_types_client.py
@@ -20,7 +20,6 @@
class EncryptionTypesClient(rest_client.RestClient):
- api_version = "v2"
def is_resource_deleted(self, id):
try:
diff --git a/tempest/lib/services/volume/v2/extensions_client.py b/tempest/lib/services/volume/v2/extensions_client.py
index 09279d5..45b7a56 100644
--- a/tempest/lib/services/volume/v2/extensions_client.py
+++ b/tempest/lib/services/volume/v2/extensions_client.py
@@ -19,8 +19,7 @@
class ExtensionsClient(rest_client.RestClient):
- """Volume V2 extensions client."""
- api_version = "v2"
+ """Volume extensions client."""
def list_extensions(self):
url = 'extensions'
diff --git a/tempest/lib/services/volume/v2/hosts_client.py b/tempest/lib/services/volume/v2/hosts_client.py
index f44bda3..c395325 100644
--- a/tempest/lib/services/volume/v2/hosts_client.py
+++ b/tempest/lib/services/volume/v2/hosts_client.py
@@ -20,8 +20,7 @@
class HostsClient(rest_client.RestClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume API requests"""
def list_hosts(self, **params):
"""Lists all hosts.
diff --git a/tempest/lib/services/volume/v2/limits_client.py b/tempest/lib/services/volume/v2/limits_client.py
index ce9fba9..9500254 100644
--- a/tempest/lib/services/volume/v2/limits_client.py
+++ b/tempest/lib/services/volume/v2/limits_client.py
@@ -19,9 +19,7 @@
class LimitsClient(rest_client.RestClient):
- """Volume V2 limits client."""
-
- api_version = "v2"
+ """Volume limits client."""
def show_limits(self):
"""Returns the details of a volume absolute limits."""
diff --git a/tempest/lib/services/volume/v2/qos_client.py b/tempest/lib/services/volume/v2/qos_client.py
index 47d3914..f8b8c3c 100644
--- a/tempest/lib/services/volume/v2/qos_client.py
+++ b/tempest/lib/services/volume/v2/qos_client.py
@@ -19,13 +19,11 @@
class QosSpecsClient(rest_client.RestClient):
- """Volume V2 QoS client.
+ """Volume QoS client.
Client class to send CRUD QoS API requests
"""
- api_version = "v2"
-
def is_resource_deleted(self, qos_id):
try:
self.show_qos(qos_id)
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
index 733b1ac..eeeb268 100644
--- a/tempest/lib/services/volume/v2/quota_classes_client.py
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -19,9 +19,7 @@
class QuotaClassesClient(rest_client.RestClient):
- """Volume quota class V2 client."""
-
- api_version = "v2"
+ """Volume quota class client."""
def show_quota_class_set(self, quota_class_id):
"""List quotas for a quota class.
diff --git a/tempest/lib/services/volume/v2/quotas_client.py b/tempest/lib/services/volume/v2/quotas_client.py
index e4b2895..8906294 100644
--- a/tempest/lib/services/volume/v2/quotas_client.py
+++ b/tempest/lib/services/volume/v2/quotas_client.py
@@ -20,8 +20,7 @@
class QuotasClient(rest_client.RestClient):
- """Client class to send CRUD Volume Quotas API V2 requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume Quotas API requests"""
def show_default_quota_set(self, tenant_id):
"""List the default volume quota set for a tenant."""
diff --git a/tempest/lib/services/volume/v2/scheduler_stats_client.py b/tempest/lib/services/volume/v2/scheduler_stats_client.py
index 0d04f85..bd5fa6d 100644
--- a/tempest/lib/services/volume/v2/scheduler_stats_client.py
+++ b/tempest/lib/services/volume/v2/scheduler_stats_client.py
@@ -19,7 +19,6 @@
class SchedulerStatsClient(rest_client.RestClient):
- api_version = "v2"
def list_pools(self, detail=False):
"""List all the volumes pools (hosts).
diff --git a/tempest/lib/services/volume/v2/services_client.py b/tempest/lib/services/volume/v2/services_client.py
index bc55469..09036a4 100644
--- a/tempest/lib/services/volume/v2/services_client.py
+++ b/tempest/lib/services/volume/v2/services_client.py
@@ -20,8 +20,7 @@
class ServicesClient(rest_client.RestClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume API requests"""
def list_services(self, **params):
url = 'os-services'
diff --git a/tempest/lib/services/volume/v2/snapshot_manage_client.py b/tempest/lib/services/volume/v2/snapshot_manage_client.py
index aecd30b..43fd328 100644
--- a/tempest/lib/services/volume/v2/snapshot_manage_client.py
+++ b/tempest/lib/services/volume/v2/snapshot_manage_client.py
@@ -19,9 +19,7 @@
class SnapshotManageClient(rest_client.RestClient):
- """Snapshot manage V2 client."""
-
- api_version = "v2"
+ """Snapshot manage client."""
def manage_snapshot(self, **kwargs):
"""Manage a snapshot."""
diff --git a/tempest/lib/services/volume/v2/snapshots_client.py b/tempest/lib/services/volume/v2/snapshots_client.py
index 4bc2842..1f05180 100644
--- a/tempest/lib/services/volume/v2/snapshots_client.py
+++ b/tempest/lib/services/volume/v2/snapshots_client.py
@@ -18,8 +18,7 @@
class SnapshotsClient(rest_client.RestClient):
- """Client class to send CRUD Volume V2 API requests."""
- api_version = "v2"
+ """Client class to send CRUD Volume API requests."""
create_resp = 202
def list_snapshots(self, detail=False, **params):
diff --git a/tempest/lib/services/volume/v2/transfers_client.py b/tempest/lib/services/volume/v2/transfers_client.py
index 2dfbe7b..291aadf 100644
--- a/tempest/lib/services/volume/v2/transfers_client.py
+++ b/tempest/lib/services/volume/v2/transfers_client.py
@@ -20,8 +20,7 @@
class TransfersClient(rest_client.RestClient):
- """Client class to send CRUD Volume Transfer V2 API requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume Transfer API requests"""
def create_volume_transfer(self, **kwargs):
"""Create a volume transfer.
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index af4fd8c..ef5e434 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -21,8 +21,7 @@
class TypesClient(rest_client.RestClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume API requests"""
def is_resource_deleted(self, id):
try:
diff --git a/tempest/lib/services/volume/v2/volume_manage_client.py b/tempest/lib/services/volume/v2/volume_manage_client.py
index 12f4240..ddae127 100644
--- a/tempest/lib/services/volume/v2/volume_manage_client.py
+++ b/tempest/lib/services/volume/v2/volume_manage_client.py
@@ -19,9 +19,7 @@
class VolumeManageClient(rest_client.RestClient):
- """Volume manage V2 client."""
-
- api_version = "v2"
+ """Volume manage client."""
def manage_volume(self, **kwargs):
"""Manage existing volume.
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index da3f2b5..dd019f6 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -23,8 +23,7 @@
class VolumesClient(base_client.BaseClient):
- """Client class to send CRUD Volume V2 API requests"""
- api_version = "v2"
+ """Client class to send CRUD Volume API requests"""
def _prepare_params(self, params):
"""Prepares params for use in get or _ext_get methods.
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index cf53b67..9965fe5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -83,6 +83,7 @@
if CONF.service_available.cinder:
cls.volumes_client = cls.os_primary.volumes_client_latest
cls.snapshots_client = cls.os_primary.snapshots_client_latest
+ cls.backups_client = cls.os_primary.backups_client_latest
# ## Test functions library
#
@@ -244,6 +245,37 @@
volume = self.volumes_client.show_volume(volume['id'])['volume']
return volume
+ def create_backup(self, volume_id, name=None, description=None,
+ force=False, snapshot_id=None, incremental=False,
+ container=None):
+
+ name = name or data_utils.rand_name(
+ self.__class__.__name__ + "-backup")
+ kwargs = {'name': name,
+ 'description': description,
+ 'force': force,
+ 'snapshot_id': snapshot_id,
+ 'incremental': incremental,
+ 'container': container}
+ backup = self.backups_client.create_backup(volume_id=volume_id,
+ **kwargs)['backup']
+ self.addCleanup(self.backups_client.delete_backup, backup['id'])
+ waiters.wait_for_volume_resource_status(self.backups_client,
+ backup['id'], 'available')
+ return backup
+
+ def restore_backup(self, backup_id):
+ restore = self.backups_client.restore_backup(backup_id)['restore']
+ self.addCleanup(self.volumes_client.delete_volume,
+ restore['volume_id'])
+ waiters.wait_for_volume_resource_status(self.backups_client,
+ backup_id, 'available')
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ restore['volume_id'],
+ 'available')
+ self.assertEqual(backup_id, restore['backup_id'])
+ return restore
+
def create_volume_snapshot(self, volume_id, name=None, description=None,
metadata=None, force=False):
name = name or data_utils.rand_name(
@@ -266,10 +298,8 @@
def create_volume_type(self, client=None, name=None, backend_name=None):
if not client:
client = self.os_admin.volume_types_v2_client
- if not name:
- class_name = self.__class__.__name__
- name = data_utils.rand_name(class_name + '-volume-type')
- randomized_name = data_utils.rand_name('scenario-type-' + name)
+ randomized_name = name or data_utils.rand_name(
+ 'volume-type-' + self.__class__.__name__)
LOG.debug("Creating a volume type: %s on backend %s",
randomized_name, backend_name)
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index b5220e9..8c210d5 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -57,8 +57,7 @@
@utils.services('compute', 'volume', 'image')
def test_encrypted_cinder_volumes_luks(self):
server = self.launch_instance()
- volume = self.create_encrypted_volume('nova.volume.encryptors.'
- 'luks.LuksEncryptor',
+ volume = self.create_encrypted_volume('luks',
volume_type='luks')
self.attach_detach_volume(server, volume)
@@ -67,7 +66,6 @@
@utils.services('compute', 'volume', 'image')
def test_encrypted_cinder_volumes_cryptsetup(self):
server = self.launch_instance()
- volume = self.create_encrypted_volume('nova.volume.encryptors.'
- 'cryptsetup.CryptsetupEncryptor',
+ volume = self.create_encrypted_volume('plain',
volume_type='cryptsetup')
self.attach_detach_volume(server, volume)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 89b9fdd..8aa729b 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -32,7 +32,6 @@
"""The test suite for server advanced operations
This test case stresses some advanced server instance operations:
- * Resizing a volume-backed instance
* Sequence suspend resume
"""
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
new file mode 100644
index 0000000..c23b564
--- /dev/null
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -0,0 +1,91 @@
+# Copyright 2018 Red Hat, Inc.
+# 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.common import utils
+from tempest import config
+from tempest.lib import decorators
+from tempest.scenario import manager
+
+CONF = config.CONF
+
+
+class TestVolumeBackupRestore(manager.ScenarioTest):
+ """Test cinder backup and restore
+
+ This testcase verifies content preservation after backup and restore
+ operations by booting a server from a restored backup and check the
+ connectivity to it.
+
+ The following is the scenario outline:
+ 1. Create volume from image.
+ 2. Create a backup for the volume.
+ 3. Restore the backup.
+ 4. Boot a server from the restored backup.
+ 5. Create a floating ip.
+ 6. Check server connectivity.
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(TestVolumeBackupRestore, cls).skip_checks()
+ if not CONF.volume_feature_enabled.backup:
+ raise cls.skipException('Backup is not enable.')
+
+ @decorators.idempotent_id('2ce5e55c-4085-43c1-98c6-582525334ad7')
+ @decorators.attr(type='slow')
+ @utils.services('compute', 'volume', 'image')
+ def test_volume_backup_restore(self):
+ # Create volume from image
+ img_uuid = CONF.compute.image_ref
+ volume = self.create_volume(imageRef=img_uuid)
+ volume_details = self.volumes_client.show_volume(
+ volume['id'])['volume']
+ self.assertEqual('true', volume_details['bootable'])
+
+ # Create a backup
+ backup = self.create_backup(volume_id=volume['id'])
+
+ # Restore the backup
+ restored_volume_id = self.restore_backup(backup['id'])['volume_id']
+
+ # Verify the restored backup volume is bootable
+ restored_volume_info = self.volumes_client.show_volume(
+ restored_volume_id)['volume']
+ self.assertEqual('true', restored_volume_info['bootable'])
+
+ # Create keypair and security group
+ keypair = self.create_keypair()
+ security_group = self._create_security_group()
+
+ # Boot a server from the restored backup
+ bd_map_v2 = [{
+ 'uuid': restored_volume_id,
+ 'source_type': 'volume',
+ 'destination_type': 'volume',
+ 'boot_index': 0}]
+ server = self.create_server(image_id='',
+ block_device_mapping_v2=bd_map_v2,
+ key_name=keypair['name'],
+ security_groups=[
+ {'name': security_group['name']}])
+
+ # Create a floating ip
+ floating_ip = self.create_floating_ip(server)
+
+ # Check server connectivity
+ self.check_vm_connectivity(floating_ip['ip'],
+ username=CONF.validation.image_ssh_user,
+ private_key=keypair['private_key'],
+ should_connect=True)
diff --git a/tempest/tests/lib/services/compute/test_flavors_client.py b/tempest/tests/lib/services/compute/test_flavors_client.py
index cbd17c6..5325036 100644
--- a/tempest/tests/lib/services/compute/test_flavors_client.py
+++ b/tempest/tests/lib/services/compute/test_flavors_client.py
@@ -17,6 +17,7 @@
import fixtures
from oslo_serialization import jsonutils as json
+from tempest.api.compute import api_microversion_fixture
from tempest.lib.services.compute import flavors_client
from tempest.tests.lib import fake_auth_provider
from tempest.tests.lib import fake_http
@@ -39,6 +40,21 @@
"vcpus": 1
}
+ FAKE_FLAVOR_UPDATE = {
+ "disk": 1,
+ "id": "1",
+ "links": [{
+ "href": "http://openstack.example.com/v2/openstack/flavors/1",
+ "rel": "self"}, {
+ "href": "http://openstack.example.com/openstack/flavors/1",
+ "rel": "bookmark"}],
+ "name": "m1.tiny",
+ "ram": 512,
+ "swap": 1,
+ "vcpus": 1,
+ "description": 'new'
+ }
+
EXTRA_SPECS = {"extra_specs": {
"key1": "value1",
"key2": "value2"}
@@ -106,6 +122,25 @@
def test_create_flavor__byte_body(self):
self._test_create_flavor(bytes_body=True)
+ def _test_update_flavor(self, bytes_body=False):
+ self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+ '2.55'))
+ expected = {"flavor": TestFlavorsClient.FAKE_FLAVOR_UPDATE}
+ request = {"flavor": {"description": "updated description"}}
+ self.check_service_client_function(
+ self.client.update_flavor,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ expected,
+ bytes_body,
+ flavor_id='8c7aae5a-d315-4216-875b-ed9b6a5bcfc6',
+ **request)
+
+ def test_update_flavor_str_body(self):
+ self._test_update_flavor(bytes_body=False)
+
+ def test_update_flavor__byte_body(self):
+ self._test_update_flavor(bytes_body=True)
+
def test_delete_flavor(self):
self.check_service_client_function(
self.client.delete_flavor,