]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
XenAPINFS: Create volume from image (generic)
authorMate Lakat <mate.lakat@citrix.com>
Wed, 13 Feb 2013 10:48:34 +0000 (10:48 +0000)
committerMate Lakat <mate.lakat@citrix.com>
Wed, 20 Feb 2013 18:14:50 +0000 (18:14 +0000)
Fixes bug 1130706

Related to blueprint xenapinfs-glance-integration

This patch enables users to use any images recognised by qemu-img to
create volumes on XenAPINFS.

Change-Id: I9dd8ec237309da82ec7f73db1acb48e5b7411c9e

cinder/tests/test_xenapi_sm.py
cinder/volume/drivers/xenapi/lib.py
cinder/volume/drivers/xenapi/sm.py
cinder/volume/drivers/xenapi/tools.py [new file with mode: 0644]
tools/test-requires

index b652ed9802cf28953082f8790b84ef731080e582..9a07aba0ed902495534db162484133b48630cf7f 100644 (file)
@@ -23,7 +23,11 @@ from cinder.volume import configuration as conf
 from cinder.volume import driver as parent_driver
 from cinder.volume.drivers.xenapi import lib
 from cinder.volume.drivers.xenapi import sm as driver
+from cinder.volume.drivers.xenapi import tools
+import contextlib
+import mock
 import mox
+import StringIO
 import unittest
 
 
@@ -260,7 +264,67 @@ class DriverTestCase(unittest.TestCase):
         drv.delete_snapshot(snapshot)
         mock.VerifyAll()
 
-    def test_copy_image_to_volume_success(self):
+    def test_copy_image_to_volume_xenserver_case(self):
+        mock, drv = self._setup_mock_driver(
+            'server', 'serverpath', '/var/run/sr-mount')
+
+        mock.StubOutWithMock(drv, '_use_glance_plugin_to_copy_image_to_volume')
+        mock.StubOutWithMock(driver, 'is_xenserver_image')
+        context = MockContext('token')
+
+        driver.is_xenserver_image(
+            context, 'image_service', 'image_id').AndReturn(True)
+        drv._use_glance_plugin_to_copy_image_to_volume(
+            context, 'volume', 'image_service', 'image_id').AndReturn('result')
+        mock.ReplayAll()
+        result = drv.copy_image_to_volume(
+            context, "volume", "image_service", "image_id")
+        self.assertEquals('result', result)
+        mock.VerifyAll()
+
+    def test_copy_image_to_volume_non_xenserver_case(self):
+        mock, drv = self._setup_mock_driver(
+            'server', 'serverpath', '/var/run/sr-mount')
+
+        mock.StubOutWithMock(drv, '_use_image_utils_to_pipe_bytes_to_volume')
+        mock.StubOutWithMock(driver, 'is_xenserver_image')
+        context = MockContext('token')
+
+        driver.is_xenserver_image(
+            context, 'image_service', 'image_id').AndReturn(False)
+        drv._use_image_utils_to_pipe_bytes_to_volume(
+            context, 'volume', 'image_service', 'image_id').AndReturn(True)
+        mock.ReplayAll()
+        drv.copy_image_to_volume(
+            context, "volume", "image_service", "image_id")
+        mock.VerifyAll()
+
+    def test_use_image_utils_to_pipe_bytes_to_volume(self):
+        mock, drv = self._setup_mock_driver(
+            'server', 'serverpath', '/var/run/sr-mount')
+
+        volume = dict(provider_location='sr-uuid/vdi-uuid')
+        context = MockContext('token')
+
+        mock.StubOutWithMock(driver.image_utils, 'fetch_to_raw')
+
+        @contextlib.contextmanager
+        def simple_context(value):
+            yield value
+
+        drv.nfs_ops.volume_attached_here(
+            'server', 'serverpath', 'sr-uuid', 'vdi-uuid', False).AndReturn(
+                simple_context('device'))
+
+        driver.image_utils.fetch_to_raw(
+            context, 'image_service', 'image_id', 'device')
+
+        mock.ReplayAll()
+        drv._use_image_utils_to_pipe_bytes_to_volume(
+            context, volume, "image_service", "image_id")
+        mock.VerifyAll()
+
+    def test_use_glance_plugin_to_copy_image_to_volume_success(self):
         mock, drv = self._setup_mock_driver(
             'server', 'serverpath', '/var/run/sr-mount')
 
@@ -280,11 +344,11 @@ class DriverTestCase(unittest.TestCase):
             'server', 'serverpath', 'sr-uuid', 'vdi-uuid', 2)
 
         mock.ReplayAll()
-        drv.copy_image_to_volume(
+        drv._use_glance_plugin_to_copy_image_to_volume(
             MockContext('token'), volume, "ignore", "image_id")
         mock.VerifyAll()
 
-    def test_copy_image_to_volume_fail(self):
+    def test_use_glance_plugin_to_copy_image_to_volume_fail(self):
         mock, drv = self._setup_mock_driver(
             'server', 'serverpath', '/var/run/sr-mount')
 
@@ -304,7 +368,7 @@ class DriverTestCase(unittest.TestCase):
 
         self.assertRaises(
             exception.ImageCopyFailure,
-            lambda: drv.copy_image_to_volume(
+            lambda: drv._use_glance_plugin_to_copy_image_to_volume(
                 MockContext('token'), volume, "ignore", "image_id"))
 
         mock.VerifyAll()
@@ -336,3 +400,24 @@ class DriverTestCase(unittest.TestCase):
         stats = drv.get_volume_stats()
 
         self.assertEquals('xensm', stats['storage_protocol'])
+
+
+class ToolsTest(unittest.TestCase):
+    @mock.patch('cinder.volume.drivers.xenapi.tools._stripped_first_line_of')
+    def test_get_this_vm_uuid(self, mock_read_first_line):
+        mock_read_first_line.return_value = 'someuuid'
+        self.assertEquals('someuuid', tools.get_this_vm_uuid())
+        mock_read_first_line.assert_called_once_with('/sys/hypervisor/uuid')
+
+    def test_stripped_first_line_of(self):
+        mock_context_manager = mock.Mock()
+        mock_context_manager.__enter__ = mock.Mock(
+            return_value=StringIO.StringIO('  blah  \n second line \n'))
+        mock_context_manager.__exit__ = mock.Mock(return_value=False)
+        mock_open = mock.Mock(return_value=mock_context_manager)
+
+        with mock.patch('__builtin__.open', mock_open):
+            self.assertEquals(
+                'blah', tools._stripped_first_line_of('/somefile'))
+
+        mock_open.assert_called_once_with('/somefile', 'rb')
index 2f309f8be7ab17bc7894bacb9c2e37e8db8d0b49..4c48fc2dbbb31039a65f36d53f7847e0c970c306 100644 (file)
@@ -16,6 +16,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from cinder.volume.drivers.xenapi import tools
 import contextlib
 import os
 import pickle
@@ -35,6 +36,55 @@ class OperationsBase(object):
         return self.session.call_xenapi(method, *args)
 
 
+class VMOperations(OperationsBase):
+    def get_by_uuid(self, vm_uuid):
+        return self.call_xenapi('VM.get_by_uuid', vm_uuid)
+
+    def get_vbds(self, vm_uuid):
+        return self.call_xenapi('VM.get_VBDs', vm_uuid)
+
+
+class VBDOperations(OperationsBase):
+    def create(self, vm_ref, vdi_ref, userdevice, bootable, mode, type,
+               empty, other_config):
+        vbd_rec = dict(
+            VM=vm_ref,
+            VDI=vdi_ref,
+            userdevice=str(userdevice),
+            bootable=bootable,
+            mode=mode,
+            type=type,
+            empty=empty,
+            other_config=other_config,
+            qos_algorithm_type='',
+            qos_algorithm_params=dict()
+        )
+        return self.call_xenapi('VBD.create', vbd_rec)
+
+    def destroy(self, vbd_ref):
+        self.call_xenapi('VBD.destroy', vbd_ref)
+
+    def get_device(self, vbd_ref):
+        return self.call_xenapi('VBD.get_device', vbd_ref)
+
+    def plug(self, vbd_ref):
+        return self.call_xenapi('VBD.plug', vbd_ref)
+
+    def unplug(self, vbd_ref):
+        return self.call_xenapi('VBD.unplug', vbd_ref)
+
+    def get_vdi(self, vbd_ref):
+        return self.call_xenapi('VBD.get_VDI', vbd_ref)
+
+
+class PoolOperations(OperationsBase):
+    def get_all(self):
+        return self.call_xenapi('pool.get_all')
+
+    def get_default_SR(self, pool_ref):
+        return self.call_xenapi('pool.get_default_SR', pool_ref)
+
+
 class PbdOperations(OperationsBase):
     def get_all(self):
         return self.call_xenapi('PBD.get_all')
@@ -161,6 +211,9 @@ class XenAPISession(object):
         self.SR = SrOperations(self)
         self.VDI = VdiOperations(self)
         self.host = HostOperations(self)
+        self.pool = PoolOperations(self)
+        self.VBD = VBDOperations(self)
+        self.VM = VMOperations(self)
 
     def close(self):
         return self.call_xenapi('logout')
@@ -465,3 +518,25 @@ class NFSBasedVolumeOperations(object):
                 os.path.join(sr_base_path, sr_uuid), auth_token, dict())
         finally:
             self.disconnect_volume(vdi_uuid)
+
+    @contextlib.contextmanager
+    def volume_attached_here(self, server, serverpath, sr_uuid, vdi_uuid,
+                             readonly=True):
+        self.connect_volume(server, serverpath, sr_uuid, vdi_uuid)
+
+        with self._session_factory.get_session() as session:
+            vm_uuid = tools.get_this_vm_uuid()
+            vm_ref = session.VM.get_by_uuid(vm_uuid)
+            vdi_ref = session.VDI.get_by_uuid(vdi_uuid)
+            vbd_ref = session.VBD.create(
+                vm_ref, vdi_ref, userdevice='autodetect', bootable=False,
+                mode='RO' if readonly else 'RW', type='disk', empty=False,
+                other_config=dict())
+            session.VBD.plug(vbd_ref)
+            device = session.VBD.get_device(vbd_ref)
+            try:
+                yield "/dev/" + device
+            finally:
+                session.VBD.unplug(vbd_ref)
+                session.VBD.destroy(vbd_ref)
+                self.disconnect_volume(vdi_uuid)
index 8435eae0f47f75b4fad3f1eb1fdfc7edfe9e8515..e7749587556f381b9ea7317265023860f61319ce 100644 (file)
@@ -21,6 +21,7 @@ from oslo.config import cfg
 from cinder import exception
 from cinder import flags
 from cinder.image import glance
+from cinder.image import image_utils
 from cinder.openstack.common import log as logging
 from cinder.volume import driver
 from cinder.volume.drivers.xenapi import lib as xenapi_lib
@@ -155,6 +156,27 @@ class XenAPINFSDriver(driver.VolumeDriver):
         pass
 
     def copy_image_to_volume(self, context, volume, image_service, image_id):
+        if is_xenserver_image(context, image_service, image_id):
+            return self._use_glance_plugin_to_copy_image_to_volume(
+                context, volume, image_service, image_id)
+
+        return self._use_image_utils_to_pipe_bytes_to_volume(
+            context, volume, image_service, image_id)
+
+    def _use_image_utils_to_pipe_bytes_to_volume(self, context, volume,
+                                                 image_service, image_id):
+        sr_uuid, vdi_uuid = volume['provider_location'].split('/')
+        with self.nfs_ops.volume_attached_here(FLAGS.xenapi_nfs_server,
+                                               FLAGS.xenapi_nfs_serverpath,
+                                               sr_uuid, vdi_uuid,
+                                               False) as device:
+            image_utils.fetch_to_raw(context,
+                                     image_service,
+                                     image_id,
+                                     device)
+
+    def _use_glance_plugin_to_copy_image_to_volume(self, context, volume,
+                                                   image_service, image_id):
         sr_uuid, vdi_uuid = volume['provider_location'].split('/')
 
         api_servers = glance.get_api_servers()
@@ -212,3 +234,12 @@ class XenAPINFSDriver(driver.VolumeDriver):
                 reserved_percentage=0)
 
         return self._stats
+
+
+def is_xenserver_image(context, image_service, image_id):
+    image_meta = image_service.show(context, image_id)
+
+    return (
+        image_meta['disk_format'] == 'vhd'
+        and image_meta['container_format'] == 'ovf'
+    )
diff --git a/cinder/volume/drivers/xenapi/tools.py b/cinder/volume/drivers/xenapi/tools.py
new file mode 100644 (file)
index 0000000..d452fbf
--- /dev/null
@@ -0,0 +1,7 @@
+def _stripped_first_line_of(filename):
+    with open(filename, 'rb') as f:
+        return f.readline().strip()
+
+
+def get_this_vm_uuid():
+    return _stripped_first_line_of('/sys/hypervisor/uuid')
index aa1ef040269bc840d3003e4cca458967cf232a6f..29220438a50a797a82623c9d39da2913d5de4c1f 100644 (file)
@@ -2,6 +2,7 @@
 distribute>=0.6.28
 
 coverage
+mock
 mox>=0.5.3
 nose
 nosexcover