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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user