summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGorka Eguileor <geguileo@redhat.com>2017-04-06 17:55:15 +0200
committerGorka Eguileor <geguileo@redhat.com>2017-06-16 16:09:35 +0200
commitf67d46c5383b3c454f365b653a36d3cd043a7814 (patch)
treebfc9b03c3e219766a2cb378748cbedb583f1502f
parent56c8665d3d342ce90f5d9433966c0f244063b4c1 (diff)
Add open-iscsi manual scan support
It was recently added to open-iscsi the functionality to disable automatic LUN scans on iscsid start, on login, and on reception of AEN/AER messages reporting LUN data has changed. Those 3 cases were one of the causes why Nova-CPU and Cinder-Volumes nodes would have unexpected devices. With this new feature we can prevent them from appearing unnexpectedly. This patch adds the mechanism required to configure our sessions for manual scans in a backward compatible way. Manual scans are enabled setting `node.session.scan` to `manual`. Change-Id: I146a74f9f79c68a89677b9b26a324e06a35886f2
Notes
Notes (review): Code-Review+1: xiaoleiguo <xiaolg@awcloud.com> Code-Review+2: Patrick East <patrick.east@purestorage.com> Code-Review+2: Sean McGinnis <sean.mcginnis@gmail.com> Workflow+1: Sean McGinnis <sean.mcginnis@gmail.com> Verified+2: Jenkins Submitted-by: Jenkins Submitted-at: Fri, 16 Jun 2017 18:00:48 +0000 Reviewed-on: https://review.openstack.org/455394 Project: openstack/os-brick Branch: refs/heads/master
-rw-r--r--os_brick/initiator/connectors/iscsi.py20
-rw-r--r--os_brick/tests/initiator/connectors/test_iscsi.py57
-rw-r--r--releasenotes/notes/iscsi_manual_scan_support-d64a1c3c8e1986b4.yaml11
3 files changed, 67 insertions, 21 deletions
diff --git a/os_brick/initiator/connectors/iscsi.py b/os_brick/initiator/connectors/iscsi.py
index 6c94796..04ecef0 100644
--- a/os_brick/initiator/connectors/iscsi.py
+++ b/os_brick/initiator/connectors/iscsi.py
@@ -481,7 +481,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
481 """ 481 """
482 device = hctl = None 482 device = hctl = None
483 portal = props['target_portal'] 483 portal = props['target_portal']
484 session = self._connect_to_iscsi_portal(props) 484 session, manual_scan = self._connect_to_iscsi_portal(props)
485 do_scans = rescans > 0 485 do_scans = rescans > 0
486 retry = 1 486 retry = 1
487 if session: 487 if session:
@@ -493,8 +493,9 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
493 hctl = self._linuxscsi.get_hctl(session, 493 hctl = self._linuxscsi.get_hctl(session,
494 props['target_lun']) 494 props['target_lun'])
495 # Scan is sent on connect by iscsid, so skip first rescan 495 # Scan is sent on connect by iscsid, so skip first rescan
496 # but on manual scan mode we have to do it ourselves.
496 if hctl: 497 if hctl:
497 if retry > 1: 498 if retry > 1 or manual_scan:
498 self._linuxscsi.scan_iscsi(*hctl) 499 self._linuxscsi.scan_iscsi(*hctl)
499 500
500 device = self._linuxscsi.device_name_by_hctl(session, 501 device = self._linuxscsi.device_name_by_hctl(session,
@@ -846,12 +847,19 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
846 # volume is using the same target. 847 # volume is using the same target.
847 # iscsiadm returns 21 for "No records found" after version 2.0-871 848 # iscsiadm returns 21 for "No records found" after version 2.0-871
848 LOG.info("Trying to connect to iSCSI portal %s", portal) 849 LOG.info("Trying to connect to iSCSI portal %s", portal)
849 err = self._run_iscsiadm(connection_properties, (), 850 out, err = self._run_iscsiadm(connection_properties, (),
850 check_exit_code=(0, 21, 255))[1] 851 check_exit_code=(0, 21, 255))
851 if err: 852 if err:
852 self._run_iscsiadm(connection_properties, 853 self._run_iscsiadm(connection_properties,
853 ('--interface', self._get_transport(), 854 ('--interface', self._get_transport(),
854 '--op', 'new')) 855 '--op', 'new'))
856 # Try to set the scan mode to manual
857 res = self._iscsiadm_update(connection_properties,
858 'node.session.scan', 'manual',
859 check_exit_code=False)
860 manual_scan = not res[1]
861 else:
862 manual_scan = 'node.session.scan = manual' in out
855 863
856 if connection_properties.get('auth_method'): 864 if connection_properties.get('auth_method'):
857 self._iscsiadm_update(connection_properties, 865 self._iscsiadm_update(connection_properties,
@@ -872,7 +880,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
872 for s in sessions: 880 for s in sessions:
873 # Found our session, return session_id 881 # Found our session, return session_id
874 if 'tcp:' == s[0] and portal == s[2] and s[4] == target_iqn: 882 if 'tcp:' == s[0] and portal == s[2] and s[4] == target_iqn:
875 return s[1] 883 return s[1], manual_scan
876 884
877 try: 885 try:
878 # exit_code=15 means the session already exists, so it should 886 # exit_code=15 means the session already exists, so it should
@@ -884,7 +892,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
884 '%(portal)s (exit code %(err)s).', 892 '%(portal)s (exit code %(err)s).',
885 {'iqn': target_iqn, 'portal': portal, 893 {'iqn': target_iqn, 'portal': portal,
886 'err': err.exit_code}) 894 'err': err.exit_code})
887 return None 895 return None, None
888 896
889 self._iscsiadm_update(connection_properties, 897 self._iscsiadm_update(connection_properties,
890 "node.startup", 898 "node.startup",
diff --git a/os_brick/tests/initiator/connectors/test_iscsi.py b/os_brick/tests/initiator/connectors/test_iscsi.py
index 1c3ded8..61f1caa 100644
--- a/os_brick/tests/initiator/connectors/test_iscsi.py
+++ b/os_brick/tests/initiator/connectors/test_iscsi.py
@@ -831,14 +831,19 @@ Setting up iSCSI targets: unused
831 ('tcp:', session, 'ip1:port1', '-1', 'tgt1')] 831 ('tcp:', session, 'ip1:port1', '-1', 'tgt1')]
832 ] 832 ]
833 with mock.patch.object(self.connector, '_execute') as exec_mock: 833 with mock.patch.object(self.connector, '_execute') as exec_mock:
834 exec_mock.side_effect = [('', 'error'), ('', None), ('', None), 834 exec_mock.side_effect = [('', 'error'), ('', None),
835 ('', None), ('', None),
835 ('', None)] 836 ('', None)]
836 res = self.connector._connect_to_iscsi_portal(self.CON_PROPS) 837 res = self.connector._connect_to_iscsi_portal(self.CON_PROPS)
837 self.assertEqual(session, res) 838
839 # True refers to "manual scans", since the call to update
840 # node.session.scan didn't fail they are set to manual
841 self.assertEqual((session, True), res)
838 prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1' 842 prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1'
839 expected_cmds = [ 843 expected_cmds = [
840 prefix, 844 prefix,
841 prefix + ' --interface default --op new', 845 prefix + ' --interface default --op new',
846 prefix + ' --op update -n node.session.scan -v manual',
842 prefix + ' --login', 847 prefix + ' --login',
843 prefix + ' --op update -n node.startup -v automatic' 848 prefix + ' --op update -n node.startup -v automatic'
844 ] 849 ]
@@ -856,7 +861,8 @@ Setting up iSCSI targets: unused
856 con_props.update(auth_method='CHAP', auth_username='user', 861 con_props.update(auth_method='CHAP', auth_username='user',
857 auth_password='pwd') 862 auth_password='pwd')
858 res = self.connector._connect_to_iscsi_portal(con_props) 863 res = self.connector._connect_to_iscsi_portal(con_props)
859 self.assertEqual(session, res) 864 # False refers to "manual scans", so we have automatic iscsi scans
865 self.assertEqual((session, False), res)
860 prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1' 866 prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1'
861 expected_cmds = [ 867 expected_cmds = [
862 prefix, 868 prefix,
@@ -867,13 +873,35 @@ Setting up iSCSI targets: unused
867 self.assertListEqual(expected_cmds, self.cmds) 873 self.assertListEqual(expected_cmds, self.cmds)
868 get_sessions_mock.assert_called_once_with() 874 get_sessions_mock.assert_called_once_with()
869 875
876 @ddt.data('auto', 'manual')
877 @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
878 def test_connect_to_iscsi_portal_manual_scan_feature(self, manual_scan,
879 get_sessions_mock):
880 """Node and session already exists and iscsi supports manual scans."""
881 session = 'session2'
882 get_sessions_mock.return_value = [('tcp:', session, 'ip1:port1',
883 '-1', 'tgt1')]
884 con_props = self.CON_PROPS.copy()
885 node_props = ('node.startup = automatic\nnode.session.scan = ' +
886 manual_scan)
887 with mock.patch.object(self.connector, '_execute') as exec_mock:
888 exec_mock.side_effect = [(node_props, None)]
889 res = self.connector._connect_to_iscsi_portal(con_props)
890 # False refers to "manual scans", so we have automatic iscsi scans
891 self.assertEqual((session, manual_scan == 'manual'), res)
892
893 actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list]
894 self.assertListEqual(['iscsiadm -m node -T tgt1 -p ip1:port1'],
895 actual_cmds)
896 get_sessions_mock.assert_called_once_with()
897
870 @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full') 898 @mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
871 def test_connect_to_iscsi_portal_fail_login(self, get_sessions_mock): 899 def test_connect_to_iscsi_portal_fail_login(self, get_sessions_mock):
872 get_sessions_mock.return_value = [] 900 get_sessions_mock.return_value = []
873 with mock.patch.object(self.connector, '_execute') as exec_mock: 901 with mock.patch.object(self.connector, '_execute') as exec_mock:
874 exec_mock.side_effect = [('', None), putils.ProcessExecutionError] 902 exec_mock.side_effect = [('', None), putils.ProcessExecutionError]
875 res = self.connector._connect_to_iscsi_portal(self.CON_PROPS) 903 res = self.connector._connect_to_iscsi_portal(self.CON_PROPS)
876 self.assertIsNone(res) 904 self.assertEqual((None, None), res)
877 expected_cmds = ['iscsiadm -m node -T tgt1 -p ip1:port1', 905 expected_cmds = ['iscsiadm -m node -T tgt1 -p ip1:port1',
878 'iscsiadm -m node -T tgt1 -p ip1:port1 --login'] 906 'iscsiadm -m node -T tgt1 -p ip1:port1 --login']
879 actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list] 907 actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list]
@@ -1140,7 +1168,7 @@ Setting up iSCSI targets: unused
1140 hctl = [mock.sentinel.host, mock.sentinel.channel, 1168 hctl = [mock.sentinel.host, mock.sentinel.channel,
1141 mock.sentinel.target, mock.sentinel.lun] 1169 mock.sentinel.target, mock.sentinel.lun]
1142 1170
1143 connect_mock.return_value = mock.sentinel.session 1171 connect_mock.return_value = (mock.sentinel.session, False)
1144 1172
1145 with mock.patch.object(lscsi, 'get_hctl', 1173 with mock.patch.object(lscsi, 'get_hctl',
1146 side_effect=(None, hctl)) as hctl_mock: 1174 side_effect=(None, hctl)) as hctl_mock:
@@ -1161,7 +1189,7 @@ Setting up iSCSI targets: unused
1161 dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl) 1189 dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl)
1162 1190
1163 @mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal', 1191 @mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal',
1164 return_value=None) 1192 return_value=(None, False))
1165 def test_connect_vol_no_session(self, connect_mock): 1193 def test_connect_vol_no_session(self, connect_mock):
1166 data = self._get_connect_vol_data() 1194 data = self._get_connect_vol_data()
1167 1195
@@ -1183,22 +1211,21 @@ Setting up iSCSI targets: unused
1183 hctl = [mock.sentinel.host, mock.sentinel.channel, 1211 hctl = [mock.sentinel.host, mock.sentinel.channel,
1184 mock.sentinel.target, mock.sentinel.lun] 1212 mock.sentinel.target, mock.sentinel.lun]
1185 1213
1186 connect_mock.return_value = mock.sentinel.session 1214 # True because we are simulating we have manual scans
1215 connect_mock.return_value = (mock.sentinel.session, True)
1187 1216
1188 with mock.patch.object(lscsi, 'get_hctl', 1217 with mock.patch.object(lscsi, 'get_hctl',
1189 side_effect=(None, hctl)) as hctl_mock: 1218 side_effect=(hctl,)) as hctl_mock:
1190 self.connector._connect_vol(3, self.CON_PROPS, data) 1219 self.connector._connect_vol(3, self.CON_PROPS, data)
1191 1220
1192 expected = self._get_connect_vol_data() 1221 expected = self._get_connect_vol_data()
1193 expected.update(num_logins=1, stopped_threads=1) 1222 expected.update(num_logins=1, stopped_threads=1)
1194 self.assertDictEqual(expected, data) 1223 self.assertDictEqual(expected, data)
1195 1224
1196 hctl_mock.assert_has_calls([mock.call(mock.sentinel.session, 1225 hctl_mock.assert_called_once_with(mock.sentinel.session,
1197 self.CON_PROPS['target_lun']), 1226 self.CON_PROPS['target_lun'])
1198 mock.call(mock.sentinel.session, 1227 # We have 3 scans because on manual mode we also scan on connect
1199 self.CON_PROPS['target_lun'])]) 1228 scan_mock.assert_has_calls([mock.call(*hctl)] * 3)
1200
1201 scan_mock.assert_has_calls([mock.call(*hctl), mock.call(*hctl)])
1202 dev_name_mock.assert_has_calls( 1229 dev_name_mock.assert_has_calls(
1203 [mock.call(mock.sentinel.session, hctl), 1230 [mock.call(mock.sentinel.session, hctl),
1204 mock.call(mock.sentinel.session, hctl)]) 1231 mock.call(mock.sentinel.session, hctl)])
@@ -1217,7 +1244,7 @@ Setting up iSCSI targets: unused
1217 hctl = [mock.sentinel.host, mock.sentinel.channel, 1244 hctl = [mock.sentinel.host, mock.sentinel.channel,
1218 mock.sentinel.target, mock.sentinel.lun] 1245 mock.sentinel.target, mock.sentinel.lun]
1219 1246
1220 connect_mock.return_value = mock.sentinel.session 1247 connect_mock.return_value = (mock.sentinel.session, False)
1221 1248
1222 with mock.patch.object(lscsi, 'get_hctl', 1249 with mock.patch.object(lscsi, 'get_hctl',
1223 return_value=hctl) as hctl_mock, \ 1250 return_value=hctl) as hctl_mock, \
diff --git a/releasenotes/notes/iscsi_manual_scan_support-d64a1c3c8e1986b4.yaml b/releasenotes/notes/iscsi_manual_scan_support-d64a1c3c8e1986b4.yaml
new file mode 100644
index 0000000..40020c7
--- /dev/null
+++ b/releasenotes/notes/iscsi_manual_scan_support-d64a1c3c8e1986b4.yaml
@@ -0,0 +1,11 @@
1---
2features:
3 - |
4 Support for setting the scan mode on the Open-iSCSI initiator. If
5 installed iscsiadm supports this feature OS-Brick will set all it's new
6 sessions to manual scan.
7fixes:
8 - |
9 On systems with scan mode support on open-iSCSI we'll no longer see
10 unwanted devices polluting our system due to the automatic initiator scan
11 or to AEN/AER messages from the backend.