Introduce managing existing snapshot to rbd driver
Now Ceph can support to rename snapshot name, so managing existing snapshot will be introduced. Change-Id: Ia65c3d1de5a30251ec83a8ec6a8c381ba68e5c9f Implements: bp driver-rbd-to-support-managing-existing-snapshot
This commit is contained in:
parent
5f66f158bf
commit
e5abf57fe9
@ -187,6 +187,16 @@ class RBDTestCase(test.TestCase):
|
|||||||
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
self.snapshot = fake_snapshot.fake_snapshot_obj(
|
||||||
self.context, name='snapshot-0000000a')
|
self.context, name='snapshot-0000000a')
|
||||||
|
|
||||||
|
self.snapshot_b = fake_snapshot.fake_snapshot_obj(
|
||||||
|
self.context,
|
||||||
|
**{'name': u'snapshot-0000000n',
|
||||||
|
'expected_attrs': ['volume'],
|
||||||
|
'volume': {'id': fake.VOLUME_ID,
|
||||||
|
'name': 'cinder-volume',
|
||||||
|
'size': 128,
|
||||||
|
'host': 'host@fakebackend#fakepool'}
|
||||||
|
})
|
||||||
|
|
||||||
@ddt.data({'cluster_name': None, 'pool_name': 'rbd'},
|
@ddt.data({'cluster_name': None, 'pool_name': 'rbd'},
|
||||||
{'cluster_name': 'volumes', 'pool_name': None})
|
{'cluster_name': 'volumes', 'pool_name': None})
|
||||||
@ddt.unpack
|
@ddt.unpack
|
||||||
@ -1608,6 +1618,90 @@ class RBDTestCase(test.TestCase):
|
|||||||
mock_exec.assert_called_once_with(self.volume_a.name, remote,
|
mock_exec.assert_called_once_with(self.volume_a.name, remote,
|
||||||
'mirror_image_promote', False)
|
'mirror_image_promote', False)
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot_get_size(self):
|
||||||
|
with mock.patch.object(self.driver.rbd.Image(), 'size') as \
|
||||||
|
mock_rbd_image_size:
|
||||||
|
with mock.patch.object(self.driver.rbd.Image(), 'close') \
|
||||||
|
as mock_rbd_image_close:
|
||||||
|
mock_rbd_image_size.return_value = 2 * units.Gi
|
||||||
|
existing_ref = {'source-name': self.snapshot_b.name}
|
||||||
|
return_size = self.driver.manage_existing_snapshot_get_size(
|
||||||
|
self.snapshot_b,
|
||||||
|
existing_ref)
|
||||||
|
self.assertEqual(2, return_size)
|
||||||
|
mock_rbd_image_size.assert_called_once_with()
|
||||||
|
mock_rbd_image_close.assert_called_once_with()
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot_get_non_integer_size(self):
|
||||||
|
rbd_snapshot = self.driver.rbd.Image.return_value
|
||||||
|
rbd_snapshot.size.return_value = int(1.75 * units.Gi)
|
||||||
|
existing_ref = {'source-name': self.snapshot_b.name}
|
||||||
|
return_size = self.driver.manage_existing_snapshot_get_size(
|
||||||
|
self.snapshot_b, existing_ref)
|
||||||
|
self.assertEqual(2, return_size)
|
||||||
|
rbd_snapshot.size.assert_called_once_with()
|
||||||
|
rbd_snapshot.close.assert_called_once_with()
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot_get_invalid_size(self):
|
||||||
|
|
||||||
|
with mock.patch.object(self.driver.rbd.Image(), 'size') as \
|
||||||
|
mock_rbd_image_size:
|
||||||
|
with mock.patch.object(self.driver.rbd.Image(), 'close') \
|
||||||
|
as mock_rbd_image_close:
|
||||||
|
mock_rbd_image_size.return_value = 'abcd'
|
||||||
|
existing_ref = {'source-name': self.snapshot_b.name}
|
||||||
|
self.assertRaises(
|
||||||
|
exception.VolumeBackendAPIException,
|
||||||
|
self.driver.manage_existing_snapshot_get_size,
|
||||||
|
self.snapshot_b, existing_ref)
|
||||||
|
|
||||||
|
mock_rbd_image_size.assert_called_once_with()
|
||||||
|
mock_rbd_image_close.assert_called_once_with()
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot_with_invalid_rbd_image(self):
|
||||||
|
self.mock_rbd.Image.side_effect = self.mock_rbd.ImageNotFound
|
||||||
|
|
||||||
|
invalid_snapshot = 'snapshot-invalid'
|
||||||
|
invalid_ref = {'source-name': invalid_snapshot}
|
||||||
|
|
||||||
|
self.assertRaises(exception.ManageExistingInvalidReference,
|
||||||
|
self.driver.manage_existing_snapshot_get_size,
|
||||||
|
self.snapshot_b, invalid_ref)
|
||||||
|
# Make sure the exception was raised
|
||||||
|
self.assertEqual([self.mock_rbd.ImageNotFound],
|
||||||
|
RAISED_EXCEPTIONS)
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot(self):
|
||||||
|
proxy = self.mock_proxy.return_value
|
||||||
|
proxy.__enter__.return_value = proxy
|
||||||
|
exist_snapshot = 'snapshot-exist'
|
||||||
|
existing_ref = {'source-name': exist_snapshot}
|
||||||
|
proxy.rename_snap.return_value = 0
|
||||||
|
self.driver.manage_existing_snapshot(self.snapshot_b, existing_ref)
|
||||||
|
proxy.rename_snap.assert_called_with(exist_snapshot,
|
||||||
|
self.snapshot_b.name)
|
||||||
|
|
||||||
|
@common_mocks
|
||||||
|
def test_manage_existing_snapshot_with_exist_rbd_image(self):
|
||||||
|
proxy = self.mock_proxy.return_value
|
||||||
|
proxy.__enter__.return_value = proxy
|
||||||
|
proxy.rename_snap.side_effect = MockImageExistsException
|
||||||
|
|
||||||
|
exist_snapshot = 'snapshot-exist'
|
||||||
|
existing_ref = {'source-name': exist_snapshot}
|
||||||
|
self.assertRaises(self.mock_rbd.ImageExists,
|
||||||
|
self.driver.manage_existing_snapshot,
|
||||||
|
self.snapshot_b, existing_ref)
|
||||||
|
|
||||||
|
# Make sure the exception was raised
|
||||||
|
self.assertEqual(RAISED_EXCEPTIONS,
|
||||||
|
[self.mock_rbd.ImageExists])
|
||||||
|
|
||||||
|
|
||||||
class ManagedRBDTestCase(test_driver.BaseDriverTestCase):
|
class ManagedRBDTestCase(test_driver.BaseDriverTestCase):
|
||||||
driver_name = "cinder.volume.drivers.rbd.RBDDriver"
|
driver_name = "cinder.volume.drivers.rbd.RBDDriver"
|
||||||
|
@ -1322,3 +1322,71 @@ class RBDDriver(driver.CloneableImageVD,
|
|||||||
|
|
||||||
def migrate_volume(self, context, volume, host):
|
def migrate_volume(self, context, volume, host):
|
||||||
return (False, None)
|
return (False, None)
|
||||||
|
|
||||||
|
def manage_existing_snapshot_get_size(self, snapshot, existing_ref):
|
||||||
|
"""Return size of an existing image for manage_existing.
|
||||||
|
|
||||||
|
:param snapshot:
|
||||||
|
snapshot ref info to be set
|
||||||
|
:param existing_ref:
|
||||||
|
existing_ref is a dictionary of the form:
|
||||||
|
{'source-name': <name of snapshot>}
|
||||||
|
"""
|
||||||
|
# Check that the reference is valid
|
||||||
|
if not isinstance(existing_ref, dict):
|
||||||
|
existing_ref = {"source-name": existing_ref}
|
||||||
|
if 'source-name' not in existing_ref:
|
||||||
|
reason = _('Reference must contain source-name element.')
|
||||||
|
raise exception.ManageExistingInvalidReference(
|
||||||
|
existing_ref=existing_ref, reason=reason)
|
||||||
|
|
||||||
|
volume_name = utils.convert_str(snapshot.volume_name)
|
||||||
|
snapshot_name = utils.convert_str(existing_ref['source-name'])
|
||||||
|
|
||||||
|
with RADOSClient(self) as client:
|
||||||
|
# Raise an exception if we didn't find a suitable rbd image.
|
||||||
|
try:
|
||||||
|
rbd_snapshot = self.rbd.Image(client.ioctx, volume_name,
|
||||||
|
snapshot=snapshot_name)
|
||||||
|
except self.rbd.ImageNotFound:
|
||||||
|
kwargs = {'existing_ref': snapshot_name,
|
||||||
|
'reason': 'Specified snapshot does not exist.'}
|
||||||
|
raise exception.ManageExistingInvalidReference(**kwargs)
|
||||||
|
|
||||||
|
snapshot_size = rbd_snapshot.size()
|
||||||
|
rbd_snapshot.close()
|
||||||
|
|
||||||
|
# RBD image size is returned in bytes. Attempt to parse
|
||||||
|
# size as a float and round up to the next integer.
|
||||||
|
try:
|
||||||
|
convert_size = int(math.ceil(float(snapshot_size) / units.Gi))
|
||||||
|
return convert_size
|
||||||
|
except ValueError:
|
||||||
|
exception_message = (_("Failed to manage existing snapshot "
|
||||||
|
"%(name)s, because reported size "
|
||||||
|
"%(size)s was not a floating-point"
|
||||||
|
" number.")
|
||||||
|
% {'name': snapshot_name,
|
||||||
|
'size': snapshot_size})
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
data=exception_message)
|
||||||
|
|
||||||
|
def manage_existing_snapshot(self, snapshot, existing_ref):
|
||||||
|
"""Manages an existing snapshot.
|
||||||
|
|
||||||
|
Renames the snapshot name to match the expected name for the snapshot.
|
||||||
|
Error checking done by manage_existing_get_size is not repeated.
|
||||||
|
|
||||||
|
:param snapshot:
|
||||||
|
snapshot ref info to be set
|
||||||
|
:param existing_ref:
|
||||||
|
existing_ref is a dictionary of the form:
|
||||||
|
{'source-name': <name of rbd snapshot>}
|
||||||
|
"""
|
||||||
|
if not isinstance(existing_ref, dict):
|
||||||
|
existing_ref = {"source-name": existing_ref}
|
||||||
|
volume_name = utils.convert_str(snapshot.volume_name)
|
||||||
|
with RBDVolumeProxy(self, volume_name) as volume:
|
||||||
|
snapshot_name = existing_ref['source-name']
|
||||||
|
volume.rename_snap(utils.convert_str(snapshot_name),
|
||||||
|
utils.convert_str(snapshot.name))
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Allow rbd driver to manage existing snapshot.
|
Loading…
Reference in New Issue
Block a user