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:
parent
e7dab03cb8
commit
3c18a2e41a
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user