Volume discovery and local storage management lib
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

1555 lines
73 KiB

# (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):
SINGLE_CON_PROPS = {'volume_id': 'vol_id',
'target_portal': 'ip1:port1',
'target_iqn': 'tgt1',
'encryption': False,
'target_lun': '1'}
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.object(iscsi.ISCSIConnector, '_get_ips_iqns_luns')
@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, iql_mock):
self.connector.use_multipath = True
iql_mock.return_value = 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'],
]
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)
iql_mock.assert_called_once_with(self.CON_PROPS, discover=False)
@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 node --op show -p %s' % location,
'iscsiadm -m discoverydb -t sendtargets -I %(iface)s'
' -p %(location)s --discover' % {'iface': interface,
'location': location},
'iscsiadm -m node --op show -p %s' % 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 node --op show -p %s' % location,
'iscsiadm -m discoverydb -t sendtargets -I default -p %s'
' --discover' % location,
'iscsiadm -m node --op show -p %s' % 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,1 iqn.1992-08.com.netapp:sn.33615311
10.15.85.19:3260,2 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,
device_info=mock.sentinel.dev_info)
@ddt.data(True, False)
@mock.patch.object(iscsi.ISCSIConnector, '_get_transport')
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsiadm_bare')
def test_get_discoverydb_portals(self, is_iser, iscsiadm_mock,
transport_mock):
params = {
'iqn1': self.SINGLE_CON_PROPS['target_iqn'],
'iqn2': 'iqn.2004-04.com.qnap:ts-831x:iscsi.cinder-2017.9ef',
'addr': self.SINGLE_CON_PROPS['target_portal'].replace(':', ','),
'ip1': self.SINGLE_CON_PROPS['target_portal'],
'ip2': '192.168.1.3:3260',
'transport': 'iser' if is_iser else 'default',
'other_transport': 'default' if is_iser else 'iser',
}
iscsiadm_mock.return_value = (
'SENDTARGETS:\n'
'DiscoveryAddress: 192.168.1.33,3260\n'
'DiscoveryAddress: %(addr)s\n'
'Target: %(iqn1)s\n'
' Portal: %(ip2)s,1\n'
' Iface Name: %(transport)s\n'
' Portal: %(ip1)s,1\n'
' Iface Name: %(transport)s\n'
' Portal: %(ip1)s,1\n'
' Iface Name: %(other_transport)s\n'
'Target: %(iqn2)s\n'
' Portal: %(ip2)s,1\n'
' Iface Name: %(transport)s\n'
' Portal: %(ip1)s,1\n'
' Iface Name: %(transport)s\n'
'DiscoveryAddress: 192.168.1.38,3260\n'
'iSNS:\n'
'No targets found.\n'
'STATIC:\n'
'No targets found.\n'
'FIRMWARE:\n'
'No targets found.\n' % params, None)
transport_mock.return_value = 'iser' if is_iser else 'non-iser'
res = self.connector._get_discoverydb_portals(self.SINGLE_CON_PROPS)
expected = [(params['ip2'], params['iqn1'],
self.SINGLE_CON_PROPS['target_lun']),
(params['ip1'], params['iqn1'],
self.SINGLE_CON_PROPS['target_lun']),
(params['ip2'], params['iqn2'],
self.SINGLE_CON_PROPS['target_lun']),
(params['ip1'], params['iqn2'],
self.SINGLE_CON_PROPS['target_lun'])]
self.assertListEqual(expected, res)
iscsiadm_mock.assert_called_once_with(
['-m', 'discoverydb', '-o', 'show', '-P', 1])
transport_mock.assert_called_once_with()
@mock.patch.object(iscsi.ISCSIConnector, '_get_transport', return_value='')
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsiadm_bare')
def test_get_discoverydb_portals_error(self, iscsiadm_mock,
transport_mock):
"""DiscoveryAddress is not present."""
iscsiadm_mock.return_value = (
'SENDTARGETS:\n'
'DiscoveryAddress: 192.168.1.33,3260\n'
'DiscoveryAddress: 192.168.1.38,3260\n'
'iSNS:\n'
'No targets found.\n'
'STATIC:\n'
'No targets found.\n'
'FIRMWARE:\n'
'No targets found.\n', None)
self.assertRaises(exception.TargetPortalsNotFound,
self.connector._get_discoverydb_portals,
self.SINGLE_CON_PROPS)
iscsiadm_mock.assert_called_once_with(
['-m', 'discoverydb', '-o', 'show', '-P', 1])
transport_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_get_transport', return_value='')
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsiadm_bare')
def test_get_discoverydb_portals_error_is_present(self, iscsiadm_mock,
transport_mock):
"""DiscoveryAddress is present but wrong iterface."""
params = {
'iqn': self.SINGLE_CON_PROPS['target_iqn'],
'addr': self.SINGLE_CON_PROPS['target_portal'].replace(':', ','),
'ip': self.SINGLE_CON_PROPS['target_portal'],
}
iscsiadm_mock.return_value = (
'SENDTARGETS:\n'
'DiscoveryAddress: 192.168.1.33,3260\n'
'DiscoveryAddress: %(addr)s\n'
'Target: %(iqn)s\n'
' Portal: %(ip)s,1\n'
' Iface Name: iser\n'
'DiscoveryAddress: 192.168.1.38,3260\n'
'iSNS:\n'
'No targets found.\n'
'STATIC:\n'
'No targets found.\n'
'FIRMWARE:\n'
'No targets found.\n' % params, None)
self.assertRaises(exception.TargetPortalsNotFound,
self.connector._get_discoverydb_portals,
self.SINGLE_CON_PROPS)
iscsiadm_mock.assert_called_once_with(
['-m', 'discoverydb', '-o', 'show', '-P', 1])
transport_mock.assert_called_once_with()
@ddt.data(('/dev/sda', False),
('/dev/disk/by-id/scsi-WWID', False),
('/dev/dm-11', True),
('/dev/disk/by-id/dm-uuid-mpath-MPATH', True))
@ddt.unpack
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_dev_path')
@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, path_used, was_multipath, remove_mock,
flush_mock, con_devs_mock, discon_mock,
get_dev_path_mock):
get_dev_path_mock.return_value = path_used
# 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,
device_info=mock.sentinel.device_info)
get_dev_path_mock.called_once_with(self.CON_PROPS,
mock.sentinel.device_info)
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,
path_used, was_multipath)
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,
'', False)
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)
def test_cleanup_connection_no_data_discoverydb(self):
self.connector.use_multipath = True
with mock.patch.object(self.connector, '_get_discoverydb_portals',
side_effect=exception.TargetPortalsNotFound), \
mock.patch.object(self.connector._linuxscsi,
'remove_connection') as mock_remove:
# This will not raise and exception
self.connector._cleanup_connection(self.SINGLE_CON_PROPS)
mock_remove.assert_not_called()
@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", "192.168.204.82:3261"]
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] + ',1', iqns[0], ips[1] + ',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 = [
'ip1:port1', 'ip2:port2', 'ip3:port3']
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)
expected = list(self.connector._get_all_targets(self.CON_PROPS))
self.assertListEqual(expected, res)
discover_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_get_discoverydb_portals')
@mock.patch.object(iscsi.ISCSIConnector, '_discover_iscsi_portals')
def test_get_ips_iqns_luns_discoverydb(self, discover_mock,
db_portals_mock):
db_portals_mock.return_value = [('ip1:port1', 'tgt1', '1'),
('ip2:port2', 'tgt2', '2')]
res = self.connector._get_ips_iqns_luns(self.SINGLE_CON_PROPS,
discover=False)
self.assertListEqual(db_portals_mock.return_value, res)
db_portals_mock.assert_called_once_with(self.SINGLE_CON_PROPS)
discover_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_discover_iscsi_portals')
def test_get_ips_iqns_luns_no_target_iqns_share_iqn(self, discover_mock):
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(self.SINGLE_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):
discover_mock.return_value = [('ip1:port1', 'tgt1', '1'),
('ip2:port2', 'tgt2', '2')]
res = self.connector._get_ips_iqns_luns(self.SINGLE_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 manual iscsi scans
self.assertEqual((session, True), res)
prefix = 'iscsiadm -m node -T tgt1 -p ip1:port1'
expected_cmds = [
prefix,
prefix + ' --op update -n node.session.scan -v manual',
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()
@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), ('', 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 '
'--op update -n node.session.scan -v manual',
'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': 'wwn',
'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': 'wwn',
'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)
@mock.patch.object(iscsi.ISCSIConnector, '_get_device_link')
def test__get_connect_result(self, get_link_mock):
props = self.CON_PROPS.copy()
props['encrypted'] = False
res = self.connector._get_connect_result(props, 'wwn', ['sda', 'sdb'])
expected = {'type': 'block', 'scsi_wwn': 'wwn', 'path': '/dev/sda'}
self.assertDictEqual(expected, res)
get_link_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_get_device_link')
def test__get_connect_result_mpath(self, get_link_mock):
props = self.CON_PROPS.copy()
props['encrypted'] = False
res = self.connector._get_connect_result(props, 'wwn', ['sda', 'sdb'],
'mpath')
expected = {'type': 'block', 'scsi_wwn': 'wwn', 'path': '/dev/mpath',
'multipath_id': 'wwn'}
self.assertDictEqual(expected, res)
get_link_mock.assert_not_called()
@mock.patch.object(iscsi.ISCSIConnector, '_get_device_link',
return_value='/dev/disk/by-id/scsi-wwn')
def test__get_connect_result_encrypted(self, get_link_mock):
props = self.CON_PROPS.copy()
props['encrypted'] = True
res = self.connector._get_connect_result(props, 'wwn', ['sda', 'sdb'])
expected = {'type': 'block', 'scsi_wwn': 'wwn',
'path': get_link_mock.return_value}
self.assertDictEqual(expected, res)
get_link_mock.assert_called_once_with('wwn', '/dev/sda', None)
@mock.patch('os.path.realpath', return_value='/dev/sda')
def test__get_device_link(self, realpath_mock):
symlink = '/dev/disk/by-id/scsi-wwn'
res = self.connector._get_device_link('wwn', '/dev/sda', None)
self.assertEqual(symlink, res)
realpath_mock.assert_called_once_with(symlink)
@mock.patch('os.path.realpath', return_value='/dev/dm-0')
def test__get_device_link_multipath(self, realpath_mock):
symlink = '/dev/disk/by-id/dm-uuid-mpath-wwn'
res = self.connector._get_device_link('wwn', '/dev/dm-0', 'wwn')
self.assertEqual(symlink, res)
realpath_mock.assert_called_once_with(symlink)
@mock.patch('os.path.realpath', side_effect=('/dev/sdz', '/dev/sdy',
'/dev/sda', '/dev/sdx'))
@mock.patch('os.listdir', return_value=['dm-...', 'scsi-wwn', 'scsi-...'])
def test__get_device_link_check_links(self, listdir_mock, realpath_mock):
res = self.connector._get_device_link('wwn', '/dev/sda', None)
self.assertEqual(res, '/dev/disk/by-id/scsi-wwn')
listdir_mock.assert_called_once_with('/dev/disk/by-id/')
realpath_mock.assert_has_calls([
mock.call('/dev/disk/by-id/scsi-wwn'),
mock.call('/dev/disk/by-id/dm-...'),
mock.call('/dev/disk/by-id/scsi-wwn')])
@mock.patch('os.path.realpath', return_value='/dev/sdz')
@mock.patch('os.listdir', return_value=['dm-...', 'scsi-...'])
def test__get_device_link_not_found(self, listdir_mock, realpath_mock):
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._get_device_link,
'wwn', '/dev/sda', None)
listdir_mock.assert_called_once_with('/dev/disk/by-id/')
realpath_mock.assert_has_calls([
mock.call('/dev/disk/by-id/scsi-wwn'),
mock.call('/dev/disk/by-id/dm-...'),
mock.call('/dev/disk/by-id/scsi-...')])
@mock.patch.object(iscsi.ISCSIConnector, '_run_iscsiadm_bare')
def test_get_node_startup_values(self, run_iscsiadm_bare_mock):
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
name3 = 'volume-00000001-3'
vol = {'id': 1, 'name': name1}
location = '10.0.2.15:3260'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
iqn3 = 'iqn.2010-10.org.openstack:%s' % name3
connection_properties = self.iscsi_connection(vol, [location], [iqn1])
node_startup1 = "manual"
node_startup2 = "automatic"
node_startup3 = "manual"
node_values = (
'# BEGIN RECORD 2.0-873\n'
'node.name = %s\n'
'node.tpgt = 1\n'
'node.startup = %s\n'
'iface.hwaddress = <empty>\n'
'# END RECORD\n'
'# BEGIN RECORD 2.0-873\n'
'node.name = %s\n'
'node.tpgt = 1\n'
'node.startup = %s\n'
'iface.hwaddress = <empty>\n'
'# END RECORD\n'
'# BEGIN RECORD 2.0-873\n'
'node.name = %s\n'
'node.tpgt = 1\n'
'node.startup = %s\n'
'iface.hwaddress = <empty>\n'
'# END RECORD\n') % (iqn1, node_startup1, iqn2, node_startup2,
iqn3, node_startup3)
run_iscsiadm_bare_mock.return_value = (node_values, None)
node_startups =\
self.connector._get_node_startup_values(
connection_properties['data'])
expected_node_startups = {iqn1: node_startup1, iqn2: node_startup2,
iqn3: node_startup3}
self.assertEqual(node_startups, expected_node_startups)
@mock.patch.object(iscsi.ISCSIConnector, '_execute')
def test_get_node_startup_values_no_nodes(self, exec_mock):
connection_properties = {'target_portal': 'ip1:port1'}
no_nodes_output = ''
no_nodes_err = 'iscsiadm: No records found\n'
exec_mock.return_value = (no_nodes_output, no_nodes_err)
res = self.connector._get_node_startup_values(connection_properties)
self.assertEqual({}, res)
exec_mock.assert_called_once_with(
'iscsiadm', '-m', 'node', '--op', 'show', '-p',
connection_properties['target_portal'],
root_helper=self.connector._root_helper, run_as_root=True,
check_exit_code=(0, 21))
@mock.patch.object(iscsi.ISCSIConnector, '_get_node_startup_values')
@mock.patch.object(iscsi.ISCSIConnector, '_iscsiadm_update')
def test_recover_node_startup_values(self, iscsiadm_update_mock,
get_node_startup_values_mock):
name1 = 'volume-00000001-1'
name2 = 'volume-00000001-2'
name3 = 'volume-00000001-3'
vol = {'id': 1, 'name': name1}
location = '10.0.2.15:3260'
iqn1 = 'iqn.2010-10.org.openstack:%s' % name1
iqn2 = 'iqn.2010-10.org.openstack:%s' % name2
iqn3 = 'iqn.2010-10.org.openstack:%s' % name3
connection_properties = self.iscsi_connection(vol, [location], [iqn1])
recover_connection = self.iscsi_connection(vol, [location], [iqn2])
node_startup1 = "manual"
node_startup2 = "automatic"
node_startup3 = "manual"
get_node_startup_values_mock.return_value = {iqn1: node_startup1,
iqn2: node_startup2,
iqn3: node_startup3}
old_node_startup_values = {iqn1: node_startup1,
iqn2: "manual",
iqn3: node_startup3}
self.connector._recover_node_startup_values(
connection_properties['data'], old_node_startup_values)
iscsiadm_update_mock.assert_called_once_with(
recover_connection['data'], "node.startup", "manual")