Merge "Enhance extend functionality"
This commit is contained in:
commit
f7d41144c7
|
@ -148,6 +148,13 @@ class RBDConnector(connectors.rbd.RBDConnector):
|
|||
return False
|
||||
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):
|
||||
if os.path.exists(real_dev_path):
|
||||
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,
|
||||
force=False, ignore_errors=False):
|
||||
self._setup_rbd_class()
|
||||
pool, volume = connection_properties['name'].split('/')
|
||||
conf_file = device_info['conf']
|
||||
link_name = self.get_rbd_device_name(pool, volume)
|
||||
real_dev_path = os.path.realpath(link_name)
|
||||
link_name, real_dev_path = self._get_vol_data(connection_properties)
|
||||
|
||||
self._unmap(real_dev_path, conf_file, connection_properties)
|
||||
if self.containerized:
|
||||
|
@ -193,6 +197,20 @@ class RBDConnector(connectors.rbd.RBDConnector):
|
|||
# Don't check again to speed things on following connections
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ DEFAULT_PROJECT_ID = 'cinderlib'
|
|||
DEFAULT_USER_ID = 'cinderlib'
|
||||
BACKEND_NAME_SNAPSHOT_FIELD = 'progress'
|
||||
CONNECTIONS_OVO_FIELD = 'volume_attachment'
|
||||
GB = 1024 ** 3
|
||||
|
||||
# This cannot go in the setup method because cinderlib objects need them to
|
||||
# be setup to set OVO_CLASS
|
||||
|
@ -500,6 +501,11 @@ class Volume(NamedObject):
|
|||
finally:
|
||||
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):
|
||||
new_vol_attrs['source_vol_id'] = self.id
|
||||
new_vol = Volume(self, **new_vol_attrs)
|
||||
|
@ -867,6 +873,9 @@ class Connection(Object, LazyVolumeAttr):
|
|||
def save(self):
|
||||
self.persistence.set_connection(self)
|
||||
|
||||
def extend(self):
|
||||
return self.connector.extend_volume(self.conn_info['data'])
|
||||
|
||||
|
||||
class Snapshot(NamedObject, LazyVolumeAttr):
|
||||
OVO_CLASS = cinder_objs.Snapshot
|
||||
|
|
|
@ -179,6 +179,28 @@ class BackendFunctBasic(base_tests.BaseFunctTestCase):
|
|||
result_new_size = self._get_vol_size(vol)
|
||||
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):
|
||||
vol = self._create_vol(self.backend)
|
||||
original_size = self._get_vol_size(vol, do_detach=False)
|
||||
|
|
|
@ -282,3 +282,10 @@ class TestConnection(base.BaseTest):
|
|||
def test_connected(self, value):
|
||||
with mock.patch('cinderlib.objects.Connection.conn_info', value):
|
||||
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):
|
||||
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.persistence.set_volume.assert_called_once_with(vol)
|
||||
self.assertEqual('available', vol.status)
|
||||
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):
|
||||
vol = objects.Volume(self.backend_name, status='available', size=10)
|
||||
self.backend.driver.extend_volume.side_effect = exception.NotFound
|
||||
|
|
|
@ -363,3 +363,19 @@ class TestRBDConnector(base.BaseTest):
|
|||
exists_mock.assert_called_once_with(link)
|
||||
remove_mock.assert_called_once_with(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,
|
||||
)
|
||||
|
||||
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
|
||||
------------
|
||||
|
||||
|
|
|
@ -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
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
vol = lvm.create_volume(size=1)
|
||||
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('Detected new size is %s bytes' % new_size)
|
||||
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
|
||||
-------------
|
||||
|
||||
|
|
|
@ -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