From 807579755c4a116309eca5b2bcdbab9d1f393bab Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Wed, 20 Sep 2017 10:44:11 -0400 Subject: [PATCH] Support qemu >= 2.10 Qemu 2.10 added the requirement of a --force-share flag to qemu-img info when reading information about a disk that is in use by a guest. We do this a lot in Nova for operations like gathering information before live migration. Up until this point all qemu/libvirt version matching has been solely inside the libvirt driver, however all the image manip code was moved out to nova.virt.images. We need the version of QEMU available there. This does it by initializing that version on driver init host. The net effect is also that broken libvirt connections are figured out earlier, as there is an active probe for this value. Co-Authored-By: Sean Dague Change-Id: Iae2962bb86100f03fd3ad9aac3767da876291e74 Closes-Bug: #1718295 --- nova/test.py | 3 +++ nova/tests/unit/virt/libvirt/test_driver.py | 20 +++++++++++++++-- nova/tests/unit/virt/libvirt/test_utils.py | 25 +++++++++++++++++++++ nova/virt/images.py | 10 +++++++++ nova/virt/libvirt/driver.py | 14 +++++++----- 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/nova/test.py b/nova/test.py index f0e6953b965c..9b9ea9507e83 100644 --- a/nova/test.py +++ b/nova/test.py @@ -61,6 +61,7 @@ from nova.tests.unit import conf_fixture from nova.tests.unit import policy_fixture from nova.tests import uuidsentinel as uuids from nova import utils +from nova.virt import images CONF = cfg.CONF @@ -303,6 +304,8 @@ class TestCase(testtools.TestCase): # Reset the traits sync flag objects.resource_provider._TRAITS_SYNCED = False + # Reset the global QEMU version flag. + images.QEMU_VERSION = None mox_fixture = self.useFixture(moxstubout.MoxStubout()) self.mox = mox_fixture.mox diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index fe54dc75e7f9..4e9f2bd250e0 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -986,6 +986,23 @@ class LibvirtConnTestCase(test.NoDBTestCase, break self.assertFalse(version_arg_found) + # NOTE(sdague): python2.7 and python3.5 have different behaviors + # when it comes to comparing against the sentinel, so + # has_min_version is needed to pass python3.5. + @mock.patch.object(nova.virt.libvirt.host.Host, "has_min_version", + return_value=True) + @mock.patch.object(fakelibvirt.Connection, 'getVersion', + return_value=mock.sentinel.qemu_version) + def test_qemu_image_version(self, mock_get_libversion, min_ver): + """Test that init_host sets qemu image version + + A sentinel is used here so that we aren't chasing this value + against minimums that get raised over time. + """ + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + drvr.init_host("dummyhost") + self.assertEqual(images.QEMU_VERSION, mock.sentinel.qemu_version) + @mock.patch.object(fakelibvirt.Connection, 'getLibVersion', return_value=versionutils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_OTHER_ARCH.get( @@ -11605,9 +11622,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, return_value=service_mock), mock.patch.object(host.Host, "get_capabilities")): - drvr.init_host("wibble") self.assertRaises(exception.HypervisorUnavailable, - drvr.get_num_instances) + drvr.init_host, ("wibble",)) self.assertTrue(service_mock.disabled) def test_service_resume_after_broken_connection(self): diff --git a/nova/tests/unit/virt/libvirt/test_utils.py b/nova/tests/unit/virt/libvirt/test_utils.py index 646a72c85991..49945a3cd3bf 100644 --- a/nova/tests/unit/virt/libvirt/test_utils.py +++ b/nova/tests/unit/virt/libvirt/test_utils.py @@ -171,6 +171,31 @@ blah BLAH: bb self.assertEqual(98304, image_info.disk_size) self.assertEqual(65536, image_info.cluster_size) + @mock.patch('os.path.exists', return_value=True) + @mock.patch('nova.utils.execute') + def test_qemu_info_canon_qemu_2_10(self, mock_execute, mock_exists): + images.QEMU_VERSION = images.QEMU_VERSION_REQ_SHARED + path = "disk.config" + example_output = """image: disk.config +file format: raw +virtual size: 64M (67108864 bytes) +cluster_size: 65536 +disk size: 96K +blah BLAH: bb +""" + mock_execute.return_value = (example_output, '') + image_info = images.qemu_img_info(path) + mock_execute.assert_called_once_with('env', 'LC_ALL=C', 'LANG=C', + 'qemu-img', 'info', path, + '--force-share', + prlimit=images.QEMU_IMG_LIMITS) + mock_exists.assert_called_once_with(path) + self.assertEqual('disk.config', image_info.image) + self.assertEqual('raw', image_info.file_format) + self.assertEqual(67108864, image_info.virtual_size) + self.assertEqual(98304, image_info.disk_size) + self.assertEqual(65536, image_info.cluster_size) + @mock.patch('os.path.exists', return_value=True) @mock.patch('nova.utils.execute') def test_qemu_info_canon2(self, mock_execute, mock_exists): diff --git a/nova/virt/images.py b/nova/virt/images.py index dae6bc7ef52d..be2a9d9e0621 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -19,6 +19,7 @@ Handling of VM disk images. """ +import operator import os from oslo_concurrency import processutils @@ -42,6 +43,11 @@ QEMU_IMG_LIMITS = processutils.ProcessLimits( cpu_time=30, address_space=1 * units.Gi) +# This is set by the libvirt driver on startup. The version is used to +# determine what flags need to be set on the command line. +QEMU_VERSION = None +QEMU_VERSION_REQ_SHARED = 2010000 + def qemu_img_info(path, format=None): """Return an object containing the parsed output from qemu-img info.""" @@ -60,6 +66,10 @@ def qemu_img_info(path, format=None): cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) if format is not None: cmd = cmd + ('-f', format) + # Check to see if the qemu version is >= 2.10 because if so, we need + # to add the --force-share flag. + if QEMU_VERSION and operator.ge(QEMU_VERSION, QEMU_VERSION_REQ_SHARED): + cmd = cmd + ('--force-share',) out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) except processutils.ProcessExecutionError as exp: # this means we hit prlimits, make the exception more specific diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 82dc2b99f6ad..8d4eb90ecf56 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -481,11 +481,15 @@ class LibvirtDriver(driver.ComputeDriver): _('Nova requires libvirt version %s or greater.') % self._version_to_string(MIN_LIBVIRT_VERSION)) - if (CONF.libvirt.virt_type in ("qemu", "kvm") and - not self._host.has_min_version(hv_ver=MIN_QEMU_VERSION)): - raise exception.InternalError( - _('Nova requires QEMU version %s or greater.') % - self._version_to_string(MIN_QEMU_VERSION)) + if CONF.libvirt.virt_type in ("qemu", "kvm"): + if self._host.has_min_version(hv_ver=MIN_QEMU_VERSION): + # "qemu-img info" calls are version dependent, so we need to + # store the version in the images module. + images.QEMU_VERSION = self._host.get_connection().getVersion() + else: + raise exception.InternalError( + _('Nova requires QEMU version %s or greater.') % + self._version_to_string(MIN_QEMU_VERSION)) if CONF.libvirt.virt_type == 'parallels': if not self._host.has_min_version(hv_ver=MIN_VIRTUOZZO_VERSION):