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:
parent
a1ed8a6b29
commit
9b0c5c4318
|
@ -5521,8 +5521,8 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
|
||||||
|
|
||||||
self.assertEqual('fakehost.foo', host['name'])
|
self.assertEqual('fakehost.foo', host['name'])
|
||||||
|
|
||||||
def test_concurrent_create_host(self):
|
def test_create_host_concurrent(self):
|
||||||
# tests concurrent requests to create host
|
# tests concurrent requests of create host
|
||||||
# setup_mock_client driver with default configuration
|
# setup_mock_client driver 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()
|
||||||
|
@ -5538,23 +5538,7 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
|
||||||
'desc': 'host WWN/iSCSI name already used by another host'})]
|
'desc': 'host WWN/iSCSI name already used by another host'})]
|
||||||
mock_client.getHost.side_effect = [
|
mock_client.getHost.side_effect = [
|
||||||
hpeexceptions.HTTPNotFound('fake'),
|
hpeexceptions.HTTPNotFound('fake'),
|
||||||
{'name': self.FAKE_HOST,
|
{'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]}]}]
|
|
||||||
|
|
||||||
with mock.patch.object(hpecommon.HPE3PARCommon,
|
with mock.patch.object(hpecommon.HPE3PARCommon,
|
||||||
'_create_client') as mock_create_client:
|
'_create_client') as mock_create_client:
|
||||||
|
@ -6687,6 +6671,45 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
|
||||||
self.assertEqual('test-user', auth_username)
|
self.assertEqual('test-user', auth_username)
|
||||||
self.assertEqual('test-pass', auth_password)
|
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):
|
def test_create_modify_host(self):
|
||||||
# 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
|
||||||
|
|
|
@ -36,6 +36,7 @@ except ImportError:
|
||||||
hpeexceptions = None
|
hpeexceptions = None
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils.excutils import save_and_reraise_exception
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LE, _LW
|
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
|
from cinder.volume import utils as volume_utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# EXISTENT_PATH error code returned from hpe3parclient
|
||||||
|
EXISTENT_PATH = 73
|
||||||
DEFAULT_ISCSI_PORT = 3260
|
DEFAULT_ISCSI_PORT = 3260
|
||||||
CHAP_USER_KEY = "HPQ-cinder-CHAP-name"
|
CHAP_USER_KEY = "HPQ-cinder-CHAP-name"
|
||||||
CHAP_PASS_KEY = "HPQ-cinder-CHAP-secret"
|
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.
|
3.0.11 - _create_3par_iscsi_host() now accepts iscsi_iqn as list only.
|
||||||
Bug #1590180
|
Bug #1590180
|
||||||
3.0.12 - Added entry point tracing
|
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.
|
# The name of the CI wiki page.
|
||||||
CI_WIKI_NAME = "HPE_Storage_CI"
|
CI_WIKI_NAME = "HPE_Storage_CI"
|
||||||
|
@ -528,9 +534,29 @@ class HPE3PARISCSIDriver(driver.TransferVD,
|
||||||
return host_found
|
return host_found
|
||||||
else:
|
else:
|
||||||
persona_id = int(persona_id)
|
persona_id = int(persona_id)
|
||||||
common.client.createHost(hostname, iscsiNames=iscsi_iqn,
|
try:
|
||||||
optional={'domain': domain,
|
common.client.createHost(hostname, iscsiNames=iscsi_iqn,
|
||||||
'persona': persona_id})
|
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
|
return hostname
|
||||||
|
|
||||||
def _modify_3par_iscsi_host(self, common, hostname, iscsi_iqn):
|
def _modify_3par_iscsi_host(self, common, hostname, iscsi_iqn):
|
||||||
|
|
Loading…
Reference in New Issue