XenAPI improve post snapshot coalesce detection
The coalesce detection is not working well after this change: ae2a27ce19f3e24d4a8c713a73e617f4cd71d4b4 The snapshot operation will introduce a new VHD file into the VDI chain, and in many cases, that would be coalesced during the next SR scan. So we need to walk the chain after the snapshot has been taken, not before. This fixes the cause of the mentioned bug, but it doesn't help with launching the partially corrupted snapshots created because of this bug, so this only partially fixes the bug. Change-Id: I0bf7535ae3f7d9e6820f8dc07075892953d80a78 Partial-Bug: #1362595
This commit is contained in:
parent
44a1e4aafc
commit
48b6af7169
@ -1855,112 +1855,66 @@ class SnapshotAttachedHereTestCase(VMUtilsTestBase):
|
||||
mock_safe_destroy_vdis.assert_called_once_with(session, ["snap_ref"])
|
||||
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
@mock.patch.object(vm_utils, '_get_vhd_parent_uuid')
|
||||
@mock.patch.object(vm_utils, '_count_parents_children')
|
||||
@mock.patch.object(vm_utils, '_scan_sr')
|
||||
def test_wait_for_vhd_coalesce(self, mock_scan_sr,
|
||||
mock_count_parents_children, mock_get_vhd_parent_uuid,
|
||||
mock_sleep):
|
||||
def test_wait_for_vhd_coalesce_leaf_node(self, mock_sleep):
|
||||
instance = {"uuid": "fake"}
|
||||
vm_utils._wait_for_vhd_coalesce("session", instance,
|
||||
"sr_ref", "vdi_ref", ["uuid"])
|
||||
self.assertFalse(mock_sleep.called)
|
||||
|
||||
cfg.CONF.import_opt('vhd_coalesce_max_attempts',
|
||||
'nova.virt.xenapi.driver', group="xenserver")
|
||||
max_sr_scan_count = cfg.CONF.xenserver.vhd_coalesce_max_attempts - 1
|
||||
@mock.patch.object(vm_utils, '_count_children')
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
def test_wait_for_vhd_coalesce_parent_snapshot(self, mock_sleep,
|
||||
mock_count):
|
||||
mock_count.return_value = 2
|
||||
instance = {"uuid": "fake"}
|
||||
|
||||
vhd_chain = ['vdi_base_ref', 'vdi_coalescable_ref', 'vdi_leaf_ref']
|
||||
instance = {"uuid": "uuid"}
|
||||
sr_ref = 'sr_ref'
|
||||
session = mock.Mock()
|
||||
vm_utils._wait_for_vhd_coalesce("session", instance,
|
||||
"sr_ref", "vdi_ref", ["uuid1", "uuid2"])
|
||||
|
||||
def fake_scan_sr(session, sr_ref):
|
||||
fake_scan_sr.count += 1
|
||||
if fake_scan_sr.count == max_sr_scan_count:
|
||||
vhd_chain.remove('vdi_coalescable_ref')
|
||||
|
||||
def fake_get_vhd_parent_uuid(session, vdi_ref):
|
||||
index = vhd_chain.index(vdi_ref)
|
||||
if index > 0:
|
||||
return vhd_chain[index - 1].replace('ref', 'uuid')
|
||||
return None
|
||||
|
||||
def fake_call_xenapi(method, *args):
|
||||
if method == 'VDI.get_by_uuid':
|
||||
return args[0].replace('uuid', 'ref')
|
||||
|
||||
fake_scan_sr.count = 0
|
||||
fake_scan_sr.running = True
|
||||
mock_scan_sr.side_effect = fake_scan_sr
|
||||
session.call_xenapi.side_effect = fake_call_xenapi
|
||||
mock_get_vhd_parent_uuid.side_effect = fake_get_vhd_parent_uuid
|
||||
|
||||
mock_count_parents_children.return_value = 1
|
||||
|
||||
vm_utils._wait_for_vhd_coalesce(session, instance, sr_ref,
|
||||
'vdi_leaf_ref', ['vdi_base_uuid'])
|
||||
self.assertEqual(max_sr_scan_count, mock_scan_sr.call_count)
|
||||
session.call_plugin_serialized.has_calls(session, "vdi_ref")
|
||||
# We'll sleep one fewer times than we scan the SR due to
|
||||
# the scan at the start
|
||||
self.assertEqual(max_sr_scan_count - 1, mock_sleep.call_count)
|
||||
self.assertFalse(mock_sleep.called)
|
||||
self.assertTrue(mock_count.called)
|
||||
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
@mock.patch.object(vm_utils, '_get_vhd_parent_uuid')
|
||||
@mock.patch.object(vm_utils, '_count_parents_children')
|
||||
@mock.patch.object(vm_utils, '_count_children')
|
||||
@mock.patch.object(vm_utils, '_scan_sr')
|
||||
def test_wait_for_vhd_coalesce_1317792(self, mock_scan_sr,
|
||||
mock_count_parents_children,
|
||||
mock_get_vhd_parent_uuid,
|
||||
mock_sleep):
|
||||
def test_wait_for_vhd_coalesce_raises(self, mock_scan_sr,
|
||||
mock_count, mock_get_vhd_parent_uuid, mock_sleep):
|
||||
mock_count.return_value = 1
|
||||
instance = {"uuid": "fake"}
|
||||
|
||||
cfg.CONF.import_opt('vhd_coalesce_max_attempts',
|
||||
'nova.virt.xenapi.driver', group="xenserver")
|
||||
self.assertRaises(exception.NovaException,
|
||||
vm_utils._wait_for_vhd_coalesce, "session", instance,
|
||||
"sr_ref", "vdi_ref", ["uuid1", "uuid2"])
|
||||
|
||||
vhd_chain = ['vdi_base_ref', 'vdi_coalescable_ref1',
|
||||
'vdi_coalescable_ref2', 'vdi_leaf_ref']
|
||||
instance = {"uuid": "uuid"}
|
||||
sr_ref = 'sr_ref'
|
||||
session = mock.Mock()
|
||||
self.assertTrue(mock_count.called)
|
||||
self.assertEqual(20, mock_sleep.call_count)
|
||||
self.assertEqual(20, mock_scan_sr.call_count)
|
||||
|
||||
def fake_scan_sr(session, sr_ref):
|
||||
fake_scan_sr.count += 1
|
||||
if fake_scan_sr.count == 1:
|
||||
vhd_chain.remove('vdi_coalescable_ref1')
|
||||
elif fake_scan_sr.count == 2:
|
||||
vhd_chain.remove('vdi_coalescable_ref2')
|
||||
@mock.patch.object(greenthread, 'sleep')
|
||||
@mock.patch.object(vm_utils, '_get_vhd_parent_uuid')
|
||||
@mock.patch.object(vm_utils, '_count_children')
|
||||
@mock.patch.object(vm_utils, '_scan_sr')
|
||||
def test_wait_for_vhd_coalesce_success(self, mock_scan_sr,
|
||||
mock_count, mock_get_vhd_parent_uuid, mock_sleep):
|
||||
mock_count.return_value = 1
|
||||
instance = {"uuid": "fake"}
|
||||
mock_get_vhd_parent_uuid.side_effect = ["bad", "uuid2"]
|
||||
|
||||
def fake_get_vhd_parent_uuid(session, vdi_ref):
|
||||
index = vhd_chain.index(vdi_ref)
|
||||
if index > 0:
|
||||
return vhd_chain[index - 1].replace('ref', 'uuid')
|
||||
return None
|
||||
vm_utils._wait_for_vhd_coalesce("session", instance,
|
||||
"sr_ref", "vdi_ref", ["uuid1", "uuid2"])
|
||||
|
||||
def fake_call_xenapi(method, *args):
|
||||
if method == 'VDI.get_by_uuid':
|
||||
return args[0].replace('uuid', 'ref')
|
||||
|
||||
fake_scan_sr.count = 0
|
||||
fake_scan_sr.running = True
|
||||
mock_scan_sr.side_effect = fake_scan_sr
|
||||
session.call_xenapi.side_effect = fake_call_xenapi
|
||||
mock_get_vhd_parent_uuid.side_effect = fake_get_vhd_parent_uuid
|
||||
|
||||
mock_count_parents_children.return_value = 1
|
||||
|
||||
vm_utils._wait_for_vhd_coalesce(
|
||||
session, instance, sr_ref,
|
||||
'vdi_leaf_ref', ['vdi_base_uuid', 'vdi_coalescable_uuid1'])
|
||||
session.call_plugin_serialized.has_calls(session, "vdi_ref")
|
||||
self.assertEqual(1, mock_sleep.call_count)
|
||||
self.assertEqual(2, mock_scan_sr.call_count)
|
||||
|
||||
@mock.patch.object(vm_utils, '_get_all_vdis_in_sr')
|
||||
@mock.patch.object(vm_utils, '_get_vhd_parent_uuid')
|
||||
def test_count_parents_children(self, mock_get_parent_uuid,
|
||||
mock_get_all_vdis_in_sr):
|
||||
mock_get_parent_uuid.return_value = 'parent1'
|
||||
def test_count_children(self, mock_get_all_vdis_in_sr):
|
||||
vdis = [('child1', {'sm_config': {'vhd-parent': 'parent1'}}),
|
||||
('child2', {'sm_config': {'vhd-parent': 'parent2'}}),
|
||||
('child3', {'sm_config': {'vhd-parent': 'parent1'}})]
|
||||
mock_get_all_vdis_in_sr.return_value = vdis
|
||||
self.assertEqual(2, vm_utils._count_parents_children('session',
|
||||
'child3', 'sr'))
|
||||
self.assertEqual(2, vm_utils._count_children('session',
|
||||
'parent1', 'sr'))
|
||||
|
||||
|
||||
class ImportMigratedDisksTestCase(VMUtilsTestBase):
|
||||
|
@ -719,7 +719,9 @@ def _snapshot_attached_here_impl(session, instance, vm_ref, label, userdevice,
|
||||
if post_snapshot_callback is not None:
|
||||
post_snapshot_callback(task_state=task_states.IMAGE_PENDING_UPLOAD)
|
||||
try:
|
||||
# Ensure no VHDs will vanish while we migrate them
|
||||
# When the VDI snapshot is taken a new parent is introduced.
|
||||
# If we have taken a snapshot before, the new parent can be coalesced.
|
||||
# We need to wait for this to happen before trying to copy the chain.
|
||||
_wait_for_vhd_coalesce(session, instance, sr_ref, vm_vdi_ref,
|
||||
vdi_uuid_chain)
|
||||
|
||||
@ -2044,10 +2046,9 @@ def _child_vhds(session, sr_ref, vdi_uuid, old_snapshots_only=False):
|
||||
return list(children)
|
||||
|
||||
|
||||
def _count_parents_children(session, vdi_ref, sr_ref):
|
||||
def _count_children(session, parent_vdi_uuid, sr_ref):
|
||||
# Search for any other vdi which has the same parent as us to work out
|
||||
# whether we have siblings and therefore if coalesce is possible
|
||||
parent_vdi_uuid = _get_vhd_parent_uuid(session, vdi_ref)
|
||||
children = 0
|
||||
for _ref, rec in _get_all_vdis_in_sr(session, sr_ref):
|
||||
if (rec['sm_config'].get('vhd-parent') == parent_vdi_uuid):
|
||||
@ -2067,31 +2068,47 @@ def _wait_for_vhd_coalesce(session, instance, sr_ref, vdi_ref,
|
||||
in vdi_uuid_list may also be coalesced (except the base UUID - which is
|
||||
guaranteed to remain)
|
||||
"""
|
||||
# NOTE(bobba): If we don't have any VDIs in the target list, then no
|
||||
# coalescing can be guaranteed (e.g. file based SRs don't do leaf
|
||||
# coalescing)
|
||||
if len(vdi_uuid_list) == 0:
|
||||
# If the base disk was a leaf node, there will be no coalescing
|
||||
# after a VDI snapshot.
|
||||
if len(vdi_uuid_list) == 1:
|
||||
LOG.debug("Old chain is single VHD, coalesce not possible.",
|
||||
instance=instance)
|
||||
return
|
||||
|
||||
# Check if we have siblings. If so, coalesce will not take place.
|
||||
if _count_parents_children(session, vdi_ref, sr_ref) > 1:
|
||||
# If the parent of the original disk has other children,
|
||||
# there will be no coalesce because of the VDI snapshot.
|
||||
# For example, the first snapshot for an instance that has been
|
||||
# spawned from a cached image, will not coalesce, because of this rule.
|
||||
parent_vdi_uuid = vdi_uuid_list[1]
|
||||
if _count_children(session, parent_vdi_uuid, sr_ref) > 1:
|
||||
LOG.debug("Parent has other children, coalesce is unlikely.",
|
||||
instance=instance)
|
||||
return
|
||||
|
||||
# When the VDI snapshot is taken, a new parent is created.
|
||||
# Assuming it is not one of the above cases, that new parent
|
||||
# can be coalesced, so we need to wait for that to happen.
|
||||
max_attempts = CONF.xenserver.vhd_coalesce_max_attempts
|
||||
# Remove the leaf node from list, to get possible good parents
|
||||
# when the coalesce has completed.
|
||||
# Its possible that other coalesce operation happen, so we need
|
||||
# to consider the full chain, rather than just the most recent parent.
|
||||
good_parent_uuids = vdi_uuid_list[1:]
|
||||
for i in xrange(max_attempts):
|
||||
# NOTE(sirp): This rescan is necessary to ensure the VM's `sm_config`
|
||||
# matches the underlying VHDs.
|
||||
# This can also kick XenServer into performing a pending coalesce.
|
||||
_scan_sr(session, sr_ref)
|
||||
parent_uuid = _get_vhd_parent_uuid(session, vdi_ref)
|
||||
if parent_uuid and (parent_uuid not in vdi_uuid_list):
|
||||
if parent_uuid and (parent_uuid not in good_parent_uuids):
|
||||
LOG.debug("Parent %(parent_uuid)s not yet in parent list"
|
||||
" %(vdi_uuid_list)s, waiting for coalesce...",
|
||||
" %(good_parent_uuids)s, waiting for coalesce...",
|
||||
{'parent_uuid': parent_uuid,
|
||||
'vdi_uuid_list': vdi_uuid_list},
|
||||
'good_parent_uuids': good_parent_uuids},
|
||||
instance=instance)
|
||||
else:
|
||||
parent_ref = session.call_xenapi("VDI.get_by_uuid", parent_uuid)
|
||||
_get_vhd_parent_uuid(session, parent_ref)
|
||||
LOG.debug("Coalesce detected, because parent is: %s" % parent_uuid,
|
||||
instance=instance)
|
||||
return
|
||||
|
||||
greenthread.sleep(CONF.xenserver.vhd_coalesce_poll_interval)
|
||||
|
Loading…
x
Reference in New Issue
Block a user