Merge "Patch to fix test_rebuild_volume_backed_server" into mcp/caracal
diff --git a/releasenotes/notes/add-boot-from-volume-option-312d02c0c84f2092.yaml b/releasenotes/notes/add-boot-from-volume-option-312d02c0c84f2092.yaml
new file mode 100644
index 0000000..0a0b78e
--- /dev/null
+++ b/releasenotes/notes/add-boot-from-volume-option-312d02c0c84f2092.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - A new config option in group compute-feature-enabled
+ boot_from_volume which specifies if nova allow to boot
+ instances from volume. This functionality is not available
+ on some hypervisors and cinder backends like ironic and
+ ceph.
diff --git a/requirements.txt b/requirements.txt
index 6e66046..c6dd58a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,3 +23,4 @@
debtcollector>=1.2.0 # Apache-2.0
defusedxml>=0.7.1 # PSFL
fasteners>=0.16.0 # Apache-2.0
+tenacity>=4.4.0 # Apache-2.0
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
index 293e284..5bc9206 100644
--- a/tempest/api/compute/admin/test_create_server.py
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -136,3 +136,36 @@
servers_client=self.client)
disks_num_eph = len(linux_client.get_disks().split('\n'))
self.assertEqual(disks_num + 1, disks_num_eph)
+
+
+class ServersTestUEFI(base.BaseV2ComputeAdminTest):
+ """Test creating server with UEFI firmware type"""
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.prepare_instance_network()
+ super(ServersTestUEFI, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServersTestUEFI, cls).setup_clients()
+ cls.client = cls.servers_client
+
+ @decorators.idempotent_id('94feb6c3-d07e-b3b9-def8-64fd082d9b21')
+ def test_created_server_uefi(self):
+ # create custom image with uefi type
+ custom_img = self.create_image_with_custom_property(
+ hw_machine_type='q35',
+ hw_firmware_type='uefi',
+ )
+ # create the server and wait for it to become ready
+ validation_resources = self.get_class_validation_resources(
+ self.os_primary)
+ server = self.create_test_server(
+ image_id=custom_img, validatable=True,
+ validation_resources=validation_resources, wait_until='SSHABLE')
+ # check UEFI boot loader in console log server
+ uefi_boot_loader = "UEFI Misc Device"
+ console_log = self.client.get_console_output(server['id'])['output']
+ self.assertTrue(console_log, "Console output was empty.")
+ self.assertIn(uefi_boot_loader, console_log)
diff --git a/tempest/api/compute/admin/test_volume.py b/tempest/api/compute/admin/test_volume.py
index 2813d7a..b0f20f6 100644
--- a/tempest/api/compute/admin/test_volume.py
+++ b/tempest/api/compute/admin/test_volume.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest.common import waiters
from tempest import config
@@ -37,50 +39,13 @@
cls.prepare_instance_network()
super(BaseAttachSCSIVolumeTest, cls).setup_credentials()
- def _create_image_with_custom_property(self, **kwargs):
- """Wrapper utility that returns the custom image.
-
- Creates a new image by downloading the default image's bits and
- uploading them to a new image. Any kwargs are set as image properties
- on the new image.
-
- :param return image_id: The UUID of the newly created image.
- """
- image = self.admin_image_client.show_image(CONF.compute.image_ref)
- # NOTE(danms): We need to stream this, so chunked=True means we get
- # back a urllib3.HTTPResponse and have to carefully pass it to
- # store_image_file() to upload it in pieces.
- image_data_resp = self.admin_image_client.show_image_file(
- CONF.compute.image_ref, chunked=True)
- create_dict = {
- 'container_format': image['container_format'],
- 'disk_format': image['disk_format'],
- 'min_disk': image['min_disk'],
- 'min_ram': image['min_ram'],
- 'visibility': 'public',
- }
- if 'kernel_id' in image:
- create_dict['kernel_id'] = image['kernel_id']
- if 'ramdisk_id' in image:
- create_dict['ramdisk_id'] = image['ramdisk_id']
-
- create_dict.update(kwargs)
- try:
- new_image = self.admin_image_client.create_image(**create_dict)
- self.addCleanup(self.admin_image_client.wait_for_resource_deletion,
- new_image['id'])
- self.addCleanup(
- self.admin_image_client.delete_image, new_image['id'])
- self.admin_image_client.store_image_file(new_image['id'],
- image_data_resp)
- finally:
- image_data_resp.release_conn()
- return new_image['id']
-
class AttachSCSIVolumeTestJSON(BaseAttachSCSIVolumeTest):
"""Test attaching scsi volume to server"""
+ @testtools.skipIf(
+ CONF.compute_feature_enabled.barbican_integration_enabled,
+ "Not supported when barbican integration enabled.")
@decorators.idempotent_id('777e468f-17ca-4da4-b93d-b7dbf56c0494')
def test_attach_scsi_disk_with_config_drive(self):
"""Test the attach/detach volume with config drive/scsi disk
@@ -90,7 +55,7 @@
virtio-scsi mode with further asserting list volume attachments
in instance after attach and detach of the volume.
"""
- custom_img = self._create_image_with_custom_property(
+ custom_img = self.create_image_with_custom_property(
hw_scsi_model='virtio-scsi',
hw_disk_bus='scsi',
hw_cdrom_bus='scsi')
diff --git a/tempest/api/compute/admin/test_volume_swap.py b/tempest/api/compute/admin/test_volume_swap.py
index 9576b74..fa416b1 100644
--- a/tempest/api/compute/admin/test_volume_swap.py
+++ b/tempest/api/compute/admin/test_volume_swap.py
@@ -36,6 +36,10 @@
raise cls.skipException("Cinder is not available")
if not CONF.compute_feature_enabled.swap_volume:
raise cls.skipException("Swapping volumes is not supported.")
+ if CONF.compute_feature_enabled.attach_encrypted_volume:
+ raise cls.skipException(
+ 'Volume swap is not available for OS configurations '
+ 'with crypted volumes.')
def wait_for_server_volume_swap(self, server_id, old_volume_id,
new_volume_id):
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 2557e47..313f73d 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -701,3 +701,37 @@
for target_host in hosts:
if source_host != target_host:
return target_host
+
+ def create_image_with_custom_property(self, base_image=None, **kwargs):
+ """Wrapper utility that returns the custom image.
+
+ Creates a new image by downloading the base image bits and
+ uploading them to a new image. Any kwargs are set as image properties
+ on the new image.
+
+ :param return image_id: The UUID of the newly created image.
+ """
+ if base_image is None:
+ base_image = CONF.compute.image_ref
+ image = self.admin_image_client.show_image(base_image)
+ image_data_resp = self.admin_image_client.show_image_file(
+ CONF.compute.image_ref, chunked=True)
+ create_dict = {
+ 'container_format': image['container_format'],
+ 'disk_format': image['disk_format'],
+ 'min_disk': image['min_disk'],
+ 'min_ram': image['min_ram'],
+ 'visibility': 'public',
+ }
+ create_dict.update(kwargs)
+ try:
+ new_image = self.admin_image_client.create_image(**create_dict)
+ self.addCleanup(self.admin_image_client.wait_for_resource_deletion,
+ new_image['id'])
+ self.addCleanup(
+ self.admin_image_client.delete_image, new_image['id'])
+ self.admin_image_client.store_image_file(new_image['id'],
+ image_data_resp)
+ finally:
+ image_data_resp.release_conn()
+ return new_image['id']
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 8984d1d..688b31b 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -109,21 +109,24 @@
"""
port = self.ports_client.show_port(port_id)['port']
device_id = port['device_id']
+ dns_name = port.get('dns_name')
start = int(time.time())
# NOTE(mriedem): Nova updates the port's device_id to '' rather than
# None, but it's not contractual so handle Falsey either way.
- while device_id:
+ while any([device_id, dns_name]):
time.sleep(self.build_interval)
port = self.ports_client.show_port(port_id)['port']
device_id = port['device_id']
+ dns_name = port.get('dns_name')
timed_out = int(time.time()) - start >= self.build_timeout
- if device_id and timed_out:
- message = ('Port %s failed to detach (device_id %s) within '
- 'the required time (%s s).' %
- (port_id, device_id, self.build_timeout))
+ if any([device_id, dns_name]) and timed_out:
+ message = ('Port %s failed to detach (device_id %s), '
+ '(dns_name %s) within the required time (%s s).' %
+ (port_id, device_id or 'is out',
+ dns_name or 'is out', self.build_timeout))
raise lib_exc.TimeoutException(message)
return port
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 6664e15..6f97b1f 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -182,6 +182,8 @@
if not utils.get_service_list()['volume']:
msg = "Volume service not enabled."
raise cls.skipException(msg)
+ if not CONF.compute_feature_enabled.boot_from_volume:
+ raise cls.skipException("Booting from volume is not enabled.")
class ServersTestFqdnHostnames(base.BaseV2ComputeTest):
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index 1308b19..c90aea8 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -64,7 +64,15 @@
def _validate_novnc_html(self, vnc_url):
"""Verify we can connect to novnc and get back the javascript."""
- resp = urllib3.PoolManager().request('GET', vnc_url)
+ cert_params = {}
+
+ if CONF.identity.disable_ssl_certificate_validation:
+ cert_params['cert_reqs'] = "CERT_NONE"
+ else:
+ cert_params["cert_reqs"] = "CERT_REQUIRED"
+ cert_params["ca_certs"] = CONF.identity.ca_certificates_file
+
+ resp = urllib3.PoolManager(**cert_params).request('GET', vnc_url)
# Make sure that the GET request was accepted by the novncproxy
self.assertEqual(resp.status, 200, 'Got a Bad HTTP Response on the '
'initial call: ' + str(resp.status))
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 97c2774..3b4adad 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -74,6 +74,7 @@
class ServerRescueTestJSONUnderV235(ServerRescueTestBase):
"""Test server rescue with compute microversion less than 2.36"""
+ min_microversion = '2.1'
max_microversion = '2.35'
# TODO(zhufl): After 2.35 we should switch to neutron client to create
diff --git a/tempest/api/compute/test_quotas.py b/tempest/api/compute/test_quotas.py
index 38ca53b..286e0a5 100644
--- a/tempest/api/compute/test_quotas.py
+++ b/tempest/api/compute/test_quotas.py
@@ -13,10 +13,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import testtools
+
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
+from tempest import config
from tempest.lib import decorators
+CONF = config.CONF
+
class QuotasTestJSON(base.BaseV2ComputeTest):
"""Test compute quotas"""
@@ -76,6 +81,8 @@
for quota in expected_quota_set:
self.assertIn(quota, quota_set.keys())
+ @testtools.skipIf(not CONF.auth.use_dynamic_credentials,
+ 'does not support static credentials')
@decorators.idempotent_id('cd65d997-f7e4-4966-a7e9-d5001b674fdc')
def test_compare_tenant_quotas_with_default_quotas(self):
"""Test tenants are created with the default compute quota values"""
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 7ea8f09..f7432d0 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -267,6 +267,12 @@
"""
server, validation_resources = self._create_server()
volume = self.create_volume()
+
+ if volume['multiattach']:
+ raise self.skipException(
+ "Attaching multiattach volumes is not supported "
+ "for shelved-offloaded instances.")
+
num_vol = self._count_volumes(server, validation_resources)
self._shelve_server(server, validation_resources)
attachment = self.attach_volume(server, volume)
@@ -277,8 +283,7 @@
# Get volume attachment of the server
volume_attachment = self.servers_client.show_volume_attachment(
- server['id'],
- attachment['id'])['volumeAttachment']
+ server['id'], attachment['id'])['volumeAttachment']
self.assertEqual(server['id'], volume_attachment['serverId'])
self.assertEqual(attachment['id'], volume_attachment['id'])
# Check the mountpoint is not None after unshelve server even in
@@ -298,6 +303,12 @@
"""
server, validation_resources = self._create_server()
volume = self.create_volume()
+
+ if volume['multiattach']:
+ raise self.skipException(
+ "Attaching multiattach volumes is not supported for "
+ "shelved-offloaded instances.")
+
num_vol = self._count_volumes(server, validation_resources)
self._shelve_server(server, validation_resources)
@@ -307,8 +318,8 @@
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
- # Unshelve the instance and check that we have the expected number of
- # volume(s)
+ # Unshelve the instance and check that we have
+ # the expected number of volume(s)
self._unshelve_server_and_check_volumes(
server, validation_resources, num_vol)
diff --git a/tempest/api/network/admin/test_routers.py b/tempest/api/network/admin/test_routers.py
index 216b15d..d8ef4a3 100644
--- a/tempest/api/network/admin/test_routers.py
+++ b/tempest/api/network/admin/test_routers.py
@@ -12,6 +12,7 @@
# 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 time
import testtools
@@ -22,6 +23,7 @@
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
CONF = config.CONF
@@ -117,7 +119,30 @@
for k, v in exp_ext_gw_info.items():
self.assertEqual(v, actual_ext_gw_info[k])
+ def _wait_for_ports(self, router_id, timeout=30):
+ start = int(time.time())
+ list_body = self.admin_ports_client.list_ports(
+ network_id=CONF.network.public_network_id,
+ device_id=router_id,
+ device_owner="network:router_gateway")
+
+ while not len(list_body['ports']):
+ time.sleep(5)
+ list_body = self.admin_ports_client.list_ports(
+ network_id=CONF.network.public_network_id,
+ device_id=router_id,
+ device_owner="network:router_gateway")
+ timed_out = int(time.time()) - start >= timeout
+ if not len(list_body['ports']) and timed_out:
+ message = ('Router %s failed to attach ports within '
+ 'the required time (%s s).' %
+ (router_id, timeout))
+ raise lib_exc.TimeoutException(message)
+
def _verify_gateway_port(self, router_id):
+ # Workaround for PRODX-8489
+ if config.is_tungstenfabric_backend_enabled():
+ self._wait_for_ports(router_id)
list_body = self.admin_ports_client.list_ports(
network_id=CONF.network.public_network_id,
device_id=router_id,
diff --git a/tempest/api/network/test_agent_management_negative.py b/tempest/api/network/test_agent_management_negative.py
index d1c02ce..36d44d5 100644
--- a/tempest/api/network/test_agent_management_negative.py
+++ b/tempest/api/network/test_agent_management_negative.py
@@ -14,11 +14,19 @@
# under the License.
from tempest.api.network import base
+from tempest.common import utils
from tempest.lib import decorators
class AgentManagementNegativeTest(base.BaseNetworkTest):
+ @classmethod
+ def skip_checks(cls):
+ super(AgentManagementNegativeTest, cls).skip_checks()
+ if not utils.is_extension_enabled('agent', 'network'):
+ msg = "agent extension not enabled."
+ raise cls.skipException(msg)
+
@decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
@decorators.attr(type=['negative'])
def test_list_agents_non_admin(self):
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index c7f6b8f..e6d0f7e 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -191,10 +191,20 @@
protocol = 'tcp'
port_range_min = 77
port_range_max = 77
- self._create_verify_security_group_rule(sg_id, direction,
- self.ethertype, protocol,
- port_range_min,
- port_range_max)
+
+ if config.is_tungstenfabric_backend_enabled():
+ if self.ethertype == 'IPv6':
+ remote_ip_prefix = '::/0'
+ else:
+ remote_ip_prefix = '0.0.0.0/0'
+ self._create_verify_security_group_rule(
+ sg_id, direction, self.ethertype, protocol, port_range_min,
+ port_range_max, remote_ip_prefix=remote_ip_prefix)
+ else:
+ self._create_verify_security_group_rule(sg_id, direction,
+ self.ethertype, protocol,
+ port_range_min,
+ port_range_max)
@decorators.idempotent_id('c9463db8-b44d-4f52-b6c0-8dbda99f26ce')
def test_create_security_group_rule_with_icmp_type_code(self):
@@ -219,9 +229,18 @@
protocol = 'ipv6-icmp' if self._ip_version == 6 else 'icmp'
icmp_type_codes = [(3, 2), (3, 0), (8, 0), (0, 0), (11, None)]
for icmp_type, icmp_code in icmp_type_codes:
- self._create_verify_security_group_rule(sg_id, direction,
- self.ethertype, protocol,
- icmp_type, icmp_code)
+ if config.is_tungstenfabric_backend_enabled():
+ if self.ethertype == 'IPv6':
+ remote_ip_prefix = '::/0'
+ else:
+ remote_ip_prefix = '0.0.0.0/0'
+ self._create_verify_security_group_rule(
+ sg_id, direction, self.ethertype, protocol, icmp_type,
+ icmp_code, remote_ip_prefix=remote_ip_prefix)
+ else:
+ self._create_verify_security_group_rule(
+ sg_id, direction, self.ethertype, protocol, icmp_type,
+ icmp_code)
@decorators.idempotent_id('c2ed2deb-7a0c-44d8-8b4c-a5825b5c310b')
def test_create_security_group_rule_with_remote_group_id(self):
diff --git a/tempest/api/network/test_subnetpools_extensions.py b/tempest/api/network/test_subnetpools_extensions.py
index 689844b..f398062 100644
--- a/tempest/api/network/test_subnetpools_extensions.py
+++ b/tempest/api/network/test_subnetpools_extensions.py
@@ -45,6 +45,9 @@
if not utils.is_extension_enabled('subnet_allocation', 'network'):
msg = "subnet_allocation extension not enabled."
raise cls.skipException(msg)
+ if not utils.is_extension_enabled('default-subnetpools', 'network'):
+ msg = "default-subnetpools extension not enabled."
+ raise cls.skipException(msg)
@decorators.attr(type='smoke')
@decorators.idempotent_id('62595970-ab1c-4b7f-8fcc-fddfe55e9811')
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index f055d19..d4bf0d7 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -88,9 +88,12 @@
nafter = self._get_bytes_used()
self.assertEqual(nbefore, nafter)
+ # NOTE(vsaienko): the quotas imlementation on active/active rgw deployment
+ # have no coordination, as result quota overflow might happen, since
+ # this is upstream and we can't fix downstream. Remove test from smoke
+ # Related-Prod: PRODX-11581
@decorators.idempotent_id('3a387039-697a-44fc-a9c0-935de31f426b')
@utils.requires_ext(extension='container_quotas', service='object')
- @decorators.attr(type="smoke")
def test_upload_too_many_objects(self):
"""Attempts to upload many objects that exceeds the count limit."""
for _ in range(QUOTA_COUNT):
diff --git a/tempest/api/volume/admin/test_volume_manage.py b/tempest/api/volume/admin/test_volume_manage.py
index 609ec15..3d7cb15 100644
--- a/tempest/api/volume/admin/test_volume_manage.py
+++ b/tempest/api/volume/admin/test_volume_manage.py
@@ -79,8 +79,11 @@
new_vol_id)['volume']
self.assertNotIn(new_vol_id, [org_vol_id])
self.assertEqual(new_vol_info['name'], new_vol_name)
- for key in ['size',
- 'volume_type',
- 'availability_zone',
- 'os-vol-host-attr:host']:
+ check_attrs = ['size',
+ 'volume_type',
+ 'availability_zone'
+ ]
+ if CONF.volume.storage_protocol != 'ceph':
+ check_attrs.append('os-vol-host-attr:host')
+ for key in check_attrs:
self.assertEqual(new_vol_info[key], org_vol_info[key])
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 7c25f3d..4cb2262 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -179,10 +179,11 @@
keys_with_change = ('volume_type',)
# NOTE(vsaienko): with active-active cluster deployment volume
- # services registered with different hostname.
- if CONF.volume_feature_enabled.cluster_active_active:
- keys_with_change += ('os-vol-host-attr:host',)
- else:
+ # services registered with different hostname since we don't know
+ # which service process request host might or might not be changed.
+ # TODO(vsaienko): Revisit logic when is fixed
+ # https://bugs.launchpad.net/cinder/+bug/1874414
+ if not CONF.volume_feature_enabled.cluster_active_active:
keys_with_no_change += ('os-vol-host-attr:host',)
# Check the volume information after the retype
diff --git a/tempest/api/volume/test_volumes_filters.py b/tempest/api/volume/test_volumes_filters.py
new file mode 100644
index 0000000..74ba9cb
--- /dev/null
+++ b/tempest/api/volume/test_volumes_filters.py
@@ -0,0 +1,50 @@
+# Copyright 2021 Mirantis Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import testtools
+
+from tempest.api.volume import base
+from tempest import config
+from tempest.lib import decorators
+
+
+CONF = config.CONF
+
+
+class VolumesFilter(base.BaseVolumeAdminTest):
+ @testtools.skipUnless(
+ "InstanceLocalityFilter" in CONF.volume.scheduler_default_filters,
+ "Cinder InstanceLocalityFilter is disabled",
+ )
+ @testtools.skipUnless(
+ CONF.volume_feature_enabled.instance_locality_enabled,
+ "InstanceLocalityFilter test is disabled",
+ )
+ @decorators.idempotent_id("5c13f4f7-5add-4fad-8ef7-dccca0f76295")
+ def test_instancelocalityfilter(self):
+ # 1. Create instance
+ # 2. Create volume by using local_to_instance hint
+ # 3. Compare server host and volume host are the same.
+ server = self.create_server()
+ server_host = self.admin_manager.servers_client.show_server(
+ server["id"])["server"]["OS-EXT-SRV-ATTR:host"]
+ volume = self.create_volume(hints={"local_to_instance": server["id"]})
+ fetched_volume = self.admin_volume_client.show_volume(volume["id"])[
+ "volume"]
+ self.assertEqual(
+ server_host, fetched_volume["os-vol-host-attr:host"].split("@")
+ [0],
+ "The fetched Volume host is different "
+ "from the created instance",)
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index d8480df..7c319db 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -264,6 +264,13 @@
@decorators.idempotent_id('449c4ed2-ecdd-47bb-98dc-072aeccf158c')
def test_reserve_volume_with_negative_volume_status(self):
"""Test reserving already reserved volume should fail"""
+
+ # Skip test if the volume has "multiattach" property
+ volume = self.volumes_client.show_volume(self.volume['id'])
+ if volume['multiattach']:
+ raise self.skipException('Reserving multiattach volumes is not'
+ ' supported.')
+
# Mark volume as reserved.
self.volumes_client.reserve_volume(self.volume['id'])
# Mark volume which is marked as reserved before
diff --git a/tempest/config.py b/tempest/config.py
index aeef88e..2fc4223 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -622,6 +622,14 @@
cfg.BoolOpt('unified_limits',
default=False,
help='Does the test environment support unified limits?'),
+ cfg.BoolOpt('boot_from_volume',
+ default=True,
+ help='Does the test environment support booting instances '
+ 'from volume. This depends on hypervisor and volume '
+ 'backend/type.'),
+ cfg.BoolOpt('barbican_integration_enabled',
+ default=False,
+ help='Does the test environment support Barbican integration'),
]
@@ -1041,6 +1049,9 @@
"If both values are not specified, Tempest avoids tests "
"which require a microversion. Valid values are string "
"with format 'X.Y' or string 'latest'",),
+ cfg.ListOpt('scheduler_default_filters',
+ default=[],
+ help="The list of enabled scheduler filters.",),
]
volume_feature_group = cfg.OptGroup(name='volume-feature-enabled',
@@ -1093,7 +1104,11 @@
cfg.BoolOpt('cluster_active_active',
default=False,
help='The boolean flag to indicate if active-active mode '
- 'is used by volume backend.')
+ 'is used by volume backend.'),
+ cfg.BoolOpt('instance_locality_enabled',
+ default=False,
+ help='The boolean flag to run instance locality tests '
+ 'on environment.')
]
@@ -1336,6 +1351,10 @@
help="The boolean flag to specify the type of environment. "
"Skip tests that cannot be run in production. "
"For example: create/delete TLDs in Designate tests."),
+ cfg.StrOpt('state_path',
+ help="The top-level directory for maintaining Tempest state. "
+ "For example store configuration files mounted do docker "
+ "containers."),
]
_opts = [
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 ba3d787..5d1f315 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,12 @@
import copy
+from tempest.lib.api_schema.response.compute.v2_1 import servers \
+ as servers
from tempest.lib.api_schema.response.compute.v2_16 import servers \
as serversv216
+
# Compute microversion 2.19:
# 1. New attributes in 'server' dict.
# 'description'
@@ -63,3 +66,4 @@
list_volume_attachments = copy.deepcopy(serversv216.list_volume_attachments)
show_instance_action = copy.deepcopy(serversv216.show_instance_action)
create_backup = copy.deepcopy(serversv216.create_backup)
+list_instance_actions = copy.deepcopy(servers.list_instance_actions)
diff --git a/tempest/lib/api_schema/response/compute/v2_58/servers.py b/tempest/lib/api_schema/response/compute/v2_58/servers.py
index 637b765..5c22c79 100644
--- a/tempest/lib/api_schema/response/compute/v2_58/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_58/servers.py
@@ -12,8 +12,10 @@
import copy
from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_1 import servers as servers
from tempest.lib.api_schema.response.compute.v2_57 import servers as servers257
+
# microversion 2.58 added updated_at to the response
show_instance_action = copy.deepcopy(servers257.show_instance_action)
show_instance_action['response_body']['properties']['instanceAction'][
@@ -21,6 +23,14 @@
show_instance_action['response_body']['properties']['instanceAction'][
'required'].append('updated_at')
+# microversion 2.58 added updated_at to the response
+list_instance_actions = copy.deepcopy(servers.list_instance_actions)
+list_instance_actions['response_body']['properties']['instanceActions'][
+ 'items']['properties'].update({'updated_at': parameter_types.date_time})
+list_instance_actions['response_body']['properties']['instanceActions'][
+ 'items']['required'].append('updated_at')
+
+
# 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.
diff --git a/tempest/lib/common/constants.py b/tempest/lib/common/constants.py
new file mode 100644
index 0000000..57fdd93
--- /dev/null
+++ b/tempest/lib/common/constants.py
@@ -0,0 +1,5 @@
+# Retry constants
+RETRY_ATTEMPTS = 30
+RETRY_INITIAL_DELAY = 1
+RETRY_BACKOFF = 3
+RETRY_MAX = 10
diff --git a/tempest/lib/common/dynamic_creds.py b/tempest/lib/common/dynamic_creds.py
index eb18aad..b243cd5 100644
--- a/tempest/lib/common/dynamic_creds.py
+++ b/tempest/lib/common/dynamic_creds.py
@@ -16,7 +16,9 @@
import netaddr
from oslo_log import log as logging
+import tenacity
+import tempest.lib.common.constants as const
from tempest.lib.common import cred_client
from tempest.lib.common import cred_provider
from tempest.lib.common.utils import data_utils
@@ -533,6 +535,11 @@
del self._creds[creds_name]
return self.get_credentials(roles, scope=scope, by_role=True)
+ @tenacity.retry(
+ retry=tenacity.retry_if_exception_type(lib_exc.Conflict),
+ wait=tenacity.wait_incrementing(
+ const.RETRY_INITIAL_DELAY, const.RETRY_BACKOFF, const.RETRY_MAX),
+ stop=tenacity.stop_after_attempt(const.RETRY_ATTEMPTS))
def _clear_isolated_router(self, router_id, router_name):
client = self.routers_admin_client
try:
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index a2f2931..4e1dc59 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -94,6 +94,7 @@
self.build_interval = build_interval
self.build_timeout = build_timeout
self.trace_requests = trace_requests
+ self.ca_certs = ca_certs
self._skip_path = False
self.general_header_lc = set(('cache-control', 'connection',
diff --git a/tempest/lib/common/utils/linux/remote_client.py b/tempest/lib/common/utils/linux/remote_client.py
index 662b452..bdf35e7 100644
--- a/tempest/lib/common/utils/linux/remote_client.py
+++ b/tempest/lib/common/utils/linux/remote_client.py
@@ -31,35 +31,33 @@
return function(self, *args, **kwargs)
except Exception as e:
caller = test_utils.find_test_caller() or "not found"
- if not isinstance(e, tempest.lib.exceptions.SSHTimeout):
- message = ('Executing command on %(ip)s failed. '
- 'Error: %(error)s' % {'ip': self.ip_address,
- 'error': e})
- message = '(%s) %s' % (caller, message)
- LOG.error(message)
- raise
- else:
- try:
- original_exception = sys.exc_info()
- if self.server:
+ message = ('Executing command on %(ip)s failed. '
+ 'Error: %(error)s' % {'ip': self.ip_address,
+ 'error': e})
+ message = '(%s) %s' % (caller, message)
+ LOG.error(message)
+ try:
+ original_exception = sys.exc_info()
+ if self.server:
+ if isinstance(e, tempest.lib.exceptions.SSHTimeout):
msg = 'Caller: %s. Timeout trying to ssh to server %s'
LOG.debug(msg, caller, self.server)
- if self.console_output_enabled and self.servers_client:
- try:
- msg = 'Console log for server %s: %s'
- console_log = (
- self.servers_client.get_console_output(
- self.server['id'])['output'])
- LOG.debug(msg, self.server['id'], console_log)
- except Exception:
- msg = 'Could not get console_log for server %s'
- LOG.debug(msg, self.server['id'])
- # raise the original ssh timeout exception
- raise
- finally:
- # Delete the traceback to avoid circular references
- _, _, trace = original_exception
- del trace
+ if self.console_output_enabled and self.servers_client:
+ try:
+ msg = 'Console log for server %s: %s'
+ console_log = (
+ self.servers_client.get_console_output(
+ self.server['id'])['output'])
+ LOG.debug(msg, self.server['id'], console_log)
+ except Exception:
+ msg = 'Could not get console_log for server %s'
+ LOG.debug(msg, self.server['id'])
+ # raise the original ssh exception
+ raise
+ finally:
+ # Delete the traceback to avoid circular references
+ _, _, trace = original_exception
+ del trace
return wrapper
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 58008aa..274e62d 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -721,6 +721,8 @@
resp, body = self.get("servers/%s/os-instance-actions" %
server_id)
body = json.loads(body)
+ # select proper schema depending on microverion
+ schema = self.get_schema(self.schema_versions_info)
self.validate_response(schema.list_instance_actions, resp, body)
return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/object_storage/object_client.py b/tempest/lib/services/object_storage/object_client.py
index 65e8227..c7ac80f 100644
--- a/tempest/lib/services/object_storage/object_client.py
+++ b/tempest/lib/services/object_storage/object_client.py
@@ -167,11 +167,14 @@
:param parsed_url: parsed url of the remote location
"""
context = None
- # If CONF.identity.disable_ssl_certificate_validation is true,
- # do not check ssl certification.
- if self.dscv:
- context = ssl._create_unverified_context()
if parsed_url.scheme == 'https':
+ # If CONF.identity.disable_ssl_certificate_validation is true,
+ # do not check ssl certification.
+ if self.dscv:
+ context = ssl._create_unverified_context()
+ else:
+ context = ssl.create_default_context(
+ cafile=self.ca_certs)
conn = httplib.HTTPSConnection(parsed_url.netloc,
context=context)
else:
diff --git a/tempest/lib/services/volume/v3/volumes_client.py b/tempest/lib/services/volume/v3/volumes_client.py
index c6f8973..65dc258 100644
--- a/tempest/lib/services/volume/v3/volumes_client.py
+++ b/tempest/lib/services/volume/v3/volumes_client.py
@@ -105,14 +105,17 @@
self.validate_response(schema.show_volume, resp, body)
return rest_client.ResponseBody(resp, body)
- def create_volume(self, **kwargs):
+ def create_volume(self, hints=None, **kwargs):
"""Creates a new Volume.
For a full list of available parameters, please refer to the official
API reference:
https://docs.openstack.org/api-ref/block-storage/v3/index.html#create-a-volume
"""
- post_body = json.dumps({'volume': kwargs})
+ obj = {'volume': kwargs}
+ if hints is not None:
+ obj['OS-SCH-HNT:scheduler_hints'] = hints
+ post_body = json.dumps(obj)
resp, body = self.post('volumes', post_body)
body = json.loads(body)
schema = self.get_schema(self.schema_versions_info)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 5f30909..3105e85 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -777,7 +777,20 @@
linux_client = remote_client.RemoteClient(
ip_address, username, pkey=private_key, password=password,
server=server, servers_client=self.servers_client)
- linux_client.validate_authentication()
+ try:
+ linux_client.validate_authentication()
+ except Exception as e:
+ message = ('Initializing SSH connection to %(ip)s failed. '
+ 'Error: %(error)s' % {'ip': ip_address,
+ 'error': e})
+ caller = test_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ LOG.exception(message)
+ servers = (server,) if server else None
+ self.log_console_output(servers=servers)
+ raise
+
return linux_client
def image_create(self, name='scenario-img', **kwargs):
@@ -1343,6 +1356,16 @@
return self.create_volume(name=name, imageRef=image_id, **kwargs)
+class ScenarioTestWithNetwork(ScenarioTest):
+ """Base class for tests with default network"""
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources(network=True, subnet=True,
+ dhcp=True, router=True)
+ super(ScenarioTestWithNetwork, cls).setup_credentials()
+
+
class NetworkScenarioTest(ScenarioTest):
"""Base class for network scenario tests.
@@ -1697,7 +1720,8 @@
return network, subnet, router
-class EncryptionScenarioTest(ScenarioTest):
+class EncryptionScenarioTest(ScenarioTestWithNetwork):
+
"""Base class for encryption scenario tests"""
@classmethod
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 3830fbc..ad86d0f 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -27,7 +27,7 @@
CONF = config.CONF
-class TestServerBasicOps(manager.ScenarioTest):
+class TestServerBasicOps(manager.ScenarioTestWithNetwork):
"""The test suite for server basic operations
diff --git a/tempest/scenario/test_volume_backup_restore.py b/tempest/scenario/test_volume_backup_restore.py
index 07ca38a..ca563da 100644
--- a/tempest/scenario/test_volume_backup_restore.py
+++ b/tempest/scenario/test_volume_backup_restore.py
@@ -22,7 +22,7 @@
CONF = config.CONF
-class TestVolumeBackupRestore(manager.ScenarioTest):
+class TestVolumeBackupRestore(manager.ScenarioTestWithNetwork):
"""Test cinder backup and restore
This testcase verifies content preservation after backup and restore
diff --git a/tempest/serial_tests/api/admin/test_aggregates.py b/tempest/serial_tests/api/admin/test_aggregates.py
index cedeec0..ce54957 100644
--- a/tempest/serial_tests/api/admin/test_aggregates.py
+++ b/tempest/serial_tests/api/admin/test_aggregates.py
@@ -222,6 +222,9 @@
@decorators.idempotent_id('96be03c7-570d-409c-90f8-e4db3c646996')
def test_aggregate_add_host_create_server_with_az(self):
"""Test adding a host to the given aggregate and creating a server"""
+ if CONF.production:
+ raise self.skipException("Not allowed to run this test "
+ "on production environment")
self.useFixture(fixtures.LockFixture('availability_zone'))
az_name = data_utils.rand_name(
prefix=CONF.resource_name_prefix, name=self.az_name_prefix)
@@ -235,12 +238,20 @@
if agg['availability_zone']:
hosts_in_zone.extend(agg['hosts'])
hosts = [v for v in self.hosts_available if v not in hosts_in_zone]
- if not hosts:
+ hosts_available = []
+ for host in hosts:
+ hypervisor_servers = (
+ self.os_admin.hypervisor_client.list_servers_on_hypervisor(
+ host)["hypervisors"][0].get("servers", None))
+ if not hypervisor_servers:
+ hosts_available.append(host)
+ if not hosts_available:
raise self.skipException("All hosts are already in other "
- "availability zones, so can't add "
+ "availability zones or have running "
+ "instances, so can't add "
"host to aggregate. \nAggregates list: "
"%s" % aggregates)
- host = hosts[0]
+ host = hosts_available[0]
self.client.add_host(aggregate['id'], host=host)
self.addCleanup(self.client.remove_host, aggregate['id'], host=host)
diff --git a/tempest/serial_tests/scenario/test_aggregates_basic_ops.py b/tempest/serial_tests/scenario/test_aggregates_basic_ops.py
index a831fe5..cc45297 100644
--- a/tempest/serial_tests/scenario/test_aggregates_basic_ops.py
+++ b/tempest/serial_tests/scenario/test_aggregates_basic_ops.py
@@ -63,7 +63,11 @@
hosts_available = []
for host in svc_list:
if (host['state'] == 'up' and host['status'] == 'enabled'):
- hosts_available.append(host['host'])
+ hypervisor_servers = (
+ self.os_admin.hypervisor_client.list_servers_on_hypervisor(
+ host["host"])["hypervisors"][0].get("servers", None))
+ if not hypervisor_servers:
+ hosts_available.append(host["host"])
aggregates = self.aggregates_client.list_aggregates()['aggregates']
hosts_in_zone = []
for agg in aggregates:
@@ -72,7 +76,8 @@
hosts = [v for v in hosts_available if v not in hosts_in_zone]
if not hosts:
raise self.skipException("All hosts are already in other "
- "availability zones, so can't add "
+ "availability zones or have running "
+ "instances, so can't add "
"host to aggregate. \nAggregates list: "
"%s" % aggregates)
return hosts[0]
@@ -120,6 +125,9 @@
@decorators.attr(type='slow')
@utils.services('compute')
def test_aggregate_basic_ops(self):
+ if CONF.production:
+ raise self.skipException("Not allowed to run this test "
+ "on production environment")
self.useFixture(fixtures.LockFixture('availability_zone'))
az = 'foo_zone'
aggregate_name = data_utils.rand_name(
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 937f93a..5801f04 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -180,9 +180,7 @@
def test_validate_debug_ssh_console(self):
self.assertRaises(lib_exc.SSHTimeout,
self.conn.validate_authentication)
- msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
- 'TestRemoteClientWithServer:test_validate_debug_ssh_console',
- self.server)
+ msg = 'Executing command on 127.0.0.1 failed.'
self.assertIn(msg, self.log.output)
self.assertIn('Console output for', self.log.output)
@@ -190,9 +188,7 @@
self.assertRaises(lib_exc.SSHTimeout,
self.conn.exec_command, 'fake command')
self.assertIn('fake command', self.log.output)
- msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
- 'TestRemoteClientWithServer:test_exec_command_debug_ssh_console',
- self.server)
+ msg = 'Executing command on 127.0.0.1 failed.'
self.assertIn(msg, self.log.output)
self.assertIn('Console output for', self.log.output)
@@ -204,9 +200,7 @@
def test_validate_debug_ssh_console(self):
self.assertRaises(lib_exc.SSHTimeout,
self.conn.validate_authentication)
- msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
- 'TestRemoteClientWithBrokenServer:test_validate_debug_ssh_console',
- self.server)
+ msg = 'Executing command on 127.0.0.1 failed.'
self.assertIn(msg, self.log.output)
msg = 'Could not get console_log for server %s' % self.server['id']
self.assertIn(msg, self.log.output)
@@ -215,10 +209,7 @@
self.assertRaises(lib_exc.SSHTimeout,
self.conn.exec_command, 'fake command')
self.assertIn('fake command', self.log.output)
- caller = ":".join(['TestRemoteClientWithBrokenServer',
- 'test_exec_command_debug_ssh_console'])
- msg = 'Caller: %s. Timeout trying to ssh to server %s' % (
- caller, self.server)
+ msg = 'Executing command on 127.0.0.1 failed.'
self.assertIn(msg, self.log.output)
msg = 'Could not get console_log for server %s' % self.server['id']
self.assertIn(msg, self.log.output)
diff --git a/tempest/tests/lib/common/utils/linux/test_remote_client.py b/tempest/tests/lib/common/utils/linux/test_remote_client.py
index df23e63..c41f178 100644
--- a/tempest/tests/lib/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/lib/common/utils/linux/test_remote_client.py
@@ -15,6 +15,8 @@
from unittest import mock
+import fixtures
+
from tempest.lib.common import ssh
from tempest.lib.common.utils.linux import remote_client
from tempest.lib import exceptions as lib_exc
@@ -29,6 +31,12 @@
class TestRemoteClient(base.TestCase):
+ def setUp(self):
+ super(TestRemoteClient, self).setUp()
+ self.log = self.useFixture(fixtures.FakeLogger(
+ name='tempest.lib.common.utils.linux.remote_client',
+ level='DEBUG'))
+
@mock.patch.object(ssh.Client, 'exec_command', return_value='success')
def test_exec_command(self, mock_ssh_exec_command):
client = remote_client.RemoteClient('192.168.1.10', 'username')
@@ -50,9 +58,8 @@
client = remote_client.RemoteClient('192.168.1.10', 'username',
server=server)
self.assertRaises(lib_exc.SSHTimeout, client.exec_command, 'ls')
- mock_debug.assert_called_with(
- 'Caller: %s. Timeout trying to ssh to server %s',
- 'TestRemoteClient:test_debug_ssh_without_console', server)
+ msg = 'Executing command on 192.168.1.10 failed.'
+ self.assertIn(msg, self.log.output)
@mock.patch.object(remote_client.LOG, 'debug')
@mock.patch.object(ssh.Client, 'exec_command')