From 9b0c5c4318d3c348ce185a3f4dd94f9e803bf264 Mon Sep 17 00:00:00 2001 From: Vijay Ladani Date: Mon, 21 Nov 2016 20:31:08 -0800 Subject: [PATCH] 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 --- .../unit/volume/drivers/hpe/test_hpe3par.py | 61 +++++++++++++------ cinder/volume/drivers/hpe/hpe_3par_iscsi.py | 34 +++++++++-- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py b/cinder/tests/unit/volume/drivers/hpe/test_hpe3par.py index 7c314fe8d69..6f41ffb2c97 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 5df59c041e2..c3c3104eedd 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):