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))