libvirt: Bump MIN_{LIBVIRT,QEMU}_VERSION and NEXT_MIN_{LIBVIRT,QEMU}_VERSION

I8e349849db0b1a540d295c903f1470917b82fd97 bumped these versions late in
the Victoria cycle and it's time to do the same again during Wallaby.

The new MIN_{LIBVIRT,QEMU}_VERSIONs are:

MIN_LIBVIRT_VERSION = (6, 0, 0)
MIN_QEMU_VERSION = (4, 2, 0)

These versions are met by the three defined LTS distros supported by the
Wallaby release [1][2] of Ubuntu 20.04, CentOS 8 and openSUSE Leap 15.2.

The following constants are removed as part of this patch as since
I864494e11ff697788167c996a39f38d3d833d0d7 we now use these new minimum
versions in tests straight away, thus breaking many tests exercising
these now obsolete constants.

- MIN_LIBVIRT_VTPM
- MIN_LIBVIRT_S390X_CPU_COMPARE
- MIN_{LIBVIRT,QEMU}_BLOCKDEV

The removal of MIN_{LIBVIRT,QEMU}_BLOCKDEV means that the swap_volume
will always use the blockCopy libvirt method to copy contents between
disks. This in turn requires that fakelibvirt correctly model the
required flags and blockCopy method.

A future change will look into switching the remaining blockRebase calls
in the live snapshot flow to blockCopy.

Finally, NEXT_MIN_{LIBVIRT,QEMU}_VERSION are also updated to:

NEXT_MIN_LIBVIRT_VERSION = (7, 0, 0)
NEXT_MIN_QEMU_VERSION = (5, 2, 0)

[1] https://governance.openstack.org/tc/reference/runtimes/wallaby.html
[2] https://wiki.openstack.org/wiki/LibvirtDistroSupportMatrix

Change-Id: I017083b27cd9d145eecb01106388d4ce880ba823
This commit is contained in:
Lee Yarwood 2020-09-28 13:16:21 +01:00
parent 3a390c2c82
commit 95724bbaef
5 changed files with 34 additions and 290 deletions

View File

@ -19,7 +19,6 @@ from castellan.common.objects import passphrase
from castellan.key_manager import key_manager
from oslo_log import log as logging
from oslo_utils import uuidutils
from oslo_utils import versionutils
import nova.conf
from nova import context as nova_context
@ -28,7 +27,6 @@ from nova import exception
from nova import objects
from nova.tests.functional.api import client
from nova.tests.functional.libvirt import base
from nova.virt.libvirt import driver
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
@ -137,10 +135,7 @@ class VTPMServersTest(base.ServersTestBase):
# TODO(stephenfin): This should be moved to the base class
def start_compute(self, hostname='compute1'):
libvirt_version = versionutils.convert_version_to_int(
driver.MIN_LIBVIRT_VTPM)
fake_connection = self._get_connection(
libvirt_version=libvirt_version, hostname=hostname)
fake_connection = self._get_connection(hostname=hostname)
# This is fun. Firstly we need to do a global'ish mock so we can
# actually start the service.

View File

@ -61,6 +61,10 @@ VIR_DOMAIN_XML_INACTIVE = 2
VIR_DOMAIN_XML_UPDATE_CPU = 4
VIR_DOMAIN_XML_MIGRATABLE = 8
VIR_DOMAIN_BLOCK_COPY_SHALLOW = 1
VIR_DOMAIN_BLOCK_COPY_REUSE_EXT = 2
VIR_DOMAIN_BLOCK_COPY_TRANSIENT_JOB = 4
VIR_DOMAIN_BLOCK_REBASE_SHALLOW = 1
VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT = 2
VIR_DOMAIN_BLOCK_REBASE_COPY = 8
@ -1347,6 +1351,9 @@ class Domain(object):
error_domain=VIR_FROM_QEMU)
return 0
def blockCopy(self, disk, base, flags=0):
return 0
def blockCommit(self, disk, base, top, flags):
return 0

View File

@ -1625,18 +1625,16 @@ class LibvirtConnTestCase(test.NoDBTestCase,
)
mock_getgrnam.assert_called_with('admins')
@mock.patch.object(host.Host, 'has_min_version')
@mock.patch('shutil.which')
@mock.patch('pwd.getpwnam')
@mock.patch('grp.getgrnam')
def test__check_vtpm_support(
self, mock_getgrnam, mock_getpwnam, mock_which, mock_version,
self, mock_getgrnam, mock_getpwnam, mock_which
):
"""Test checking for vTPM support when everything is configured
correctly.
"""
self.flags(swtpm_enabled=True, virt_type='kvm', group='libvirt')
mock_version.return_value = True
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
drvr.init_host('dummyhost')
@ -1644,7 +1642,6 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_which.assert_has_calls(
[mock.call('swtpm_setup'), mock.call().__bool__()],
)
mock_version.assert_called_with(lv_ver=(5, 6, 0))
@mock.patch.object(libvirt_driver.LOG, 'warning')
def test_check_cpu_set_configuration__no_configuration(self, mock_log):
@ -10811,26 +10808,6 @@ class LibvirtConnTestCase(test.NoDBTestCase,
ret = conn._compare_cpu(None, None, instance)
self.assertIsNone(ret)
def test_compare_cpu_virt_platform_s390x(self):
_fake_s390xcpu_info = {
"arch": "s390x",
"model": "test_model",
"vendor": "test_vendor",
"topology": {
"sockets": 1,
"cores": 8,
"threads": 16
},
"features": ["feature1", "feature2"]
}
instance = objects.Instance(**self.test_instance)
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
ret = conn._compare_cpu(None,
jsonutils.dumps(_fake_s390xcpu_info),
instance)
self.assertIsNone(ret)
@mock.patch.object(host.Host, 'compare_cpu')
@mock.patch.object(nova.virt.libvirt, 'config')
def test_compare_cpu_invalid_cpuinfo_raises(self, mock_vconfig,
@ -19432,11 +19409,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertRaises(NotImplementedError, drvr.swap_volume, self.context,
{}, {}, None, None, None)
@mock.patch.object(fakelibvirt.Connection, 'getVersion')
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
@mock.patch('nova.virt.libvirt.host.Host.write_instance_config')
def test_swap_volume_copy(self, mock_write_instance_config,
mock_libvirt_ver, mock_qemu_ver):
def test_swap_volume_copy(self, mock_write_instance_config):
"""Assert the happy path of calling virDomainBlockCopy to swap"""
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
@ -19448,10 +19422,6 @@ class LibvirtConnTestCase(test.NoDBTestCase,
target_dev=mock.sentinel.target_dev,
source_path=None)
mock_libvirt_ver.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_BLOCKDEV)
mock_qemu_ver.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_QEMU_BLOCKDEV)
mock_dev.is_job_complete.return_value = True
mock_guest.get_block_device.return_value = mock_dev
mock_guest.get_xml_desc.side_effect = [
@ -19480,11 +19450,8 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_write_instance_config.assert_called_once_with(
mock.sentinel.new_xml_desc)
@mock.patch.object(fakelibvirt.Connection, 'getVersion')
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
@mock.patch('nova.virt.libvirt.host.Host.write_instance_config')
def test_swap_volume_copy_failure(self, mock_write_instance_config,
mock_libvirt_ver, mock_qemu_ver):
def test_swap_volume_copy_failure(self, mock_write_instance_config):
"""Assert that exception.VolumeRebaseFailed is raised on failure"""
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
@ -19496,10 +19463,6 @@ class LibvirtConnTestCase(test.NoDBTestCase,
target_dev=mock.sentinel.target_dev,
source_path=None)
mock_libvirt_ver.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_BLOCKDEV)
mock_qemu_ver.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_QEMU_BLOCKDEV)
mock_dev.copy.side_effect = test.TestingException()
mock_guest.get_block_device.return_value = mock_dev
mock_guest.get_xml_desc.side_effect = [
@ -19520,105 +19483,6 @@ class LibvirtConnTestCase(test.NoDBTestCase,
mock_write_instance_config.assert_called_once_with(
mock.sentinel.original_xml_desc)
@mock.patch('nova.virt.libvirt.guest.BlockDevice.is_job_complete',
return_value=True)
def _test_swap_volume_rebase(self, mock_is_job_complete, source_type,
resize=False, fail=False):
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
hw_firmware_type = image_meta.properties.get(
'hw_firmware_type')
mock_dom = mock.MagicMock()
guest = libvirt_guest.Guest(mock_dom)
with mock.patch.object(drvr._conn, 'defineXML',
create=True) as mock_define:
srcfile = "/first/path"
dstfile = "/second/path"
orig_xml = str(mock.sentinel.orig_xml)
new_xml = str(mock.sentinel.new_xml)
mock_dom.XMLDesc.return_value = orig_xml
mock_dom.isPersistent.return_value = True
def fake_rebase_success(*args, **kwargs):
# Make sure the XML is set after the rebase so we know
# get_xml_desc was called after the update.
mock_dom.XMLDesc.return_value = new_xml
if not fail:
mock_dom.blockRebase.side_effect = fake_rebase_success
# If the swap succeeds, make sure we use the new XML to
# redefine the domain.
expected_xml = new_xml
else:
if resize:
mock_dom.blockResize.side_effect = test.TestingException()
expected_exception = test.TestingException
else:
mock_dom.blockRebase.side_effect = test.TestingException()
expected_exception = exception.VolumeRebaseFailed
# If the swap fails, make sure we use the original domain XML
# to redefine the domain.
expected_xml = orig_xml
# Run the swap volume code.
mock_conf = mock.MagicMock(source_type=source_type,
source_path=dstfile)
if not fail:
drvr._swap_volume(guest, srcfile, mock_conf, 1,
hw_firmware_type)
else:
self.assertRaises(expected_exception, drvr._swap_volume, guest,
srcfile, mock_conf, 1, hw_firmware_type)
# Verify we read the original persistent config.
expected_call_count = 1
expected_calls = [mock.call(
flags=(fakelibvirt.VIR_DOMAIN_XML_INACTIVE |
fakelibvirt.VIR_DOMAIN_XML_SECURE))]
if not fail:
# Verify we read the updated live config.
expected_call_count = 2
expected_calls.append(
mock.call(flags=fakelibvirt.VIR_DOMAIN_XML_SECURE))
self.assertEqual(expected_call_count, mock_dom.XMLDesc.call_count)
mock_dom.XMLDesc.assert_has_calls(expected_calls)
# Verify we called with the correct flags.
expected_flags = (fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_COPY |
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT)
if source_type == 'block':
expected_flags = (expected_flags |
fakelibvirt.VIR_DOMAIN_BLOCK_REBASE_COPY_DEV)
mock_dom.blockRebase.assert_called_once_with(srcfile, dstfile, 0,
flags=expected_flags)
# Verify we defined the expected XML.
mock_define.assert_called_once_with(expected_xml)
# Verify we called resize with the correct args.
if resize:
mock_dom.blockResize.assert_called_once_with(
srcfile, 1 * units.Gi, flags=1)
def test_swap_volume_rebase_file(self):
self._test_swap_volume_rebase('file')
def test_swap_volume_rebase_block(self):
"""If the swapped volume is type="block", make sure that we give
libvirt the correct VIR_DOMAIN_BLOCK_REBASE_COPY_DEV flag to ensure the
correct type="block" XML is generated (bug 1691195)
"""
self._test_swap_volume_rebase('block')
def test_swap_volume_rebase_fail(self):
self._test_swap_volume_rebase('block', fail=True)
def test_swap_volume_rebase_resize_fail(self):
self._test_swap_volume_rebase('file', resize=True, fail=True)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._swap_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_volume_config')
@ -19669,99 +19533,17 @@ class LibvirtConnTestCase(test.NoDBTestCase,
old_connection_info,
instance)
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._swap_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_volume_config')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._connect_volume')
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_swap_volume_without_device_path_blocked(self, get_guest,
connect_volume, get_volume_config, swap_volume, disconnect_volume):
"""Assert that NotImplementedError is raised when swap_volume is called
without a source_path prior to MIN_LIBVIRT_BLOCKDEV.
"""
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
instance = objects.Instance(**self.test_instance)
old_connection_info = {'driver_volume_type': 'rbd',
'serial': 'old-volume-id',
'data': {'access_mode': 'rw'}}
new_connection_info = {'driver_volume_type': 'rbd',
'serial': 'new-volume-id',
'data': {'access_mode': 'rw'}}
mock_guest = mock.MagicMock()
mock_guest.get_disk.return_value = True
get_guest.return_value = mock_guest
get_volume_config.return_value = mock.MagicMock(source_path=None)
self.assertRaises(NotImplementedError, conn.swap_volume, self.context,
old_connection_info, new_connection_info, instance,
'/dev/vdb', 1)
@mock.patch.object(fakelibvirt.Connection, 'getVersion')
@mock.patch.object(fakelibvirt.Connection, 'getLibVersion')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._swap_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_volume_config')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._connect_volume')
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
def test_swap_volume_blockdev_without_device_path(self, get_guest,
connect_volume, get_volume_config, swap_volume, disconnect_volume,
lib_version, qemu_version):
"""Assert that swap_volume correctly calls down to _swap_volume when
source_path isn't provided after MIN_LIBVIRT_BLOCKDEV.
"""
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI())
lib_version.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_LIBVIRT_BLOCKDEV)
qemu_version.return_value = versionutils.convert_version_to_int(
libvirt_driver.MIN_QEMU_BLOCKDEV)
instance = objects.Instance(**self.test_instance)
old_connection_info = {'driver_volume_type': 'rbd',
'serial': 'old-volume-id',
'data': {'access_mode': 'rw'}}
new_connection_info = {'driver_volume_type': 'rbd',
'serial': 'new-volume-id',
'data': {'access_mode': 'rw'}}
mock_dom = mock.MagicMock()
guest = libvirt_guest.Guest(mock_dom)
mock_dom.XMLDesc.return_value = """<domain>
<devices>
<disk type='file'>
<source file='/fake-old-volume'/>
<target dev='vdb' bus='virtio'/>
</disk>
</devices>
</domain>
"""
mock_dom.name.return_value = 'inst'
mock_dom.UUIDString.return_value = 'uuid'
get_guest.return_value = guest
conf = mock.MagicMock(source_path='/fake-new-volume')
get_volume_config.return_value = conf
conn.swap_volume(self.context, old_connection_info,
new_connection_info, instance, '/dev/vdb', 1)
get_guest.assert_called_once_with(instance)
connect_volume.assert_called_once_with(self.context,
new_connection_info, instance)
swap_volume.assert_called_once_with(guest, 'vdb', conf, 1, None)
disconnect_volume.assert_called_once_with(self.context,
old_connection_info,
instance)
@mock.patch.object(libvirt_driver.LibvirtDriver, '_get_volume_encryption')
@mock.patch('nova.virt.libvirt.guest.BlockDevice.rebase')
@mock.patch('nova.virt.libvirt.guest.BlockDevice.copy')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._disconnect_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._connect_volume')
@mock.patch('nova.virt.libvirt.driver.LibvirtDriver._get_volume_config')
@mock.patch('nova.virt.libvirt.guest.Guest.get_disk')
@mock.patch('nova.virt.libvirt.host.Host.get_guest')
@mock.patch('nova.virt.libvirt.host.Host.write_instance_config')
def test_swap_volume_disconnect_new_volume_on_rebase_error(self,
def test_swap_volume_disconnect_new_volume_on_copy_error(self,
write_config, get_guest, get_disk, get_volume_config,
connect_volume, disconnect_volume, rebase, get_volume_encryption):
connect_volume, disconnect_volume, copy, get_volume_encryption):
"""Assert that disconnect_volume is called for the new volume if an
error is encountered while rebasing
"""
@ -19772,7 +19554,7 @@ class LibvirtConnTestCase(test.NoDBTestCase,
get_volume_encryption.return_value = {}
exc = fakelibvirt.make_libvirtError(fakelibvirt.libvirtError,
'internal error', error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)
rebase.side_effect = exc
copy.side_effect = exc
self.assertRaises(exception.VolumeRebaseFailed, conn.swap_volume,
self.context, mock.sentinel.old_connection_info,

View File

@ -101,7 +101,6 @@ import nova.privsep.utils
from nova.storage import rbd_utils
from nova import utils
from nova import version
from nova.virt import arch
from nova.virt import block_device as driver_block_device
from nova.virt import configdrive
from nova.virt.disk import api as disk_api
@ -219,15 +218,15 @@ patch_tpool_proxy()
# versions. Over time, this will become a common min version
# for all architectures/hypervisors, as this value rises to
# meet them.
MIN_LIBVIRT_VERSION = (5, 0, 0)
MIN_QEMU_VERSION = (4, 0, 0)
MIN_LIBVIRT_VERSION = (6, 0, 0)
MIN_QEMU_VERSION = (4, 2, 0)
# TODO(berrange): Re-evaluate this at start of each release cycle
# to decide if we want to plan a future min version bump.
# MIN_LIBVIRT_VERSION can be updated to match this after
# NEXT_MIN_LIBVIRT_VERSION has been at a higher value for
# one cycle
NEXT_MIN_LIBVIRT_VERSION = (6, 0, 0)
NEXT_MIN_QEMU_VERSION = (4, 2, 0)
NEXT_MIN_LIBVIRT_VERSION = (7, 0, 0)
NEXT_MIN_QEMU_VERSION = (5, 2, 0)
# Virtuozzo driver support
MIN_VIRTUOZZO_VERSION = (7, 0, 0)
@ -246,15 +245,6 @@ VGPU_RESOURCE_SEMAPHORE = 'vgpu_resources'
LIBVIRT_PERF_EVENT_PREFIX = 'VIR_PERF_PARAM_'
# -blockdev support (replacing -drive)
MIN_LIBVIRT_BLOCKDEV = (6, 0, 0)
MIN_QEMU_BLOCKDEV = (4, 2, 0)
# Virtual TPM (vTPM) support
MIN_LIBVIRT_VTPM = (5, 6, 0)
MIN_LIBVIRT_S390X_CPU_COMPARE = (5, 9, 0)
class LibvirtDriver(driver.ComputeDriver):
def __init__(self, virtapi, read_only=False):
@ -742,14 +732,6 @@ class LibvirtDriver(driver.ComputeDriver):
"'kvm'; found '%s'.")
raise exception.InvalidConfiguration(msg % CONF.libvirt.virt_type)
if not self._host.has_min_version(lv_ver=MIN_LIBVIRT_VTPM):
msg = _(
'vTPM support requires Libvirt version %(libvirt)s or '
'greater.')
raise exception.InvalidConfiguration(msg % {
'libvirt': libvirt_utils.version_to_string(MIN_LIBVIRT_VTPM),
})
# These executables need to be installed for libvirt to make use of
# emulated TPM.
# NOTE(stephenfin): This checks using the PATH of the user running
@ -1888,23 +1870,8 @@ class LibvirtDriver(driver.ComputeDriver):
guest.delete_configuration(support_uefi)
try:
# NOTE(lyarwood): Use virDomainBlockCopy from libvirt >= 6.0.0
# and QEMU >= 4.2.0 with -blockdev domains allowing QEMU to
# copy to remote disks.
if self._host.has_min_version(lv_ver=MIN_LIBVIRT_BLOCKDEV,
hv_ver=MIN_QEMU_BLOCKDEV):
dev.copy(conf.to_xml(), reuse_ext=True)
else:
# TODO(lyarwood): Remove the following use of
# virDomainBlockRebase once MIN_LIBVIRT_VERSION hits >=
# 6.0.0 and MIN_QEMU_VERSION hits >= 4.2.0.
# Start copy with VIR_DOMAIN_BLOCK_REBASE_REUSE_EXT flag to
# allow writing to existing external volume file. Use
# VIR_DOMAIN_BLOCK_REBASE_COPY_DEV if it's a block device
# to make sure XML is generated correctly (bug 1691195)
copy_dev = conf.source_type == 'block'
dev.rebase(conf.source_path, copy=True, reuse_ext=True,
copy_dev=copy_dev)
dev.copy(conf.to_xml(), reuse_ext=True)
while not dev.is_job_complete():
time.sleep(0.5)
@ -1964,14 +1931,6 @@ class LibvirtDriver(driver.ComputeDriver):
# eventually do this for us.
self._connect_volume(context, new_connection_info, instance)
conf = self._get_volume_config(new_connection_info, disk_info)
if (not conf.source_path and not
self._host.has_min_version(lv_ver=MIN_LIBVIRT_BLOCKDEV,
hv_ver=MIN_QEMU_BLOCKDEV)):
self._disconnect_volume(context, new_connection_info, instance)
raise NotImplementedError(_("Swap only supports host devices and "
"files with Libvirt < 6.0.0 or QEMU "
"< 4.2.0"))
hw_firmware_type = instance.image_meta.properties.get(
'hw_firmware_type')
@ -8798,18 +8757,6 @@ class LibvirtDriver(driver.ComputeDriver):
else:
cpu = self._vcpu_model_to_cpu_config(guest_cpu)
# s390x doesn't support cpu model in host info, so compare
# cpu info will raise an error anyway, thus have to avoid check
# see bug 1854126 for more info
if (
cpu.arch in (arch.S390X, arch.S390) and
not self._host.has_min_version(MIN_LIBVIRT_S390X_CPU_COMPARE)
):
LOG.debug("on s390x platform, the min libvirt version "
"support cpu model compare is %s",
MIN_LIBVIRT_S390X_CPU_COMPARE)
return
u = ("http://libvirt.org/html/libvirt-libvirt-host.html#"
"virCPUCompareResult")
m = _("CPU doesn't have compatibility.\n\n%(ret)s\n\nRefer to %(u)s")

View File

@ -0,0 +1,13 @@
---
upgrade:
- |
The minimum required version of libvirt used by the `nova-compute` service
is now 6.0.0. The next minimum required version to be used in a future
release is 7.0.0.
The minimum required version of QEMU used by the `nova-compute` service is
now 4.2.0. The next minimum required version to be used in a future release
is 5.2.0.
Failing to meet these minimum versions when using the libvirt compute
driver will result in the `nova-compute` service not starting.