FC Allow for multipath volumes with different LUNs

We made assumptions in the fibre channel connector code that
there was only ever a single lun per volume, even with many
wwns per connections. There is need to support multiple luns
per multipath device, similar to how the iSCSI volumes work.

What we do is allow a list for 'target_luns' and 'target_wwns'
in the connection properties, similar to how the iSCSI connector
treats things like 'target_portals', 'target_luns', etc. we
then group together 'targets' as combination of wwpns and the
lun associated with them. This grouping is used to through
the attach and detach workflow now to determine dev paths and
scsi target information for rescans.

All existing calls with 'target_lun' and 'target_wwn' will
continue working as before, the new plural keys are optional.

Change-Id: I393a028457a162228666d8497b695984fefdfab4
Closes-Bug: #1774293
This commit is contained in:
Patrick East 2018-07-16 10:31:52 -07:00
parent 50ef90251c
commit ba2569855d
4 changed files with 363 additions and 86 deletions

View File

@ -20,6 +20,7 @@ from oslo_service import loopingcall
import six
from os_brick import exception
from os_brick.i18n import _
from os_brick import initiator
from os_brick.initiator.connectors import base
from os_brick.initiator import linuxfc
@ -70,12 +71,70 @@ class FibreChannelConnector(base.BaseLinuxConnector):
"""Where do we look for FC based volumes."""
return '/dev/disk/by-path'
def _get_possible_volume_paths(self, connection_properties, hbas):
ports = connection_properties['target_wwn']
possible_devs = self._get_possible_devices(hbas, ports)
def _add_targets_to_connection_properties(self, connection_properties):
target_wwn = connection_properties.get('target_wwn')
target_wwns = connection_properties.get('target_wwns')
if target_wwns:
wwns = target_wwns
elif isinstance(target_wwn, list):
wwns = target_wwn
elif isinstance(target_wwn, six.string_types):
wwns = [target_wwn]
else:
wwns = []
lun = connection_properties.get('target_lun', 0)
host_paths = self._get_host_devices(possible_devs, lun)
target_lun = connection_properties.get('target_lun', 0)
target_luns = connection_properties.get('target_luns')
if target_luns:
luns = target_luns
elif isinstance(target_lun, int):
luns = [target_lun]
else:
luns = []
if len(luns) == len(wwns):
# Handles single wwwn + lun or multiple, potentially
# different wwns or luns
targets = list(zip(wwns, luns))
elif len(luns) == 1 and len(wwns) > 1:
# For the case of multiple wwns, but a single lun (old path)
targets = []
for wwn in wwns:
targets.append((wwn, luns[0]))
else:
# Something is wrong, this shouldn't happen
msg = _("Unable to find potential volume paths for FC device "
"with lun: %(lun)s and wwn: %(wwn)s") % {
"lun": target_lun, "wwn": target_wwn}
LOG.error(msg)
raise exception.VolumePathsNotFound(msg)
connection_properties['targets'] = targets
wwpn_lun_map = dict()
for wwpn, lun in targets:
wwpn_lun_map[wwpn] = lun
# If there is an initiator_target_map we can update it too
if 'initiator_target_map' in connection_properties:
itmap = connection_properties['initiator_target_map']
new_itmap = dict()
for init_wwpn in itmap:
target_wwpns = itmap[init_wwpn]
init_targets = []
for target_wwpn in target_wwpns:
if target_wwpn in wwpn_lun_map:
init_targets.append((target_wwpn,
wwpn_lun_map[target_wwpn]))
new_itmap[init_wwpn] = init_targets
connection_properties['initiator_target_lun_map'] = new_itmap
return connection_properties
def _get_possible_volume_paths(self, connection_properties, hbas):
targets = connection_properties['targets']
possible_devs = self._get_possible_devices(hbas, targets)
host_paths = self._get_host_devices(possible_devs)
return host_paths
def get_volume_paths(self, connection_properties):
@ -100,6 +159,9 @@ class FibreChannelConnector(base.BaseLinuxConnector):
Try and update the local kernel's size information
for an FC volume.
"""
connection_properties = self._add_targets_to_connection_properties(
connection_properties)
volume_paths = self.get_volume_paths(connection_properties)
if volume_paths:
return self._linuxscsi.extend_volume(volume_paths)
@ -126,6 +188,9 @@ class FibreChannelConnector(base.BaseLinuxConnector):
LOG.debug("execute = %s", self._execute)
device_info = {'type': 'block'}
connection_properties = self._add_targets_to_connection_properties(
connection_properties)
hbas = self._linuxfc.get_fc_hbas_info()
host_devices = self._get_possible_volume_paths(
connection_properties, hbas)
@ -194,9 +259,14 @@ class FibreChannelConnector(base.BaseLinuxConnector):
LOG.debug("connect_volume returning %s", device_info)
return device_info
def _get_host_devices(self, possible_devs, lun):
def _get_host_devices(self, possible_devs):
"""Compute the device paths on the system with an id, wwn, and lun
:param possible_devs: list of (pci_id, wwn, lun) tuples
:return: list of device paths on the system based on the possible_devs
"""
host_devices = []
for pci_num, target_wwn in possible_devs:
for pci_num, target_wwn, lun in possible_devs:
host_device = "/dev/disk/by-path/pci-%s-fc-%s-lun-%s" % (
pci_num,
target_wwn,
@ -204,14 +274,13 @@ class FibreChannelConnector(base.BaseLinuxConnector):
host_devices.append(host_device)
return host_devices
def _get_possible_devices(self, hbas, wwnports):
def _get_possible_devices(self, hbas, targets):
"""Compute the possible fibre channel device options.
:param hbas: available hba devices.
:param wwnports: possible wwn addresses. Can either be string
or list of strings.
:param targets: tuple of possible wwn addresses and lun combinations.
:returns: list of (pci_id, wwn) tuples
:returns: list of (pci_id, wwn, lun) tuples
Given one or more wwn (mac addresses for fibre channel) ports
do the matrix math to figure out a set of pci device, wwn
@ -219,23 +288,13 @@ class FibreChannelConnector(base.BaseLinuxConnector):
provides a search space for the device connection.
"""
# the wwn (think mac addresses for fiber channel devices) can
# either be a single value or a list. Normalize it to a list
# for further operations.
wwns = []
if isinstance(wwnports, list):
for wwn in wwnports:
wwns.append(str(wwn))
elif isinstance(wwnports, six.string_types):
wwns.append(str(wwnports))
raw_devices = []
for hba in hbas:
pci_num = self._get_pci_num(hba)
if pci_num is not None:
for wwn in wwns:
for wwn, lun in targets:
target_wwn = "0x%s" % wwn.lower()
raw_devices.append((pci_num, target_wwn))
raw_devices.append((pci_num, target_wwn, lun))
return raw_devices
@utils.trace
@ -257,6 +316,10 @@ class FibreChannelConnector(base.BaseLinuxConnector):
devices = []
wwn = None
connection_properties = self._add_targets_to_connection_properties(
connection_properties)
volume_paths = self.get_volume_paths(connection_properties)
mpath_path = None
for path in volume_paths:

View File

@ -19,7 +19,6 @@ import os
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
import six
from os_brick.initiator import linuxscsi
@ -42,20 +41,18 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
single WWNN for all ports, so caller should expect us to return either
explicit channel and targets or wild cards if we cannot determine them.
:returns: List of lists with [c, t] entries, the channel and target
The connection properties will need to have "target" values defined in
it which are expected to be tuples of (wwpn, lun).
:returns: List of lists with [c, t, l] entries, the channel and target
may be '-' wildcards if unable to determine them.
"""
# We want the target's WWPNs, so we use the initiator_target_map if
# present for this hba or default to target_wwns if not present.
wwpns = conn_props['target_wwn']
targets = conn_props['targets']
if 'initiator_target_map' in conn_props:
wwpns = conn_props['initiator_target_map'].get(hba['port_name'],
wwpns)
# If it's not a string then it's an iterable (most likely a list),
# so we need to create a BRE for the grep query.
if not isinstance(wwpns, six.string_types):
wwpns = '\|'.join(wwpns)
targets = conn_props['initiator_target_lun_map'].get(
hba['port_name'], targets)
# Leave only the number from the host_device field (ie: host6)
host_device = hba['host_device']
@ -63,33 +60,36 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
host_device = host_device[4:]
path = '/sys/class/fc_transport/target%s:' % host_device
# Since we'll run the command in a shell ensure BRE are being used
cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpns,
'path': path}
try:
# We need to run command in shell to expand the * glob
out, _err = self._execute(cmd, shell=True)
return [line.split('/')[4].split(':')[1:]
for line in out.split('\n') if line.startswith(path)]
except Exception as exc:
LOG.debug('Could not get HBA channel and SCSI target ID, path: '
'%(path)s*, reason: %(reason)s', {'path': path,
'reason': exc})
return [['-', '-']]
ctls = []
for wwpn, lun in targets:
cmd = 'grep -Gil "%(wwpns)s" %(path)s*/port_name' % {'wwpns': wwpn,
'path': path}
try:
# We need to run command in shell to expand the * glob
out, _err = self._execute(cmd, shell=True)
ctls += [line.split('/')[4].split(':')[1:] + [lun]
for line in out.split('\n') if line.startswith(path)]
except Exception as exc:
LOG.debug('Could not get HBA channel and SCSI target ID, path:'
' %(path)s*, reason: %(reason)s', {'path': path,
'reason': exc})
# If we didn't find any paths just give back wildcards for
# the channel and target ids.
ctls.append(['-', '-', lun])
return ctls
def rescan_hosts(self, hbas, connection_properties):
LOG.debug('Rescaning HBAs %(hbas)s with connection properties '
'%(conn_props)s', {'hbas': hbas,
'conn_props': connection_properties})
target_lun = connection_properties['target_lun']
get_cts = self._get_hba_channel_scsi_target
get_ctsl = self._get_hba_channel_scsi_target
# Use initiator_target_map provided by backend as HBA exclussion map
ports = connection_properties.get('initiator_target_map')
# Use initiator_target_map provided by backend as HBA exclusion map
ports = connection_properties.get('initiator_target_lun_map')
if ports:
hbas = [hba for hba in hbas if hba['port_name'] in ports]
LOG.debug('Using initiator target map to exclude HBAs')
process = [(hba, get_cts(hba, connection_properties))
process = [(hba, get_ctsl(hba, connection_properties))
for hba in hbas]
# With no target map we'll check if target implements single WWNN for
@ -100,20 +100,20 @@ class LinuxFibreChannel(linuxscsi.LinuxSCSI):
no_info = []
for hba in hbas:
cts = get_cts(hba, connection_properties)
ctls = get_ctsl(hba, connection_properties)
found_info = True
for hba_channel, target_id in cts:
for hba_channel, target_id, target_lun in ctls:
if hba_channel == '-' or target_id == '-':
found_info = False
target_list = with_info if found_info else no_info
target_list.append((hba, cts))
target_list.append((hba, ctls))
process = with_info or no_info
msg = "implements" if with_info else "doesn't implement"
LOG.debug('FC target %s single WWNN for all ports.', msg)
for hba, cts in process:
for hba_channel, target_id in cts:
for hba, ctls in process:
for hba_channel, target_id, target_lun in ctls:
LOG.debug('Scanning host %(host)s (wwnn: %(wwnn)s, c: '
'%(channel)s, t: %(target)s, l: %(lun)s)',
{'host': hba['host_device'],

View File

@ -11,6 +11,8 @@
# 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 ddt
import mock
import os
import six
@ -23,6 +25,7 @@ from os_brick.initiator import linuxscsi
from os_brick.tests.initiator import test_connector
@ddt.ddt
class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
def setUp(self):
@ -65,13 +68,13 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
'device_path': hbas[0]['ClassDevicePath']}]
return info
def fibrechan_connection(self, volume, location, wwn):
def fibrechan_connection(self, volume, location, wwn, lun=1):
return {'driver_volume_type': 'fibrechan',
'data': {
'volume_id': volume['id'],
'target_portal': location,
'target_wwn': wwn,
'target_lun': 1,
'target_lun': lun,
}}
@mock.patch.object(linuxfc.LinuxFibreChannel, 'get_fc_hbas')
@ -123,8 +126,10 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
location = '10.0.2.15:3260'
wwn = '1234567890123456'
connection_info = self.fibrechan_connection(vol, location, wwn)
volume_paths = self.connector.get_volume_paths(
connection_info['data'])
conn_data = self.connector._add_targets_to_connection_properties(
connection_info['data']
)
volume_paths = self.connector.get_volume_paths(conn_data)
expected = ['/dev/disk/by-path/pci-0000:05:00.2'
'-fc-0x1234567890123456-lun-1']
@ -167,10 +172,15 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
name = 'volume-00000001'
vol = {'id': 1, 'name': name}
# Should work for string, unicode, and list
wwns = ['1234567890123456', six.text_type('1234567890123456'),
['1234567890123456', '1234567890123457']]
for wwn in wwns:
connection_info = self.fibrechan_connection(vol, location, wwn)
wwns_luns = [
('1234567890123456', 1),
(six.text_type('1234567890123456'), 1),
(['1234567890123456', '1234567890123457'], 1),
(['1234567890123456', '1234567890123457'], 1),
]
for wwn, lun in wwns_luns:
connection_info = self.fibrechan_connection(vol, location,
wwn, lun)
dev_info = self.connector.connect_volume(connection_info['data'])
exp_wwn = wwn[0] if isinstance(wwn, list) else wwn
dev_str = ('/dev/disk/by-path/pci-0000:05:00.2-fc-0x%s-lun-1' %
@ -186,13 +196,13 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
# Should not work for anything other than string, unicode, and list
connection_info = self.fibrechan_connection(vol, location, 123)
self.assertRaises(exception.NoFibreChannelHostsFound,
self.assertRaises(exception.VolumePathsNotFound,
self.connector.connect_volume,
connection_info['data'])
get_fc_hbas_mock.side_effect = [[]]
get_fc_hbas_info_mock.side_effect = [[]]
self.assertRaises(exception.NoFibreChannelHostsFound,
self.assertRaises(exception.VolumePathsNotFound,
self.connector.connect_volume,
connection_info['data'])
@ -450,3 +460,160 @@ class FibreChannelConnectorTestCase(test_connector.ConnectorTestCase):
find_mp_dev_mock,
'rw',
True)
@ddt.data(
{
"target_info": {
"target_lun": 1,
"target_wwn": '1234567890123456',
},
"expected_targets": [
('1234567890123456', 1)
]
},
{
"target_info": {
"target_lun": 1,
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 1),
]
},
{
"target_info": {
"target_luns": [1, 1],
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 1),
]
},
{
"target_info": {
"target_luns": [1, 2],
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 2),
]
},
{
"target_info": {
"target_luns": [1, 1],
"target_wwns": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 1),
]
},
{
"target_info": {
"target_lun": 7,
"target_luns": [1, 1],
"target_wwn": 'foo',
"target_wwns": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 1),
]
},
# Add the zone map in now
{
"target_info": {
"target_lun": 1,
"target_wwn": '1234567890123456',
},
"expected_targets": [
('1234567890123456', 1)
],
"itmap": {
'0004567890123456': ['1234567890123456']
},
"expected_map": {
'0004567890123456': [('1234567890123456', 1)]
}
},
{
"target_info": {
"target_lun": 1,
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 1),
],
"itmap": {
'0004567890123456': ['1234567890123456',
'1234567890123457']
},
"expected_map": {
'0004567890123456': [('1234567890123456', 1),
('1234567890123457', 1)]
}
},
{
"target_info": {
"target_luns": [1, 2],
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 2),
],
"itmap": {
'0004567890123456': ['1234567890123456'],
'1004567890123456': ['1234567890123457'],
},
"expected_map": {
'0004567890123456': [('1234567890123456', 1)],
'1004567890123456': [('1234567890123457', 2)],
}
},
{
"target_info": {
"target_luns": [1, 2],
"target_wwn": ['1234567890123456', '1234567890123457'],
},
"expected_targets": [
('1234567890123456', 1),
('1234567890123457', 2),
],
"itmap": {
'0004567890123456': ['1234567890123456',
'1234567890123457']
},
"expected_map": {
'0004567890123456': [('1234567890123456', 1),
('1234567890123457', 2)]
}
},
)
@ddt.unpack
def test__add_targets_to_connection_properties(self, target_info,
expected_targets,
itmap=None,
expected_map=None):
volume = {'id': 'fake_uuid'}
wwn = '1234567890123456'
conn = self.fibrechan_connection(volume, "10.0.2.15:3260", wwn)
conn['data'].update(target_info)
if itmap:
conn['data']['initiator_target_map'] = itmap
connection_info = self.connector._add_targets_to_connection_properties(
conn['data'])
self.assertIn('targets', connection_info)
self.assertEqual(expected_targets, connection_info['targets'])
if itmap:
self.assertIn('initiator_target_lun_map', connection_info)
self.assertEqual(expected_map,
connection_info['initiator_target_lun_map'])

View File

@ -50,9 +50,17 @@ class LinuxFCTestCase(base.TestCase):
connection_properties = {
'initiator_target_map': {'50014380186af83c': ['514f0c50023f6c00'],
'50014380186af83e': ['514f0c50023f6c01']},
'initiator_target_lun_map': {
'50014380186af83c': [('514f0c50023f6c00', 1)],
'50014380186af83e': [('514f0c50023f6c01', 1)]
},
'target_discovered': False,
'target_lun': 1,
'target_wwn': ['514f0c50023f6c00', '514f0c50023f6c01']
'target_wwn': ['514f0c50023f6c00', '514f0c50023f6c01'],
'targets': [
('514f0c50023f6c00', 1),
('514f0c50023f6c01', 1),
]
}
hbas = [
@ -69,6 +77,7 @@ class LinuxFCTestCase(base.TestCase):
]
if not zone_manager:
del connection_properties['initiator_target_map']
del connection_properties['initiator_target_lun_map']
return hbas, connection_properties
def test__get_hba_channel_scsi_target_single_wwpn(self):
@ -76,6 +85,7 @@ class LinuxFCTestCase(base.TestCase):
'')
hbas, con_props = self.__get_rescan_info()
con_props['target_wwn'] = con_props['target_wwn'][0]
con_props['targets'] = con_props['targets'][0:1]
with mock.patch.object(self.lfc, '_execute',
return_value=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
@ -83,22 +93,56 @@ class LinuxFCTestCase(base.TestCase):
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '1']]
expected = [['0', '1', 1]]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_multiple_wwpn(self):
execute_results = ('/sys/class/fc_transport/target6:0:1/port_name\n'
'/sys/class/fc_transport/target6:0:2/port_name\n',
'')
execute_results = [
['/sys/class/fc_transport/target6:0:1/port_name\n', ''],
['/sys/class/fc_transport/target6:0:2/port_name\n', ''],
]
hbas, con_props = self.__get_rescan_info()
with mock.patch.object(self.lfc, '_execute',
return_value=execute_results) as execute_mock:
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
execute_mock.assert_called_once_with(
'grep -Gil "514f0c50023f6c00\|514f0c50023f6c01" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '1'], ['0', '2']]
expected_cmds = [
mock.call('grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True),
mock.call('grep -Gil "514f0c50023f6c01" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True),
]
execute_mock.assert_has_calls(expected_cmds)
expected = [['0', '1', 1], ['0', '2', 1]]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_multiple_wwpn_and_luns(self):
execute_results = [
['/sys/class/fc_transport/target6:0:1/port_name\n', ''],
['/sys/class/fc_transport/target6:0:2/port_name\n', ''],
]
hbas, con_props = self.__get_rescan_info()
con_props['target_lun'] = [1, 7]
con_props['targets'] = [
('514f0c50023f6c00', 1),
('514f0c50023f6c01', 7),
]
with mock.patch.object(self.lfc, '_execute',
side_effect=execute_results) as execute_mock:
res = self.lfc._get_hba_channel_scsi_target(hbas[0], con_props)
expected_cmds = [
mock.call('grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True),
mock.call('grep -Gil "514f0c50023f6c01" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True),
]
execute_mock.assert_has_calls(expected_cmds)
expected = [['0', '1', 1], ['0', '2', 7]]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_zone_manager(self):
@ -112,7 +156,7 @@ class LinuxFCTestCase(base.TestCase):
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
expected = [['0', '1']]
expected = [['0', '1', 1]]
self.assertListEqual(expected, res)
def test__get_hba_channel_scsi_target_not_found(self):
@ -135,11 +179,11 @@ class LinuxFCTestCase(base.TestCase):
'grep -Gil "514f0c50023f6c00" '
'/sys/class/fc_transport/target6:*/port_name',
shell=True)
self.assertEqual([['-', '-']], res)
self.assertEqual(res, [['-', '-', 1]])
def test_rescan_hosts_initiator_map(self):
"""Test FC rescan with initiator map and not every HBA connected."""
get_chan_results = [[['2', '3'], ['4', '5']], [['6', '7']]]
get_chan_results = [[['2', '3', 1], ['4', '5', 1]], [['6', '7', 1]]]
hbas, con_props = self.__get_rescan_info(zone_manager=True)
@ -177,12 +221,14 @@ class LinuxFCTestCase(base.TestCase):
def test_rescan_hosts_single_wwnn(self):
"""Test FC rescan with no initiator map and single WWNN for ports."""
get_chan_results = [[['2', '3'], ['4', '5']],
[['6', '7']], [['-', '-']]]
get_chan_results = [
[['2', '3', 1], ['4', '5', 1]],
[['6', '7', 1]],
[['-', '-', 1]],
]
hbas, con_props = self.__get_rescan_info(zone_manager=False)
hbas, con_props = self.__get_rescan_info(zone_manager=True)
# Remove the initiator map
con_props.pop('initiator_target_map')
# This HBA is the one that is not included in the single WWNN.
hbas.append({'device_path': ('/sys/devices/pci0000:00/0000:00:02.0/'
'0000:04:00.2/host8/fc_host/host8'),
@ -216,10 +262,11 @@ class LinuxFCTestCase(base.TestCase):
def test_rescan_hosts_wildcard(self):
"""Test when we don't have initiator map or target is single WWNN."""
get_chan_results = [[['-', '-']], [['-', '-']]]
get_chan_results = [[['-', '-', 1]], [['-', '-', 1]]]
hbas, con_props = self.__get_rescan_info(zone_manager=True)
# Remove the initiator map
con_props.pop('initiator_target_map')
con_props.pop('initiator_target_lun_map')
with mock.patch.object(self.lfc, '_get_hba_channel_scsi_target',
side_effect=get_chan_results), \
mock.patch.object(self.lfc, '_execute',