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 <sean@dague.net>

Change-Id: Iae2962bb86100f03fd3ad9aac3767da876291e74
Closes-Bug: #1718295
This commit is contained in:
Matt Riedemann 2017-09-20 10:44:11 -04:00 committed by Sean Dague
parent b926a1d4df
commit 807579755c
5 changed files with 65 additions and 7 deletions

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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

View File

@ -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):