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 <[email protected]> --- 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 <[email protected]> _______________________________________________ vdsm-patches mailing list [email protected] https://lists.fedorahosted.org/mailman/listinfo/vdsm-patches
