3par driver handles concurrent host create conflict

One of the step in creating a FC VLUN is to create a host on array.
When there happens to have simultaneous request for creating same host,
hpe3parclient will create host for one and return HTTP 409 conflict for others,
with error code 73, message "host WWN/iSCSI name already used by another host".
This was not handled by 3PAR driver and the HTTP exception would propagate up to
volume manager where it would log it. This patch addresses this bug by catching
this exception and reusing the same host.

Change-Id: I66e4f80696c782ab869ef0d7848fc6f3b3100c04
Closes-Bug: #1597454
This commit is contained in:
Jay Mehta 2016-06-29 14:35:37 -07:00
parent e7dab03cb8
commit 3c18a2e41a
3 changed files with 108 additions and 11 deletions

View File

@ -78,8 +78,10 @@ class HTTPConflict(ClientException):
message = "Conflict"
def __init__(self, error=None):
if error and 'message' in error:
self._error_desc = error['message']
if error:
super(HTTPConflict, self).__init__(error)
if 'message' in error:
self._error_desc = error['message']
def get_description(self):
return self._error_desc

View File

@ -78,6 +78,8 @@ HPE3PAR_CPG_MAP = 'OpenStackCPG:DestOpenStackCPG fakepool:destfakepool'
SYNC_MODE = 1
PERIODIC_MODE = 2
SYNC_PERIOD = 900
# EXISTENT_PATH error code returned from hpe3parclient
EXISTENT_PATH = 73
class Comment(object):
@ -5479,6 +5481,67 @@ class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
self.assertEqual('fakehost.foo', host['name'])
def test_concurrent_create_host(self):
# tests concurrent requests to 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,
'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,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
common = self.driver._login()
host = 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(wwns=['123456789012345',
'123456789054321']),
mock.call.createHost(
self.FAKE_HOST,
FCWwns=['123456789012345', '123456789054321'],
optional={'domain': None, 'persona': 2}),
mock.call.queryHost(wwns=['123456789012345',
'123456789054321']),
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

@ -35,9 +35,10 @@ 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 _, _LI, _LW
from cinder.i18n import _, _LI, _LW, _LE
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.hpe import hpe_3par_common as hpecommon
@ -46,6 +47,9 @@ from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
# EXISTENT_PATH error code returned from hpe3parclient
EXISTENT_PATH = 73
@interface.volumedriver
class HPE3PARFCDriver(driver.TransferVD,
@ -105,10 +109,12 @@ class HPE3PARFCDriver(driver.TransferVD,
3.0.7 - Remove metadata that tracks the instance ID. bug #1572665
3.0.8 - NSP feature, creating FC Vlun as match set instead of
host sees. bug #1577993
3.0.9 - Handling HTTP conflict 409, host WWN/iSCSI name already used
by another host, while creating 3PAR FC Host. bug #1597454
"""
VERSION = "3.0.8"
VERSION = "3.0.9"
def __init__(self, *args, **kwargs):
super(HPE3PARFCDriver, self).__init__(*args, **kwargs)
@ -435,16 +441,41 @@ class HPE3PARFCDriver(driver.TransferVD,
return host_found
else:
persona_id = int(persona_id)
common.client.createHost(hostname, FCWwns=wwns,
optional={'domain': domain,
'persona': persona_id})
try:
common.client.createHost(hostname, FCWwns=wwns,
optional={'domain': domain,
'persona': persona_id})
except hpeexceptions.HTTPConflict as path_conflict:
msg = _LE("Create FC host caught HTTP conflict code: %s")
LOG.exception(msg, path_conflict.get_code())
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(wwns=wwns)
if hosts and hosts['members'] and (
'name' in hosts['members'][0]):
hostname = hosts['members'][0]['name']
else:
# re rasise last caught exception
ctxt.reraise = True
else:
# re rasise last caught exception
# for other HTTP conflict
ctxt.reraise = True
return hostname
def _modify_3par_fibrechan_host(self, common, hostname, wwn):
mod_request = {'pathOperation': common.client.HOST_EDIT_ADD,
'FCWWNs': wwn}
common.client.modifyHost(hostname, mod_request)
try:
common.client.modifyHost(hostname, mod_request)
except hpeexceptions.HTTPConflict as path_conflict:
msg = _LE("Modify FC Host %(hostname)s caught "
"HTTP conflict code: %(code)s")
LOG.exception(msg,
{'hostname': hostname,
'code': path_conflict.get_code()})
def _create_host(self, common, volume, connector):
"""Creates or modifies existing 3PAR host."""
@ -464,8 +495,9 @@ class HPE3PARFCDriver(driver.TransferVD,
domain,
persona_id)
host = common._get_3par_host(hostname)
return self._add_new_wwn_to_host(common, host, connector['wwpns'])
return host
else:
return self._add_new_wwn_to_host(common, host, connector['wwpns'])
def _add_new_wwn_to_host(self, common, host, wwns):
"""Add wwns to a host if one or more don't exist.