3PAR: Provide new option to specify NSP for single path attachments
This fix aims to resolve below mentioned bugs: https://bugs.launchpad.net/os-brick/+bug/1812665 https://bugs.launchpad.net/cinder/+bug/1809249 https://bugs.launchpad.net/cinder/+bug/1734917 Given a system connected to HPE 3PAR via FC and multipath is disabled. When user tries to create bootable volume, it fails intermittently with following error: Fibre Channel volume device not found This happens when a zone is created using second or later target nsp from 3PAR backend. In this case, HPE 3PAR client code picks up first target nsp to form initiator target map. To avoid above mentioned failure, user can specify target nsp in 3PAR backend section of cinder.conf as follows: hpe3par_target_nsp = <n:s:p> This target information is read from cinder.conf and respective wwn information is fetched. Later initiator target map is created using wwn information and bootable volume is created successfully. Change-Id: If77d384afbc7097ed55a03e9b4bc7f8e1966a5c5 Closes bug: #1809249
This commit is contained in:
parent
0fdb1dd2d5
commit
b7c81a7b42
@ -6940,6 +6940,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
# setup_mock_client drive with default configuration
|
||||
# and return the mock HTTP 3PAR client
|
||||
mock_client = self.setup_driver()
|
||||
mock_client.getStorageSystemInfo.return_value = (
|
||||
{'id': self.CLIENT_ID})
|
||||
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
||||
mock_client.getCPG.return_value = {}
|
||||
mock_client.getHost.side_effect = [
|
||||
@ -6995,6 +6997,7 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
mock.call.queryHost(wwns=['123456789012345']),
|
||||
mock.call.getHost(self.FAKE_HOST),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST),
|
||||
mock.call.createVLUN(
|
||||
self.VOLUME_3PAR_NAME,
|
||||
@ -7004,6 +7007,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
||||
|
||||
mock_client.assert_has_calls(
|
||||
self.get_id_login +
|
||||
self.standard_logout +
|
||||
self.standard_login +
|
||||
expected +
|
||||
self.standard_logout)
|
||||
@ -7025,6 +7030,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
return fake_map
|
||||
mock_lookup.return_value = fake_lookup_object()
|
||||
mock_client = self.setup_driver()
|
||||
mock_client.getStorageSystemInfo.return_value = (
|
||||
{'id': self.CLIENT_ID})
|
||||
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
||||
mock_client.getCPG.return_value = {}
|
||||
mock_client.getHost.side_effect = [
|
||||
@ -7087,6 +7094,7 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
mock.ANY,
|
||||
mock.call.getHost(self.FAKE_HOST),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST),
|
||||
mock.call.getPorts(),
|
||||
mock.call.createVLUN(
|
||||
@ -7098,6 +7106,89 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
||||
|
||||
mock_client.assert_has_calls(
|
||||
self.get_id_login +
|
||||
self.standard_logout +
|
||||
self.standard_login +
|
||||
expected +
|
||||
self.standard_logout)
|
||||
|
||||
self.assertDictEqual(expected_properties, result)
|
||||
|
||||
def test_initialize_connection_single_path_target_nsp(self):
|
||||
# setup_mock_client drive with default configuration
|
||||
# and return the mock HTTP 3PAR client
|
||||
mock_client = self.setup_driver()
|
||||
self.driver.configuration.hpe3par_target_nsp = '2:1:2'
|
||||
mock_client.getStorageSystemInfo.return_value = (
|
||||
{'id': self.CLIENT_ID})
|
||||
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
||||
mock_client.getCPG.return_value = {}
|
||||
mock_client.getHost.side_effect = [
|
||||
hpeexceptions.HTTPNotFound('fake'),
|
||||
{'name': self.FAKE_HOST,
|
||||
'FCPaths': [{'driverVersion': None,
|
||||
'firmwareVersion': None,
|
||||
'hostSpeed': 0,
|
||||
'model': None,
|
||||
'vendor': None,
|
||||
'wwn': self.wwn[0]}]}]
|
||||
mock_client.queryHost.return_value = {
|
||||
'members': [{
|
||||
'name': self.FAKE_HOST
|
||||
}]
|
||||
}
|
||||
|
||||
mock_client.getHostVLUNs.side_effect = [
|
||||
hpeexceptions.HTTPNotFound('fake'),
|
||||
[{'active': True,
|
||||
'volumeName': self.VOLUME_3PAR_NAME,
|
||||
'portPos': {'node': 7, 'slot': 1, 'cardPort': 1},
|
||||
'remoteName': self.wwn[0],
|
||||
'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
|
||||
user_target_wwn = '0987654321234'
|
||||
expected_properties = {
|
||||
'driver_volume_type': 'fibre_channel',
|
||||
'data': {
|
||||
'encrypted': False,
|
||||
'target_lun': 90,
|
||||
'target_wwn': [user_target_wwn],
|
||||
'target_discovered': True,
|
||||
'initiator_target_map':
|
||||
{'123456789012345': [user_target_wwn]}}}
|
||||
|
||||
with mock.patch.object(hpecommon.HPE3PARCommon,
|
||||
'_create_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
result = self.driver.initialize_connection(
|
||||
self.volume,
|
||||
self.connector.copy())
|
||||
|
||||
expected = [
|
||||
mock.call.getVolume(self.VOLUME_3PAR_NAME),
|
||||
mock.call.getCPG(HPE3PAR_CPG),
|
||||
mock.call.getHost(self.FAKE_HOST),
|
||||
mock.call.queryHost(wwns=['123456789012345']),
|
||||
mock.call.getHost(self.FAKE_HOST),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getPorts(),
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST),
|
||||
mock.call.createVLUN(
|
||||
self.VOLUME_3PAR_NAME,
|
||||
auto=True,
|
||||
hostname=self.FAKE_HOST,
|
||||
lun=None),
|
||||
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
||||
|
||||
mock_client.assert_has_calls(
|
||||
self.get_id_login +
|
||||
self.standard_logout +
|
||||
self.standard_login +
|
||||
expected +
|
||||
self.standard_logout)
|
||||
|
@ -118,6 +118,15 @@ hpe3par_opts = [
|
||||
cfg.BoolOpt('hpe3par_iscsi_chap_enabled',
|
||||
default=False,
|
||||
help="Enable CHAP authentication for iSCSI connections."),
|
||||
cfg.StrOpt('hpe3par_target_nsp',
|
||||
default="",
|
||||
help="The nsp of 3PAR backend to be used when: "
|
||||
"(1) multipath is not enabled in cinder.conf. "
|
||||
"(2) Fiber Channel Zone Manager is not used. "
|
||||
"(3) the 3PAR backend is prezoned with this "
|
||||
" specific nsp only. "
|
||||
"For example if nsp is 2 1 2, the format of the "
|
||||
"option's value is 2:1:2"),
|
||||
]
|
||||
|
||||
|
||||
|
@ -173,10 +173,22 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
|
||||
host = self._create_host(common, volume, connector)
|
||||
target_wwns, init_targ_map, numPaths = \
|
||||
self._build_initiator_target_map(common, connector)
|
||||
if not connector.get('multipath'):
|
||||
target_wwns = target_wwns[:1]
|
||||
|
||||
multipath = connector.get('multipath')
|
||||
LOG.debug("multipath: %(multipath)s",
|
||||
{'multipath': multipath})
|
||||
user_target = None
|
||||
if not multipath:
|
||||
user_target = self._get_user_target(common)
|
||||
initiator = connector.get('wwpns')[0]
|
||||
init_targ_map[initiator] = init_targ_map[initiator][:1]
|
||||
if user_target is None:
|
||||
target_wwns = target_wwns[:1]
|
||||
init_targ_map[initiator] = \
|
||||
init_targ_map[initiator][:1]
|
||||
else:
|
||||
target_wwns = [user_target]
|
||||
init_targ_map[initiator] = [user_target]
|
||||
|
||||
# check if a VLUN already exists for this host
|
||||
existing_vlun = common.find_existing_vlun(volume, host)
|
||||
|
||||
@ -411,3 +423,26 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
|
||||
self._modify_3par_fibrechan_host(common, host['name'], new_wwns)
|
||||
host = common._get_3par_host(host['name'])
|
||||
return host
|
||||
|
||||
def _get_user_target(self, common):
|
||||
target_nsp = common.config.hpe3par_target_nsp
|
||||
|
||||
if not target_nsp:
|
||||
return None
|
||||
else:
|
||||
# Get target wwn from target nsp
|
||||
fc_ports = common.get_active_fc_target_ports()
|
||||
|
||||
target_wwn = ''
|
||||
for port in fc_ports:
|
||||
nsp = port['nsp']
|
||||
if target_nsp == nsp:
|
||||
target_wwn = port['portWWN']
|
||||
break
|
||||
|
||||
if not target_wwn:
|
||||
LOG.warning("Did not get wwn for target nsp: "
|
||||
"%(nsp)s", {'nsp': target_nsp})
|
||||
return None
|
||||
else:
|
||||
return target_wwn
|
||||
|
@ -413,3 +413,51 @@ the HPE 3PAR Fibre Channel and iSCSI drivers.
|
||||
:config-target: 3PAR
|
||||
|
||||
cinder.volume.drivers.hpe.hpe_3par_common
|
||||
|
||||
|
||||
Specify NSP for FC Bootable Volume
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Given a system connected to HPE 3PAR via FC and multipath setting is
|
||||
NOT used in cinder.conf. When the user tries to create a bootable
|
||||
volume, it fails intermittently with the following error:
|
||||
Fibre Channel volume device not found
|
||||
|
||||
This happens when a zone is created using second or later target from
|
||||
3PAR backend. In this case, HPE 3PAR client code picks up first target
|
||||
to form initiator target map. This can be illustrated with below
|
||||
example.
|
||||
|
||||
Sample output of showport command:
|
||||
|
||||
``$ showport -sortcol 6``
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
N:S:P Mode State ----Node_WWN---- -Port_WWN/HW_Addr- Type Protocol Partner FailoverState
|
||||
0:1:1 target ready 2FF70002AC002DB6 20110002AC002DB6 host FC - -
|
||||
0:1:2 target ready 2FF70002AC002DB6 20120002AC002DB6 host FC 1:1:2 none
|
||||
1:1:1 initiator ready 2FF70002AC002DB6 21110002AC002DB6 rcfc FC - -
|
||||
1:1:2 target ready 2FF70002AC002DB6 21120002AC002DB6 host FC 0:1:2 none
|
||||
2:1:1 initiator ready 2FF70002AC002DB6 22110002AC002DB6 rcfc FC - -
|
||||
2:1:2 target ready 2FF70002AC002DB6 22120002AC002DB6 host FC 3:1:2 none
|
||||
3:1:1 target ready 2FF70002AC002DB6 23110002AC002DB6 host FC - -
|
||||
3:1:2 target ready 2FF70002AC002DB6 23120002AC002DB6 host FC 2:1:2 none
|
||||
|
||||
Suppose zone is created using targets "2:1:2" and "3:1:2" from above
|
||||
output. Then initiator target map is created using target "0:1:1" only.
|
||||
In such a case, the path is not found, and bootable volume creation fails.
|
||||
|
||||
To avoid above mentioned failure, the user can specify the target in 3PAR
|
||||
backend section of cinder.conf as follows:
|
||||
|
||||
``hpe3par_target_nsp = 3:1:2``
|
||||
|
||||
Using above mentioned nsp, respective wwn information is fetched.
|
||||
Later initiator target map is created using wwn information and
|
||||
bootable volume is created successfully.
|
||||
|
||||
Note: If above mentioned option (nsp) is not specified in cinder.conf,
|
||||
then the original flow is executed i.e first target is picked and
|
||||
bootable volume creation may fail.
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
This change fixes bug 1809249 - 3PAR driver picks wrong port when
|
||||
not in multipath mode. Now the user can specify target NSP (Node-
|
||||
Slot-Port) in cinder.conf. This information is used to create
|
||||
initiator target map accordingly. And then bootable volume is
|
||||
created successfully.
|
Loading…
Reference in New Issue
Block a user