Ceph rbd volume manage/unmanage support

Ceph rbd support for managing and unmanaging volumes.
Partially Implements: blueprint add-export-import-volumes

Change-Id: I272f65621a3dac50a154b7f2caa545ae6fb0e54f
This commit is contained in:
ling-yun
2014-05-29 20:36:20 +08:00
parent 2f7c404216
commit 15c715eed8
2 changed files with 138 additions and 0 deletions

View File

@@ -58,6 +58,10 @@ class MockImageBusyException(MockException):
"""Used as mock for rbd.ImageBusy."""
class MockImageExistsException(MockException):
"""Used as mock for rbd.ImageExists."""
def common_mocks(f):
"""Decorator to set mocks common to all tests.
@@ -84,6 +88,7 @@ def common_mocks(f):
inst.mock_rados.Error = Exception
inst.mock_rbd.ImageBusy = MockImageBusyException
inst.mock_rbd.ImageNotFound = MockImageNotFoundException
inst.mock_rbd.ImageExists = MockImageExistsException
inst.driver.rbd = inst.mock_rbd
inst.driver.rados = inst.mock_rados
@@ -175,6 +180,76 @@ class RBDTestCase(test.TestCase):
client.__exit__.assert_called_once()
mock_supports_layering.assert_called_once()
@common_mocks
def test_manage_existing_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 = {'rbd_name': self.volume_name}
return_size = self.driver.manage_existing_get_size(
self.volume,
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_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 = {'rbd_name': self.volume_name}
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.manage_existing_get_size,
self.volume, existing_ref)
mock_rbd_image_size.assert_called_once_with()
mock_rbd_image_close.assert_called_once_with()
@common_mocks
def test_manage_existing(self):
client = self.mock_client.return_value
client.__enter__.return_value = client
with mock.patch.object(driver, 'RADOSClient') as mock_rados_client:
with mock.patch.object(self.driver.rbd.RBD(), 'rename') as \
mock_rbd_image_rename:
exist_volume = 'vol-exist'
existing_ref = {'rbd_name': exist_volume}
mock_rbd_image_rename.return_value = 0
mock_rbd_image_rename(mock_rados_client.ioctx,
exist_volume,
self.volume_name)
self.driver.manage_existing(self.volume, existing_ref)
mock_rbd_image_rename.assert_called_with(
mock_rados_client.ioctx,
exist_volume,
self.volume_name)
@common_mocks
def test_manage_existing_with_exist_rbd_image(self):
client = self.mock_client.return_value
client.__enter__.return_value = client
self.mock_rbd.Image.rename = mock.Mock()
self.mock_rbd.Image.rename.side_effect = \
MockImageExistsException
exist_volume = 'vol-exist'
existing_ref = {'rbd_name': exist_volume}
self.assertRaises(self.mock_rbd.ImageExists,
self.driver.manage_existing,
self.volume, existing_ref)
#make sure the exception was raised
self.assertEqual(RAISED_EXCEPTIONS,
[self.mock_rbd.ImageExists])
@common_mocks
def test_create_volume_no_layering(self):
client = self.mock_client.return_value

View File

@@ -859,3 +859,66 @@ class RBDDriver(driver.VolumeDriver):
LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.",
{'old_size': old_size, 'new_size': new_size})
def manage_existing(self, volume, existing_ref):
"""Manages an existing image.
Renames the image name to match the expected name for the volume.
Error checking done by manage_existing_get_size is not repeated.
:param volume:
volume ref info to be set
:param existing_ref:
existing_ref is a dictionary of the form:
{'rbd_name': <name of rbd image>}
"""
# Raise an exception if we didn't find a suitable rbd image.
with RADOSClient(self) as client:
rbd_name = existing_ref['rbd_name']
self.rbd.RBD().rename(client.ioctx, strutils.safe_encode(rbd_name),
strutils.safe_encode(volume['name']))
def manage_existing_get_size(self, volume, existing_ref):
"""Return size of an existing image for manage_existing.
:param volume:
volume ref info to be set
:param existing_ref:
existing_ref is a dictionary of the form:
{'rbd_name': <name of rbd image>}
"""
# Check that the reference is valid
if 'rbd_name' not in existing_ref:
reason = _('Reference must contain rbd_name element.')
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=reason)
rbd_name = strutils.safe_encode(existing_ref['rbd_name'])
with RADOSClient(self) as client:
# Raise an exception if we didn't find a suitable rbd image.
try:
rbd_image = self.rbd.Image(client.ioctx, rbd_name)
image_size = rbd_image.size()
except self.rbd.ImageNotFound:
kwargs = {'existing_ref': rbd_name,
'reason': 'Specified rbd image does not exist.'}
raise exception.ManageExistingInvalidReference(**kwargs)
finally:
rbd_image.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(int(image_size))) / units.Gi
return convert_size
except ValueError:
exception_message = (_("Failed to manage existing volume "
"%(name)s, because reported size "
"%(size)s was not a floating-point"
" number.")
% {'name': rbd_name,
'size': image_size})
raise exception.VolumeBackendAPIException(
data=exception_message)