Change in vdsm[master]: [wip] import/export methods

fsimonce at redhat.com fsimonce at redhat.com
Tue May 21 16:02:22 UTC 2013


Federico Simoncelli has uploaded a new change for review.

Change subject: [wip] import/export methods
......................................................................

[wip] import/export methods

Change-Id: Ib208bb20d88633167b5c66b42febe912439a022d
Signed-off-by: Federico Simoncelli <fsimonce at redhat.com>
---
M client/vdsClient.py
M vdsm.spec.in
M vdsm/API.py
M vdsm/BindingXMLRPC.py
M vdsm/storage/Makefile.am
M vdsm/storage/hsm.py
M vdsm/storage/image.py
A vdsm/storage/imageSharing.py
M vdsm/storage/outOfProcess.py
M vdsm/storage/remoteFileHandler.py
M vdsm/storage/sp.py
11 files changed, 261 insertions(+), 7 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/55/14955/1

diff --git a/client/vdsClient.py b/client/vdsClient.py
index 332b438..4d089f1 100644
--- a/client/vdsClient.py
+++ b/client/vdsClient.py
@@ -18,6 +18,7 @@
 #
 
 import sys
+import ast
 import getopt
 import traceback
 import xmlrpclib
@@ -1296,6 +1297,30 @@
 
         return 0, image['uuid']
 
+    def exportImage(self, args):
+        spUUID, sdUUID, imgUUID, volUUID, exportInfoString = args
+        exportInfo = ast.literal_eval(exportInfoString)
+
+        image = self.s.exportImage(exportInfo, spUUID, sdUUID, imgUUID,
+                                   volUUID)
+
+        if image['status']['code']:
+            return image['status']['code'], image['status']['message']
+
+        return 0, image['uuid']
+
+    def importImage(self, args):
+        spUUID, sdUUID, imgUUID, volUUID, importInfoString = args
+        importInfo = ast.literal_eval(importInfoString)
+
+        image = self.s.importImage(importInfo, spUUID, sdUUID, imgUUID,
+                                   volUUID)
+
+        if image['status']['code']:
+            return image['status']['code'], image['status']['message']
+
+        return 0, image['uuid']
+
     def moveMultiImage(self, args):
         spUUID = args[0]
         srcDomUUID = args[1]
@@ -2291,6 +2316,14 @@
                            'Synchronize image data between storage domains '
                            'within same pool.'
                            )),
+        'exportImage': (serv.exportImage,
+                        ('<spUUID> <sdUUID> <imgUUID> <volUUID> <exportInfo>',
+                         ''
+                         )),
+        'importImage': (serv.importImage,
+                        ('<spUUID> <sdUUID> <imgUUID> <volUUID> <importInfo>',
+                         ''
+                         )),
         'moveMultiImage': (serv.moveMultiImage,
                            ('<spUUID> <srcDomUUID> <dstDomUUID> '
                             '<imgList>({imgUUID=postzero,'
diff --git a/vdsm.spec.in b/vdsm.spec.in
index 2f8c735..9c4e4df 100644
--- a/vdsm.spec.in
+++ b/vdsm.spec.in
@@ -811,6 +811,7 @@
 %{_datadir}/%{vdsm_name}/storage/hba.py*
 %{_datadir}/%{vdsm_name}/storage/hsm.py*
 %{_datadir}/%{vdsm_name}/storage/image.py*
+%{_datadir}/%{vdsm_name}/storage/imageSharing.py*
 %{_datadir}/%{vdsm_name}/storage/iscsiadm.py*
 %{_datadir}/%{vdsm_name}/storage/iscsi.py*
 %{_datadir}/%{vdsm_name}/storage/localFsSD.py*
diff --git a/vdsm/API.py b/vdsm/API.py
index ee72116..c9fd67f 100644
--- a/vdsm/API.py
+++ b/vdsm/API.py
@@ -794,6 +794,14 @@
         return self._irs.syncImageData(self._spUUID, self._sdUUID, self._UUID,
                                        dstSdUUID, syncType)
 
+    def send(self, exportInfo, volUUID=None):
+        return self._irs.exportImage(self._spUUID, self._sdUUID, self._UUID,
+                                     volUUID, exportInfo)
+
+    def receive(self, importInfo, volUUID=None):
+        return self._irs.importImage(self._spUUID, self._sdUUID, self._UUID,
+                                     volUUID, importInfo)
+
 
 class LVMVolumeGroup(APIBase):
     ctorArgs = ['lvmvolumegroupID']
diff --git a/vdsm/BindingXMLRPC.py b/vdsm/BindingXMLRPC.py
index 9a4db12..fa12dcd 100644
--- a/vdsm/BindingXMLRPC.py
+++ b/vdsm/BindingXMLRPC.py
@@ -502,6 +502,14 @@
         image = API.Image(imgUUID, spUUID, sdUUID)
         return image.syncData(dstSdUUID, syncType)
 
+    def imageExport(self, exportInfo, spUUID, sdUUID, imgUUID, volUUID=None):
+        image = API.Image(imgUUID, spUUID, sdUUID)
+        return image.send(exportInfo, volUUID)
+
+    def imageImport(self, importInfo, spUUID, sdUUID, imgUUID, volUUID=None):
+        image = API.Image(imgUUID, spUUID, sdUUID)
+        return image.receive(importInfo, volUUID)
+
     def poolConnect(self, spUUID, hostID, scsiKey, msdUUID, masterVersion,
                     options=None):
         pool = API.StoragePool(spUUID)
@@ -837,6 +845,8 @@
                 (self.imageMove, 'moveImage'),
                 (self.imageCloneStructure, 'cloneImageStructure'),
                 (self.imageSyncData, 'syncImageData'),
+                (self.imageExport, 'exportImage'),
+                (self.imageImport, 'importImage'),
                 (self.poolConnect, 'connectStoragePool'),
                 (self.poolConnectStorageServer, 'connectStorageServer'),
                 (self.poolCreate, 'createStoragePool'),
diff --git a/vdsm/storage/Makefile.am b/vdsm/storage/Makefile.am
index 5173277..bcfd9c7 100644
--- a/vdsm/storage/Makefile.am
+++ b/vdsm/storage/Makefile.am
@@ -38,6 +38,7 @@
 	hba.py \
 	hsm.py \
 	image.py \
+	imageSharing.py \
 	iscsiadm.py \
 	iscsi.py \
 	localFsSD.py \
diff --git a/vdsm/storage/hsm.py b/vdsm/storage/hsm.py
index 15b8331..ae5c609 100644
--- a/vdsm/storage/hsm.py
+++ b/vdsm/storage/hsm.py
@@ -1602,6 +1602,28 @@
         self._spmSchedule(spUUID, "syncImageData", pool.syncImageData,
                           sdUUID, imgUUID, dstSdUUID, syncType)
 
+    @public
+    def exportImage(self, spUUID, sdUUID, imgUUID, volUUID, exportInfo):
+        """
+        """
+        self.validateSdUUID(sdUUID)
+
+        pool = self.getPool(spUUID)
+        # NOTE: this could become an hsm task
+        self._spmSchedule(spUUID, "exportImage", pool.exportImage, sdUUID,
+                          imgUUID, volUUID, exportInfo)
+
+    @public
+    def importImage(self, spUUID, sdUUID, imgUUID, volUUID, importInfo):
+        """
+        """
+        self.validateSdUUID(sdUUID)
+
+        pool = self.getPool(spUUID)
+        # NOTE: this could become an hsm task
+        self._spmSchedule(spUUID, "importImage", pool.importImage, sdUUID,
+                          imgUUID, volUUID, importInfo)
+
     @deprecated
     @public
     def moveMultipleImages(self, spUUID, srcDomUUID, dstDomUUID, imgDict,
diff --git a/vdsm/storage/image.py b/vdsm/storage/image.py
index 5f39c91..8f81c8a 100644
--- a/vdsm/storage/image.py
+++ b/vdsm/storage/image.py
@@ -1265,3 +1265,41 @@
 
         self.log.info("Merge src=%s with dst=%s was successfully finished.",
                       srcVol.getVolumePath(), dstVol.getVolumePath())
+
+    def _activateVolumeForImportExport(self, domain, imgUUID, volUUID=None):
+        chain = self.getChain(domain.sdUUID, imgUUID, volUUID)
+        template = chain[0].getParentVolume()
+
+        if template or len(chain) > 1:
+            self.log.error("Importing and exporting an image with more "
+                           "than one volume is not supported")
+            raise se.CopyImageError()
+
+        domain.activateVolumes(imgUUID, volUUIDs=[chain[0].volUUID])
+        return chain[0]
+
+    def send(self, exportInfo, sdUUID, imgUUID, volUUID=None):
+        domain = sdCache.produce(sdUUID)
+        expVol = self._activateVolumeForImportExport(domain, imgUUID, volUUID)
+
+        try:
+            expVolPath = expVol.getVolumePath()
+            # In the future based on the exportInfo data we'll select
+            # an appropriate backend to export to. Since now we're just
+            # supporting glance we hardwire it directly.
+            return domain.oop.imageSharing.exportImage(expVolPath, exportInfo)
+        finally:
+            domain.deactivateVolumes(imgUUID, volUUIDs=[expVol.volUUID])
+
+    def receive(self, importInfo, sdUUID, imgUUID, volUUID=None):
+        domain = sdCache.produce(sdUUID)
+        impVol = self._activateVolumeForImportExport(domain, imgUUID, volUUID)
+
+        try:
+            impVolPath = impVol.getVolumePath()
+            # In the future based on the importInfo data we'll select
+            # an appropriate backend to import from. Since now we're just
+            # supporting glance we hardwire it directly.
+            return domain.oop.imageSharing.importImage(impVolPath, importInfo)
+        finally:
+            domain.deactivateVolumes(imgUUID, volUUIDs=[impVol.volUUID])
diff --git a/vdsm/storage/imageSharing.py b/vdsm/storage/imageSharing.py
new file mode 100644
index 0000000..92d2122
--- /dev/null
+++ b/vdsm/storage/imageSharing.py
@@ -0,0 +1,120 @@
+# Copyright 2013 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import types
+import logging
+import httplib
+from urlparse import urlparse
+
+
+log = logging.getLogger("Storage.ImageSharing")
+
+
+# Decorators used to mark the import/export methods
+class ImportMethod(object):
+    def __init__(self, method):
+        self.method = method
+
+    def __call__(self, fun):
+        fun._importMethod = self.method
+        return fun
+
+
+class ExportMethod(object):
+    def __init__(self, method):
+        self.method = method
+
+    def __call__(self, fun):
+        fun._exportMethod = self.method
+        return fun
+
+
+# HTTP import/export section
+HTTP_READ_BUFFER = 262144  # 256K
+
+
+def _httpClientConnect(reqAction, shareInfo):
+    urlInfo = urlparse(shareInfo.get('url'))
+
+    if urlInfo.scheme == "https":
+        client = httplib.HTTPSConnection(urlInfo.hostname, urlInfo.port)
+    elif urlInfo.scheme == "http":
+        client = httplib.HTTPConnection(urlInfo.hostname, urlInfo.port)
+    else:
+        raise RuntimeError("Unsupported scheme %s" % urlInfo.scheme)
+
+    client.connect()
+    client.putrequest(reqAction, urlInfo.path)
+
+    headers = shareInfo.get("headers", {})
+
+    for headerName, headerValue in headers.iteritems():
+        client.putheader(headerName, headerValue)
+
+    client.endheaders()
+    return client
+
+
+ at ImportMethod("http")
+def httpImportImage(dstImgPath, importInfo):
+    client = _httpClientConnect("GET", importInfo)
+    response = client.getresponse()
+
+    with open(dstImgPath, 'wb') as image:
+        while True:
+            data = response.read(HTTP_READ_BUFFER)
+            if len(data) == 0:
+                break  # completed successfully
+            image.write(data)
+
+
+ at ExportMethod("http")
+def httpExportImage(srcImgPath, exportInfo):
+    client = _httpClientConnect("POST", exportInfo)
+    response = client.getresponse()
+
+    with open(srcImgPath, 'rb') as image:
+        while True:
+            data = image.read(HTTP_READ_BUFFER)
+            if len(data) == 0:
+                break  # completed successfully
+            response.write(HTTP_READ_BUFFER)
+
+
+# Official entry points for importing and exporting
+def importImage(dstImgPath, importInfo):
+    # NOTE: don't use iteritems as the dictionary might change size
+    # during the iteration.
+    reqMethod = importInfo.get("method")
+    for functionName, functionImpl in globals().items():
+        if (isinstance(functionImpl, types.FunctionType) and
+                getattr(functionImpl, "_importMethod", None) == reqMethod):
+            return functionImpl(dstImgPath, importInfo)
+    raise RuntimeError("Import method %s not found" % reqMethod)
+
+
+def exportImage(srcImgPath, exportInfo):
+    # NOTE: don't use iteritems as the dictionary might change size
+    # during the iteration.
+    reqMethod = exportInfo.get("method")
+    for functionName, functionImpl in globals().items():
+        if (isinstance(functionImpl, types.FunctionType) and
+                getattr(functionImpl, "_exportMethod", None) == reqMethod):
+            return functionImpl(srcImgPath, exportInfo)
+    raise RuntimeError("Export method %s not found" % reqMethod)
diff --git a/vdsm/storage/outOfProcess.py b/vdsm/storage/outOfProcess.py
index bf61d54..8e0377a 100644
--- a/vdsm/storage/outOfProcess.py
+++ b/vdsm/storage/outOfProcess.py
@@ -79,9 +79,7 @@
 
 
 def OopWrapper(procPool):
-    return _ModuleWrapper("oop", procPool, DEFAULT_TIMEOUT,
-                          (("os",
-                            ("path",)),
-                           "glob",
-                           "fileUtils",
-                           "utils"))
+    return _ModuleWrapper("oop", procPool, DEFAULT_TIMEOUT, (
+        ("os", ("path",)), "glob", "fileUtils", "utils", "fileUtils",
+        "imageSharing")
+    )
diff --git a/vdsm/storage/remoteFileHandler.py b/vdsm/storage/remoteFileHandler.py
index 9c19b1c..c69c136 100644
--- a/vdsm/storage/remoteFileHandler.py
+++ b/vdsm/storage/remoteFileHandler.py
@@ -46,6 +46,7 @@
 
 import misc
 import fileUtils
+import imageSharing
 import zombieReaper
 from vdsm import utils
 
@@ -404,7 +405,7 @@
 
                 server.registerFunction(func)
 
-            for mod in (os, glob, fileUtils):
+            for mod in (os, glob, fileUtils, imageSharing):
                 server.registerModule(mod)
             server.registerModule(utils, name="utils")
         except Exception:
diff --git a/vdsm/storage/sp.py b/vdsm/storage/sp.py
index ed79808..caeee0f 100644
--- a/vdsm/storage/sp.py
+++ b/vdsm/storage/sp.py
@@ -1864,6 +1864,28 @@
             image.Image(self.poolPath).syncData(
                 sdUUID, imgUUID, dstSdUUID, syncType)
 
+    def exportImage(self, sdUUID, imgUUID, volUUID, exportInfo):
+        """
+        """
+        imgResourceLock = rmanager.acquireResource(
+            sd.getNamespace(sdUUID, IMAGE_NAMESPACE), imgUUID,
+            rm.LockType.shared)
+
+        with imgResourceLock:
+            return image.Image(self.poolPath).send(
+                exportInfo, sdUUID, imgUUID, volUUID)
+
+    def importImage(self, sdUUID, imgUUID, volUUID, importInfo):
+        """
+        """
+        imgResourceLock = rmanager.acquireResource(
+            sd.getNamespace(sdUUID, IMAGE_NAMESPACE), imgUUID,
+            rm.LockType.exclusive)
+
+        with imgResourceLock:
+            return image.Image(self.poolPath).receive(
+                importInfo, sdUUID, imgUUID, volUUID)
+
     def moveMultipleImages(self, srcDomUUID, dstDomUUID, imgDict, vmUUID,
                            force):
         """


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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib208bb20d88633167b5c66b42febe912439a022d
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Federico Simoncelli <fsimonce at redhat.com>


More information about the vdsm-patches mailing list