Merge "Added Handling Newer Quobyte API Error Codes" into stable/pike

This commit is contained in:
Zuul 2018-07-30 15:00:51 +00:00 committed by Gerrit Code Review
commit 3e78ae16bc
5 changed files with 86 additions and 16 deletions

View File

@ -33,6 +33,8 @@ from manila import utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
ERROR_ENOENT = 2 ERROR_ENOENT = 2
ERROR_ENTITY_NOT_FOUND = -24
ERROR_GARBAGE_ARGS = -3
class JsonRpc(object): class JsonRpc(object):
@ -57,7 +59,7 @@ class JsonRpc(object):
self._cert_file = cert_file self._cert_file = cert_file
@utils.synchronized('quobyte-request') @utils.synchronized('quobyte-request')
def call(self, method_name, user_parameters): def call(self, method_name, user_parameters, expected_errors=[]):
# prepare request # prepare request
self._id += 1 self._id += 1
parameters = {'retry': 'INFINITELY'} # Backend specific setting parameters = {'retry': 'INFINITELY'} # Backend specific setting
@ -94,17 +96,19 @@ class JsonRpc(object):
if result.status_code == codes['OK']: if result.status_code == codes['OK']:
LOG.debug("Retrieved data from Quobyte backend: %s", result.text) LOG.debug("Retrieved data from Quobyte backend: %s", result.text)
response = result.json() response = result.json()
return self._checked_for_application_error(response) return self._checked_for_application_error(response,
expected_errors)
# If things did not work out provide error info # If things did not work out provide error info
LOG.debug("Backend request resulted in error: %s", result.text) LOG.debug("Backend request resulted in error: %s", result.text)
result.raise_for_status() result.raise_for_status()
def _checked_for_application_error(self, result): def _checked_for_application_error(self, result, expected_errors=[]):
if 'error' in result and result['error']: if 'error' in result and result['error']:
if 'message' in result['error'] and 'code' in result['error']: if 'message' in result['error'] and 'code' in result['error']:
if result["error"]["code"] == ERROR_ENOENT: if result["error"]["code"] in expected_errors:
return None # No Entry # hit an expected error, return empty result
return None
else: else:
raise exception.QBRpcException( raise exception.QBRpcException(
result=result["error"]["message"], result=result["error"]["message"],

View File

@ -76,9 +76,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
1.2.1 - Improved capacity calculation 1.2.1 - Improved capacity calculation
1.2.2 - Minor optimizations 1.2.2 - Minor optimizations
1.2.3 - Updated RPC layer for improved stability 1.2.3 - Updated RPC layer for improved stability
1.2.4 - Fixed handling updated QB API error codes
""" """
DRIVER_VERSION = '1.2.3' DRIVER_VERSION = '1.2.4'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(QuobyteShareDriver, self).__init__(False, *args, **kwargs) super(QuobyteShareDriver, self).__init__(False, *args, **kwargs)
@ -190,7 +191,8 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
"""Resolve a volume name to the global volume uuid.""" """Resolve a volume name to the global volume uuid."""
result = self.rpc.call('resolveVolumeName', dict( result = self.rpc.call('resolveVolumeName', dict(
volume_name=volume_name, volume_name=volume_name,
tenant_domain=tenant_domain)) tenant_domain=tenant_domain), [jsonrpc.ERROR_ENOENT,
jsonrpc.ERROR_ENTITY_NOT_FOUND])
if result: if result:
return result['volume_uuid'] return result['volume_uuid']
return None # not found return None # not found
@ -223,6 +225,10 @@ class QuobyteShareDriver(driver.ExecuteMixin, driver.ShareDriver,):
self._get_project_name(context, share['project_id'])) self._get_project_name(context, share['project_id']))
if not volume_uuid: if not volume_uuid:
# create tenant, expect ERROR_GARBAGE_ARGS if it already exists
self.rpc.call('setTenant',
dict(tenant=dict(tenant_id=share['project_id'])),
expected_errors=[jsonrpc.ERROR_GARBAGE_ARGS])
result = self.rpc.call('createVolume', dict( result = self.rpc.call('createVolume', dict(
name=share['name'], name=share['name'],
tenant_domain=share['project_id'], tenant_domain=share['project_id'],

View File

@ -141,6 +141,29 @@ class QuobyteJsonRpcTestCase(test.TestCase):
verify=fake_ca_file) verify=fake_ca_file)
self.assertEqual("Sweet gorilla of Manila", result) self.assertEqual("Sweet gorilla of Manila", result)
@mock.patch.object(jsonrpc.JsonRpc, "_checked_for_application_error",
return_value="Sweet gorilla of Manila")
@mock.patch.object(requests, "post",
return_value=FakeResponse(
200, {"result": "Sweet gorilla of Manila"}))
def test_https_call_verify_expected_error(self, mock_req_get, mock_check):
fake_ca_file = tempfile.TemporaryFile()
self.rpc = jsonrpc.JsonRpc(url="https://test",
user_credentials=("me", "team"),
ca_file=fake_ca_file)
result = self.rpc.call('method', {'param': 'value'},
expected_errors=[42])
mock_req_get.assert_called_once_with(
url=self.rpc._url,
json=mock.ANY, # not checking here as of undefined order in dict
auth=self.rpc._credentials,
verify=fake_ca_file)
mock_check.assert_called_once_with(
{'result': 'Sweet gorilla of Manila'}, [42])
self.assertEqual("Sweet gorilla of Manila", result)
@mock.patch.object(requests, "post", side_effect=exceptions.HTTPError) @mock.patch.object(requests, "post", side_effect=exceptions.HTTPError)
def test_jsonrpc_call_http_exception(self, req_get_mock): def test_jsonrpc_call_http_exception(self, req_get_mock):
self.assertRaises(exceptions.HTTPError, self.assertRaises(exceptions.HTTPError,
@ -169,12 +192,22 @@ class QuobyteJsonRpcTestCase(test.TestCase):
(self.rpc._checked_for_application_error( (self.rpc._checked_for_application_error(
result=resultdict))) result=resultdict)))
def test_checked_for_application_error_enf(self):
resultdict = {"result": "Sweet gorilla of Manila",
"error": {"message": "No Gorilla",
"code": jsonrpc.ERROR_ENTITY_NOT_FOUND}}
self.assertIsNone(
self.rpc._checked_for_application_error(
result=resultdict,
expected_errors=[jsonrpc.ERROR_ENTITY_NOT_FOUND]))
def test_checked_for_application_error_no_entry(self): def test_checked_for_application_error_no_entry(self):
resultdict = {"result": "Sweet gorilla of Manila", resultdict = {"result": "Sweet gorilla of Manila",
"error": {"message": "No Gorilla", "error": {"message": "No Gorilla",
"code": jsonrpc.ERROR_ENOENT}} "code": jsonrpc.ERROR_ENOENT}}
self.assertIsNone( self.assertIsNone(
self.rpc._checked_for_application_error(result=resultdict)) self.rpc._checked_for_application_error(
result=resultdict, expected_errors=[jsonrpc.ERROR_ENOENT]))
def test_checked_for_application_error_exception(self): def test_checked_for_application_error_exception(self):
self.assertRaises(exception.QBRpcException, self.assertRaises(exception.QBRpcException,

View File

@ -29,7 +29,7 @@ from manila.tests import fake_share
CONF = cfg.CONF CONF = cfg.CONF
def fake_rpc_handler(name, *args): def fake_rpc_handler(name, *args, **kwargs):
if name == 'resolveVolumeName': if name == 'resolveVolumeName':
return None return None
elif name == 'createVolume': elif name == 'createVolume':
@ -136,8 +136,23 @@ class QuobyteShareDriverTestCase(test.TestCase):
self._driver.create_share(self._context, self.share) self._driver.create_share(self._context, self.share)
self._driver.rpc.call.assert_called_with( resolv_params = {'tenant_domain': 'fake_project_uuid',
'exportVolume', dict(protocol='NFS', volume_uuid='voluuid')) 'volume_name': 'fakename'}
sett_params = {'tenant': {'tenant_id': 'fake_project_uuid'}}
create_params = dict(
name='fakename',
tenant_domain='fake_project_uuid',
root_user_id='root',
root_group_id='root',
configuration_name='BASE')
self._driver.rpc.call.assert_has_calls([
mock.call('resolveVolumeName', resolv_params,
[jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]),
mock.call('setTenant', sett_params,
expected_errors=[jsonrpc.ERROR_GARBAGE_ARGS]),
mock.call('createVolume', create_params),
mock.call('exportVolume', dict(protocol='NFS',
volume_uuid='voluuid'))])
def test_create_share_wrong_protocol(self): def test_create_share_wrong_protocol(self):
share = {'share_proto': 'WRONG_PROTOCOL'} share = {'share_proto': 'WRONG_PROTOCOL'}
@ -162,7 +177,8 @@ class QuobyteShareDriverTestCase(test.TestCase):
resolv_params = {'volume_name': 'fakename', resolv_params = {'volume_name': 'fakename',
'tenant_domain': 'fake_project_uuid'} 'tenant_domain': 'fake_project_uuid'}
self._driver.rpc.call.assert_has_calls([ self._driver.rpc.call.assert_has_calls([
mock.call('resolveVolumeName', resolv_params), mock.call('resolveVolumeName', resolv_params,
[jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]),
mock.call('deleteVolume', {'volume_uuid': 'voluuid'})]) mock.call('deleteVolume', {'volume_uuid': 'voluuid'})])
def test_delete_share_existing_volume_disabled(self): def test_delete_share_existing_volume_disabled(self):
@ -178,8 +194,7 @@ class QuobyteShareDriverTestCase(test.TestCase):
self._driver.delete_share(self._context, self.share) self._driver.delete_share(self._context, self.share)
self._driver.rpc.call.assert_called_with( self._driver.rpc.call.assert_called_with(
'exportVolume', {'volume_uuid': 'voluuid', 'exportVolume', {'volume_uuid': 'voluuid', 'remove_export': True})
'remove_export': True})
@mock.patch.object(quobyte.LOG, 'warning') @mock.patch.object(quobyte.LOG, 'warning')
def test_delete_share_nonexisting_volume(self, mock_warning): def test_delete_share_nonexisting_volume(self, mock_warning):
@ -277,8 +292,9 @@ class QuobyteShareDriverTestCase(test.TestCase):
exp_params = {'volume_name': 'fake_vol_name', exp_params = {'volume_name': 'fake_vol_name',
'tenant_domain': 'fake_domain_name'} 'tenant_domain': 'fake_domain_name'}
self._driver.rpc.call.assert_called_with('resolveVolumeName', self._driver.rpc.call.assert_called_with(
exp_params) 'resolveVolumeName', exp_params,
[jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND])
def test_resolve_volume_name_NOENT(self): def test_resolve_volume_name_NOENT(self):
self._driver.rpc.call = mock.Mock( self._driver.rpc.call = mock.Mock(
@ -287,6 +303,12 @@ class QuobyteShareDriverTestCase(test.TestCase):
self.assertIsNone( self.assertIsNone(
self._driver._resolve_volume_name('fake_vol_name', self._driver._resolve_volume_name('fake_vol_name',
'fake_domain_name')) 'fake_domain_name'))
self._driver.rpc.call.assert_called_once_with(
'resolveVolumeName',
dict(volume_name='fake_vol_name',
tenant_domain='fake_domain_name'),
[jsonrpc.ERROR_ENOENT, jsonrpc.ERROR_ENTITY_NOT_FOUND]
)
def test_resolve_volume_name_other_error(self): def test_resolve_volume_name_other_error(self):
self._driver.rpc.call = mock.Mock( self._driver.rpc.call = mock.Mock(

View File

@ -0,0 +1,5 @@
---
fixes:
- |
The Quobyte driver now handles updated error codes from Quobyte API
versions 1.4+ .