Adam Litke has uploaded a new change for review.
Change subject: Live Merge: add reconcileVolumeChain verb
......................................................................
Live Merge: add reconcileVolumeChain verb
If a live merge operation is catastrophically interrupted (we completely
lose the host on which the VM was running during the merge) engine must
have a way to discover what happened by examining storage only.
When the active layer was being merged, it's critical to know whether
the the pivot completed or not. Choosing the wrong leaf volume could
result in data corruption. When an internal volume is being merged the
situation is less dire but providing a way for engine to resolve the
final status of the merge provides for a substantially better user
experience since the snapshot can be unlocked to allow a cold merge to
be attempted.
This new verb must run on SPM and is only valid for images which are not
in use by running VMs. It will use qemu-img to examine the actual
volume chain and will correct the vdsm metadata if needed. Finally, the
current volume chain is returned. This chain can be used by engine to
understand what happened with the interrupted merge command.
Change-Id: Ia8927a42d2bc9adcf7c6b26babb327a00e91e49e
Signed-off-by: Adam Litke <alitke(a)redhat.com>
---
M client/vdsClient.py
M vdsm/API.py
M vdsm/rpc/BindingXMLRPC.py
M vdsm/rpc/Bridge.py
M vdsm/rpc/vdsmapi-schema.json
M vdsm/storage/hsm.py
6 files changed, 93 insertions(+), 0 deletions(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/88/31788/1
diff --git a/client/vdsClient.py b/client/vdsClient.py
index 35f4ca5..783afa6 100644
--- a/client/vdsClient.py
+++ b/client/vdsClient.py
@@ -1432,6 +1432,17 @@
return ret['status']['code'],
ret['status']['message']
+ def reconcileVolumeChain(self, args):
+ if len(args) != 4:
+ raise ValueError('Wrong number of parameters')
+
+ ret = self.s.reconcileVolumeChain(*args)
+ if 'volumes' in ret:
+ for v in ret['volumes']:
+ print v
+ return 0, ''
+ return ret['status']['code'],
ret['status']['message']
+
def moveMultiImage(self, args):
spUUID = args[0]
srcDomUUID = args[1]
@@ -2544,6 +2555,10 @@
'<spUUID> <sdUUID> <imgUUID> [<volUUID>]',
'Teardown an image, releasing the prepared volumes.'
)),
+ 'reconcileVolumeChain': (serv.reconcileVolumeChain, (
+ '<spUUID> <sdUUID> <imgUUID> <leafVolUUID>',
+ 'Reconcile an image volume chain and return the current chain.'
+ )),
'moveMultiImage': (serv.moveMultiImage,
('<spUUID> <srcDomUUID> <dstDomUUID>
'
'<imgList>({imgUUID=postzero,'
diff --git a/vdsm/API.py b/vdsm/API.py
index dceec2d..310ec83 100644
--- a/vdsm/API.py
+++ b/vdsm/API.py
@@ -906,6 +906,10 @@
methodArgs, callback, self._spUUID, self._sdUUID, self._UUID,
volUUID)
+ def reconcileVolumeChain(self, leafVolUUID):
+ return self._irs.reconcileVolumeChain(self._spUUID, self._sdUUID,
+ self._UUID, leafVolUUID)
+
class LVMVolumeGroup(APIBase):
ctorArgs = ['lvmvolumegroupID']
diff --git a/vdsm/rpc/BindingXMLRPC.py b/vdsm/rpc/BindingXMLRPC.py
index b6ddd1d..a5286ed 100644
--- a/vdsm/rpc/BindingXMLRPC.py
+++ b/vdsm/rpc/BindingXMLRPC.py
@@ -710,6 +710,10 @@
image = API.Image(imgUUID, spUUID, sdUUID)
return image.teardown(volUUID)
+ def imageReconcileVolumeChain(self, spUUID, sdUUID, imgUUID, leafUUID):
+ image = API.Image(imgUUID, spUUID, sdUUID)
+ return image.reconcileVolumeChain(leafUUID)
+
def poolConnect(self, spUUID, hostID, scsiKey, msdUUID, masterVersion,
domainsMap=None, options=None):
pool = API.StoragePool(spUUID)
@@ -1055,6 +1059,7 @@
(self.imageDownload, 'downloadImage'),
(self.imagePrepare, 'prepareImage'),
(self.imageTeardown, 'teardownImage'),
+ (self.imageReconcileVolumeChain, 'reconcileVolumeChain'),
(self.poolConnect, 'connectStoragePool'),
(self.poolConnectStorageServer, 'connectStorageServer'),
(self.poolCreate, 'createStoragePool'),
diff --git a/vdsm/rpc/Bridge.py b/vdsm/rpc/Bridge.py
index 25c49f9..0a7a214 100644
--- a/vdsm/rpc/Bridge.py
+++ b/vdsm/rpc/Bridge.py
@@ -397,6 +397,7 @@
'Image_download': {'ret': 'uuid'},
'Image_mergeSnapshots': {'ret': 'uuid'},
'Image_move': {'ret': 'uuid'},
+ 'Image_reconcileVolumeChain': {'ret': 'volumes'},
'ISCSIConnection_discoverSendTargets': {'ret':
'fullTargets'},
'LVMVolumeGroup_create': {'ret': 'uuid'},
'LVMVolumeGroup_getInfo': {'ret': 'info'},
diff --git a/vdsm/rpc/vdsmapi-schema.json b/vdsm/rpc/vdsmapi-schema.json
index 2ff8e0b..60b4282 100644
--- a/vdsm/rpc/vdsmapi-schema.json
+++ b/vdsm/rpc/vdsmapi-schema.json
@@ -4351,6 +4351,29 @@
'data': {'storagepoolID': 'UUID', 'storagedomainID':
'UUID',
'imageID': 'UUID', '*volumeID': 'UUID'}}
+##
+# @Image.reconcileVolumeChain:
+#
+# Reconcile an image volume chain and return the current chain.
+#
+# @storagepoolID: The UUID of the Storage Pool associated with the Image
+#
+# @storagedomainID: The UUID of the Storage Domain associated with the Image
+#
+# @imageID: The UUID of the Image
+#
+# @leafVolID: The UUID of the original leaf volume
+#
+# Returns:
+# A list of volume UUIDs representing the current volume chain
+#
+# Since: 4.16.0
+##
+{'command': {'class': 'Image', 'name':
'reconcileVolumeChain'},
+ 'data': {'storagepoolID': 'UUID', 'storagedomainID':
'UUID',
+ 'imageID': 'UUID', 'leafVolID': 'UUID'}
+ 'returns': ['UUID']}
+
## Category: @LVMVolumeGroup ###################################################
##
# @LVMVolumeGroup:
diff --git a/vdsm/storage/hsm.py b/vdsm/storage/hsm.py
index fc7681c..c2ccc4d 100644
--- a/vdsm/storage/hsm.py
+++ b/vdsm/storage/hsm.py
@@ -1860,6 +1860,51 @@
subChainTailVol.setLegality(volume.ILLEGAL_VOL)
@public
+ def reconcileVolumeChain(self, spUUID, sdUUID, imgUUID, leafVolUUID):
+ """
+ In some situations (such as when a live merge is interrupted), the
+ vdsm volume chain could become out of sync with the actual chain as
+ understood by qemu. This API uses qemu-img to determine the correct
+ chain and synchronizes vdsm metadata accordingly. Returns the correct
+ volume chain. NOT for use on images of running VMs.
+ """
+ argsStr = ("spUUID=%s, sdUUID=%s, imgUUID=%s, leafVolUUID=%s" %
+ (spUUID, sdUUID, imgUUID, leafVolUUID))
+ vars.task.setDefaultException(se.StorageException("%s" % argsStr))
+ dom = sdCache.produce(sdUUID=sdUUID)
+ self.prepareImage(sdUUID, spUUID, imgUUID, leafVolUUID)
+
+ # Walk the volume chain using qemu-img. Not safe for running VMs
+ retVolumes = []
+ volUUID = leafVolUUID
+ while volUUID is not None:
+ retVolumes.insert(0, volUUID)
+ vol = dom.produceVolume(imgUUID, volUUID)
+ qemuImgFormat = volume.fmt2str(vol.getFormat())
+ imgInfo = qemuimg.info(vol.volumePath, qemuImgFormat)
+ backingFile = imgInfo.get('backingfile')
+ if backingFile is not None:
+ volUUID = os.path.basename(backingFile)
+ else:
+ volUUID = None
+
+ # A merge of the active layer has copy and pivot phases.
+ # During copy, data is copied from the leaf into its parent. Writes
+ # are mirrored to both volumes. So even after copying is complete the
+ # volumes will remain consistent. Finally, the VM is pivoted from the
+ # old leaf to the new leaf and mirroring to the old leaf ceases. During
+ # mirroring and before pivoting, we mark the old leaf ILLEGAL so we
+ # know it's safe to delete in case the operation is interrupted.
+ vol = dom.produceVolume(imgUUID, leafVolUUID)
+ if vol.getLegality() == volume.ILLEGAL_VOL:
+ retVolumes.remove(leafVolUUID)
+
+ # Now that we know the correct volume chain, sync the storge metadata
+ self.imageSyncVolumeChain(sdUUID, imgUUID, retVolumes[0], retVolumes)
+ dom.deactivateImage(imgUUID)
+ return dict(volumes=retVolumes)
+
+ @public
def mergeSnapshots(self, sdUUID, spUUID, vmUUID, imgUUID, ancestor,
successor, postZero=False):
"""
--
To view, visit
http://gerrit.ovirt.org/31788
To unsubscribe, visit
http://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia8927a42d2bc9adcf7c6b26babb327a00e91e49e
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Adam Litke <alitke(a)redhat.com>