Merge "Enhance extend functionality"
This commit is contained in:
commit
f7d41144c7
@ -148,6 +148,13 @@ class RBDConnector(connectors.rbd.RBDConnector):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _get_vol_data(self, connection_properties):
|
||||||
|
self._setup_rbd_class()
|
||||||
|
pool, volume = connection_properties['name'].split('/')
|
||||||
|
link_name = self.get_rbd_device_name(pool, volume)
|
||||||
|
real_dev_path = os.path.realpath(link_name)
|
||||||
|
return link_name, real_dev_path
|
||||||
|
|
||||||
def _unmap(self, real_dev_path, conf_file, connection_properties):
|
def _unmap(self, real_dev_path, conf_file, connection_properties):
|
||||||
if os.path.exists(real_dev_path):
|
if os.path.exists(real_dev_path):
|
||||||
cmd = ['rbd', 'unmap', real_dev_path, '--conf', conf_file]
|
cmd = ['rbd', 'unmap', real_dev_path, '--conf', conf_file]
|
||||||
@ -157,11 +164,8 @@ class RBDConnector(connectors.rbd.RBDConnector):
|
|||||||
|
|
||||||
def disconnect_volume(self, connection_properties, device_info,
|
def disconnect_volume(self, connection_properties, device_info,
|
||||||
force=False, ignore_errors=False):
|
force=False, ignore_errors=False):
|
||||||
self._setup_rbd_class()
|
|
||||||
pool, volume = connection_properties['name'].split('/')
|
|
||||||
conf_file = device_info['conf']
|
conf_file = device_info['conf']
|
||||||
link_name = self.get_rbd_device_name(pool, volume)
|
link_name, real_dev_path = self._get_vol_data(connection_properties)
|
||||||
real_dev_path = os.path.realpath(link_name)
|
|
||||||
|
|
||||||
self._unmap(real_dev_path, conf_file, connection_properties)
|
self._unmap(real_dev_path, conf_file, connection_properties)
|
||||||
if self.containerized:
|
if self.containerized:
|
||||||
@ -193,6 +197,20 @@ class RBDConnector(connectors.rbd.RBDConnector):
|
|||||||
# Don't check again to speed things on following connections
|
# Don't check again to speed things on following connections
|
||||||
RBDConnector._setup_rbd_class = lambda *args: None
|
RBDConnector._setup_rbd_class = lambda *args: None
|
||||||
|
|
||||||
|
def extend_volume(self, connection_properties):
|
||||||
|
"""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
|
||||||
|
link_name, real_dev_path = self._get_vol_data(connection_properties)
|
||||||
|
|
||||||
|
device_name = os.path.basename(real_dev_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)
|
||||||
|
|
||||||
_setup_rbd_class = _setup_class
|
_setup_rbd_class = _setup_class
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ DEFAULT_PROJECT_ID = 'cinderlib'
|
|||||||
DEFAULT_USER_ID = 'cinderlib'
|
DEFAULT_USER_ID = 'cinderlib'
|
||||||
BACKEND_NAME_SNAPSHOT_FIELD = 'progress'
|
BACKEND_NAME_SNAPSHOT_FIELD = 'progress'
|
||||||
CONNECTIONS_OVO_FIELD = 'volume_attachment'
|
CONNECTIONS_OVO_FIELD = 'volume_attachment'
|
||||||
|
GB = 1024 ** 3
|
||||||
|
|
||||||
# This cannot go in the setup method because cinderlib objects need them to
|
# This cannot go in the setup method because cinderlib objects need them to
|
||||||
# be setup to set OVO_CLASS
|
# be setup to set OVO_CLASS
|
||||||
@ -500,6 +501,11 @@ class Volume(NamedObject):
|
|||||||
finally:
|
finally:
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
if volume.status == 'in-use' and self.local_attach:
|
||||||
|
return self.local_attach.extend()
|
||||||
|
# Must return size in bytes
|
||||||
|
return size * GB
|
||||||
|
|
||||||
def clone(self, **new_vol_attrs):
|
def clone(self, **new_vol_attrs):
|
||||||
new_vol_attrs['source_vol_id'] = self.id
|
new_vol_attrs['source_vol_id'] = self.id
|
||||||
new_vol = Volume(self, **new_vol_attrs)
|
new_vol = Volume(self, **new_vol_attrs)
|
||||||
@ -867,6 +873,9 @@ class Connection(Object, LazyVolumeAttr):
|
|||||||
def save(self):
|
def save(self):
|
||||||
self.persistence.set_connection(self)
|
self.persistence.set_connection(self)
|
||||||
|
|
||||||
|
def extend(self):
|
||||||
|
return self.connector.extend_volume(self.conn_info['data'])
|
||||||
|
|
||||||
|
|
||||||
class Snapshot(NamedObject, LazyVolumeAttr):
|
class Snapshot(NamedObject, LazyVolumeAttr):
|
||||||
OVO_CLASS = cinder_objs.Snapshot
|
OVO_CLASS = cinder_objs.Snapshot
|
||||||
|
@ -179,6 +179,28 @@ class BackendFunctBasic(base_tests.BaseFunctTestCase):
|
|||||||
result_new_size = self._get_vol_size(vol)
|
result_new_size = self._get_vol_size(vol)
|
||||||
self.assertSize(new_size, result_new_size)
|
self.assertSize(new_size, result_new_size)
|
||||||
|
|
||||||
|
def test_extend_attached(self):
|
||||||
|
vol = self._create_vol(self.backend)
|
||||||
|
original_size = vol.size
|
||||||
|
# Attach, get size, and leave volume attached
|
||||||
|
result_original_size = self._get_vol_size(vol, do_detach=False)
|
||||||
|
self.assertSize(original_size, result_original_size)
|
||||||
|
|
||||||
|
new_size = vol.size + 1
|
||||||
|
# Extending the volume should also extend the local view of the volume
|
||||||
|
reported_size = vol.extend(new_size)
|
||||||
|
|
||||||
|
# The instance size must have been updated
|
||||||
|
self.assertEqual(new_size, vol.size)
|
||||||
|
self.assertEqual(new_size, vol._ovo.size)
|
||||||
|
|
||||||
|
# Returned size must match the requested one
|
||||||
|
self.assertEqual(new_size * (1024 ** 3), reported_size)
|
||||||
|
|
||||||
|
# Get size of attached volume on the host and detach it
|
||||||
|
result_new_size = self._get_vol_size(vol)
|
||||||
|
self.assertSize(new_size, result_new_size)
|
||||||
|
|
||||||
def test_clone(self):
|
def test_clone(self):
|
||||||
vol = self._create_vol(self.backend)
|
vol = self._create_vol(self.backend)
|
||||||
original_size = self._get_vol_size(vol, do_detach=False)
|
original_size = self._get_vol_size(vol, do_detach=False)
|
||||||
|
@ -282,3 +282,10 @@ class TestConnection(base.BaseTest):
|
|||||||
def test_connected(self, value):
|
def test_connected(self, value):
|
||||||
with mock.patch('cinderlib.objects.Connection.conn_info', value):
|
with mock.patch('cinderlib.objects.Connection.conn_info', value):
|
||||||
self.assertEqual(value, self.conn.connected)
|
self.assertEqual(value, self.conn.connected)
|
||||||
|
|
||||||
|
def test_extend(self):
|
||||||
|
self.conn._ovo.connection_info['conn'] = {'data': mock.sentinel.data}
|
||||||
|
with mock.patch('cinderlib.objects.Connection.connector') as mock_conn:
|
||||||
|
res = self.conn.extend()
|
||||||
|
mock_conn.extend_volume.assert_called_once_with(mock.sentinel.data)
|
||||||
|
self.assertEqual(mock_conn.extend_volume.return_value, res)
|
||||||
|
@ -173,13 +173,26 @@ class TestVolume(base.BaseTest):
|
|||||||
|
|
||||||
def test_extend(self):
|
def test_extend(self):
|
||||||
vol = objects.Volume(self.backend_name, status='available', size=10)
|
vol = objects.Volume(self.backend_name, status='available', size=10)
|
||||||
vol.extend(11)
|
res = vol.extend(11)
|
||||||
|
|
||||||
|
self.assertEqual(11 * (1024 ** 3), res) # size is in bytes not GBi
|
||||||
self.backend.driver.extend_volume.assert_called_once_with(vol._ovo, 11)
|
self.backend.driver.extend_volume.assert_called_once_with(vol._ovo, 11)
|
||||||
self.persistence.set_volume.assert_called_once_with(vol)
|
self.persistence.set_volume.assert_called_once_with(vol)
|
||||||
self.assertEqual('available', vol.status)
|
self.assertEqual('available', vol.status)
|
||||||
self.assertEqual(11, vol.size)
|
self.assertEqual(11, vol.size)
|
||||||
|
|
||||||
|
def test_extend_attached(self):
|
||||||
|
vol = objects.Volume(self.backend_name, status='in-use', size=10)
|
||||||
|
vol.local_attach = mock.Mock()
|
||||||
|
res = vol.extend(11)
|
||||||
|
|
||||||
|
self.assertEqual(vol.local_attach.extend.return_value, res)
|
||||||
|
self.backend.driver.extend_volume.assert_called_once_with(vol._ovo, 11)
|
||||||
|
vol.local_attach.extend.assert_called_once_with()
|
||||||
|
self.persistence.set_volume.assert_called_once_with(vol)
|
||||||
|
self.assertEqual('in-use', vol.status)
|
||||||
|
self.assertEqual(11, vol.size)
|
||||||
|
|
||||||
def test_extend_error(self):
|
def test_extend_error(self):
|
||||||
vol = objects.Volume(self.backend_name, status='available', size=10)
|
vol = objects.Volume(self.backend_name, status='available', size=10)
|
||||||
self.backend.driver.extend_volume.side_effect = exception.NotFound
|
self.backend.driver.extend_volume.side_effect = exception.NotFound
|
||||||
|
@ -363,3 +363,19 @@ class TestRBDConnector(base.BaseTest):
|
|||||||
exists_mock.assert_called_once_with(link)
|
exists_mock.assert_called_once_with(link)
|
||||||
remove_mock.assert_called_once_with(link)
|
remove_mock.assert_called_once_with(link)
|
||||||
link_mock.assert_called_once_with(source, link)
|
link_mock.assert_called_once_with(source, link)
|
||||||
|
|
||||||
|
@mock.patch('six.moves.builtins.open')
|
||||||
|
@mock.patch.object(nos_brick.RBDConnector, '_get_vol_data')
|
||||||
|
def test_extend_volume(self, get_data_mock, open_mock):
|
||||||
|
get_data_mock.return_value = (
|
||||||
|
'/dev/rbd/rbd/volume-56539d26-2b78-49b8-8b96-160a62b0831f',
|
||||||
|
'/dev/rbd10')
|
||||||
|
|
||||||
|
cm_open = open_mock.return_value.__enter__.return_value
|
||||||
|
cm_open.read.return_value = '5368709120'
|
||||||
|
res = self.connector.extend_volume(mock.sentinel.connector_properties)
|
||||||
|
|
||||||
|
self.assertEqual(5 * (1024 ** 3), res) # 5 GBi
|
||||||
|
get_data_mock.assert_called_once_with(
|
||||||
|
mock.sentinel.connector_properties)
|
||||||
|
open_mock.assert_called_once_with('/sys/devices/rbd/10/size')
|
||||||
|
@ -256,6 +256,61 @@ know when instantiating the driver by passing the
|
|||||||
use_multipath_for_image_xfer=True,
|
use_multipath_for_image_xfer=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Extend
|
||||||
|
------
|
||||||
|
|
||||||
|
The `Connection` object has an `extend` method that will refresh the host's
|
||||||
|
view of an attached volume to reflect the latest size of the volume and return
|
||||||
|
the new size in bytes.
|
||||||
|
|
||||||
|
There is no need to manually call this method for volumes that are locally
|
||||||
|
attached to the node that calls the `Volume`'s `extend` method, since that call
|
||||||
|
takes care of it.
|
||||||
|
|
||||||
|
When extending volumes that are attached to nodes other than the one calling
|
||||||
|
the `Volume`'s `extend` method we will need to either detach and re-attach the
|
||||||
|
volume on the host following the mechanisms explained above, or refresh the
|
||||||
|
current view of the volume.
|
||||||
|
|
||||||
|
How we refresh the host's view of an attached volume will depend on how we are
|
||||||
|
attaching the volumes.
|
||||||
|
|
||||||
|
With access to the metadata persistence storage
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In this case things are easier, just like it was on the
|
||||||
|
`Remote connection`_.
|
||||||
|
|
||||||
|
Assuming we have a `volume_id` variable with the volume, and `storage` has the
|
||||||
|
`Backend` instance, all we need to do is:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
vol = storage.Volume.get_by_id(volume_id)
|
||||||
|
vol.connections[0].extend()
|
||||||
|
|
||||||
|
No access to the metadata persistence storage
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This is more inconvenient, as you'll have to handle the data exchange manually
|
||||||
|
as well as the *OS-Brick* library calls to do the extend.
|
||||||
|
|
||||||
|
We'll need to get the connector information on the host that is going to do
|
||||||
|
the attach. Asuuming the dictionary is available in `connection_info` the
|
||||||
|
code would look like this:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from os_brick.initiator import connector
|
||||||
|
|
||||||
|
connector_dict = connection_info['connector']
|
||||||
|
protocol = connection_info['conn']['driver_volume_type']
|
||||||
|
|
||||||
|
conn = connector.InitiatorConnector.factory(
|
||||||
|
protocol, 'sudo', user_multipath=True,
|
||||||
|
device_scan_attempts=3, conn=connector_dict)
|
||||||
|
conn.extend()
|
||||||
|
|
||||||
Multi attach
|
Multi attach
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -224,16 +224,24 @@ The only parameter received by the `extend` method is the new size, and this
|
|||||||
must always be greater than the current value because *cinderlib* is not
|
must always be greater than the current value because *cinderlib* is not
|
||||||
validating this at the moment.
|
validating this at the moment.
|
||||||
|
|
||||||
|
The call will return the new size of the volume in bytes.
|
||||||
|
|
||||||
Example of creating, extending, and deleting a volume:
|
Example of creating, extending, and deleting a volume:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
vol = lvm.create_volume(size=1)
|
vol = lvm.create_volume(size=1)
|
||||||
print('Vol %s has %s GBi' % (vol.id, vol.size))
|
print('Vol %s has %s GBi' % (vol.id, vol.size))
|
||||||
vol.extend(2)
|
new_size = vol.extend(2)
|
||||||
print('Extended vol %s has %s GBi' % (vol.id, vol.size))
|
print('Extended vol %s has %s GBi' % (vol.id, vol.size))
|
||||||
|
print('Detected new size is %s bytes' % new_size)
|
||||||
vol.delete()
|
vol.delete()
|
||||||
|
|
||||||
|
A call to `extend` on a locally attached volume will automatically update the
|
||||||
|
host's view of the volume to reflect the new size. For non locally attached
|
||||||
|
volumes please refer to the `extend section in the connections
|
||||||
|
<connections.html#extend>`_ section.
|
||||||
|
|
||||||
Other methods
|
Other methods
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
8
releasenotes/notes/enhance-extend-687d8e9c4a58e517.yaml
Normal file
8
releasenotes/notes/enhance-extend-687d8e9c4a58e517.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Enhance volume extend functionality in cinderlib by supporting the refresh
|
||||||
|
of the host's view of an attached volume that has been extended in the
|
||||||
|
backend to reflect the new size. A call to volume.extend will
|
||||||
|
automatically extend the view if the volume is locally attached and
|
||||||
|
connection.extend will do the same when run on a non controller host.
|
Loading…
Reference in New Issue
Block a user