]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Fix missing clone_image API support for sheepdog driver.
authorRay Chen <chenrano2002@gmail.com>
Thu, 10 Jul 2014 15:01:17 +0000 (23:01 +0800)
committerYAMADA Hideki <yamada.hideki@lab.ntt.co.jp>
Thu, 2 Apr 2015 13:17:08 +0000 (13:17 +0000)
If image is backend by sheepdog, it will use create_cloned_volume
function to help to clone this image. Normally sheepdog can clone
only from snapshot.

This patch set is derived from abandoned change:
Ibd355082e7c3215c00f6576404566577f7a2601c

Change-Id: I6f6834ff5443f2d1a21d4ae10e26e1cab3664a01
Closes-Bug: #1140177

cinder/tests/test_sheepdog.py
cinder/volume/drivers/sheepdog.py

index 70fa065bb484b8120b073af647f9af3ba23fb6c4..58267edeb11f23165b270dfee48c49034fd76266 100644 (file)
@@ -19,10 +19,14 @@ import contextlib
 
 import mock
 from oslo_concurrency import processutils
+from oslo_utils import importutils
 from oslo_utils import units
+import six
 
+from cinder import exception
 from cinder.image import image_utils
 from cinder import test
+from cinder.volume import configuration as conf
 from cinder.volume.drivers import sheepdog
 
 
@@ -58,7 +62,13 @@ class FakeImageService:
 class SheepdogTestCase(test.TestCase):
     def setUp(self):
         super(SheepdogTestCase, self).setUp()
-        self.driver = sheepdog.SheepdogDriver()
+        self.driver = sheepdog.SheepdogDriver(
+            configuration=conf.Configuration(None))
+
+        db_driver = self.driver.configuration.db_driver
+        self.db = importutils.import_module(db_driver)
+        self.driver.db = self.db
+        self.driver.do_setup(None)
 
     def test_update_volume_stats(self):
         def fake_stats(*args):
@@ -127,6 +137,124 @@ class SheepdogTestCase(test.TestCase):
                                                 'size': 1},
                                          FakeImageService(), None)
 
+    def test_create_cloned_volume(self):
+        src_vol = {
+            'project_id': 'testprjid',
+            'name': six.text_type('volume-00000001'),
+            'size': '20',
+            'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
+        }
+        target_vol = {
+            'project_id': 'testprjid',
+            'name': six.text_type('volume-00000002'),
+            'size': '20',
+            'id': '582a1efa-be6a-11e4-a73b-0aa186c60fe0',
+        }
+
+        with mock.patch.object(self.driver,
+                               '_try_execute') as mock_exe:
+            self.driver.create_cloned_volume(target_vol, src_vol)
+
+            snapshot_name = src_vol['name'] + '-temp-snapshot'
+            qemu_src_volume_name = "sheepdog:%s" % src_vol['name']
+            qemu_snapshot_name = '%s:%s' % (qemu_src_volume_name,
+                                            snapshot_name)
+            qemu_target_volume_name = "sheepdog:%s" % target_vol['name']
+            calls = [
+                mock.call('qemu-img', 'snapshot', '-c',
+                          snapshot_name, qemu_src_volume_name),
+                mock.call('qemu-img', 'create', '-b',
+                          qemu_snapshot_name,
+                          qemu_target_volume_name,
+                          '%sG' % target_vol['size']),
+            ]
+            mock_exe.assert_has_calls(calls)
+
+    def test_create_cloned_volume_failure(self):
+        fake_name = six.text_type('volume-00000001')
+        fake_size = '20'
+        fake_vol = {'project_id': 'testprjid', 'name': fake_name,
+                    'size': fake_size,
+                    'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+        src_vol = fake_vol
+
+        patch = mock.patch.object
+        with patch(self.driver, '_try_execute',
+                   side_effect=processutils.ProcessExecutionError):
+            with patch(self.driver, 'create_snapshot'):
+                with patch(self.driver, 'delete_snapshot'):
+                    self.assertRaises(exception.VolumeBackendAPIException,
+                                      self.driver.create_cloned_volume,
+                                      fake_vol,
+                                      src_vol)
+
+    def test_clone_image_success(self):
+        context = {}
+        fake_name = six.text_type('volume-00000001')
+        fake_size = '2'
+        fake_vol = {'project_id': 'testprjid', 'name': fake_name,
+                    'size': fake_size,
+                    'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'}
+
+        image_location = ('sheepdog:192.168.1.111:7000:Alice', None)
+        image_id = "caa4ffd0-fake-fake-fake-f8631a807f5a"
+        image_meta = {'id': image_id, 'size': 1, 'disk_format': 'raw'}
+        image_service = ''
+
+        patch = mock.patch.object
+        with patch(self.driver, '_try_execute', return_value=True):
+            with patch(self.driver, 'create_cloned_volume'):
+                with patch(self.driver, '_resize'):
+                    model_updated, cloned = self.driver.clone_image(
+                        context, fake_vol, image_location,
+                        image_meta, image_service)
+
+        self.assertTrue(cloned)
+        self.assertEqual("sheepdog:%s" % fake_name,
+                         model_updated['provider_location'])
+
+    def test_clone_image_failure(self):
+        context = {}
+        fake_vol = {}
+        image_location = ('image_location', None)
+        image_meta = {}
+        image_service = ''
+
+        with mock.patch.object(self.driver, '_is_cloneable',
+                               lambda *args: False):
+            result = self.driver.clone_image(
+                context, fake_vol, image_location, image_meta, image_service)
+            self.assertEqual(({}, False), result)
+
+    def test_is_cloneable(self):
+        uuid = '87f1b01c-f46c-4537-bd5d-23962f5f4316'
+        location = 'sheepdog:ip:port:%s' % uuid
+        image_meta = {'id': uuid, 'size': 1, 'disk_format': 'raw'}
+        invalid_image_meta = {'id': uuid, 'size': 1, 'disk_format': 'iso'}
+
+        with mock.patch.object(self.driver, '_try_execute') as try_execute:
+            self.assertTrue(
+                self.driver._is_cloneable(location, image_meta))
+            expected_cmd = ('collie', 'vdi', 'list',
+                            '--address', 'ip',
+                            '--port', 'port',
+                            uuid)
+            try_execute.assert_called_once_with(*expected_cmd)
+
+            # check returning False without executing a command
+            self.assertFalse(
+                self.driver._is_cloneable('invalid-location', image_meta))
+            self.assertFalse(
+                self.driver._is_cloneable(location, invalid_image_meta))
+            self.assertEqual(1, try_execute.call_count)
+
+        error = processutils.ProcessExecutionError
+        with mock.patch.object(self.driver, '_try_execute',
+                               side_effect=error) as fail_try_execute:
+            self.assertFalse(
+                self.driver._is_cloneable(location, image_meta))
+            fail_try_execute.assert_called_once_with(*expected_cmd)
+
     def test_extend_volume(self):
         fake_name = u'volume-00000001'
         fake_size = '20'
index 21d46f9b1044aaeebf980bbe236436a4ae46a91f..4bacc42617882e9833d5a9e2261a79d4d2ebbb57 100644 (file)
@@ -63,8 +63,80 @@ class SheepdogDriver(driver.VolumeDriver):
             exception_message = _("Sheepdog is not working")
             raise exception.VolumeBackendAPIException(data=exception_message)
 
+    def _is_cloneable(self, image_location, image_meta):
+        """Check the image can be clone or not."""
+
+        if image_location is None:
+            return False
+
+        if not image_location.startswith("sheepdog:"):
+            LOG.debug("Image is not stored in sheepdog.")
+            return False
+
+        if image_meta['disk_format'] != 'raw':
+            LOG.debug("Image clone requires image format to be "
+                      "'raw' but image %s(%s) is '%s'.",
+                      image_location,
+                      image_meta['id'],
+                      image_meta['disk_format'])
+            return False
+
+        cloneable = False
+        # check whether volume is stored in sheepdog
+        try:
+            # The image location would be like
+            # "sheepdog:192.168.10.2:7000:Alice"
+            (label, ip, port, name) = image_location.split(":", 3)
+
+            self._try_execute('collie', 'vdi', 'list', '--address', ip,
+                              '--port', port, name)
+            cloneable = True
+        except processutils.ProcessExecutionError as e:
+            LOG.debug("Can not find vdi %(image)s: %(err)s",
+                      {'image': name, 'err': e})
+
+        return cloneable
+
+    def clone_image(self, context, volume,
+                    image_location, image_meta,
+                    image_service):
+        """Create a volume efficiently from an existing image."""
+        image_location = image_location[0] if image_location else None
+        if not self._is_cloneable(image_location, image_meta):
+            return {}, False
+
+        # The image location would be like
+        # "sheepdog:192.168.10.2:7000:Alice"
+        (label, ip, port, name) = image_location.split(":", 3)
+        volume_ref = {'name': name, 'size': image_meta['size']}
+        self.create_cloned_volume(volume, volume_ref)
+        self._resize(volume)
+
+        vol_path = self.local_path(volume)
+        return {'provider_location': vol_path}, True
+
     def create_cloned_volume(self, volume, src_vref):
-        raise NotImplementedError()
+        """Clone a sheepdog volume from another volume."""
+
+        snapshot_name = src_vref['name'] + '-temp-snapshot'
+        snapshot = {
+            'name': snapshot_name,
+            'volume_name': src_vref['name'],
+            'volume_size': src_vref['size'],
+        }
+
+        self.create_snapshot(snapshot)
+
+        try:
+            # Create volume
+            self.create_volume_from_snapshot(volume, snapshot)
+        except processutils.ProcessExecutionError:
+            msg = _('Failed to create cloned volume %s.') % volume['id']
+            LOG.error(msg)
+            raise exception.VolumeBackendAPIException(msg)
+        finally:
+            # Delete temp Snapshot
+            self.delete_snapshot(snapshot)
 
     def create_volume(self, volume):
         """Create a sheepdog volume."""