HPE3PAR: handle conflict in iscsi host create

One of the step in creating iSCSI VLUN is to create a host on array.
If there are simultaneous requests comming on 3par array for creating
same host, hpe3parclient creates host for first request and returns
HTTP 409 conflict error for others, with error code 73, message "host
WWN/iSCSI name already used by another host". This was not handled by
3PAR iscsi driver and the HTTP exception would propagate up to volume
manager where it would log it. This patch addresses issue by catching
this exception and reusing the same host.

Change-Id: I102409539c9a691c1816a342163dd049855f57da
Closes-Bug: #1642945
This commit is contained in:
Vijay Ladani 2016-11-21 20:31:08 -08:00
parent a1ed8a6b29
commit 9b0c5c4318
2 changed files with 72 additions and 23 deletions

View File

@ -5521,8 +5521,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
self.assertEqual('fakehost.foo', host['name'])
def test_concurrent_create_host(self):
# tests concurrent requests to create host
def test_create_host_concurrent(self):
# tests concurrent requests of create host
# setup_mock_client driver with default configuration
# and return the mock HTTP 3PAR client
mock_client = self.setup_driver()
@ -5538,23 +5538,7 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
'desc': 'host WWN/iSCSI name already used by another host'})]
mock_client.getHost.side_effect = [
hpeexceptions.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]},
{'driverVersion': None,
'firmwareVersion': None,
'hostSpeed': 0,
'model': None,
'portPos': {'cardPort': 1, 'node': 0,
'slot': 2},
'vendor': None,
'wwn': self.wwn[1]}]}]
{'name': self.FAKE_HOST}]
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
@ -6687,6 +6671,45 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
self.assertEqual('test-user', auth_username)
self.assertEqual('test-pass', auth_password)
def test_create_host_concurrent(self):
# tests concurrent requests of create host
# setup_mock_client driver with default configuration
# and return the mock HTTP 3PAR client
mock_client = self.setup_driver()
mock_client.getVolume.return_value = {'userCPG': HPE3PAR_CPG}
mock_client.getCPG.return_value = {}
mock_client.queryHost.side_effect = [
None, {'members': [{'name': self.FAKE_HOST}]}]
mock_client.createHost.side_effect = [
hpeexceptions.HTTPConflict(
{'code': EXISTENT_PATH,
'desc': 'host WWN/iSCSI name already used by another host'})]
mock_client.getHost.side_effect = [
hpeexceptions.HTTPNotFound('fake'),
{'name': self.FAKE_HOST}]
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
host, user, pwd = self.driver._create_host(
common, self.volume, self.connector)
expected = [
mock.call.getVolume('osv-0DM4qZEVSKON-DXN-NwVpw'),
mock.call.getCPG(HPE3PAR_CPG),
mock.call.getHost(self.FAKE_HOST),
mock.call.queryHost(iqns=['iqn.1993-08.org.debian:01:222']),
mock.call.createHost(
self.FAKE_HOST,
optional={'domain': None, 'persona': 2},
iscsiNames=['iqn.1993-08.org.debian:01:222']),
mock.call.queryHost(iqns=['iqn.1993-08.org.debian:01:222']),
mock.call.getHost(self.FAKE_HOST)]
mock_client.assert_has_calls(expected)
self.assertEqual(self.FAKE_HOST, host['name'])
def test_create_modify_host(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client

View File

@ -36,6 +36,7 @@ except ImportError:
hpeexceptions = None
from oslo_log import log as logging
from oslo_utils.excutils import save_and_reraise_exception
from cinder import exception
from cinder.i18n import _, _LE, _LW
@ -47,6 +48,9 @@ from cinder.volume.drivers.san import san
from cinder.volume import utils as volume_utils
LOG = logging.getLogger(__name__)
# EXISTENT_PATH error code returned from hpe3parclient
EXISTENT_PATH = 73
DEFAULT_ISCSI_PORT = 3260
CHAP_USER_KEY = "HPQ-cinder-CHAP-name"
CHAP_PASS_KEY = "HPQ-cinder-CHAP-secret"
@ -119,10 +123,12 @@ class HPE3PARISCSIDriver(driver.TransferVD,
3.0.11 - _create_3par_iscsi_host() now accepts iscsi_iqn as list only.
Bug #1590180
3.0.12 - Added entry point tracing
3.0.13 - Handling HTTP conflict 409, host WWN/iSCSI name already used
by another host, while creating 3PAR iSCSI Host. bug #1642945
"""
VERSION = "3.0.12"
VERSION = "3.0.13"
# The name of the CI wiki page.
CI_WIKI_NAME = "HPE_Storage_CI"
@ -528,9 +534,29 @@ class HPE3PARISCSIDriver(driver.TransferVD,
return host_found
else:
persona_id = int(persona_id)
common.client.createHost(hostname, iscsiNames=iscsi_iqn,
optional={'domain': domain,
'persona': persona_id})
try:
common.client.createHost(hostname, iscsiNames=iscsi_iqn,
optional={'domain': domain,
'persona': persona_id})
except hpeexceptions.HTTPConflict as path_conflict:
msg = _LE("Create iSCSI host caught HTTP conflict code: %s")
with save_and_reraise_exception(reraise=False) as ctxt:
if path_conflict.get_code() is EXISTENT_PATH:
# Handle exception : EXISTENT_PATH - host WWN/iSCSI
# name already used by another host
hosts = common.client.queryHost(iqns=iscsi_iqn)
if hosts and hosts['members'] and (
'name' in hosts['members'][0]):
hostname = hosts['members'][0]['name']
else:
# re-raise last caught exception
ctxt.reraise = True
LOG.exception(msg, path_conflict.get_code())
else:
# re-raise last caught exception
# for other HTTP conflict
ctxt.reraise = True
LOG.exception(msg, path_conflict.get_code())
return hostname
def _modify_3par_iscsi_host(self, common, hostname, iscsi_iqn):