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:
wanghao 2017-03-21 17:59:51 +08:00
parent 5f66f158bf
commit e5abf57fe9
3 changed files with 165 additions and 0 deletions

View File

@ -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"

View File

@ -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))

View File

@ -0,0 +1,3 @@
---
features:
- Allow rbd driver to manage existing snapshot.