Improve iSCSI device detection speed

Current iSCSI device detection checks for the presence of devices based
on the scan time, so it checks for presence, sleeps, scans, and checks
again.  This means that if the device becomes available while we sleep
to send another scan we won't detect the device.

This patch changes this, making the searching and the rescanning
independent operations operating at different cadences.

Checking for the device will happen every seconds, and the rescans will
happen after 4, 9, and 16 seconds.

Change-Id: I716a3ea8583e289819cc37b6b5dd9730dd59406b
This commit is contained in:
Gorka Eguileor 2018-08-28 16:46:13 +02:00
parent 716ca5e5f1
commit aecf9c968b
2 changed files with 86 additions and 11 deletions

View File

@ -618,8 +618,16 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
device = hctl = None
portal = props['target_portal']
session, manual_scan = self._connect_to_iscsi_portal(props)
do_scans = rescans > 0
retry = 1
do_scans = rescans > 0 or manual_scan
# Scan is sent on connect by iscsid, but we must do it manually on
# manual scan mode. This scan cannot count towards total rescans.
if manual_scan:
num_rescans = -1
seconds_next_scan = 0
else:
num_rescans = 0
seconds_next_scan = 4
if session:
data['num_logins'] += 1
LOG.debug('Connected to %s', portal)
@ -628,11 +636,12 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
if not hctl:
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 or manual_scan:
if seconds_next_scan <= 0:
num_rescans += 1
self._linuxscsi.scan_iscsi(*hctl)
# 4 seconds on 1st rescan, 9s on 2nd, 16s on 3rd
seconds_next_scan = (num_rescans + 2) ** 2
device = self._linuxscsi.device_name_by_hctl(session,
hctl)
@ -642,11 +651,12 @@ class ISCSIConnector(base.BaseLinuxConnector, base_iscsi.BaseISCSIConnector):
except Exception:
LOG.exception('Exception scanning %s', portal)
pass
retry += 1
do_scans = (retry <= rescans and
do_scans = (num_rescans <= rescans and
not (device or data['stop_connecting']))
if do_scans:
time.sleep(retry ** 2)
time.sleep(1)
seconds_next_scan -= 1
if device:
LOG.debug('Connected to %s using %s', device,
strutils.mask_password(props))

View File

@ -1291,12 +1291,13 @@ Setting up iSCSI targets: unused
find_dm_mock.assert_not_called()
self.assertEqual(12, connect_mock.call_count)
@mock.patch('time.sleep', mock.Mock())
@mock.patch('time.sleep')
@mock.patch.object(linuxscsi.LinuxSCSI, 'scan_iscsi')
@mock.patch.object(linuxscsi.LinuxSCSI, 'device_name_by_hctl',
return_value='sda')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal')
def test_connect_vol(self, connect_mock, dev_name_mock, scan_mock):
def test_connect_vol(self, connect_mock, dev_name_mock, scan_mock,
sleep_mock):
lscsi = self.connector._linuxscsi
data = self._get_connect_vol_data()
hctl = [mock.sentinel.host, mock.sentinel.channel,
@ -1319,8 +1320,72 @@ Setting up iSCSI targets: unused
mock.call(mock.sentinel.session,
self.CON_PROPS['target_lun'])])
scan_mock.assert_called_once_with(*hctl)
scan_mock.assert_not_called()
dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl)
sleep_mock.assert_called_once_with(1)
@mock.patch('time.sleep')
@mock.patch.object(linuxscsi.LinuxSCSI, 'scan_iscsi')
@mock.patch.object(linuxscsi.LinuxSCSI, 'device_name_by_hctl',
side_effect=(None, None, None, None, 'sda'))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal')
def test_connect_vol_rescan(self, connect_mock, dev_name_mock, scan_mock,
sleep_mock):
lscsi = self.connector._linuxscsi
data = self._get_connect_vol_data()
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
connect_mock.return_value = (mock.sentinel.session, False)
with mock.patch.object(lscsi, 'get_hctl',
return_value=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,
found_devices=['sda'], just_added_devices=['sda'])
self.assertDictEqual(expected, data)
connect_mock.assert_called_once_with(self.CON_PROPS)
hctl_mock.assert_called_once_with(mock.sentinel.session,
self.CON_PROPS['target_lun'])
scan_mock.assert_called_once_with(*hctl)
self.assertEqual(5, dev_name_mock.call_count)
self.assertEqual(4, sleep_mock.call_count)
@mock.patch('time.sleep')
@mock.patch.object(linuxscsi.LinuxSCSI, 'scan_iscsi')
@mock.patch.object(linuxscsi.LinuxSCSI, 'device_name_by_hctl',
side_effect=(None, None, None, None, 'sda'))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal')
def test_connect_vol_manual(self, connect_mock, dev_name_mock, scan_mock,
sleep_mock):
lscsi = self.connector._linuxscsi
data = self._get_connect_vol_data()
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
# Simulate manual scan
connect_mock.return_value = (mock.sentinel.session, True)
with mock.patch.object(lscsi, 'get_hctl',
return_value=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,
found_devices=['sda'], just_added_devices=['sda'])
self.assertDictEqual(expected, data)
connect_mock.assert_called_once_with(self.CON_PROPS)
hctl_mock.assert_called_once_with(mock.sentinel.session,
self.CON_PROPS['target_lun'])
self.assertEqual(2, scan_mock.call_count)
self.assertEqual(5, dev_name_mock.call_count)
self.assertEqual(4, sleep_mock.call_count)
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal',
return_value=(None, False))