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
This commit is contained in:
Gorka Eguileor 2017-04-06 17:55:15 +02:00
parent 56c8665d3d
commit f67d46c538
3 changed files with 67 additions and 21 deletions

View File

@ -481,7 +481,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
"""
device = hctl = None
portal = props['target_portal']
session = self._connect_to_iscsi_portal(props)
session, manual_scan = self._connect_to_iscsi_portal(props)
do_scans = rescans > 0
retry = 1
if session:
@ -493,8 +493,9 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
hctl = self._linuxscsi.get_hctl(session,
props['target_lun'])
# Scan is sent on connect by iscsid, so skip first rescan
# but on manual scan mode we have to do it ourselves.
if hctl:
if retry > 1:
if retry > 1 or manual_scan:
self._linuxscsi.scan_iscsi(*hctl)
device = self._linuxscsi.device_name_by_hctl(session,
@ -846,12 +847,19 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
# volume is using the same target.
# iscsiadm returns 21 for "No records found" after version 2.0-871
LOG.info("Trying to connect to iSCSI portal %s", portal)
err = self._run_iscsiadm(connection_properties, (),
check_exit_code=(0, 21, 255))[1]
out, err = self._run_iscsiadm(connection_properties, (),
check_exit_code=(0, 21, 255))
if err:
self._run_iscsiadm(connection_properties,
('--interface', self._get_transport(),
'--op', 'new'))
# Try to set the scan mode to manual
res = self._iscsiadm_update(connection_properties,
'node.session.scan', 'manual',
check_exit_code=False)
manual_scan = not res[1]
else:
manual_scan = 'node.session.scan = manual' in out
if connection_properties.get('auth_method'):
self._iscsiadm_update(connection_properties,
@ -872,7 +880,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
for s in sessions:
# Found our session, return session_id
if 'tcp:' == s[0] and portal == s[2] and s[4] == target_iqn:
return s[1]
return s[1], manual_scan
try:
# exit_code=15 means the session already exists, so it should
@ -884,7 +892,7 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
'%(portal)s (exit code %(err)s).',
{'iqn': target_iqn, 'portal': portal,
'err': err.exit_code})
return None
return None, None
self._iscsiadm_update(connection_properties,
"node.startup",

View File

@ -831,14 +831,19 @@ Setting up iSCSI targets: unused
('tcp:', session, 'ip1:port1', '-1', 'tgt1')]
]
with mock.patch.object(self.connector, '_execute') as exec_mock:
exec_mock.side_effect = [('', 'error'), ('', None), ('', None),
exec_mock.side_effect = [('', 'error'), ('', None),
('', None), ('', None),
('', None)]
res = self.connector._connect_to_iscsi_portal(self.CON_PROPS)
self.assertEqual(session, res)
# True refers to "manual scans", since the call to update
# node.session.scan didn't fail they are set to manual
self.assertEqual((session, True), res)
prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1'
expected_cmds = [
prefix,
prefix + ' --interface default --op new',
prefix + ' --op update -n node.session.scan -v manual',
prefix + ' --login',
prefix + ' --op update -n node.startup -v automatic'
]
@ -856,7 +861,8 @@ Setting up iSCSI targets: unused
con_props.update(auth_method='CHAP', auth_username='user',
auth_password='pwd')
res = self.connector._connect_to_iscsi_portal(con_props)
self.assertEqual(session, res)
# False refers to "manual scans", so we have automatic iscsi scans
self.assertEqual((session, False), res)
prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1'
expected_cmds = [
prefix,
@ -867,13 +873,35 @@ Setting up iSCSI targets: unused
self.assertListEqual(expected_cmds, self.cmds)
get_sessions_mock.assert_called_once_with()
@ddt.data('auto', 'manual')
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
def test_connect_to_iscsi_portal_manual_scan_feature(self, manual_scan,
get_sessions_mock):
"""Node and session already exists and iscsi supports manual scans."""
session = 'session2'
get_sessions_mock.return_value = [('tcp:', session, 'ip1:port1',
'-1', 'tgt1')]
con_props = self.CON_PROPS.copy()
node_props = ('node.startup = automatic\nnode.session.scan = ' +
manual_scan)
with mock.patch.object(self.connector, '_execute') as exec_mock:
exec_mock.side_effect = [(node_props, None)]
res = self.connector._connect_to_iscsi_portal(con_props)
# False refers to "manual scans", so we have automatic iscsi scans
self.assertEqual((session, manual_scan == 'manual'), res)
actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list]
self.assertListEqual(['iscsiadm -m node -T tgt1 -p ip1:port1'],
actual_cmds)
get_sessions_mock.assert_called_once_with()
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
def test_connect_to_iscsi_portal_fail_login(self, get_sessions_mock):
get_sessions_mock.return_value = []
with mock.patch.object(self.connector, '_execute') as exec_mock:
exec_mock.side_effect = [('', None), putils.ProcessExecutionError]
res = self.connector._connect_to_iscsi_portal(self.CON_PROPS)
self.assertIsNone(res)
self.assertEqual((None, None), res)
expected_cmds = ['iscsiadm -m node -T tgt1 -p ip1:port1',
'iscsiadm -m node -T tgt1 -p ip1:port1 --login']
actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list]
@ -1140,7 +1168,7 @@ Setting up iSCSI targets: unused
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
connect_mock.return_value = mock.sentinel.session
connect_mock.return_value = (mock.sentinel.session, False)
with mock.patch.object(lscsi, 'get_hctl',
side_effect=(None, hctl)) as hctl_mock:
@ -1161,7 +1189,7 @@ Setting up iSCSI targets: unused
dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl)
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal',
return_value=None)
return_value=(None, False))
def test_connect_vol_no_session(self, connect_mock):
data = self._get_connect_vol_data()
@ -1183,22 +1211,21 @@ Setting up iSCSI targets: unused
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
connect_mock.return_value = mock.sentinel.session
# True because we are simulating we have manual scans
connect_mock.return_value = (mock.sentinel.session, True)
with mock.patch.object(lscsi, 'get_hctl',
side_effect=(None, hctl)) as hctl_mock:
side_effect=(hctl,)) as hctl_mock:
self.connector._connect_vol(3, self.CON_PROPS, data)
expected = self._get_connect_vol_data()
expected.update(num_logins=1, stopped_threads=1)
self.assertDictEqual(expected, data)
hctl_mock.assert_has_calls([mock.call(mock.sentinel.session,
self.CON_PROPS['target_lun']),
mock.call(mock.sentinel.session,
self.CON_PROPS['target_lun'])])
scan_mock.assert_has_calls([mock.call(*hctl), mock.call(*hctl)])
hctl_mock.assert_called_once_with(mock.sentinel.session,
self.CON_PROPS['target_lun'])
# We have 3 scans because on manual mode we also scan on connect
scan_mock.assert_has_calls([mock.call(*hctl)] * 3)
dev_name_mock.assert_has_calls(
[mock.call(mock.sentinel.session, hctl),
mock.call(mock.sentinel.session, hctl)])
@ -1217,7 +1244,7 @@ Setting up iSCSI targets: unused
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
connect_mock.return_value = mock.sentinel.session
connect_mock.return_value = (mock.sentinel.session, False)
with mock.patch.object(lscsi, 'get_hctl',
return_value=hctl) as hctl_mock, \

View File

@ -0,0 +1,11 @@
---
features:
- |
Support for setting the scan mode on the Open-iSCSI initiator. If
installed iscsiadm supports this feature OS-Brick will set all it's new
sessions to manual scan.
fixes:
- |
On systems with scan mode support on open-iSCSI we'll no longer see
unwanted devices polluting our system due to the automatic initiator scan
or to AEN/AER messages from the backend.