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:
parent
56c8665d3d
commit
f67d46c538
@ -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",
|
||||
|
@ -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, \
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user