RBD: Implement volume extension

The RBD connector doesn't implement the extend_volume method and raise
NotImplementedError when called.

This patch implements the method for all types of connected volumes:

- File descriptor
- OpenStack locally attached
- Non OpenStack locally attached

Closes-Bug: #1884554
Related-Bug: #1883720
Change-Id: I4bbf3778210d05c6934289116a85a8f344bed818
This commit is contained in:
Gorka Eguileor 2020-06-18 21:23:35 +02:00
parent 7d995b6cf6
commit 29752fb54b
3 changed files with 111 additions and 6 deletions

View File

@ -384,5 +384,45 @@ class RBDConnector(base.BaseLinuxConnector):
return self._check_valid_device(path)
def extend_volume(self, connection_properties):
# TODO(walter-boring): is this possible?
raise NotImplementedError
"""Refresh local volume view and return current size in bytes."""
# Nothing to do, RBD attached volumes are automatically refreshed, but
# we need to return the new size for compatibility
do_local_attach = connection_properties.get('do_local_attach',
self.do_local_attach)
if not do_local_attach:
handle = self._get_rbd_handle(connection_properties)
try:
# Handles should return absolute position on seek, but the RBD
# wrapper doesn't, so we need to call tell afterwards
handle.seek(0, 2)
return handle.tell()
finally:
fileutils.delete_if_exists(handle.rbd_conf)
handle.close()
# Create config file when we do the attach on the host and not the VM
conf = self.create_non_openstack_config(connection_properties)
try:
device_path = self._find_root_device(connection_properties, conf)
finally:
# If we have generated the config file we need to remove it
if conf:
try:
rbd_privsep.delete_if_exists(conf)
except Exception as exc:
LOG.warning(_('Could not remove config file %(filename)s: '
'%(exc)s'), {'filename': conf, 'exc': exc})
if not device_path:
msg = _('Cannot extend non mapped device.')
raise exception.BrickException(msg=msg)
device_name = os.path.basename(device_path) # ie: rbd0
device_number = device_name[3:] # ie: 0
# Get size from /sys/devices/rbd/0/size instead of
# /sys/class/block/rbd0/size because the latter isn't updated
with open('/sys/devices/rbd/' + device_number + '/size') as f:
size_bytes = f.read().strip()
return int(size_bytes)

View File

@ -419,12 +419,72 @@ class RBDConnectorTestCase(test_connector.ConnectorTestCase):
mock_execute.called_once_with(*show_cmd, root_helper=None,
run_as_root=True)
def test_extend_volume(self):
rbd_connector = rbd.RBDConnector(None)
self.assertRaises(NotImplementedError,
rbd_connector.extend_volume,
@mock.patch('oslo_utils.fileutils.delete_if_exists')
@mock.patch.object(rbd.RBDConnector, '_get_rbd_handle')
def test_extend_volume_handle(self, mock_handle, mock_delete):
connector = rbd.RBDConnector(None)
res = connector.extend_volume(self.connection_properties)
mock_handle.assert_called_once_with(self.connection_properties)
mock_handle.return_value.seek.assert_called_once_with(0, 2)
mock_handle.return_value.tell.assert_called_once_with()
self.assertIs(mock_handle().tell(), res)
mock_delete.assert_called_once_with(mock_handle().rbd_conf)
mock_handle.return_value.close.assert_called_once_with()
@mock.patch('oslo_utils.fileutils.delete_if_exists')
@mock.patch.object(rbd.RBDConnector, '_get_rbd_handle')
def test_extend_volume_handle_fail(self, mock_handle, mock_delete):
mock_handle.return_value.seek.side_effect = ValueError
connector = rbd.RBDConnector(None)
self.assertRaises(ValueError, connector.extend_volume,
self.connection_properties)
mock_handle.assert_called_once_with(self.connection_properties)
mock_handle.return_value.seek.assert_called_once_with(0, 2)
mock_handle().tell.assert_not_called()
mock_delete.assert_called_once_with(mock_handle.return_value.rbd_conf)
mock_handle.return_value.close.assert_called_once_with()
@mock.patch.object(rbd, 'open')
@mock.patch('os_brick.privileged.rbd.delete_if_exists')
@mock.patch.object(rbd.RBDConnector, '_find_root_device')
@mock.patch.object(rbd.RBDConnector, 'create_non_openstack_config')
def test_extend_volume_block(self, mock_config, mock_find, mock_delete,
mock_open):
mock_find.return_value = '/dev/rbd1'
file_handle = mock_open.return_value.__enter__.return_value
file_handle.read.return_value = '123456789'
connector = rbd.RBDConnector(None, do_local_attach=True)
res = connector.extend_volume(self.connection_properties)
mock_config.assert_called_once_with(self.connection_properties)
mock_find.assert_called_once_with(self.connection_properties,
mock_config.return_value)
mock_delete.assert_called_once_with(mock_config.return_value)
mock_open.assert_called_once_with('/sys/devices/rbd/1/size')
file_handle.read.assert_called_once_with()
self.assertEqual(123456789, res)
@mock.patch.object(rbd, 'open')
@mock.patch('os_brick.privileged.rbd.delete_if_exists')
@mock.patch.object(rbd.RBDConnector, '_find_root_device')
@mock.patch.object(rbd.RBDConnector, 'create_non_openstack_config')
def test_extend_volume_no_device_local(self, mock_config, mock_find,
mock_delete, mock_open):
mock_find.return_value = None
connector = rbd.RBDConnector(None, do_local_attach=True)
self.assertRaises(exception.BrickException, connector.extend_volume,
self.connection_properties)
mock_config.assert_called_once_with(self.connection_properties)
mock_find.assert_called_once_with(self.connection_properties,
mock_config.return_value)
mock_delete.assert_called_once_with(mock_config.return_value)
mock_open.assert_not_called()
def test__get_rbd_args(self):
res = rbd.RBDConnector._get_rbd_args(self.connection_properties, None)
expected = ['--id', self.user,

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Implement the extend_volume method for the RBD connector.
(Bug #1884554).