Change in vdsm[master]: Live Merge: Add new scanVolumeChain API

alitke at redhat.com alitke at redhat.com
Wed Mar 19 21:35:28 UTC 2014


Adam Litke has uploaded a new change for review.

Change subject: Live Merge: Add new scanVolumeChain API
......................................................................

Live Merge: Add new scanVolumeChain API

For live merge, we need a way to determine the actual volume chain for a
given disk image.  When the VM is up, we can just return the runtime
information (since vdsm is guaranteed to be up to date).  When the VM is
down, we enter the storage path and use the qemu-img command to fetch
backing file information.

TODO:
 - implement the IRS version which will activate all volumes and call
   qemu-img on each one to determine the backing chain.
 - Convert the VM version of the API to simply return data from the VM
   class.  We will assume that vdsm always refreshes its data on restart
   or when a block job event occurs.
 - Use the query-block flow to refresh vdsm data.

Change-Id: I2bff7774311a3d714fc64d75433acd55dd61e49f
Signed-off-by: Adam Litke <alitke at redhat.com>
---
M client/vdsClient.py
M vdsm/API.py
M vdsm/BindingXMLRPC.py
M vdsm/vm.py
4 files changed, 131 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/16/25916/1

diff --git a/client/vdsClient.py b/client/vdsClient.py
index 6344ed6..0c5af45 100644
--- a/client/vdsClient.py
+++ b/client/vdsClient.py
@@ -1385,6 +1385,19 @@
             return image['status']['code'], image['status']['message']
         return 0, image['uuid']
 
+    def scanVolumeChain(self, args):
+        sdUUID = args[0]
+        spUUID = args[1]
+        imgUUID = args[2]
+
+        if len(args) > 3:
+            chain = self.s.scanVolumeChain(sdUUID, spUUID, imgUUID, args[3])
+        else:
+            chain = self.s.scanVolumeChain(sdUUID, spUUID, imgUUID)
+        if chain['status']['code']:
+            return chain['status']['code'], chain['status']['message']
+        return 0, '\n'.join(chain['volumeChain'])
+
     def mergeSnapshots(self, args):
         sdUUID = args[0]
         spUUID = args[1]
@@ -2411,6 +2424,15 @@
                        'Do it by collapse and copy the whole chain '
                        '(baseVolUUID->srcVolUUID)'
                        )),
+        'scanVolumeChain': (serv.scanVolumeChain,
+                            ('<sdUUID> <spUUID> <imgUUID> [<vmUUID>]',
+                             'Scan the image and return the actual volume '
+                             'chain starting with the base image and ending '
+                             'with the active layer.',
+                             'If vmUUID is provided and the VM is runing, '
+                             'the VM will be queried.  Otherwise the storage '
+                             'itself will be scanned.'
+                             )),
         'mergeSnapshots': (serv.mergeSnapshots,
                            ('<sdUUID> <spUUID> <vmUUID> <imgUUID> <Ancestor '
                             'Image uuid> <Successor Image uuid> [<postZero>]',
diff --git a/vdsm/API.py b/vdsm/API.py
index ae959ef..7ca606d 100644
--- a/vdsm/API.py
+++ b/vdsm/API.py
@@ -866,6 +866,18 @@
             methodArgs, callback, self._spUUID, self._sdUUID, self._UUID,
             volUUID)
 
+    def scanVolumeChain(self, vmUUID=None):
+        # If vmUUID is provided, we try to use the running VM to get the
+        # information which is faster.  Otherwise fall back to the storage-only
+        # implementation.
+        if vmUUID:
+            try:
+                v = self._cif.vmContainer.get(vmUUID)
+                return v.getImageVolumeChain(self._UUID)
+            except KeyError:
+                pass
+        raise RuntimeError("Not implemented yet")
+
 
 class LVMVolumeGroup(APIBase):
     ctorArgs = ['lvmvolumegroupID']
diff --git a/vdsm/BindingXMLRPC.py b/vdsm/BindingXMLRPC.py
index 259c262..c13c35a 100644
--- a/vdsm/BindingXMLRPC.py
+++ b/vdsm/BindingXMLRPC.py
@@ -601,6 +601,10 @@
         image = API.Image(imgUUID, spUUID, sdUUID)
         return image.download(methodArgs, volUUID)
 
+    def imageScanVolumeChain(self, spUUID, sdUUID, imgUUID, vmUUID=None):
+        image = API.Image(imgUUID, spUUID, sdUUID)
+        return image.scanVolumeChain(vmUUID)
+
     def poolConnect(self, spUUID, hostID, scsiKey, msdUUID, masterVersion,
                     domainsMap=None, options=None):
         pool = API.StoragePool(spUUID)
@@ -949,6 +953,7 @@
                 (self.imageSyncData, 'syncImageData'),
                 (self.imageUpload, 'uploadImage'),
                 (self.imageDownload, 'downloadImage'),
+                (self.imageScanVolumeChain, 'scanVolumeChain'),
                 (self.poolConnect, 'connectStoragePool'),
                 (self.poolConnectStorageServer, 'connectStorageServer'),
                 (self.poolCreate, 'createStoragePool'),
diff --git a/vdsm/vm.py b/vdsm/vm.py
index 6e3fbeb..4e64bad 100644
--- a/vdsm/vm.py
+++ b/vdsm/vm.py
@@ -31,9 +31,11 @@
 import threading
 import time
 import xml.dom.minidom
+import json
 
 # 3rd party libs imports
 import libvirt
+import libvirt_qemu
 
 # vdsm imports
 from vdsm import constants
@@ -3824,6 +3826,14 @@
 
         raise LookupError("No such drive: '%s'" % drive)
 
+    def _findDriveByPath(self, path):
+        for device in self._devices[DISK_DEVICES][:]:
+            if not hasattr(device, 'path'):
+                continue
+            if (device.path == path):
+                return device
+        raise LookupError("No drive matches path: '%s'" % path)
+
     def updateDriveVolume(self, vmDrive):
         if not vmDrive.device == 'disk' or not isVdsmImage(vmDrive):
             return
@@ -5275,6 +5285,88 @@
         hooks.before_vm_migrate_destination(srcDomXML, self.conf)
         return True
 
+    def _internalQMPMonitorCommand(self, cmdDict):
+        jsonCmd = json.dumps(cmdDict)
+        ret = libvirt_qemu.qemuMonitorCommand(self._dom, jsonCmd, 0)
+        return json.loads(ret)
+
+    def _getActualVolumeChains(self):
+        """
+        Query qemu for the actual volume chains for VM Disks.  We use
+        this when a live merge operation completes and when recovering in order
+        to synchronize our volume chain information with the actual state.
+        """
+        ret = {}
+        cmd = {'execute': 'query-block'}
+        info = self._internalQMPMonitorCommand(cmd)
+        for dev in info['return']:
+            try:
+                image = dev['inserted']['image']
+            except KeyError:
+                continue
+            try:
+                drive = self._findDriveByPath(image['filename'])
+            except LookupError:
+                continue
+            imageID = drive.imageID
+            ret[imageID] = []
+            while image:
+                # Produce an absolute path so that this information is
+                # comparable with the VM Disk paths.  It's not safe to make
+                # this information public since the paths cannot be guaranteed
+                # across hosts.  Use _pathListToVolumeList to convert it for
+                # export.
+                ret[imageID].insert(0, os.path.abspath(image['filename']))
+                image = image.get('backing-image')
+        return ret
+
+    def _pathListToVolumeList(self, drive, pathList):
+        """
+        Convert a list of volume paths into a list of volume IDs using the
+        existing Drives defined for this VM.
+        """
+        ret = []
+        for path in pathList:
+            volumeID = None
+            for vol in drive.volumeChain:
+                if vol['path'] == path:
+                    volumeID = vol['volumeID']
+                    break
+            if volumeID is None:
+                self.log.error("Unable to find matching drive for path: "
+                               "'%s'" % path)
+                return None
+            ret.append(volumeID)
+        return ret
+
+    def _postLiveMergeSyncVolumeChain(self, name, paths):
+        """
+        Synchronize the volumeChain info after a live merge operation has
+        completed.  One or more volumes might have been removed from the chain.
+        """
+        pass
+
+    def getImageVolumeChain(self, imageID):
+        targetDrive = None
+        for drive in self._devices[DISK_DEVICES][:]:
+            try:
+                if drive.imageID == imageID:
+                    targetDrive = drive
+            except AttributeError:
+                continue
+        if targetDrive is None:
+            return {'status': errCode['imageErr']}
+        try:
+            paths = self._getActualVolumeChains()[imageID]
+        except KeyError:
+            self.log.error("No volume chain information available for "
+                           "image '%s'" % imageID)
+            return {'status': errCode['imageErr']}
+        volumeChain = self._pathListToVolumeList(targetDrive, paths)
+        if volumeChain is None:
+            return {'status': errCode['imageErr']}
+        return {'status': doneCode, 'volumeChain': volumeChain}
+
 
 # A little unrelated hack to make xml.dom.minidom.Document.toprettyxml()
 # not wrap Text node with whitespace.


-- 
To view, visit http://gerrit.ovirt.org/25916
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I2bff7774311a3d714fc64d75433acd55dd61e49f
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Adam Litke <alitke at redhat.com>


More information about the vdsm-patches mailing list