diff --git a/os_brick/initiator/connector.py b/os_brick/initiator/connector.py index 54d5f7c81..5005c107b 100644 --- a/os_brick/initiator/connector.py +++ b/os_brick/initiator/connector.py @@ -1974,15 +1974,15 @@ class RBDConnector(BaseLinuxConnector): device_scan_attempts= device_scan_attempts, *args, **kwargs) + self.do_local_attach = kwargs.get('do_local_attach', False) @staticmethod def get_connector_properties(root_helper, *args, **kwargs): """The RBD connector properties.""" - return {} + return {'do_local_attach': kwargs.get('do_local_attach', False)} def get_volume_paths(self, connection_properties): - # TODO(walter-boring): don't know where the connector - # looks for RBD volumes. + # TODO(e0ne): Implement this for local volume. return [] def get_search_path(self): @@ -1991,7 +1991,7 @@ class RBDConnector(BaseLinuxConnector): return None def get_all_available_volumes(self, connection_properties=None): - # TODO(walter-boring): not sure what to return here for RBD + # TODO(e0ne): Implement this for local volumes. return [] def _get_rbd_handle(self, connection_properties): @@ -2009,6 +2009,17 @@ class RBDConnector(BaseLinuxConnector): linuxrbd.RBDImageMetadata(rbd_volume, pool, user, conf)) return rbd_handle + @staticmethod + def get_rbd_device_name(pool, volume): + """Return device name which will be generated by RBD kernel module. + + :param pool: RBD pool name. + :type pool: string + :param volume: RBD image name. + :type volume: string + """ + return '/dev/rbd/{pool}/{volume}'.format(pool=pool, volume=volume) + @utils.trace def connect_volume(self, connection_properties): """Connect to a volume. @@ -2019,6 +2030,29 @@ class RBDConnector(BaseLinuxConnector): :returns: dict """ + do_local_attach = connection_properties.get('do_local_attach', + self.do_local_attach) + + if do_local_attach: + # NOTE(e0ne): sanity check if ceph-common is installed. + cmd = ['which', 'rbd'] + try: + self._execute(*cmd) + except putils.ProcessExecutionError: + msg = _("ceph-common package is not installed.") + LOG.error(msg) + raise exception.BrickException(message=msg) + + # NOTE(e0ne): map volume to a block device + # via the rbd kernel module. + pool, volume = connection_properties['name'].split('/') + cmd = ['rbd', 'map', volume, '--pool', pool] + self._execute(*cmd, root_helper=self._root_helper, + run_as_root=True) + + return {'path': RBDConnector.get_rbd_device_name(pool, volume), + 'type': 'block'} + rbd_handle = self._get_rbd_handle(connection_properties) return {'path': rbd_handle} @@ -2032,10 +2066,19 @@ class RBDConnector(BaseLinuxConnector): :param device_info: historical difference, but same as connection_props :type device_info: dict """ - if device_info: - rbd_handle = device_info.get('path', None) - if rbd_handle is not None: - rbd_handle.close() + 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] + self._execute(*cmd, root_helper=self._root_helper, + run_as_root=True) + else: + if device_info: + rbd_handle = device_info.get('path', None) + if rbd_handle is not None: + rbd_handle.close() def check_valid_device(self, path, run_as_root=True): """Verify an existing RBD handle is connected and valid.""" diff --git a/os_brick/tests/initiator/test_connector.py b/os_brick/tests/initiator/test_connector.py index 0b2f9a4e3..4fc408460 100644 --- a/os_brick/tests/initiator/test_connector.py +++ b/os_brick/tests/initiator/test_connector.py @@ -75,7 +75,8 @@ class ConnectorUtilsTestCase(base.TestCase): 'ip': MY_IP, 'multipath': multipath_result, 'os_type': os_type, - 'platform': platform} + 'platform': platform, + 'do_local_attach': False} self.assertEqual(props, props_actual) def test_brick_get_connector_properties_connectors_called(self): @@ -2395,7 +2396,7 @@ class RBDConnectorTestCase(ConnectorTestCase): props = connector.RBDConnector.get_connector_properties( 'sudo', multipath=True, enforce_multipath=True) - expected_props = {} + expected_props = {'do_local_attach': False} self.assertEqual(expected_props, props) @mock.patch('os_brick.initiator.linuxrbd.rbd') @@ -2426,6 +2427,19 @@ class RBDConnectorTestCase(ConnectorTestCase): self.assertTrue(isinstance(device_info['path'], linuxrbd.RBDVolumeIOWrapper)) + @mock.patch.object(priv_rootwrap, 'execute') + def test_connect_local_volume(self, mock_execute): + rbd = connector.RBDConnector(None, do_local_attach=True) + conn = {'name': 'pool/image'} + device_info = rbd.connect_volume(conn) + execute_call1 = mock.call('which', 'rbd') + cmd = ['rbd', 'map', 'image', '--pool', 'pool'] + execute_call2 = mock.call(*cmd, root_helper=None, run_as_root=True) + mock_execute.assert_has_calls([execute_call1, execute_call2]) + expected_info = {'path': '/dev/rbd/pool/image', + 'type': 'block'} + self.assertEqual(expected_info, device_info) + @mock.patch('os_brick.initiator.linuxrbd.rbd') @mock.patch('os_brick.initiator.linuxrbd.rados') @mock.patch.object(linuxrbd.RBDVolumeIOWrapper, 'close') @@ -2437,6 +2451,17 @@ class RBDConnectorTestCase(ConnectorTestCase): self.assertEqual(1, volume_close.call_count) + @mock.patch.object(priv_rootwrap, 'execute') + def test_disconnect_local_volume(self, mock_execute): + rbd = connector.RBDConnector(None, do_local_attach=True) + conn = {'name': 'pool/image'} + rbd.disconnect_volume(conn, None) + + dev_name = '/dev/rbd/pool/image' + cmd = ['rbd', 'unmap', dev_name] + mock_execute.assert_called_once_with(*cmd, root_helper=None, + run_as_root=True) + def test_extend_volume(self): rbd = connector.RBDConnector(None) self.assertRaises(NotImplementedError, diff --git a/releasenotes/notes/local-attach-in-rbd-connector-c06347fb164b084a.yaml b/releasenotes/notes/local-attach-in-rbd-connector-c06347fb164b084a.yaml new file mode 100644 index 000000000..6bebc4e86 --- /dev/null +++ b/releasenotes/notes/local-attach-in-rbd-connector-c06347fb164b084a.yaml @@ -0,0 +1,5 @@ +--- +features: + - Local attach feature in RBD connector. + We use RBD kernel module to attach and detach volumes locally without + Nova.