Merge "Trivial docstring cleanup in TestServerAdvancedOps"
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..c22888d 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
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_servers.py b/tempest/api/compute/admin/test_servers.py
index 3f06c4e..cdfc44a 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -16,6 +16,7 @@
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -61,9 +62,13 @@
@decorators.idempotent_id('d56e9540-73ed-45e0-9b88-98fc419087eb')
def test_list_servers_detailed_filter_by_invalid_status(self):
params = {'status': 'invalid_status'}
- body = self.client.list_servers(detail=True, **params)
- servers = body['servers']
- self.assertEmpty(servers)
+ if self.is_requested_microversion_compatible('2.37'):
+ body = self.client.list_servers(detail=True, **params)
+ servers = body['servers']
+ self.assertEmpty(servers)
+ else:
+ self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+ detail=True, **params)
@decorators.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
def test_list_servers_by_admin(self):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 760e356..975728c 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -127,6 +127,36 @@
cls.image_ssh_password = CONF.validation.image_ssh_password
@classmethod
+ def is_requested_microversion_compatible(cls, max_version):
+ """Check the compatibility of selected request microversion
+
+ This method will check if selected request microversion
+ (cls.request_microversion) for test is compatible with respect
+ to 'max_version'. Compatible means if selected request microversion
+ is in the range(<=) of 'max_version'.
+
+ :param max_version: maximum microversion to compare for compatibility.
+ Example: '2.30'
+ :returns: True if selected request microversion is compatible with
+ 'max_version'. False in other case.
+ """
+ try:
+ req_version_obj = api_version_request.APIVersionRequest(
+ cls.request_microversion)
+ # NOTE(gmann): This is case where this method is used before calling
+ # resource_setup(), where cls.request_microversion is set. There may
+ # not be any such case but still we can handle this case.
+ except AttributeError:
+ request_microversion = (
+ api_version_utils.select_request_microversion(
+ cls.min_microversion,
+ CONF.compute.min_microversion))
+ req_version_obj = api_version_request.APIVersionRequest(
+ request_microversion)
+ max_version_obj = api_version_request.APIVersionRequest(max_version)
+ return req_version_obj <= max_version_obj
+
+ @classmethod
def server_check_teardown(cls):
"""Checks is the shared server clean enough for subsequent test.
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 393e68f..18a78f0 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -79,10 +79,16 @@
@decorators.attr(type=['negative'])
@decorators.idempotent_id('fcdf192d-0f74-4d89-911f-1ec002b822c4')
def test_list_servers_status_non_existing(self):
- # Return an empty list when invalid status is specified
- body = self.client.list_servers(status='non_existing_status')
- servers = body['servers']
- self.assertEmpty(servers)
+ # When invalid status is specified, up to microversion 2.37,
+ # an empty list is returnd, and starting from microversion 2.38,
+ # a 400 error is returned in that case.
+ if self.is_requested_microversion_compatible('2.37'):
+ body = self.client.list_servers(status='non_existing_status')
+ servers = body['servers']
+ self.assertEmpty(servers)
+ else:
+ self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
+ status='non_existing_status')
@decorators.attr(type=['negative'])
@decorators.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
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/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/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/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_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/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 09bccab..c85af1f 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -156,11 +156,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/tests/api/compute/test_base.py b/tempest/tests/api/compute/test_base.py
index 5024100..47f4ad6 100644
--- a/tempest/tests/api/compute/test_base.py
+++ b/tempest/tests/api/compute/test_base.py
@@ -173,3 +173,20 @@
# make our assertions
wait_for_image_status.assert_called_once_with(
compute_images_client, image_id, 'SAVING')
+
+ def _test_version_compatible(self, max_version, expected=True):
+ actual = (compute_base.BaseV2ComputeTest.
+ is_requested_microversion_compatible(max_version))
+ self.assertEqual(expected, actual)
+
+ def test_check_lower_version(self):
+ compute_base.BaseV2ComputeTest.request_microversion = '2.8'
+ self._test_version_compatible('2.40')
+
+ def test_check_euqal_version(self):
+ compute_base.BaseV2ComputeTest.request_microversion = '2.40'
+ self._test_version_compatible('2.40')
+
+ def test_check_higher_version(self):
+ compute_base.BaseV2ComputeTest.request_microversion = '2.41'
+ self._test_version_compatible('2.40', expected=False)