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.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'},
|
||||
{'cluster_name': 'volumes', 'pool_name': None})
|
||||
@ddt.unpack
|
||||
@ -1608,6 +1618,90 @@ class RBDTestCase(test.TestCase):
|
||||
mock_exec.assert_called_once_with(self.volume_a.name, remote,
|
||||
'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):
|
||||
driver_name = "cinder.volume.drivers.rbd.RBDDriver"
|
||||
|
@ -1322,3 +1322,71 @@ class RBDDriver(driver.CloneableImageVD,
|
||||
|
||||
def migrate_volume(self, context, volume, host):
|
||||
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