Adam Litke has uploaded a new change for review.
Change subject: SDM: isolateVolumes API ......................................................................
SDM: isolateVolumes API
Change-Id: I9b67e2df82afba9956e8246c1a4f9093aed729f2 Signed-off-by: Adam Litke alitke@redhat.com --- M client/vdsClient.py M vdsm/API.py M vdsm/rpc/BindingXMLRPC.py M vdsm/rpc/vdsmapi-schema.json M vdsm/storage/hsm.py M vdsm/storage/sdm/__init__.py M vdsm/storage/sdm/blockstore.py M vdsm/storage/sdm/filestore.py M vdsm/storage/sdm/volumestore.py M vdsm/storage/storage_exception.py 10 files changed, 134 insertions(+), 1 deletion(-)
git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/79/40379/1
diff --git a/client/vdsClient.py b/client/vdsClient.py index 633d1b7..d3c482e 100755 --- a/client/vdsClient.py +++ b/client/vdsClient.py @@ -1963,6 +1963,17 @@ else: return status['status']['code'], status['status']['message']
+ def isolateVolumes(self, args): + if len(args) != 4: + raise ValueError('Wrong number of arguments') + sdUUID, srcImgUUID, dstImgUUID, volStr = args + volList = volStr.split(',') + status = self.s.isolateVolumes(sdUUID, srcImgUUID, dstImgUUID, volList) + if status['status']['code'] == 0: + return 0, '' + else: + return status['status']['code'], status['status']['message'] +
if __name__ == '__main__': if _glusterEnabled: @@ -2855,6 +2866,12 @@ '<sdUUID> <imgUUID> <volUUID>', 'Extend a thinly-provisioned block volume.' )), + 'isolateVolumes': ( + serv.isolateVolumes, ( + '<sdUUID> <srcImgUUID> <dstImgUUID> <volUUID>[...,<volUUID>]', + 'Isolate volumes from one image into a new image for ' + 'post-processing.' + )), } if _glusterEnabled: commands.update(ge.getGlusterCmdDict(serv)) diff --git a/vdsm/API.py b/vdsm/API.py index a50025f..44dddb4 100644 --- a/vdsm/API.py +++ b/vdsm/API.py @@ -1049,6 +1049,10 @@ def validate(self): return self._irs.validateStorageDomain(self._UUID)
+ def isolateVolumes(self, srcImageID, dstImageID, volumeList): + return self._irs.isolateVolumes(self._UUID, srcImageID, dstImageID, + volumeList) +
class StoragePool(APIBase): ctorArgs = ['storagepoolID'] diff --git a/vdsm/rpc/BindingXMLRPC.py b/vdsm/rpc/BindingXMLRPC.py index 7834e83..0e1e5e4 100644 --- a/vdsm/rpc/BindingXMLRPC.py +++ b/vdsm/rpc/BindingXMLRPC.py @@ -1000,6 +1000,10 @@ api = API.Global() return api.extendVolumeContainer(sdUUID, imgUUID, volUUID, size)
+ def isolateVolumes(self, sdUUID, srcImgUUID, dstImgUUID, volumeList): + api = API.StorageDomain(sdUUID) + return api.isolateVolumes(srcImgUUID, dstImgUUID, volumeList) + def getGlobalMethods(self): return ((self.vmDestroy, 'destroy'), (self.vmCreate, 'create'), @@ -1151,7 +1155,8 @@ 'storageServer_ConnectionRefs_statuses'), (self.volumeCreateContainer, 'createVolumeContainer'), (self.copyData, 'copyData'), - (self.extendVolumeContainer, 'extendVolumeContainer')) + (self.extendVolumeContainer, 'extendVolumeContainer'), + (self.isolateVolumes, 'isolateVolumes'))
def wrapApiMethod(f): diff --git a/vdsm/rpc/vdsmapi-schema.json b/vdsm/rpc/vdsmapi-schema.json index c0d8caf..a873d22 100644 --- a/vdsm/rpc/vdsmapi-schema.json +++ b/vdsm/rpc/vdsmapi-schema.json @@ -5459,6 +5459,25 @@ {'command': {'class': 'StorageDomain', 'name': 'validate'}, 'data': {'storagedomainID': 'UUID'}}
+## +# @StorageDomain.isolateVolumes: +# +# Isolate volumes from one image into a new image. +# +# @storagedomainID: The UUID of the Storage Domain +# +# @srcImageID: The UUID of the Image containing the volumes +# +# @dstImageID: The UUID of the destination Image +# +# @volumeList: Identifies a set of volumes to move +# +# Since: 4.18.0 +## +{'command': {'class': 'StorageDomain', 'name': 'isolateVolumes'}, + 'data': {'storagedomainID': 'UUID', 'srcImageID': 'UUID', + 'dstImageID': 'UUID', 'volumeList': ['UUID']}} + ## Category: @StoragePool ##################################################### ## # @StoragePool: diff --git a/vdsm/storage/hsm.py b/vdsm/storage/hsm.py index 63c9b3b..746423d 100644 --- a/vdsm/storage/hsm.py +++ b/vdsm/storage/hsm.py @@ -3760,3 +3760,13 @@ misc.validateUUID(volUUID, 'volUUID') vars.task.getSharedLock(STORAGE, sdUUID) return sdm.extendVolumeContainer(dom, imgUUID, volUUID, size) + + @public + def isolateVolumes(self, sdUUID, srcImgUUID, dstImgUUID, volumeList): + vars.task.setDefaultException( + se.IsolateVolumesError(sdUUID, srcImgUUID, dstImgUUID, volumeList)) + dom = sdCache.produce(sdUUID=sdUUID) + misc.validateUUID(srcImgUUID, 'srcImgUUID') + misc.validateUUID(dstImgUUID, 'dstImgUUID') + vars.task.getSharedLock(STORAGE, sdUUID) + return sdm.isolateVolumes(dom, srcImgUUID, dstImgUUID, volumeList) diff --git a/vdsm/storage/sdm/__init__.py b/vdsm/storage/sdm/__init__.py index da1e858..1636e63 100644 --- a/vdsm/storage/sdm/__init__.py +++ b/vdsm/storage/sdm/__init__.py @@ -208,3 +208,21 @@ domain.releaseClusterLock() if cbFn: cbFn(cbData) + + +def isolateVolumes(domain, srcImgUUID, dstImgUUID, volumeList): + cls = __getStoreClass(domain) + imageResourcesNamespace = sd.getNamespace(domain.sdUUID, IMAGE_NAMESPACE) + + hostId = getDomainHostId(domain.sdUUID) + domain.acquireClusterLock(hostId) + try: + with nested(rmanager.acquireResource(imageResourcesNamespace, + srcImgUUID, + rm.LockType.exclusive), + rmanager.acquireResource(imageResourcesNamespace, + dstImgUUID, + rm.LockType.exclusive)): + cls.isolateVolumes(domain, srcImgUUID, dstImgUUID, volumeList) + finally: + domain.releaseClusterLock() diff --git a/vdsm/storage/sdm/blockstore.py b/vdsm/storage/sdm/blockstore.py index 06a77e4..bd9d3f1 100644 --- a/vdsm/storage/sdm/blockstore.py +++ b/vdsm/storage/sdm/blockstore.py @@ -101,6 +101,18 @@ return newName
@classmethod + def _isolateVolume(cls, dom, srcImgUUID, dstImgUUID, vol): + pVolUUID = vol.getParent() + toAdd = [blockVolume.TAG_PREFIX_PARENT + volume.BLANK_UUID, + blockVolume.TAG_PREFIX_IMAGE + dstImgUUID] + toDel = [blockVolume.TAG_PREFIX_PARENT + pVolUUID, + blockVolume.TAG_PREFIX_IMAGE + srcImgUUID] + lvm.changeLVTags(dom.sdUUID, vol.volUUID, addTags=toAdd, delTags=toDel) + if pVolUUID and pVolUUID != volume.BLANK_UUID: + pVol = dom.produceVolume(srcImgUUID, pVolUUID) + cls.recheckIfLeaf(pVol) + + @classmethod def _getGCVolumes(cls, dom, onlyImg, onlyVol): lvs = lvm.getLV(dom.sdUUID) vols = [] diff --git a/vdsm/storage/sdm/filestore.py b/vdsm/storage/sdm/filestore.py index 3e99ee9..9385d87 100644 --- a/vdsm/storage/sdm/filestore.py +++ b/vdsm/storage/sdm/filestore.py @@ -98,6 +98,21 @@ return newName
@classmethod + def _isolateVolume(cls, dom, srcImgUUID, dstImgUUID, vol): + srcImgPath = os.path.join(dom.getRepoPath(), dom.sdUUID, + sd.DOMAIN_IMAGES, srcImgUUID) + dstImgPath = os.path.join(dom.getRepoPath(), dom.sdUUID, + sd.DOMAIN_IMAGES, dstImgUUID) + pUUID = vol.getParent() + vol._share(dstImgPath) + dstVol = dom.produceVolume(dstImgUUID, vol.volUUID) + dstVol.setParent(volume.BLANK_UUID) + dstVol.setImage(dstImgUUID) + newName = cls._beginRemoveVolume(dom, srcImgPath, vol.volUUID) + volInfo = volumestore.GCVol(newName, vol.volUUID, srcImgUUID, pUUID) + cls._garbageCollectVolume(dom, volInfo) + + @classmethod def _getGCVolumes(cls, dom, onlyImg, onlyVol): vols = [] volPaths = [] diff --git a/vdsm/storage/sdm/volumestore.py b/vdsm/storage/sdm/volumestore.py index d518f20..6568d75 100644 --- a/vdsm/storage/sdm/volumestore.py +++ b/vdsm/storage/sdm/volumestore.py @@ -355,3 +355,27 @@ newName = cls._beginRemoveVolume(dom, imageDir, volUUID) volInfo = GCVol(newName, volUUID, imgUUID, pUUID) cls._garbageCollectVolume(dom, volInfo) + + @classmethod + def isolateVolumes(cls, dom, srcImgUUID, dstImgUUID, volumeList): + repoPath = dom.getRepoPath() + # Create dest image + cls.createImage(repoPath, dom.sdUUID, dstImgUUID) + # Verify dest image contains only volumes in volumeList + uuidList = cls.volClass.getImageVolumes(repoPath, dom.sdUUID, + dstImgUUID) + extraVols = set(uuidList) - set(volumeList) + if extraVols: + log.error("Destination image contains unexpected volumes: %s", + extraVols) + raise se.IsolateVolumesError(dom.sdUUID, srcImgUUID, + dstImgUUID, volumeList) + # Iterate over volumes in volumeList + for volUUID in volumeList: + try: + vol = cls.volClass(repoPath, dom.sdUUID, srcImgUUID, volUUID) + except se.VolumeDoesNotExist: + log.debug("Skipping non-existent source volume %s", volUUID) + continue + vol.validateDelete() + cls._isolateVolume(dom, srcImgUUID, dstImgUUID, vol) diff --git a/vdsm/storage/storage_exception.py b/vdsm/storage/storage_exception.py index 1cfc8e4..a695cf4 100644 --- a/vdsm/storage/storage_exception.py +++ b/vdsm/storage/storage_exception.py @@ -453,6 +453,15 @@ message = "Image does not exist in domain"
+class IsolateVolumesError(StorageException): + def __init__(self, sdUUID, srcImgUUID, dstImgUUID, volumeList): + self.value = ("domain=%s srcImg=%s dstImg=%s " + "volumes=%s" % (sdUUID, srcImgUUID, dstImgUUID, + volumeList)) + code = 269 + message = "Unable to isolate volumes" + + ################################################# # Pool Exceptions #################################################