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(a)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
#################################################
--
To view, visit
https://gerrit.ovirt.org/40379
To unsubscribe, visit
https://gerrit.ovirt.org/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I9b67e2df82afba9956e8246c1a4f9093aed729f2
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Adam Litke <alitke(a)redhat.com>