Skip to content

Commit

Permalink
3PAR: Add config for NSP single path attach
Browse files Browse the repository at this point in the history
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: I2da5d4a0334f07967af5ff7aaa39a0ecc4b12204
Closes-bug: #1809249
Closes-bug: #1812665
Closes-bug: #1734917
  • Loading branch information
traghavendra committed Aug 20, 2019
1 parent 2b6ef61 commit 9e122f1
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 7 deletions.
90 changes: 90 additions & 0 deletions cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ def setup_configuration(self):
configuration.filter_function = FILTER_FUNCTION
configuration.image_volume_cache_enabled = False
configuration.replication_device = None
configuration.hpe3par_target_nsp = None
return configuration

@mock.patch(
Expand Down Expand Up @@ -6940,6 +6941,8 @@ def test_initialize_connection_single_path(self):
# 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 = [
Expand Down Expand Up @@ -7004,6 +7007,8 @@ def test_initialize_connection_single_path(self):
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)
Expand All @@ -7025,6 +7030,8 @@ def get_device_mapping_from_network(self, connector, target_wwns):
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 = [
Expand Down Expand Up @@ -7098,6 +7105,89 @@ def get_device_mapping_from_network(self, connector, target_wwns):
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)
Expand Down
8 changes: 8 additions & 0 deletions cinder/volume/drivers/hpe/hpe_3par_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@
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"),
]


Expand Down
47 changes: 40 additions & 7 deletions cinder/volume/drivers/hpe/hpe_3par_fc.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
#
"""
Volume driver for HPE 3PAR Storage array.
"""Volume driver for HPE 3PAR Storage array.
This driver requires 3.1.3 or later firmware on the 3PAR array, using
the 4.x version of the hpe3parclient.
Expand Down Expand Up @@ -112,6 +112,7 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
4.0.4 - Handle force detach case. bug #1686745
4.0.5 - Set proper backend on subsequent operation, after group
failover. bug #1773069
4.0.6 - Set NSP for single path attachments. Bug #1809249
"""

Expand Down Expand Up @@ -171,12 +172,22 @@ def initialize_connection(self, volume, connector):
try:
# we have to make sure we have a host
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]
target_wwns, init_targ_map, numPaths = (
self._build_initiator_target_map(common, connector))

multipath = connector.get('multipath')
LOG.debug("multipath: %s", 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)

Expand Down Expand Up @@ -411,3 +422,25 @@ def _add_new_wwn_to_host(self, common, host, wwns):
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

# Get target wwn from target nsp
fc_ports = common.get_active_fc_target_ports()

target_wwn = None
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 target_wwn
48 changes: 48 additions & 0 deletions doc/source/configuration/block-storage/drivers/hpe-3par-driver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
`Bug 1809249 <https://bugs.launchpad.net/cinder/+bug/1809249>`_ -
3PAR driver adds the config option `hpe3par_target_nsp` that can be
set to the 3PAR backend to use when multipath is not enabled and
the Fibre Channel Zone Manager is not used.

0 comments on commit 9e122f1

Please sign in to comment.