libvirt:on snapshot delete, use qemu-img to blockRebase if VM is stopped
Libvirt won't do a blockRebase on a domain that is not running. So,
in that case, use "qemu-img rebase" instead.
Note: For now, trying to rebase a network disk using qemu-img raises
a NovaException error because I can't test that it successfully works
for every protocol (gluster, sheepdog, etc) that executes this code
path. I successfully tested this with file-based disk.
Change-Id: I0e6819a6c8dc70b9bd7d1a9c18dc185b4537a3e4
Closes-Bug: #1444806
Closes-Bug: #1465416
(cherry picked from commit 1cf793df25
)
This commit is contained in:
parent
0871729c21
commit
aea8790218
|
@ -292,6 +292,9 @@ class FakeVirtDomain(object):
|
|||
def fsThaw(self, disks=None, flags=0):
|
||||
pass
|
||||
|
||||
def isActive(self):
|
||||
return True
|
||||
|
||||
|
||||
class CacheConcurrencyTestCase(test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
|
@ -14594,6 +14597,97 @@ class LibvirtVolumeSnapshotTestCase(test.NoDBTestCase):
|
|||
|
||||
self.mox.VerifyAll()
|
||||
|
||||
def _setup_block_rebase_domain_and_guest_mocks(self, dom_xml):
|
||||
mock_domain = mock.Mock(spec=fakelibvirt.virDomain)
|
||||
mock_domain.XMLDesc.return_value = dom_xml
|
||||
guest = libvirt_guest.Guest(mock_domain)
|
||||
|
||||
exc = fakelibvirt.make_libvirtError(
|
||||
fakelibvirt.libvirtError, 'virDomainBlockRebase() failed',
|
||||
error_code=fakelibvirt.VIR_ERR_OPERATION_INVALID)
|
||||
mock_domain.blockRebase.side_effect = exc
|
||||
|
||||
return mock_domain, guest
|
||||
|
||||
@mock.patch.object(host.Host, "has_min_version",
|
||||
mock.Mock(return_value=True))
|
||||
@mock.patch("nova.virt.libvirt.guest.Guest.is_active",
|
||||
mock.Mock(return_value=False))
|
||||
@mock.patch('nova.virt.libvirt.utils.get_disk_type',
|
||||
return_value="fake_fmt")
|
||||
@mock.patch('nova.utils.execute')
|
||||
def test_volume_snapshot_delete_when_dom_not_running(self, mock_execute,
|
||||
mock_get_disk_type):
|
||||
"""Deleting newest snapshot of a file-based image when the domain is
|
||||
not running should trigger a blockRebase using qemu-img not libvirt.
|
||||
In this test, we rebase the image with another image as backing file.
|
||||
"""
|
||||
mock_domain, guest = self._setup_block_rebase_domain_and_guest_mocks(
|
||||
self.dom_xml)
|
||||
|
||||
instance = objects.Instance(**self.inst)
|
||||
snapshot_id = 'snapshot-1234'
|
||||
with mock.patch.object(self.drvr._host, 'get_guest',
|
||||
return_value=guest):
|
||||
self.drvr._volume_snapshot_delete(self.c, instance,
|
||||
self.volume_uuid, snapshot_id,
|
||||
self.delete_info_1)
|
||||
|
||||
mock_get_disk_type.assert_called_once_with("snap.img")
|
||||
mock_execute.assert_called_once_with('qemu-img', 'rebase',
|
||||
'-b', 'snap.img', '-F',
|
||||
'fake_fmt', 'disk1_file')
|
||||
|
||||
@mock.patch.object(host.Host, "has_min_version",
|
||||
mock.Mock(return_value=True))
|
||||
@mock.patch("nova.virt.libvirt.guest.Guest.is_active",
|
||||
mock.Mock(return_value=False))
|
||||
@mock.patch('nova.virt.libvirt.utils.get_disk_type',
|
||||
return_value="fake_fmt")
|
||||
@mock.patch('nova.utils.execute')
|
||||
def test_volume_snapshot_delete_when_dom_not_running_and_no_rebase_base(
|
||||
self, mock_execute, mock_get_disk_type):
|
||||
"""Deleting newest snapshot of a file-based image when the domain is
|
||||
not running should trigger a blockRebase using qemu-img not libvirt.
|
||||
In this test, the image is rebased onto no backing file (i.e.
|
||||
it will exist independently of any backing file)
|
||||
"""
|
||||
mock_domain, mock_guest = (
|
||||
self._setup_block_rebase_domain_and_guest_mocks(self.dom_xml))
|
||||
|
||||
instance = objects.Instance(**self.inst)
|
||||
snapshot_id = 'snapshot-1234'
|
||||
with mock.patch.object(self.drvr._host, 'get_guest',
|
||||
return_value=mock_guest):
|
||||
self.drvr._volume_snapshot_delete(self.c, instance,
|
||||
self.volume_uuid, snapshot_id,
|
||||
self.delete_info_3)
|
||||
|
||||
self.assertEqual(0, mock_get_disk_type.call_count)
|
||||
mock_execute.assert_called_once_with('qemu-img', 'rebase',
|
||||
'-b', '', 'disk1_file')
|
||||
|
||||
@mock.patch.object(host.Host, "has_min_version",
|
||||
mock.Mock(return_value=True))
|
||||
@mock.patch("nova.virt.libvirt.guest.Guest.is_active",
|
||||
mock.Mock(return_value=False))
|
||||
def test_volume_snapshot_delete_when_dom_with_nw_disk_not_running(self):
|
||||
"""Deleting newest snapshot of a network disk when the domain is not
|
||||
running should raise a NovaException.
|
||||
"""
|
||||
mock_domain, mock_guest = (
|
||||
self._setup_block_rebase_domain_and_guest_mocks(
|
||||
self.dom_netdisk_xml))
|
||||
instance = objects.Instance(**self.inst)
|
||||
snapshot_id = 'snapshot-1234'
|
||||
with mock.patch.object(self.drvr._host, 'get_guest',
|
||||
return_value=mock_guest):
|
||||
ex = self.assertRaises(exception.NovaException,
|
||||
self.drvr._volume_snapshot_delete,
|
||||
self.c, instance, self.volume_uuid,
|
||||
snapshot_id, self.delete_info_1)
|
||||
self.assertIn('has not been fully tested', six.text_type(ex))
|
||||
|
||||
def test_volume_snapshot_delete_2(self):
|
||||
"""Deleting older snapshot -- blockCommit."""
|
||||
|
||||
|
|
|
@ -1798,6 +1798,51 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_snapshot)
|
||||
timer.start(interval=0.5).wait()
|
||||
|
||||
@staticmethod
|
||||
def _rebase_with_qemu_img(guest, device, active_disk_object,
|
||||
rebase_base):
|
||||
"""Rebase a device tied to a guest using qemu-img.
|
||||
|
||||
:param guest:the Guest which owns the device being rebased
|
||||
:type guest: nova.virt.libvirt.guest.Guest
|
||||
:param device: the guest block device to rebase
|
||||
:type device: nova.virt.libvirt.guest.BlockDevice
|
||||
:param active_disk_object: the guest block device to rebase
|
||||
:type active_disk_object: nova.virt.libvirt.config.\
|
||||
LibvirtConfigGuestDisk
|
||||
:param rebase_base: the new parent in the backing chain
|
||||
:type rebase_base: None or string
|
||||
"""
|
||||
|
||||
# It's unsure how well qemu-img handles network disks for
|
||||
# every protocol. So let's be safe.
|
||||
active_protocol = active_disk_object.source_protocol
|
||||
if active_protocol is not None:
|
||||
msg = _("Something went wrong when deleting a volume snapshot: "
|
||||
"rebasing a %(protocol)s network disk using qemu-img "
|
||||
"has not been fully tested") % {'protocol':
|
||||
active_protocol}
|
||||
LOG.error(msg)
|
||||
raise exception.NovaException(msg)
|
||||
|
||||
if rebase_base is None:
|
||||
# If backing_file is specified as "" (the empty string), then
|
||||
# the image is rebased onto no backing file (i.e. it will exist
|
||||
# independently of any backing file).
|
||||
backing_file = ""
|
||||
qemu_img_extra_arg = []
|
||||
else:
|
||||
# If the rebased image is going to have a backing file then
|
||||
# explicitly set the backing file format to avoid any security
|
||||
# concerns related to file format auto detection.
|
||||
backing_file = rebase_base
|
||||
b_file_fmt = libvirt_utils.get_disk_type(backing_file)
|
||||
qemu_img_extra_arg = ['-F', b_file_fmt]
|
||||
|
||||
qemu_img_extra_arg.append(active_disk_object.source_path)
|
||||
utils.execute("qemu-img", "rebase", "-b", backing_file,
|
||||
*qemu_img_extra_arg)
|
||||
|
||||
def _volume_snapshot_delete(self, context, instance, volume_id,
|
||||
snapshot_id, delete_info=None):
|
||||
"""Note:
|
||||
|
@ -1951,15 +1996,24 @@ class LibvirtDriver(driver.ComputeDriver):
|
|||
'relative': str(relative)}, instance=instance)
|
||||
|
||||
dev = guest.get_block_device(rebase_disk)
|
||||
result = dev.rebase(rebase_base, relative=relative)
|
||||
if result == 0:
|
||||
LOG.debug('blockRebase started successfully',
|
||||
instance=instance)
|
||||
if guest.is_active():
|
||||
result = dev.rebase(rebase_base, relative=relative)
|
||||
if result == 0:
|
||||
LOG.debug('blockRebase started successfully',
|
||||
instance=instance)
|
||||
|
||||
while dev.wait_for_job(abort_on_error=True):
|
||||
LOG.debug('waiting for blockRebase job completion',
|
||||
instance=instance)
|
||||
time.sleep(0.5)
|
||||
while dev.wait_for_job(abort_on_error=True):
|
||||
LOG.debug('waiting for blockRebase job completion',
|
||||
instance=instance)
|
||||
time.sleep(0.5)
|
||||
|
||||
# If the guest is not running libvirt won't do a blockRebase.
|
||||
# In that case, let's ask qemu-img to rebase the disk.
|
||||
else:
|
||||
LOG.debug('Guest is not running so doing a block rebase '
|
||||
'using "qemu-img rebase"', instance=instance)
|
||||
self._rebase_with_qemu_img(guest, dev, active_disk_object,
|
||||
rebase_base)
|
||||
|
||||
else:
|
||||
# commit with blockCommit()
|
||||
|
|
Loading…
Reference in New Issue