diff --git a/nova/tests/functional/libvirt/test_vtpm.py b/nova/tests/functional/libvirt/test_vtpm.py index add88a89a20b..c497e8987e07 100644 --- a/nova/tests/functional/libvirt/test_vtpm.py +++ b/nova/tests/functional/libvirt/test_vtpm.py @@ -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. diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index 892abc2c928e..f2c853571763 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -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 diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 2da918bf0c41..29cb5578dda5 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -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 = """ - - - - - - - - """ - 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, diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 5b026db2d4ed..52ae157cc1b3 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -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") diff --git a/releasenotes/notes/wallaby-libvirt-version-bump-6fd35d03d7f3df28.yaml b/releasenotes/notes/wallaby-libvirt-version-bump-6fd35d03d7f3df28.yaml new file mode 100644 index 000000000000..7c75c2e2133c --- /dev/null +++ b/releasenotes/notes/wallaby-libvirt-version-bump-6fd35d03d7f3df28.yaml @@ -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.