diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 7c314fe8d..6f41ffb2c 100644 --- a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py +++ b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py @@ -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 diff --git a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py index 5df59c041..c3c3104ee 100644 --- a/cinder/volume/drivers/hpe/hpe_3par_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_3par_iscsi.py @@ -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):