From 9e30e5155d00a9cd18ebec6de1b5c1834cb33d97 Mon Sep 17 00:00:00 2001 From: Lee Yarwood Date: Sat, 15 Feb 2020 17:45:50 +0000 Subject: [PATCH] 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 9415b3b41f55b66bee589bc8ff5f1c789d8a5d0e) (cherry picked from commit 36b207239b2dd4b3cad2b79d7ae32f4084c36403) (cherry picked from commit 3ce686a9fa41210fb4c08017220ce063cc869c75) (cherry picked from commit fcff1e0cb292a461b933bdc70dd21b4057711bca) --- os_brick/initiator/connectors/rbd.py | 33 +++++++++--- .../tests/initiator/connectors/test_rbd.py | 52 +++++++++++++++++-- 2 files changed, 74 insertions(+), 11 deletions(-) diff --git a/os_brick/initiator/connectors/rbd.py b/os_brick/initiator/connectors/rbd.py index 58fa6a8cf..dba6914ce 100644 --- a/os_brick/initiator/connectors/rbd.py +++ b/os_brick/initiator/connectors/rbd.py @@ -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 @@ -200,6 +201,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): @@ -214,12 +235,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) diff --git a/os_brick/tests/initiator/connectors/test_rbd.py b/os_brick/tests/initiator/connectors/test_rbd.py index 69b1b8f3f..9766899f1 100644 --- a/os_brick/tests/initiator/connectors/test_rbd.py +++ b/os_brick/tests/initiator/connectors/test_rbd.py @@ -254,13 +254,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)