Fix missing clone_image API support for sheepdog driver.

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
This commit is contained in:
Ray Chen 2014-07-10 23:01:17 +08:00 committed by YAMADA Hideki
parent 2abbe19ee3
commit c9be1e4fa2
2 changed files with 202 additions and 2 deletions

View 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'

View 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."""