rbd: Use showmapped to find the root RBD device during disconnect_volume
The original method of unmapping the higher level RBD device found under
/dev/rbd/{pool}/{volume} fails when an encryptor has been attached to
the volume locally. This is due to a symlink being created that points
to the the decrypted dm-crypt device, breaking any attempt to unmap the
RBD volume.
To avoid this we can simply find and unmap the lower level RBD device of
/dev/rbd*.
Change-Id: Id507109df80391699074773f4787f74507c4b882
(cherry picked from commit 9415b3b41f
)
This commit is contained in:
parent
a8190cd60b
commit
36b207239b
@ -18,6 +18,7 @@ import tempfile
|
||||
|
||||
from oslo_concurrency import processutils as putils
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import fileutils
|
||||
from oslo_utils import netutils
|
||||
|
||||
@ -203,6 +204,26 @@ class RBDConnector(base.BaseLinuxConnector):
|
||||
rbd_handle = self._get_rbd_handle(connection_properties)
|
||||
return {'path': rbd_handle}
|
||||
|
||||
def _find_root_device(self, connection_properties):
|
||||
"""Find the underlying /dev/rbd* device for a mapping.
|
||||
|
||||
Use the showmapped command to list all acive mappings and find the
|
||||
underlying /dev/rbd* device that corresponds to our pool and volume.
|
||||
|
||||
:param connection_properties: The dictionary that describes all
|
||||
of the target volume attributes.
|
||||
:type connection_properties: dict
|
||||
:returns: '/dev/rbd*' or None if no active mapping is found.
|
||||
"""
|
||||
__, volume = connection_properties['name'].split('/')
|
||||
cmd = ['rbd', 'showmapped', '--format=json']
|
||||
(out, err) = self._execute(*cmd, root_helper=self._root_helper,
|
||||
run_as_root=True)
|
||||
for index, mapping in jsonutils.loads(out).items():
|
||||
if mapping['name'] == volume:
|
||||
return mapping['device']
|
||||
return None
|
||||
|
||||
@utils.trace
|
||||
def disconnect_volume(self, connection_properties, device_info,
|
||||
force=False, ignore_errors=False):
|
||||
@ -217,12 +238,12 @@ class RBDConnector(base.BaseLinuxConnector):
|
||||
do_local_attach = connection_properties.get('do_local_attach',
|
||||
self.do_local_attach)
|
||||
if do_local_attach:
|
||||
pool, volume = connection_properties['name'].split('/')
|
||||
dev_name = RBDConnector.get_rbd_device_name(pool, volume)
|
||||
cmd = ['rbd', 'unmap', dev_name]
|
||||
cmd += self._get_rbd_args(connection_properties)
|
||||
self._execute(*cmd, root_helper=self._root_helper,
|
||||
run_as_root=True)
|
||||
root_device = self._find_root_device(connection_properties)
|
||||
if root_device:
|
||||
cmd = ['rbd', 'unmap', root_device]
|
||||
cmd += self._get_rbd_args(connection_properties)
|
||||
self._execute(*cmd, root_helper=self._root_helper,
|
||||
run_as_root=True)
|
||||
else:
|
||||
if device_info:
|
||||
rbd_handle = device_info.get('path', None)
|
||||
|
@ -257,13 +257,55 @@ class RBDConnectorTestCase(test_connector.ConnectorTestCase):
|
||||
'auth_username': 'fake_user',
|
||||
'hosts': ['192.168.10.2'],
|
||||
'ports': ['6789']}
|
||||
mock_execute.side_effect = [("""
|
||||
{"0":{"pool":"pool","device":"/dev/rbd0","name":"pool-image"},
|
||||
"1":{"pool":"pool","device":"/dev/rdb1","name":"pool-image_2"}}""", None),
|
||||
(None, None)]
|
||||
show_cmd = ['rbd', 'showmapped', '--format=json', '--id', 'fake_user',
|
||||
'--mon_host', '192.168.10.2:6789']
|
||||
unmap_cmd = ['rbd', 'unmap', '/dev/rbd0', '--id', 'fake_user',
|
||||
'--mon_host', '192.168.10.2:6789']
|
||||
|
||||
rbd_connector.disconnect_volume(conn, None)
|
||||
|
||||
dev_name = '/dev/rbd/pool/image'
|
||||
cmd = ['rbd', 'unmap', dev_name, '--id', 'fake_user',
|
||||
'--mon_host', '192.168.10.2:6789']
|
||||
mock_execute.assert_called_once_with(*cmd, root_helper=None,
|
||||
run_as_root=True)
|
||||
# Assert that showmapped is used before we unmap the root device
|
||||
mock_execute.has_calls([
|
||||
mock.call(*show_cmd, root_helper=None, run_as_root=True),
|
||||
mock.call(*unmap_cmd, root_helper=None, run_as_root=True)])
|
||||
|
||||
@mock.patch.object(priv_rootwrap, 'execute', return_value=None)
|
||||
def test_disconnect_local_volume_no_mapping(self, mock_execute):
|
||||
rbd_connector = rbd.RBDConnector(None, do_local_attach=True)
|
||||
conn = {'name': 'pool/not_mapped',
|
||||
'auth_username': 'fake_user',
|
||||
'hosts': ['192.168.10.2'],
|
||||
'ports': ['6789']}
|
||||
mock_execute.return_value = ("""
|
||||
{"0":{"pool":"pool","device":"/dev/rbd0","name":"pool-image"},
|
||||
"1":{"pool":"pool","device":"/dev/rdb1","name":"pool-image_2"}}""", None)
|
||||
show_cmd = ['rbd', 'showmapped', '--format=json', '--id', 'fake_user',
|
||||
'--mon_host', '192.168.10.2:6789']
|
||||
rbd_connector.disconnect_volume(conn, None)
|
||||
|
||||
# Assert that only showmapped is called when no mappings are found
|
||||
mock_execute.called_once_with(*show_cmd, root_helper=None,
|
||||
run_as_root=True)
|
||||
|
||||
@mock.patch.object(priv_rootwrap, 'execute', return_value=None)
|
||||
def test_disconnect_local_volume_no_mappings(self, mock_execute):
|
||||
rbd_connector = rbd.RBDConnector(None, do_local_attach=True)
|
||||
conn = {'name': 'pool/image',
|
||||
'auth_username': 'fake_user',
|
||||
'hosts': ['192.168.10.2'],
|
||||
'ports': ['6789']}
|
||||
mock_execute.return_value = ("{}", None)
|
||||
show_cmd = ['rbd', 'showmapped', '--format=json', '--id', 'fake_user',
|
||||
'--mon_host', '192.168.10.2:6789']
|
||||
rbd_connector.disconnect_volume(conn, None)
|
||||
|
||||
# Assert that only showmapped is called when no mappings are found
|
||||
mock_execute.called_once_with(*show_cmd, root_helper=None,
|
||||
run_as_root=True)
|
||||
|
||||
def test_extend_volume(self):
|
||||
rbd_connector = rbd.RBDConnector(None)
|
||||
|
Loading…
Reference in New Issue
Block a user