From aecf9c968bf9f395d746572cc32671e76251e9d2 Mon Sep 17 00:00:00 2001 From: Gorka Eguileor Date: Tue, 28 Aug 2018 16:46:13 +0200 Subject: [PATCH] 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 --- os_brick/initiator/connectors/iscsi.py | 26 ++++--- .../tests/initiator/connectors/test_iscsi.py | 71 ++++++++++++++++++- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/os_brick/initiator/connectors/iscsi.py b/os_brick/initiator/connectors/iscsi.py index fa21608c0..2782b6dea 100644 --- a/os_brick/initiator/connectors/iscsi.py +++ b/os_brick/initiator/connectors/iscsi.py @@ -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)) diff --git a/os_brick/tests/initiator/connectors/test_iscsi.py b/os_brick/tests/initiator/connectors/test_iscsi.py index e12f80303..d52b095e2 100644 --- a/os_brick/tests/initiator/connectors/test_iscsi.py +++ b/os_brick/tests/initiator/connectors/test_iscsi.py @@ -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))