From af2f60f9bab271ba26d4d1e8f15e0d1ce7f1ccec Mon Sep 17 00:00:00 2001 From: Aviram Bar-Haim Date: Mon, 14 Aug 2017 16:50:39 +0300 Subject: [PATCH] Fix iSCSI volume attachment over RDMA transport Recent changes assumed 'tcp:' prefix to iscsi sessions, and that broke the session status discovery for RDMA connections. in this change we add the 'iser:' transport prefix to the os-brick functions, to fix volumes attach over RDMA. Closes-bug: #1710599 Change-Id: I27b30dafcf488ebd125917e1924c828e3bdf3a99 --- os_brick/initiator/connectors/iscsi.py | 7 +- .../tests/initiator/connectors/test_iser.py | 81 +++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 os_brick/tests/initiator/connectors/test_iser.py diff --git a/os_brick/initiator/connectors/iscsi.py b/os_brick/initiator/connectors/iscsi.py index 5764ca6d6..4202cbfac 100644 --- a/os_brick/initiator/connectors/iscsi.py +++ b/os_brick/initiator/connectors/iscsi.py @@ -43,6 +43,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector): supported_transports = ['be2iscsi', 'bnx2i', 'cxgb3i', 'default', 'cxgb4i', 'qla4xxx', 'ocs', 'iser'] + VALID_SESSIONS_PREFIX = ('tcp:', 'iser:') def __init__(self, root_helper, driver=None, execute=None, use_multipath=False, @@ -782,7 +783,8 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector): nodes = self._get_iscsi_nodes() sessions = self._get_iscsi_sessions_full() # Use (portal, iqn) to map the session value - sessions_map = {(s[2], s[4]): s[1] for s in sessions if s[0] == 'tcp:'} + sessions_map = {(s[2], s[4]): s[1] for s in sessions + if s[0] in self.VALID_SESSIONS_PREFIX} # device_map will keep a tuple with devices from the connection and # others that don't belong to this connection" (belong, others) device_map = collections.defaultdict(lambda: (set(), set())) @@ -1020,7 +1022,8 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector): sessions = self._get_iscsi_sessions_full() for s in sessions: # Found our session, return session_id - if 'tcp:' == s[0] and portal == s[2] and s[4] == target_iqn: + if (s[0] in self.VALID_SESSIONS_PREFIX and + portal == s[2] and s[4] == target_iqn): return s[1], manual_scan try: diff --git a/os_brick/tests/initiator/connectors/test_iser.py b/os_brick/tests/initiator/connectors/test_iser.py new file mode 100644 index 000000000..5c546a599 --- /dev/null +++ b/os_brick/tests/initiator/connectors/test_iser.py @@ -0,0 +1,81 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from os_brick.initiator.connectors import iscsi +from os_brick.tests.initiator import test_connector + + +class ISERConnectorTestCase(test_connector.ConnectorTestCase): + + def setUp(self): + super(ISERConnectorTestCase, self).setUp() + self.connector = iscsi.ISCSIConnector( + None, execute=self.fake_execute, use_multipath=False) + self.connection_data = { + 'volume_id': 'volume_id', + 'target_portal': 'ip:port', + 'target_iqn': 'target_1', + 'target_lun': 1, + 'target_portals': ['ip:port'], + 'target_iqns': ['target_1'], + 'target_luns': [1] + } + + @mock.patch.object(iscsi.ISCSIConnector, '_get_ips_iqns_luns') + @mock.patch('glob.glob') + @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full') + @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_nodes') + def test_get_connection_devices( + self, nodes_mock, sessions_mock, glob_mock, iql_mock): + + self.connector.use_multipath = True + iql_mock.return_value = \ + self.connector._get_all_targets(self.connection_data) + + # mocked iSCSI sessions + sessions_mock.return_value = \ + [('iser:', '0', 'ip:port', '1', 'target_1')] + + # mocked iSCSI nodes + nodes_mock.return_value = [('ip:port', 'target_1')] + sys_cls = '/sys/class/scsi_host/host' + glob_mock.side_effect = [ + [sys_cls + '1/device/session/target/1:1:1:1/block/sda'] + ] + res = self.connector._get_connection_devices(self.connection_data) + expected = {('ip:port', 'target_1'): ({'sda'}, set())} + self.assertDictEqual(expected, res) + iql_mock.assert_called_once_with(self.connection_data, discover=False) + + @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full') + @mock.patch.object(iscsi.ISCSIConnector, '_execute') + def test_connect_to_iscsi_portal(self, exec_mock, sessions_mock): + """Connect to portal while session already established""" + + # connected sessions + sessions_mock.side_effect = [ + [('iser:', 'session_iser', 'ip:port', '1', 'target_1')] + ] + exec_mock.side_effect = [('', None), ('', None), ('', None)] + res = self.connector._connect_to_iscsi_portal(self.connection_data) + + # session name is expected to be in the result. + self.assertEqual(("session_iser", True), res) + prefix = 'iscsiadm -m node -T target_1 -p ip:port' + expected_cmds = [ + prefix, + prefix + ' --op update -n node.session.scan -v manual' + ] + actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list] + self.assertListEqual(expected_cmds, actual_cmds)