mock.call.getHost(self.FAKE_HOST),
mock.ANY,
mock.call.getHost(self.FAKE_HOST),
+ mock.call.getPorts(),
mock.call.createVLUN(
self.VOLUME_3PAR_NAME,
auto=True,
hostname=self.FAKE_HOST),
mock.call.getHostVLUNs(self.FAKE_HOST),
- mock.call.getPorts(),
mock.call.logout()]
mock_client.assert_has_calls(expected)
self.assertDictMatch(result, self.properties)
+ @mock.patch('cinder.zonemanager.utils.create_lookup_service')
+ def test_initialize_connection_with_lookup_single_nsp(self, mock_lookup):
+ # setup_mock_client drive with default configuration
+ # and return the mock HTTP 3PAR client
+ class fake_lookup_object:
+ def get_device_mapping_from_network(self, connector, target_wwns):
+ fake_map = {
+ 'FAB_1': {
+ 'target_port_wwn_list': ['0987654321234'],
+ 'initiator_port_wwn_list': ['123456789012345']
+ }
+ }
+ return fake_map
+ mock_lookup.return_value = fake_lookup_object()
+ mock_client = self.setup_driver()
+ mock_client.getVolume.return_value = {'userCPG': HP3PAR_CPG}
+ mock_client.getCPG.return_value = {}
+ mock_client.getHost.side_effect = [
+ hpexceptions.HTTPNotFound('fake'),
+ {'name': self.FAKE_HOST,
+ 'FCPaths': [{'driverVersion': None,
+ 'firmwareVersion': None,
+ 'hostSpeed': 0,
+ 'model': None,
+ 'portPos': {'cardPort': 1, 'node': 1,
+ 'slot': 2},
+ 'vendor': None,
+ 'wwn': self.wwn[0]}]}]
+ mock_client.findHost.return_value = self.FAKE_HOST
+ mock_client.getHostVLUNs.return_value = [
+ {'active': True,
+ 'volumeName': self.VOLUME_3PAR_NAME,
+ 'lun': 90, 'type': 0}]
+ location = ("%(volume_name)s,%(lun_id)s,%(host)s,%(nsp)s" %
+ {'volume_name': self.VOLUME_3PAR_NAME,
+ 'lun_id': 90,
+ 'host': self.FAKE_HOST,
+ 'nsp': 'something'})
+ mock_client.createVLUN.return_value = location
+
+ connector = {'ip': '10.0.0.2',
+ 'initiator': 'iqn.1993-08.org.debian:01:222',
+ 'wwpns': [self.wwn[0]],
+ 'wwnns': ["223456789012345"],
+ 'host': self.FAKE_HOST}
+
+ expected_properties = {
+ 'driver_volume_type': 'fibre_channel',
+ 'data': {
+ 'target_lun': 90,
+ 'target_wwn': ['0987654321234'],
+ 'target_discovered': True,
+ 'initiator_target_map': {'123456789012345':
+ ['0987654321234']
+ }}}
+
+ result = self.driver.initialize_connection(self.volume, connector)
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getVolume(self.VOLUME_3PAR_NAME),
+ mock.call.getCPG(HP3PAR_CPG),
+ mock.call.getHost(self.FAKE_HOST),
+ mock.ANY,
+ mock.call.getHost(self.FAKE_HOST),
+ mock.call.getPorts(),
+ mock.call.getPorts(),
+ mock.call.createVLUN(
+ self.VOLUME_3PAR_NAME,
+ auto=True,
+ hostname=self.FAKE_HOST,
+ portPos={'node': 7, 'slot': 1, 'cardPort': 1}),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
+ mock.call.logout()]
+
+ mock_client.assert_has_calls(expected)
+
+ self.assertDictMatch(result, expected_properties)
+
def test_terminate_connection(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
self.connector)
mock_client.assert_has_calls(expected)
+ @mock.patch('cinder.zonemanager.utils.create_lookup_service')
+ def test_terminate_connection_with_lookup(self, mock_lookup):
+ # setup_mock_client drive with default configuration
+ # and return the mock HTTP 3PAR client
+ class fake_lookup_object:
+ def get_device_mapping_from_network(self, connector, target_wwns):
+ fake_map = {
+ 'FAB_1': {
+ 'target_port_wwn_list': ['0987654321234'],
+ 'initiator_port_wwn_list': ['123456789012345']
+ }
+ }
+ return fake_map
+ mock_lookup.return_value = fake_lookup_object()
+ mock_client = self.setup_driver()
+
+ effects = [
+ [{'active': True, 'volumeName': self.VOLUME_3PAR_NAME,
+ 'lun': None, 'type': 0}],
+ hpexceptions.HTTPNotFound]
+
+ mock_client.getHostVLUNs.side_effect = effects
+
+ expected = [
+ mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
+ mock.call.deleteVLUN(
+ self.VOLUME_3PAR_NAME,
+ None,
+ self.FAKE_HOST),
+ mock.call.deleteHost(self.FAKE_HOST),
+ mock.call.getHostVLUNs(self.FAKE_HOST),
+ mock.call.getPorts(),
+ mock.call.logout()]
+
+ conn_info = self.driver.terminate_connection(self.volume,
+ self.connector)
+ mock_client.assert_has_calls(expected)
+ self.assertIn('data', conn_info)
+ self.assertIn('initiator_target_map', conn_info['data'])
+ mock_client.reset_mock()
+
+ mock_client.getHostVLUNs.side_effect = effects
+
+ # mock some deleteHost exceptions that are handled
+ delete_with_vlun = hpexceptions.HTTPConflict(
+ error={'message': "has exported VLUN"})
+ delete_with_hostset = hpexceptions.HTTPConflict(
+ error={'message': "host is a member of a set"})
+ mock_client.deleteHost = mock.Mock(
+ side_effect=[delete_with_vlun, delete_with_hostset])
+
+ conn_info = self.driver.terminate_connection(self.volume,
+ self.connector)
+ mock_client.assert_has_calls(expected)
+ mock_client.reset_mock()
+ mock_client.getHostVLUNs.side_effect = effects
+
+ conn_info = self.driver.terminate_connection(self.volume,
+ self.connector)
+ mock_client.assert_has_calls(expected)
+
def test_terminate_connection_more_vols(self):
mock_client = self.setup_driver()
# mock more than one vlun on the host (don't even try to remove host)
2.0.4 - Added support for managing/unmanaging of volumes
2.0.5 - Only remove FC Zone on last volume detach
2.0.6 - Added support for volume retype
+ 2.0.7 - Only one FC port is used when a single FC path
+ is present. bug #1360001
"""
- VERSION = "2.0.6"
+ VERSION = "2.0.7"
def __init__(self, *args, **kwargs):
super(HP3PARFCDriver, self).__init__(*args, **kwargs)
self.common = None
self.configuration.append_config_values(hpcommon.hp3par_opts)
self.configuration.append_config_values(san.san_opts)
+ self.lookup_service = fczm_utils.create_lookup_service()
def _init_common(self):
return hpcommon.HP3PARCommon(self.configuration)
# we have to make sure we have a host
host = self._create_host(volume, connector)
- # now that we have a host, create the VLUN
- vlun = self.common.create_vlun(volume, host)
+ target_wwns, init_targ_map, numPaths = \
+ self._build_initiator_target_map(connector)
- target_wwns, init_targ_map = self._build_initiator_target_map(
- connector)
+ # now that we have a host, create the VLUN
+ if self.lookup_service is not None and numPaths == 1:
+ nsp = None
+ active_fc_port_list = self.common.get_active_fc_target_ports()
+ for port in active_fc_port_list:
+ if port['portWWN'].lower() == target_wwns[0].lower():
+ nsp = port['nsp']
+ break
+ vlun = self.common.create_vlun(volume, host, nsp)
+ else:
+ vlun = self.common.create_vlun(volume, host)
info = {'driver_volume_type': 'fibre_channel',
'data': {'target_lun': vlun['lun'],
# No more exports for this host.
LOG.info(_("Need to remove FC Zone, building initiator "
"target map"))
- target_wwns, init_targ_map = self._build_initiator_target_map(
- connector)
+
+ target_wwns, init_targ_map, numPaths = \
+ self._build_initiator_target_map(connector)
info['data'] = {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}
"""Build the target_wwns and the initiator target map."""
fc_ports = self.common.get_active_fc_target_ports()
+ all_target_wwns = []
target_wwns = []
+ init_targ_map = {}
+ numPaths = 0
for port in fc_ports:
- target_wwns.append(port['portWWN'])
-
- initiator_wwns = connector['wwpns']
+ all_target_wwns.append(port['portWWN'])
+
+ if self.lookup_service is not None:
+ # use FC san lookup to determine which NSPs to use
+ # for the new VLUN.
+ dev_map = self.lookup_service.get_device_mapping_from_network(
+ connector['wwpns'],
+ all_target_wwns)
+
+ for fabric_name in dev_map:
+ fabric = dev_map[fabric_name]
+ target_wwns += fabric['target_port_wwn_list']
+ for initiator in fabric['initiator_port_wwn_list']:
+ if initiator not in init_targ_map:
+ init_targ_map[initiator] = []
+ init_targ_map[initiator] += fabric['target_port_wwn_list']
+ init_targ_map[initiator] = list(set(
+ init_targ_map[initiator]))
+ for target in init_targ_map[initiator]:
+ numPaths += 1
+ target_wwns = list(set(target_wwns))
+ else:
+ initiator_wwns = connector['wwpns']
+ target_wwns = all_target_wwns
- init_targ_map = {}
- for initiator in initiator_wwns:
- init_targ_map[initiator] = target_wwns
+ for initiator in initiator_wwns:
+ init_targ_map[initiator] = target_wwns
- return target_wwns, init_targ_map
+ return target_wwns, init_targ_map, numPaths
def _create_3par_fibrechan_host(self, hostname, wwns, domain, persona_id):
"""Create a 3PAR host.