libvirt: Use the mirror element to detect job completion

The mirror element was introduced in Libvirt 0.10.0 [1] and provides a
ready attribute that helps to determine when a blockjob is complete.
This is now used in addition to checking the progress of a given
blockjob to ensure Nova waits until these jobs complete fully.

[1] https://libvirt.org/git/?p=libvirt.git;a=commit;h=ae6aa8c3965e9aaa245b8e669c6324d44312ac1b

Closes-bug: #1643017
Change-Id: I0c52917a5555a70c4973f37dea1aebf878dd73b4
This commit is contained in:
Lee Yarwood 2016-11-21 11:30:01 +00:00 committed by Matthew Booth
parent 0195759207
commit 9ed7826419
4 changed files with 75 additions and 9 deletions

View File

@ -772,6 +772,24 @@ class LibvirtConfigGuestDiskTest(LibvirtConfigBaseTest):
<boot order="1"/>
</disk>""", xml)
def test_config_mirror_parse(self):
xml = """
<disk type="file" device="disk">
<driver name="qemu" type="qcow2" cache="none" discard="unmap"/>
<source file="/tmp/hello.qcow2"/>
<target bus="ide" dev="/dev/hda"/>
<serial>7a97c4a3-6f59-41d4-bf47-191d7f97f8e9</serial>
<mirror type='file' file='/tmp/new.img' format='raw' job='copy' ready='yes'>
<format type='raw'/>
<source file='/tmp/new.img'/>
</mirror>
<boot order="1"/>
</disk>"""
xmldoc = etree.fromstring(xml)
obj = config.LibvirtConfigGuestDisk()
obj.parse_dom(xmldoc)
self.assertEqual(obj.mirror.ready, "yes")
def test_config_boot_order_parse(self):
xml = """
<disk type="file" device="disk">

View File

@ -682,14 +682,32 @@ class GuestBlockTestCase(test.NoDBTestCase):
is_complete = self.gblock.is_job_complete()
self.assertFalse(is_complete)
def test_is_job_complete_finished(self):
self.domain.blockJobInfo.return_value = {
"type": 4,
"bandwidth": 18,
"cur": 100,
"end": 100}
is_complete = self.gblock.is_job_complete()
self.assertTrue(is_complete)
def test_is_job_complete_not_ready(self):
gblock = self.guest.get_block_device('vda')
disk = vconfig.LibvirtConfigGuestDisk()
disk.mirror = vconfig.LibvirtConfigGuestDiskMirror()
with mock.patch.object(self.guest, 'get_disk', return_value=disk):
self.domain.blockJobInfo.return_value = {
"type": 4,
"bandwidth": 18,
"cur": 100,
"end": 100}
is_complete = gblock.is_job_complete()
self.assertFalse(is_complete)
def test_is_job_complete_ready(self):
gblock = self.guest.get_block_device('vda')
disk = vconfig.LibvirtConfigGuestDisk()
disk.mirror = vconfig.LibvirtConfigGuestDiskMirror()
disk.mirror.ready = 'yes'
with mock.patch.object(self.guest, 'get_disk', return_value=disk):
self.domain.blockJobInfo.return_value = {
"type": 4,
"bandwidth": 18,
"cur": 100,
"end": 100}
is_complete = gblock.is_job_complete()
self.assertTrue(is_complete)
def test_is_job_complete_no_job(self):
self.domain.blockJobInfo.return_value = {}

View File

@ -716,6 +716,7 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
self.backing_store = None
self.device_addr = None
self.boot_order = None
self.mirror = None
def format_dom(self):
dev = super(LibvirtConfigGuestDisk, self).format_dom()
@ -875,6 +876,10 @@ class LibvirtConfigGuestDisk(LibvirtConfigGuestDevice):
self.device_addr = obj
elif c.tag == 'boot':
self.boot_order = c.get('order')
elif c.tag == 'mirror':
m = LibvirtConfigGuestDiskMirror()
m.parse_dom(c)
self.mirror = m
class LibvirtConfigGuestDiskBackingStore(LibvirtConfigObject):
@ -1080,6 +1085,16 @@ class LibvirtConfigGuestFilesys(LibvirtConfigGuestDevice):
return dev
class LibvirtConfigGuestDiskMirror(LibvirtConfigObject):
def __init__(self, **kwargs):
super(LibvirtConfigGuestDiskMirror, self).__init__(**kwargs)
self.ready = None
def parse_dom(self, xmldoc):
self.ready = xmldoc.get('ready')
class LibvirtConfigGuestIDMap(LibvirtConfigObject):
def __init__(self, **kwargs):

View File

@ -315,7 +315,15 @@ class Guest(object):
doc = etree.fromstring(self._domain.XMLDesc(0))
except Exception:
return None
# FIXME(lyarwood): Workaround for the device being either a target dev
# when called via swap_volume or source file when called via
# live_snapshot. This should be removed once both are refactored to use
# only the target dev of the device.
node = doc.find("./devices/disk/target[@dev='%s'].." % device)
if node is None:
node = doc.find("./devices/disk/source[@file='%s'].." % device)
if node is not None:
conf = vconfig.LibvirtConfigGuestDisk()
conf.parse_dom(node)
@ -787,7 +795,14 @@ class BlockDevice(object):
# The earliest tag which contains this commit is v2.3.0-rc1, so we
# should be able to remove this workaround when MIN_LIBVIRT_VERSION
# reaches 2.3.0, or we move to handling job events instead.
return status.end != 0 and status.cur == status.end
# NOTE(lyarwood): Use the mirror element to determine if we can pivot
# to the new disk once blockjobinfo reports progress as complete.
if status.end != 0 and status.cur == status.end:
disk = self._guest.get_disk(self._disk)
if disk and disk.mirror:
return disk.mirror.ready == 'yes'
return False
class VCPUInfo(object):