Files
os-brick/os_brick/tests/initiator/connectors/test_iscsi.py
Gorka Eguileor f67d46c538 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
2017-06-16 16:09:35 +02:00

1265 lines
60 KiB
Python

# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import collections
import mock
import os
import ddt
from oslo_concurrency import processutils as putils
from os_brick import exception
from os_brick.initiator.connectors import iscsi
from os_brick.initiator import linuxscsi
from os_brick.privileged import rootwrap as priv_rootwrap
from os_brick.tests.initiator import test_connector
@ddt.ddt
class ISCSIConnectorTestCase(test_connector.ConnectorTestCase):
CON_PROPS = {
'volume_id': 'vol_id',
'target_portal': 'ip1:port1',
'target_iqn': 'tgt1',
'target_lun': 4,
'target_portals': ['ip1:port1', 'ip2:port2', 'ip3:port3',
'ip4:port4'],
'target_iqns': ['tgt1', 'tgt2', 'tgt3', 'tgt4'],
'target_luns': [4, 5, 6, 7],
}
def setUp(self):
super(ISCSIConnectorTestCase, self).setUp()
self.connector = iscsi.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=False)
self.connector_with_multipath = iscsi.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=True)
self.mock_object(self.connector._linuxscsi, 'get_name_from_path',
return_value="/dev/sdb")
self._fake_iqn = 'iqn.1234-56.foo.bar:01:23456789abc'
self._name = 'volume-00000001'
self._iqn = 'iqn.2010-10.org.openstack:%s' % self._name
self._location = '10.0.2.15:3260'
self._lun = 1
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsi_session')
def test_get_iscsi_sessions_full(self, sessions_mock):
iscsiadm_result = ('tcp: [session1] ip1:port1,1 tgt1 (non-flash)\n'
'tcp: [session2] ip2:port2,-1 tgt2 (non-flash)\n'
'tcp: [session3] ip3:port3,1 tgt3\n')
sessions_mock.return_value = (iscsiadm_result, '')
res = self.connector._get_iscsi_sessions_full()
expected = [('tcp:', 'session1', 'ip1:port1', '1', 'tgt1'),
('tcp:', 'session2', 'ip2:port2', '-1', 'tgt2'),
('tcp:', 'session3', 'ip3:port3', '1', 'tgt3')]
self.assertListEqual(expected, res)
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsi_session',
return_value=(None, 'error'))
def test_get_iscsi_sessions_full_error(self, sessions_mock):
res = self.connector._get_iscsi_sessions_full()
self.assertEqual([], res)
sessions_mock.assert_called()
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
def test_get_iscsi_sessions(self, sessions_mock):
sessions_mock.return_value = [
('tcp:', 'session1', 'ip1:port1', '1', 'tgt1'),
('tcp:', 'session2', 'ip2:port2', '-1', 'tgt2'),
('tcp:', 'session3', 'ip3:port3', '1', 'tgt3')]
res = self.connector._get_iscsi_sessions()
expected = ['ip1:port1', 'ip2:port2', 'ip3:port3']
self.assertListEqual(expected, res)
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full',
return_value=[])
def test_get_iscsi_sessions_no_sessions(self, sessions_mock):
res = self.connector._get_iscsi_sessions()
self.assertListEqual([], res)
sessions_mock.assert_called()
@mock.patch.object(iscsi.ISCSIConnector, '_execute')
def test_get_iscsi_nodes(self, exec_mock):
iscsiadm_result = ('ip1:port1,1 tgt1\nip2:port2,-1 tgt2\n'
'ip3:port3,1 tgt3\n')
exec_mock.return_value = (iscsiadm_result, '')
res = self.connector._get_iscsi_nodes()
expected = [('ip1:port1', 'tgt1'), ('ip2:port2', 'tgt2'),
('ip3:port3', 'tgt3')]
self.assertListEqual(expected, res)
exec_mock.assert_called_once_with(
'iscsiadm', '-m', 'node', run_as_root=True,
root_helper=self.connector._root_helper, check_exit_code=False)
@mock.patch.object(iscsi.ISCSIConnector, '_execute')
def test_get_iscsi_nodes_error(self, exec_mock):
exec_mock.return_value = (None, 'error')
res = self.connector._get_iscsi_nodes()
self.assertEqual([], res)
@mock.patch('glob.glob')
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_nodes')
def test_get_connection_devices(self, nodes_mock, sessions_mock,
glob_mock):
# List sessions from other targets and non tcp sessions
sessions_mock.return_value = [
('non-tcp:', '0', 'ip1:port1', '1', 'tgt1'),
('tcp:', '1', 'ip1:port1', '1', 'tgt1'),
('tcp:', '2', 'ip2:port2', '-1', 'tgt2'),
('tcp:', '3', 'ip1:port1', '1', 'tgt4'),
('tcp:', '4', 'ip2:port2', '-1', 'tgt5')]
# List 1 node without sessions
nodes_mock.return_value = [('ip1:port1', 'tgt1'),
('ip2:port2', 'tgt2'),
('ip3:port3', 'tgt3')]
sys_cls = '/sys/class/scsi_host/host'
glob_mock.side_effect = [
[sys_cls + '1/device/session1/target6/1:2:6:4/block/sda',
sys_cls + '1/device/session1/target6/1:2:6:4/block/sda1'],
[sys_cls + '2/device/session2/target7/2:2:7:5/block/sdb',
sys_cls + '2/device/session2/target7/2:2:7:4/block/sdc'],
]
res = self.connector._get_connection_devices(self.CON_PROPS)
expected = {('ip1:port1', 'tgt1'): ({'sda'}, set()),
('ip2:port2', 'tgt2'): ({'sdb'}, {'sdc'}),
('ip3:port3', 'tgt3'): (set(), set())}
self.assertDictEqual(expected, res)
@mock.patch('glob.glob')
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_nodes')
def test_get_connection_devices_with_iqns(self, nodes_mock, sessions_mock,
glob_mock):
ips_iqns_luns = self.connector._get_all_targets(self.CON_PROPS)
# List sessions from other targets and non tcp sessions
sessions_mock.return_value = [
('non-tcp:', '0', 'ip1:port1', '1', 'tgt1'),
('tcp:', '1', 'ip1:port1', '1', 'tgt1'),
('tcp:', '2', 'ip2:port2', '-1', 'tgt2'),
('tcp:', '3', 'ip1:port1', '1', 'tgt4'),
('tcp:', '4', 'ip2:port2', '-1', 'tgt5')]
# List 1 node without sessions
nodes_mock.return_value = [('ip1:port1', 'tgt1'),
('ip2:port2', 'tgt2'),
('ip3:port3', 'tgt3')]
sys_cls = '/sys/class/scsi_host/host'
glob_mock.side_effect = [
[sys_cls + '1/device/session1/target6/1:2:6:4/block/sda',
sys_cls + '1/device/session1/target6/1:2:6:4/block/sda1'],
[sys_cls + '2/device/session2/target7/2:2:7:5/block/sdb',
sys_cls + '2/device/session2/target7/2:2:7:4/block/sdc'],
]
with mock.patch.object(iscsi.ISCSIConnector,
'_get_all_targets') as get_targets_mock:
res = self.connector._get_connection_devices(mock.sentinel.props,
ips_iqns_luns)
expected = {('ip1:port1', 'tgt1'): ({'sda'}, set()),
('ip2:port2', 'tgt2'): ({'sdb'}, {'sdc'}),
('ip3:port3', 'tgt3'): (set(), set())}
self.assertDictEqual(expected, res)
get_targets_mock.assert_not_called()
def generate_device(self, location, iqn, transport=None, lun=1):
dev_format = "ip-%s-iscsi-%s-lun-%s" % (location, iqn, lun)
if transport:
dev_format = "pci-0000:00:00.0-" + dev_format
fake_dev_path = "/dev/disk/by-path/" + dev_format
return fake_dev_path
def iscsi_connection(self, volume, location, iqn):
return {
'driver_volume_type': 'iscsi',
'data': {
'volume_id': volume['id'],
'target_portal': location,
'target_iqn': iqn,
'target_lun': 1,
}
}
def iscsi_connection_multipath(self, volume, locations, iqns, luns):
return {
'driver_volume_type': 'iscsi',
'data': {
'volume_id': volume['id'],
'target_portals': locations,
'target_iqns': iqns,
'target_luns': luns,
}
}
def iscsi_connection_chap(self, volume, location, iqn, auth_method,
auth_username, auth_password,
discovery_auth_method, discovery_auth_username,
discovery_auth_password):
return {
'driver_volume_type': 'iscsi',
'data': {
'auth_method': auth_method,
'auth_username': auth_username,
'auth_password': auth_password,
'discovery_auth_method': discovery_auth_method,
'discovery_auth_username': discovery_auth_username,
'discovery_auth_password': discovery_auth_password,
'target_lun': 1,
'volume_id': volume['id'],
'target_iqn': iqn,
'target_portal': location,
}
}
def _initiator_get_text(self, *arg, **kwargs):
text = ('## DO NOT EDIT OR REMOVE THIS FILE!\n'
'## If you remove this file, the iSCSI daemon '
'will not start.\n'
'## If you change the InitiatorName, existing '
'access control lists\n'
'## may reject this initiator. The InitiatorName must '
'be unique\n'
'## for each iSCSI initiator. Do NOT duplicate iSCSI '
'InitiatorNames.\n'
'InitiatorName=%s' % self._fake_iqn)
return text, None
def test_get_initiator(self):
def initiator_no_file(*args, **kwargs):
raise putils.ProcessExecutionError('No file')
self.connector._execute = initiator_no_file
initiator = self.connector.get_initiator()
self.assertIsNone(initiator)
self.connector._execute = self._initiator_get_text
initiator = self.connector.get_initiator()
self.assertEqual(initiator, self._fake_iqn)
def test_get_connector_properties(self):
with mock.patch.object(priv_rootwrap, 'execute') as mock_exec:
mock_exec.return_value = self._initiator_get_text()
multipath = True
enforce_multipath = True
props = iscsi.ISCSIConnector.get_connector_properties(
'sudo', multipath=multipath,
enforce_multipath=enforce_multipath)
expected_props = {'initiator': self._fake_iqn}
self.assertEqual(expected_props, props)
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsiadm_bare')
def test_brick_iscsi_validate_transport(self, mock_iscsiadm):
sample_output = ('# BEGIN RECORD 2.0-872\n'
'iface.iscsi_ifacename = %s.fake_suffix\n'
'iface.net_ifacename = <empty>\n'
'iface.ipaddress = <empty>\n'
'iface.hwaddress = 00:53:00:00:53:00\n'
'iface.transport_name = %s\n'
'iface.initiatorname = <empty>\n'
'# END RECORD')
for tport in self.connector.supported_transports:
mock_iscsiadm.return_value = (sample_output % (tport, tport), '')
self.assertEqual(tport + '.fake_suffix',
self.connector._validate_iface_transport(
tport + '.fake_suffix'))
mock_iscsiadm.return_value = ("", 'iscsiadm: Could not '
'read iface fake_transport (6)')
self.assertEqual('default',
self.connector._validate_iface_transport(
'fake_transport'))
def test_get_search_path(self):
search_path = self.connector.get_search_path()
expected = "/dev/disk/by-path"
self.assertEqual(expected, search_path)
@mock.patch.object(os.path, 'exists', return_value=True)
@mock.patch.object(iscsi.ISCSIConnector, '_get_potential_volume_paths')
def test_get_volume_paths(self, mock_potential_paths, mock_exists):
name1 = 'volume-00000001-1'
vol = {'id': 1, 'name': name1}
location = '10.0.2.15:3260'
iqn = 'iqn.2010-10.org.openstack:%s' % name1
fake_path = ("/dev/disk/by-path/ip-%(ip)s-iscsi-%(iqn)s-lun-%(lun)s" %
{'ip': '10.0.2.15', 'iqn': iqn, 'lun': 1})
fake_devices = [fake_path]
expected = fake_devices
mock_potential_paths.return_value = fake_devices
connection_properties = self.iscsi_connection(vol, [location],
[iqn])
volume_paths = self.connector.get_volume_paths(
connection_properties['data'])
self.assertEqual(expected, volume_paths)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
def test_discover_mpath_device(self, mock_multipath_device,
mock_multipath_device_path):
location1 = '10.0.2.15:3260'
location2 = '[2001:db8::1]:3260'
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
fake_multipath_dev = '/dev/mapper/fake-multipath-dev'
fake_raw_dev = '/dev/disk/by-path/fake-raw-lun'
vol = {'id': 1, 'name': name1}
connection_properties = self.iscsi_connection_multipath(
vol, [location1, location2], [iqn1, iqn2], [1, 2])
mock_multipath_device_path.return_value = fake_multipath_dev
mock_multipath_device.return_value = test_connector.FAKE_SCSI_WWN
(result_path, result_mpath_id) = (
self.connector_with_multipath._discover_mpath_device(
test_connector.FAKE_SCSI_WWN,
connection_properties['data'],
fake_raw_dev))
result = {'path': result_path, 'multipath_id': result_mpath_id}
expected_result = {'path': fake_multipath_dev,
'multipath_id': test_connector.FAKE_SCSI_WWN}
self.assertEqual(expected_result, result)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_multipath_device')
@mock.patch.object(os.path, 'realpath')
def test_discover_mpath_device_by_realpath(self, mock_realpath,
mock_multipath_device,
mock_multipath_device_path):
FAKE_SCSI_WWN = '1234567890'
location1 = '10.0.2.15:3260'
location2 = '[2001:db8::1]:3260'
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
fake_multipath_dev = None
fake_raw_dev = '/dev/disk/by-path/fake-raw-lun'
vol = {'id': 1, 'name': name1}
connection_properties = self.iscsi_connection_multipath(
vol, [location1, location2], [iqn1, iqn2], [1, 2])
mock_multipath_device_path.return_value = fake_multipath_dev
mock_multipath_device.return_value = {
'device': '/dev/mapper/%s' % FAKE_SCSI_WWN}
mock_realpath.return_value = '/dev/sdvc'
(result_path, result_mpath_id) = (
self.connector_with_multipath._discover_mpath_device(
FAKE_SCSI_WWN,
connection_properties['data'],
fake_raw_dev))
mock_multipath_device.assert_called_with('/dev/sdvc')
result = {'path': result_path, 'multipath_id': result_mpath_id}
expected_result = {'path': '/dev/mapper/%s' % FAKE_SCSI_WWN,
'multipath_id': FAKE_SCSI_WWN}
self.assertEqual(expected_result, result)
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_multipath_volume')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_single_volume')
def test_connect_volume_mp(self, con_single_mock, con_mp_mock, clean_mock):
self.connector.use_multipath = True
res = self.connector.connect_volume(self.CON_PROPS)
self.assertEqual(con_mp_mock.return_value, res)
con_single_mock.assert_not_called()
con_mp_mock.assert_called_once_with(self.CON_PROPS)
clean_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_multipath_volume')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_single_volume')
def test_connect_volume_mp_failure(self, con_single_mock, con_mp_mock,
clean_mock):
self.connector.use_multipath = True
con_mp_mock.side_effect = exception.BrickException
self.assertRaises(exception.BrickException,
self.connector.connect_volume, self.CON_PROPS)
con_single_mock.assert_not_called()
con_mp_mock.assert_called_once_with(self.CON_PROPS)
clean_mock.assert_called_once_with(self.CON_PROPS, force=True)
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_multipath_volume')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_single_volume')
def test_connect_volume_sp(self, con_single_mock, con_mp_mock, clean_mock):
self.connector.use_multipath = False
res = self.connector.connect_volume(self.CON_PROPS)
self.assertEqual(con_single_mock.return_value, res)
con_mp_mock.assert_not_called()
con_single_mock.assert_called_once_with(self.CON_PROPS)
clean_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_multipath_volume')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_single_volume')
def test_connect_volume_sp_failure(self, con_single_mock, con_mp_mock,
clean_mock):
self.connector.use_multipath = False
con_single_mock.side_effect = exception.BrickException
self.assertRaises(exception.BrickException,
self.connector.connect_volume, self.CON_PROPS)
con_mp_mock.assert_not_called()
con_single_mock.assert_called_once_with(self.CON_PROPS)
clean_mock.assert_called_once_with(self.CON_PROPS, force=True)
def test_discover_iscsi_portals(self):
location = '10.0.2.15:3260'
name = 'volume-00000001'
iqn = 'iqn.2010-10.org.openstack:%s' % name
vol = {'id': 1, 'name': name}
auth_method = 'CHAP'
auth_username = 'fake_chap_username'
auth_password = 'fake_chap_password'
discovery_auth_method = 'CHAP'
discovery_auth_username = 'fake_chap_username'
discovery_auth_password = 'fake_chap_password'
connection_properties = self.iscsi_connection_chap(
vol, location, iqn, auth_method, auth_username, auth_password,
discovery_auth_method, discovery_auth_username,
discovery_auth_password)
self.connector_with_multipath = iscsi.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=True)
for transport in ['default', 'iser', 'badTransport']:
interface = 'iser' if transport == 'iser' else 'default'
self.mock_object(self.connector_with_multipath, '_get_transport',
mock.Mock(return_value=interface))
self.connector_with_multipath._discover_iscsi_portals(
connection_properties['data'])
expected_cmds = [
'iscsiadm -m discoverydb -t sendtargets -I %(iface)s '
'-p %(location)s --op update '
'-n discovery.sendtargets.auth.authmethod -v %(auth_method)s '
'-n discovery.sendtargets.auth.username -v %(username)s '
'-n discovery.sendtargets.auth.password -v %(password)s' %
{'iface': interface, 'location': location,
'auth_method': discovery_auth_method,
'username': discovery_auth_username,
'password': discovery_auth_password},
'iscsiadm -m discoverydb -t sendtargets -I %(iface)s'
' -p %(location)s --discover' % {'iface': interface,
'location': location}]
self.assertEqual(expected_cmds, self.cmds)
# Reset to run with a different transport type
self.cmds = list()
@mock.patch.object(iscsi.ISCSIConnector,
'_run_iscsiadm_update_discoverydb')
@mock.patch.object(os.path, 'exists', return_value=True)
def test_iscsi_portals_with_chap_discovery(
self, exists, update_discoverydb):
location = '10.0.2.15:3260'
name = 'volume-00000001'
iqn = 'iqn.2010-10.org.openstack:%s' % name
vol = {'id': 1, 'name': name}
auth_method = 'CHAP'
auth_username = 'fake_chap_username'
auth_password = 'fake_chap_password'
discovery_auth_method = 'CHAP'
discovery_auth_username = 'fake_chap_username'
discovery_auth_password = 'fake_chap_password'
connection_properties = self.iscsi_connection_chap(
vol, location, iqn, auth_method, auth_username, auth_password,
discovery_auth_method, discovery_auth_username,
discovery_auth_password)
self.connector_with_multipath = iscsi.ISCSIConnector(
None, execute=self.fake_execute, use_multipath=True)
self.cmds = []
# The first call returns an error code = 6, mocking an empty
# discovery db. The second one mocks a successful return and the
# third one a dummy exit code, which will trigger the
# TargetPortalNotFound exception in connect_volume
update_discoverydb.side_effect = [
putils.ProcessExecutionError(None, None, 6),
("", ""),
putils.ProcessExecutionError(None, None, 9)]
self.connector_with_multipath._discover_iscsi_portals(
connection_properties['data'])
update_discoverydb.assert_called_with(connection_properties['data'])
expected_cmds = [
'iscsiadm -m discoverydb -t sendtargets -p %s -I default'
' --op new' % location,
'iscsiadm -m discoverydb -t sendtargets -I default -p %s'
' --discover' % location]
self.assertEqual(expected_cmds, self.cmds)
self.assertRaises(exception.TargetPortalNotFound,
self.connector_with_multipath.connect_volume,
connection_properties['data'])
def test_get_target_portals_from_iscsiadm_output(self):
connector = self.connector
test_output = '''10.15.84.19:3260 iqn.1992-08.com.netapp:sn.33615311
10.15.85.19:3260 iqn.1992-08.com.netapp:sn.33615311'''
res = connector._get_target_portals_from_iscsiadm_output(test_output)
ips = ['10.15.84.19:3260', '10.15.85.19:3260']
iqns = ['iqn.1992-08.com.netapp:sn.33615311',
'iqn.1992-08.com.netapp:sn.33615311']
expected = (ips, iqns)
self.assertEqual(expected, res)
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
def test_disconnect_volume(self, cleanup_mock):
res = self.connector.disconnect_volume(mock.sentinel.con_props,
mock.sentinel.dev_info,
mock.sentinel.Force,
mock.sentinel.ignore_errors)
self.assertEqual(cleanup_mock.return_value, res)
cleanup_mock.assert_called_once_with(
mock.sentinel.con_props,
force=mock.sentinel.Force,
ignore_errors=mock.sentinel.ignore_errors)
@mock.patch.object(iscsi.ISCSIConnector, '_disconnect_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_get_connection_devices')
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device')
@mock.patch.object(linuxscsi.LinuxSCSI, 'remove_connection',
return_value=None)
def test_cleanup_connection(self, remove_mock, flush_mock, con_devs_mock,
discon_mock):
# Return an ordered dicts instead of normal dict for discon_mock.assert
con_devs_mock.return_value = collections.OrderedDict((
(('ip1:port1', 'tgt1'), ({'sda'}, set())),
(('ip2:port2', 'tgt2'), ({'sdb'}, {'sdc'})),
(('ip3:port3', 'tgt3'), (set(), set()))))
with mock.patch.object(self.connector,
'use_multipath') as use_mp_mock:
self.connector._cleanup_connection(
self.CON_PROPS, ips_iqns_luns=mock.sentinel.ips_iqns_luns,
force=False, ignore_errors=False)
con_devs_mock.assert_called_once_with(self.CON_PROPS,
mock.sentinel.ips_iqns_luns)
remove_mock.assert_called_once_with({'sda', 'sdb'}, use_mp_mock,
False, mock.ANY)
discon_mock.assert_called_once_with(
self.CON_PROPS,
[('ip1:port1', 'tgt1'), ('ip3:port3', 'tgt3')],
False, mock.ANY)
flush_mock.assert_not_called()
@mock.patch('os_brick.exception.ExceptionChainer.__nonzero__',
mock.Mock(return_value=True))
@mock.patch('os_brick.exception.ExceptionChainer.__bool__',
mock.Mock(return_value=True))
@mock.patch.object(iscsi.ISCSIConnector, '_disconnect_connection')
@mock.patch.object(iscsi.ISCSIConnector, '_get_connection_devices')
@mock.patch.object(linuxscsi.LinuxSCSI, 'flush_multipath_device')
@mock.patch.object(linuxscsi.LinuxSCSI, 'remove_connection',
return_value=mock.sentinel.mp_name)
def test_cleanup_connection_force_failure(self, remove_mock, flush_mock,
con_devs_mock, discon_mock):
# Return an ordered dicts instead of normal dict for discon_mock.assert
con_devs_mock.return_value = collections.OrderedDict((
(('ip1:port1', 'tgt1'), ({'sda'}, set())),
(('ip2:port2', 'tgt2'), ({'sdb'}, {'sdc'})),
(('ip3:port3', 'tgt3'), (set(), set()))))
with mock.patch.object(self.connector, 'use_multipath',
wraps=True) as use_mp_mock:
self.assertRaises(exception.ExceptionChainer,
self.connector._cleanup_connection,
self.CON_PROPS,
ips_iqns_luns=mock.sentinel.ips_iqns_luns,
force=mock.sentinel.force, ignore_errors=False)
con_devs_mock.assert_called_once_with(self.CON_PROPS,
mock.sentinel.ips_iqns_luns)
remove_mock.assert_called_once_with({'sda', 'sdb'}, use_mp_mock,
mock.sentinel.force, mock.ANY)
discon_mock.assert_called_once_with(
self.CON_PROPS,
[('ip1:port1', 'tgt1'), ('ip3:port3', 'tgt3')],
mock.sentinel.force, mock.ANY)
flush_mock.assert_called_once_with(mock.sentinel.mp_name)
@ddt.data({'do_raise': False, 'force': False},
{'do_raise': True, 'force': True},
{'do_raise': True, 'force': False})
@ddt.unpack
@mock.patch.object(iscsi.ISCSIConnector, '_disconnect_from_iscsi_portal')
def test_disconnect_connection(self, disconnect_mock, do_raise, force):
will_raise = do_raise and not force
actual_call_args = []
# Since we reuse the copied dictionary on _disconnect_connection
# changing its values we cannot use mock's assert_has_calls
def my_disconnect(con_props):
actual_call_args.append(con_props.copy())
if do_raise:
raise exception.ExceptionChainer()
disconnect_mock.side_effect = my_disconnect
connections = (('ip1:port1', 'tgt1'), ('ip2:port2', 'tgt2'))
original_props = self.CON_PROPS.copy()
exc = exception.ExceptionChainer()
if will_raise:
self.assertRaises(exception.ExceptionChainer,
self.connector._disconnect_connection,
self.CON_PROPS, connections,
force=force, exc=exc)
else:
self.connector._disconnect_connection(self.CON_PROPS, connections,
force=force, exc=exc)
# Passed properties should not be altered by the method call
self.assertDictEqual(original_props, self.CON_PROPS)
expected = [original_props.copy(), original_props.copy()]
for i, (ip, iqn) in enumerate(connections):
expected[i].update(target_portal=ip, target_iqn=iqn)
# If we are failing and not forcing we won't make all the alls
if will_raise:
expected = expected[:1]
self.assertListEqual(expected, actual_call_args)
# No exceptions have been caught by ExceptionChainer context manager
self.assertEqual(do_raise, bool(exc))
def test_disconnect_from_iscsi_portal(self):
self.connector._disconnect_from_iscsi_portal(self.CON_PROPS)
expected_prefix = ('iscsiadm -m node -T %s -p %s ' %
(self.CON_PROPS['target_iqn'],
self.CON_PROPS['target_portal']))
expected = [
expected_prefix + '--op update -n node.startup -v manual',
expected_prefix + '--logout',
expected_prefix + '--op delete',
]
self.assertListEqual(expected, self.cmds)
def test_iscsiadm_discover_parsing(self):
# Ensure that parsing iscsiadm discover ignores cruft.
ips = ["192.168.204.82:3260,1", "192.168.204.82:3261,1"]
iqns = ["iqn.2010-10.org.openstack:volume-"
"f9b12623-6ce3-4dac-a71f-09ad4249bdd3",
"iqn.2010-10.org.openstack:volume-"
"f9b12623-6ce3-4dac-a71f-09ad4249bdd4"]
# This slight wonkiness brought to you by pep8, as the actual
# example output runs about 97 chars wide.
sample_input = """Loading iscsi modules: done
Starting iSCSI initiator service: done
Setting up iSCSI targets: unused
%s %s
%s %s
""" % (ips[0], iqns[0], ips[1], iqns[1])
out = self.connector.\
_get_target_portals_from_iscsiadm_output(sample_input)
self.assertEqual((ips, iqns), out)
def test_sanitize_log_run_iscsiadm(self):
# Tests that the parameters to the _run_iscsiadm function
# are sanitized for when passwords are logged.
def fake_debug(*args, **kwargs):
self.assertIn('node.session.auth.password', args[0])
self.assertNotIn('scrubme', args[0])
volume = {'id': 'fake_uuid'}
connection_info = self.iscsi_connection(volume,
"10.0.2.15:3260",
"fake_iqn")
iscsi_properties = connection_info['data']
with mock.patch.object(iscsi.LOG, 'debug',
side_effect=fake_debug) as debug_mock:
self.connector._iscsiadm_update(iscsi_properties,
'node.session.auth.password',
'scrubme')
# we don't care what the log message is, we just want to make sure
# our stub method is called which asserts the password is scrubbed
self.assertTrue(debug_mock.called)
@mock.patch.object(iscsi.ISCSIConnector, 'get_volume_paths')
def test_extend_volume_no_path(self, mock_volume_paths):
mock_volume_paths.return_value = []
volume = {'id': 'fake_uuid'}
connection_info = self.iscsi_connection(volume,
"10.0.2.15:3260",
"fake_iqn")
self.assertRaises(exception.VolumePathsNotFound,
self.connector.extend_volume,
connection_info['data'])
@mock.patch.object(linuxscsi.LinuxSCSI, 'extend_volume')
@mock.patch.object(iscsi.ISCSIConnector, 'get_volume_paths')
def test_extend_volume(self, mock_volume_paths, mock_scsi_extend):
fake_new_size = 1024
mock_volume_paths.return_value = ['/dev/vdx']
mock_scsi_extend.return_value = fake_new_size
volume = {'id': 'fake_uuid'}
connection_info = self.iscsi_connection(volume,
"10.0.2.15:3260",
"fake_iqn")
new_size = self.connector.extend_volume(connection_info['data'])
self.assertEqual(fake_new_size, new_size)
@mock.patch.object(iscsi.LOG, 'info')
@mock.patch.object(linuxscsi.LinuxSCSI, 'extend_volume')
@mock.patch.object(iscsi.ISCSIConnector, 'get_volume_paths')
def test_extend_volume_mask_password(self, mock_volume_paths,
mock_scsi_extend,
mock_log_info):
fake_new_size = 1024
mock_volume_paths.return_value = ['/dev/vdx']
mock_scsi_extend.return_value = fake_new_size
volume = {'id': 'fake_uuid'}
connection_info = self.iscsi_connection_chap(
volume, "10.0.2.15:3260", "fake_iqn",
'CHAP', 'fake_user', 'fake_password',
'CHAP1', 'fake_user1', 'fake_password1')
self.connector.extend_volume(connection_info['data'])
self.assertEqual(2, mock_log_info.call_count)
self.assertIn("'auth_password': '***'",
str(mock_log_info.call_args_list[0]))
self.assertIn("'discovery_auth_password': '***'",
str(mock_log_info.call_args_list[0]))
@mock.patch.object(iscsi.LOG, 'warning')
@mock.patch.object(linuxscsi.LinuxSCSI, 'extend_volume')
@mock.patch.object(iscsi.ISCSIConnector, 'get_volume_paths')
def test_extend_volume_mask_password_no_paths(self, mock_volume_paths,
mock_scsi_extend,
mock_log_warning):
fake_new_size = 1024
mock_volume_paths.return_value = []
mock_scsi_extend.return_value = fake_new_size
volume = {'id': 'fake_uuid'}
connection_info = self.iscsi_connection_chap(
volume, "10.0.2.15:3260", "fake_iqn",
'CHAP', 'fake_user', 'fake_password',
'CHAP1', 'fake_user1', 'fake_password1')
self.assertRaises(exception.VolumePathsNotFound,
self.connector.extend_volume,
connection_info['data'])
self.assertEqual(1, mock_log_warning.call_count)
self.assertIn("'auth_password': '***'",
str(mock_log_warning.call_args_list[0]))
self.assertIn("'discovery_auth_password': '***'",
str(mock_log_warning.call_args_list[0]))
@mock.patch.object(os.path, 'isdir')
def test_get_all_available_volumes_path_not_dir(self, mock_isdir):
mock_isdir.return_value = False
expected = []
actual = self.connector.get_all_available_volumes()
self.assertItemsEqual(expected, actual)
@mock.patch.object(iscsi.ISCSIConnector, '_get_device_path')
def test_get_potential_paths_mpath(self, get_path_mock):
self.connector.use_multipath = True
res = self.connector._get_potential_volume_paths(self.CON_PROPS)
get_path_mock.assert_called_once_with(self.CON_PROPS)
self.assertEqual(get_path_mock.return_value, res)
self.assertEqual([], self.cmds)
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions')
@mock.patch.object(iscsi.ISCSIConnector, '_get_device_path')
def test_get_potential_paths_single_path(self, get_path_mock,
get_sessions_mock):
get_path_mock.side_effect = [['path1'], ['path2'], ['path3', 'path4']]
get_sessions_mock.return_value = [
('tcp:', 'session1', 'ip1:port1', '1', 'tgt1'),
('tcp:', 'session2', 'ip2:port2', '-1', 'tgt2'),
('tcp:', 'session3', 'ip3:port3', '1', 'tgt3')]
self.connector.use_multipath = False
res = self.connector._get_potential_volume_paths(self.CON_PROPS)
self.assertEqual({'path1', 'path2', 'path3', 'path4'}, set(res))
get_sessions_mock.assert_called_once_with()
@mock.patch.object(iscsi.ISCSIConnector, '_discover_iscsi_portals')
def test_get_ips_iqns_luns_with_target_iqns(self, discover_mock):
res = self.connector._get_ips_iqns_luns(self.CON_PROPS)
self.assertEqual(discover_mock.return_value, res)
@mock.patch.object(iscsi.ISCSIConnector, '_discover_iscsi_portals')
def test_get_ips_iqns_luns_no_target_iqns_share_iqn(self, discover_mock):
con_props = {'volume_id': 'vol_id',
'target_portal': 'ip1:port1',
'target_iqn': 'tgt1',
'target_lun': '1'}
discover_mock.return_value = [('ip1:port1', 'tgt1', '1'),
('ip1:port1', 'tgt2', '1'),
('ip2:port2', 'tgt1', '2'),
('ip2:port2', 'tgt2', '2')]
res = self.connector._get_ips_iqns_luns(con_props)
expected = {('ip1:port1', 'tgt1', '1'),
('ip2:port2', 'tgt1', '2')}
self.assertEqual(expected, set(res))
@mock.patch.object(iscsi.ISCSIConnector, '_discover_iscsi_portals')
def test_get_ips_iqns_luns_no_target_iqns_diff_iqn(self, discover_mock):
con_props = {'volume_id': 'vol_id',
'target_portal': 'ip1:port1',
'target_iqn': 'tgt1',
'target_lun': '1'}
discover_mock.return_value = [('ip1:port1', 'tgt1', '1'),
('ip2:port2', 'tgt2', '2')]
res = self.connector._get_ips_iqns_luns(con_props)
self.assertEqual(discover_mock.return_value, res)
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
def test_connect_to_iscsi_portal_all_new(self, get_sessions_mock):
"""Connect creating node and session."""
session = 'session2'
get_sessions_mock.side_effect = [
[('tcp:', 'session1', 'ip1:port1', '1', 'tgt')],
[('tcp:', 'session1', 'ip1:port1', '1', 'tgt'),
('tcp:', session, 'ip1:port1', '-1', 'tgt1')]
]
with mock.patch.object(self.connector, '_execute') as exec_mock:
exec_mock.side_effect = [('', 'error'), ('', None),
('', None), ('', None),
('', None)]
res = self.connector._connect_to_iscsi_portal(self.CON_PROPS)
# 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'
]
actual_cmds = [' '.join(args[0]) for args in exec_mock.call_args_list]
self.assertListEqual(expected_cmds, actual_cmds)
self.assertEqual(2, get_sessions_mock.call_count)
@mock.patch.object(iscsi.ISCSIConnector, '_get_iscsi_sessions_full')
def test_connect_to_iscsi_portal_all_exists_chap(self, get_sessions_mock):
"""Node and session already exists and we use chap authentication."""
session = 'session2'
get_sessions_mock.return_value = [('tcp:', session, 'ip1:port1',
'-1', 'tgt1')]
con_props = self.CON_PROPS.copy()
con_props.update(auth_method='CHAP', auth_username='user',
auth_password='pwd')
res = self.connector._connect_to_iscsi_portal(con_props)
# 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,
prefix + ' --op update -n node.session.auth.authmethod -v CHAP',
prefix + ' --op update -n node.session.auth.username -v user',
prefix + ' --op update -n node.session.auth.password -v pwd',
]
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.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]
self.assertListEqual(expected_cmds, actual_cmds)
get_sessions_mock.assert_called_once_with()
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
side_effect=(None, 'tgt2'))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch('time.sleep')
def test_connect_single_volume(self, sleep_mock, cleanup_mock,
connect_mock, get_wwn_mock):
def my_connect(rescans, props, data):
if props['target_iqn'] == 'tgt2':
# Succeed on second call
data['found_devices'].append('sdz')
connect_mock.side_effect = my_connect
res = self.connector._connect_single_volume(self.CON_PROPS)
expected = {'type': 'block', 'scsi_wwn': 'tgt2', 'path': '/dev/sdz'}
self.assertEqual(expected, res)
get_wwn_mock.assert_has_calls([mock.call(['sdz']), mock.call(['sdz'])])
sleep_mock.assert_called_once_with(1)
cleanup_mock.assert_called_once_with(
{'target_lun': 4, 'volume_id': 'vol_id',
'target_portal': 'ip1:port1', 'target_iqn': 'tgt1'},
(('ip1:port1', 'tgt1', 4),),
force=True, ignore_errors=True)
@staticmethod
def _get_connect_vol_data():
return {'stop_connecting': False, 'num_logins': 0, 'failed_logins': 0,
'stopped_threads': 0, 'found_devices': [],
'just_added_devices': []}
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
side_effect=(None, 'tgt2'))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch.object(iscsi.ISCSIConnector, '_cleanup_connection')
@mock.patch('time.sleep')
def test_connect_single_volume_not_found(self, sleep_mock, cleanup_mock,
connect_mock, get_wwn_mock):
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._connect_single_volume,
self.CON_PROPS)
get_wwn_mock.assert_not_called()
# Called twice by the retry mechanism
self.assertEqual(2, sleep_mock.call_count)
props = list(self.connector._get_all_targets(self.CON_PROPS))
calls_per_try = [
mock.call({'target_portal': prop[0], 'target_iqn': prop[1],
'target_lun': prop[2], 'volume_id': 'vol_id'},
(prop,), force=True, ignore_errors=True)
for prop in props
]
cleanup_mock.assert_has_calls(calls_per_try * 3)
data = self._get_connect_vol_data()
calls_per_try = [mock.call(self.connector.device_scan_attempts,
{'target_portal': prop[0],
'target_iqn': prop[1],
'target_lun': prop[2],
'volume_id': 'vol_id'},
data)
for prop in props]
connect_mock.assert_has_calls(calls_per_try * 3)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm',
side_effect=[None, 'dm-0'])
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
return_value='wwn')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_wwid')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch('time.sleep')
def test_connect_multipath_volume_all_succeed(self, sleep_mock,
connect_mock, add_wwid_mock,
add_path_mock, get_wwn_mock,
find_dm_mock):
def my_connect(rescans, props, data):
devs = {'tgt1': 'sda', 'tgt2': 'sdb', 'tgt3': 'sdc', 'tgt4': 'sdd'}
data['stopped_threads'] += 1
data['num_logins'] += 1
dev = devs[props['target_iqn']]
data['found_devices'].append(dev)
data['just_added_devices'].append(dev)
connect_mock.side_effect = my_connect
res = self.connector._connect_multipath_volume(self.CON_PROPS)
expected = {'type': 'block', 'scsi_wwn': 'wwn', 'multipath_id': 'dm-0',
'path': '/dev/dm-0'}
self.assertEqual(expected, res)
self.assertEqual(1, get_wwn_mock.call_count)
result = list(get_wwn_mock.call_args[0][0])
result.sort()
self.assertEqual(['sda', 'sdb', 'sdc', 'sdd'], result)
add_wwid_mock.assert_called_once_with('wwn')
self.assertNotEqual(0, add_path_mock.call_count)
self.assertGreaterEqual(find_dm_mock.call_count, 2)
self.assertEqual(4, connect_mock.call_count)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm',
side_effect=[None, 'dm-0'])
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
return_value='wwn')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_wwid')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch('time.sleep')
def test_connect_multipath_volume_all_fail(self, sleep_mock, connect_mock,
add_wwid_mock, add_path_mock,
get_wwn_mock, find_dm_mock):
def my_connect(rescans, props, data):
data['stopped_threads'] += 1
data['failed_logins'] += 1
connect_mock.side_effect = my_connect
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._connect_multipath_volume,
self.CON_PROPS)
get_wwn_mock.assert_not_called()
add_wwid_mock.assert_not_called()
add_path_mock.assert_not_called()
find_dm_mock.assert_not_called()
self.assertEqual(4 * 3, connect_mock.call_count)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm',
side_effect=[None, 'dm-0'])
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
return_value='wwn')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_wwid')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch('time.sleep')
def test_connect_multipath_volume_some_fail_mp_found(self, sleep_mock,
connect_mock,
add_wwid_mock,
add_path_mock,
get_wwn_mock,
find_dm_mock):
def my_connect(rescans, props, data):
devs = {'tgt1': '', 'tgt2': 'sdb', 'tgt3': '', 'tgt4': 'sdd'}
data['stopped_threads'] += 1
dev = devs[props['target_iqn']]
if dev:
data['num_logins'] += 1
data['found_devices'].append(dev)
data['just_added_devices'].append(dev)
else:
data['failed_logins'] += 1
connect_mock.side_effect = my_connect
res = self.connector._connect_multipath_volume(self.CON_PROPS)
expected = {'type': 'block', 'scsi_wwn': 'wwn', 'multipath_id': 'dm-0',
'path': '/dev/dm-0'}
self.assertEqual(expected, res)
self.assertEqual(1, get_wwn_mock.call_count)
result = list(get_wwn_mock.call_args[0][0])
result.sort()
self.assertEqual(['sdb', 'sdd'], result)
add_wwid_mock.assert_called_once_with('wwn')
self.assertNotEqual(0, add_path_mock.call_count)
self.assertGreaterEqual(find_dm_mock.call_count, 2)
self.assertEqual(4, connect_mock.call_count)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm',
return_value=None)
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
return_value='wwn')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_wwid')
@mock.patch.object(iscsi.time, 'time', side_effect=(0, 0, 11, 0))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch('time.sleep')
def test_connect_multipath_volume_some_fail_mp_not_found(self, sleep_mock,
connect_mock,
time_mock,
add_wwid_mock,
add_path_mock,
get_wwn_mock,
find_dm_mock):
def my_connect(rescans, props, data):
devs = {'tgt1': '', 'tgt2': 'sdb', 'tgt3': '', 'tgt4': 'sdd'}
data['stopped_threads'] += 1
dev = devs[props['target_iqn']]
if dev:
data['num_logins'] += 1
data['found_devices'].append(dev)
data['just_added_devices'].append(dev)
else:
data['failed_logins'] += 1
connect_mock.side_effect = my_connect
res = self.connector._connect_multipath_volume(self.CON_PROPS)
expected = [{'type': 'block', 'scsi_wwn': 'wwn', 'path': '/dev/sdb'},
{'type': 'block', 'scsi_wwn': 'wwn', 'path': '/dev/sdd'}]
# It can only be one of the 2
self.assertIn(res, expected)
self.assertEqual(1, get_wwn_mock.call_count)
result = list(get_wwn_mock.call_args[0][0])
result.sort()
self.assertEqual(['sdb', 'sdd'], result)
add_wwid_mock.assert_called_once_with('wwn')
self.assertNotEqual(0, add_path_mock.call_count)
self.assertGreaterEqual(find_dm_mock.call_count, 4)
self.assertEqual(4, connect_mock.call_count)
@mock.patch.object(linuxscsi.LinuxSCSI, 'find_sysfs_multipath_dm',
return_value=None)
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_sysfs_wwn',
return_value='wwn')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'multipath_add_wwid')
@mock.patch.object(iscsi.time, 'time', side_effect=(0, 0, 11, 0))
@mock.patch.object(iscsi.ISCSIConnector, '_connect_vol')
@mock.patch('time.sleep', mock.Mock())
def test_connect_multipath_volume_all_loging_not_found(self,
connect_mock,
time_mock,
add_wwid_mock,
add_path_mock,
get_wwn_mock,
find_dm_mock):
def my_connect(rescans, props, data):
data['stopped_threads'] += 1
data['num_logins'] += 1
connect_mock.side_effect = my_connect
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._connect_multipath_volume,
self.CON_PROPS)
get_wwn_mock.assert_not_called()
add_wwid_mock.assert_not_called()
add_path_mock.assert_not_called()
find_dm_mock.assert_not_called()
self.assertEqual(12, connect_mock.call_count)
@mock.patch('time.sleep', mock.Mock())
@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):
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',
side_effect=(None, 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_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_called_once_with(*hctl)
dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl)
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal',
return_value=(None, False))
def test_connect_vol_no_session(self, connect_mock):
data = self._get_connect_vol_data()
self.connector._connect_vol(3, self.CON_PROPS, data)
expected = self._get_connect_vol_data()
expected.update(failed_logins=1, stopped_threads=1)
self.assertDictEqual(expected, data)
@mock.patch('time.sleep', mock.Mock())
@mock.patch.object(linuxscsi.LinuxSCSI, 'scan_iscsi')
@mock.patch.object(linuxscsi.LinuxSCSI, 'device_name_by_hctl',
return_value=None)
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal')
def test_connect_vol_not_found(self, connect_mock, dev_name_mock,
scan_mock):
lscsi = self.connector._linuxscsi
data = self._get_connect_vol_data()
hctl = [mock.sentinel.host, mock.sentinel.channel,
mock.sentinel.target, mock.sentinel.lun]
# 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=(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_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)])
@mock.patch('time.sleep', mock.Mock())
@mock.patch.object(linuxscsi.LinuxSCSI, 'scan_iscsi')
@mock.patch.object(iscsi.ISCSIConnector, '_connect_to_iscsi_portal')
def test_connect_vol_stop_connecting(self, connect_mock, scan_mock):
data = self._get_connect_vol_data()
def device_name_by_hctl(session, hctl):
data['stop_connecting'] = True
return None
lscsi = self.connector._linuxscsi
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, \
mock.patch.object(
lscsi, 'device_name_by_hctl',
side_effect=device_name_by_hctl) as dev_name_mock:
self.connector._connect_vol(3, self.CON_PROPS, data)
expected = self._get_connect_vol_data()
expected.update(num_logins=1, stopped_threads=1, stop_connecting=True)
self.assertDictEqual(expected, data)
hctl_mock.assert_called_once_with(mock.sentinel.session,
self.CON_PROPS['target_lun'])
scan_mock.assert_not_called()
dev_name_mock.assert_called_once_with(mock.sentinel.session, hctl)