Merge "Added Handling Newer Quobyte API Error Codes" into stable/pike
This commit is contained in:
commit
3e78ae16bc
@ -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"],
|
||||||
|
@ -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'],
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
5
releasenotes/notes/qb-bug-1733807-581e71e6581de28e.yaml
Normal file
5
releasenotes/notes/qb-bug-1733807-581e71e6581de28e.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
The Quobyte driver now handles updated error codes from Quobyte API
|
||||||
|
versions 1.4+ .
|
Loading…
Reference in New Issue
Block a user