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:
parent
2abbe19ee3
commit
c9be1e4fa2
|
@ -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'
|
||||
|
|
|
@ -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."""
|
||||
|
|
Loading…
Reference in New Issue