Merge "3PAR: Add config for NSP single path attach"
This commit is contained in:
commit
7bb05f327a
@ -726,6 +726,7 @@ class HPE3PARBaseDriver(test.TestCase):
|
|||||||
configuration.filter_function = FILTER_FUNCTION
|
configuration.filter_function = FILTER_FUNCTION
|
||||||
configuration.image_volume_cache_enabled = False
|
configuration.image_volume_cache_enabled = False
|
||||||
configuration.replication_device = None
|
configuration.replication_device = None
|
||||||
|
configuration.hpe3par_target_nsp = None
|
||||||
return configuration
|
return configuration
|
||||||
|
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
@ -6940,6 +6941,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
|||||||
# setup_mock_client drive with default configuration
|
# setup_mock_client drive with default configuration
|
||||||
# and return the mock HTTP 3PAR client
|
# and return the mock HTTP 3PAR client
|
||||||
mock_client = self.setup_driver()
|
mock_client = self.setup_driver()
|
||||||
|
mock_client.getStorageSystemInfo.return_value = (
|
||||||
|
{'id': self.CLIENT_ID})
|
||||||
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
||||||
mock_client.getCPG.return_value = {}
|
mock_client.getCPG.return_value = {}
|
||||||
mock_client.getHost.side_effect = [
|
mock_client.getHost.side_effect = [
|
||||||
@ -7004,6 +7007,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
|||||||
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
||||||
|
|
||||||
mock_client.assert_has_calls(
|
mock_client.assert_has_calls(
|
||||||
|
self.get_id_login +
|
||||||
|
self.standard_logout +
|
||||||
self.standard_login +
|
self.standard_login +
|
||||||
expected +
|
expected +
|
||||||
self.standard_logout)
|
self.standard_logout)
|
||||||
@ -7025,6 +7030,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
|||||||
return fake_map
|
return fake_map
|
||||||
mock_lookup.return_value = fake_lookup_object()
|
mock_lookup.return_value = fake_lookup_object()
|
||||||
mock_client = self.setup_driver()
|
mock_client = self.setup_driver()
|
||||||
|
mock_client.getStorageSystemInfo.return_value = (
|
||||||
|
{'id': self.CLIENT_ID})
|
||||||
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
|
||||||
mock_client.getCPG.return_value = {}
|
mock_client.getCPG.return_value = {}
|
||||||
mock_client.getHost.side_effect = [
|
mock_client.getHost.side_effect = [
|
||||||
@ -7098,6 +7105,89 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver):
|
|||||||
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
mock.call.getHostVLUNs(self.FAKE_HOST)]
|
||||||
|
|
||||||
mock_client.assert_has_calls(
|
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 +
|
self.standard_login +
|
||||||
expected +
|
expected +
|
||||||
self.standard_logout)
|
self.standard_logout)
|
||||||
|
@ -118,6 +118,14 @@ hpe3par_opts = [
|
|||||||
cfg.BoolOpt('hpe3par_iscsi_chap_enabled',
|
cfg.BoolOpt('hpe3par_iscsi_chap_enabled',
|
||||||
default=False,
|
default=False,
|
||||||
help="Enable CHAP authentication for iSCSI connections."),
|
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"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# 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
|
This driver requires 3.1.3 or later firmware on the 3PAR array, using
|
||||||
the 4.x version of the hpe3parclient.
|
the 4.x version of the hpe3parclient.
|
||||||
|
|
||||||
@ -112,6 +112,7 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
|
|||||||
4.0.4 - Handle force detach case. bug #1686745
|
4.0.4 - Handle force detach case. bug #1686745
|
||||||
4.0.5 - Set proper backend on subsequent operation, after group
|
4.0.5 - Set proper backend on subsequent operation, after group
|
||||||
failover. bug #1773069
|
failover. bug #1773069
|
||||||
|
4.0.6 - Set NSP for single path attachments. Bug #1809249
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -171,12 +172,22 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
|
|||||||
try:
|
try:
|
||||||
# we have to make sure we have a host
|
# we have to make sure we have a host
|
||||||
host = self._create_host(common, volume, connector)
|
host = self._create_host(common, volume, connector)
|
||||||
target_wwns, init_targ_map, numPaths = \
|
target_wwns, init_targ_map, numPaths = (
|
||||||
self._build_initiator_target_map(common, connector)
|
self._build_initiator_target_map(common, connector))
|
||||||
if not connector.get('multipath'):
|
|
||||||
target_wwns = target_wwns[:1]
|
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]
|
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
|
# check if a VLUN already exists for this host
|
||||||
existing_vlun = common.find_existing_vlun(volume, host)
|
existing_vlun = common.find_existing_vlun(volume, host)
|
||||||
|
|
||||||
@ -411,3 +422,25 @@ class HPE3PARFCDriver(hpebasedriver.HPE3PARDriverBase):
|
|||||||
self._modify_3par_fibrechan_host(common, host['name'], new_wwns)
|
self._modify_3par_fibrechan_host(common, host['name'], new_wwns)
|
||||||
host = common._get_3par_host(host['name'])
|
host = common._get_3par_host(host['name'])
|
||||||
return host
|
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
|
||||||
|
@ -413,3 +413,51 @@ the HPE 3PAR Fibre Channel and iSCSI drivers.
|
|||||||
:config-target: 3PAR
|
:config-target: 3PAR
|
||||||
|
|
||||||
cinder.volume.drivers.hpe.hpe_3par_common
|
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,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.
|
Loading…
x
Reference in New Issue
Block a user