Merge "Enable localizable REST API responses via the Accept-Language header"
This commit is contained in:
commit
5cb5d7b097
@ -23,6 +23,7 @@ import webob.exc
|
||||
|
||||
from neutron.api.v2 import attributes
|
||||
from neutron.common import exceptions
|
||||
from neutron.openstack.common import gettextutils
|
||||
from neutron.openstack.common import log as logging
|
||||
from neutron import wsgi
|
||||
|
||||
@ -70,6 +71,7 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
action = args.pop('action', None)
|
||||
content_type = format_types.get(fmt,
|
||||
request.best_match_content_type())
|
||||
language = request.best_match_language()
|
||||
deserializer = deserializers.get(content_type)
|
||||
serializer = serializers.get(content_type)
|
||||
|
||||
@ -83,6 +85,7 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
except (exceptions.NeutronException,
|
||||
netaddr.AddrFormatError) as e:
|
||||
LOG.exception(_('%s failed'), action)
|
||||
e = translate(e, language)
|
||||
body = serializer.serialize({'NeutronError': e})
|
||||
kwargs = {'body': body, 'content_type': content_type}
|
||||
for fault in faults:
|
||||
@ -91,10 +94,12 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
raise webob.exc.HTTPInternalServerError(**kwargs)
|
||||
except webob.exc.HTTPException as e:
|
||||
LOG.exception(_('%s failed'), action)
|
||||
translate(e, language)
|
||||
e.body = serializer.serialize({'NeutronError': e})
|
||||
e.content_type = content_type
|
||||
raise
|
||||
except NotImplementedError as e:
|
||||
e = translate(e, language)
|
||||
# NOTE(armando-migliaccio): from a client standpoint
|
||||
# it makes sense to receive these errors, because
|
||||
# extensions may or may not be implemented by
|
||||
@ -111,6 +116,7 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
# Do not expose details of 500 error to clients.
|
||||
msg = _('Request Failed: internal server error while '
|
||||
'processing your request.')
|
||||
msg = translate(msg, language)
|
||||
body = serializer.serialize({'NeutronError': msg})
|
||||
kwargs = {'body': body, 'content_type': content_type}
|
||||
raise webob.exc.HTTPInternalServerError(**kwargs)
|
||||
@ -126,3 +132,24 @@ def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
content_type=content_type,
|
||||
body=body)
|
||||
return resource
|
||||
|
||||
|
||||
def translate(translatable, locale):
|
||||
"""Translates the object to the given locale.
|
||||
|
||||
If the object is an exception its translatable elements are translated
|
||||
in place, if the object is a translatable string it is translated and
|
||||
returned. Otherwise, the object is returned as-is.
|
||||
|
||||
:param translatable: the object to be translated
|
||||
:param locale: the locale to translate to
|
||||
:returns: the translated object, or the object as-is if it
|
||||
was not translated
|
||||
"""
|
||||
localize = gettextutils.get_localized_message
|
||||
if isinstance(translatable, Exception):
|
||||
translatable.message = localize(translatable.message, locale)
|
||||
if isinstance(translatable, webob.exc.HTTPError):
|
||||
translatable.detail = localize(translatable.detail, locale)
|
||||
return translatable
|
||||
return localize(translatable, locale)
|
||||
|
@ -27,6 +27,9 @@ from oslo.config import cfg
|
||||
from neutron.common import config
|
||||
from neutron import service
|
||||
|
||||
from neutron.openstack.common import gettextutils
|
||||
gettextutils.install('neutron', lazy=True)
|
||||
|
||||
|
||||
def main():
|
||||
eventlet.monkey_patch()
|
||||
|
@ -25,6 +25,7 @@ import webtest
|
||||
from neutron.api.v2 import resource as wsgi_resource
|
||||
from neutron.common import exceptions as q_exc
|
||||
from neutron import context
|
||||
from neutron.openstack.common import gettextutils
|
||||
from neutron.tests import base
|
||||
from neutron import wsgi
|
||||
|
||||
@ -98,8 +99,23 @@ class RequestTestCase(base.BaseTestCase):
|
||||
def test_context_without_neutron_context(self):
|
||||
self.assertTrue(self.req.context.is_admin)
|
||||
|
||||
def test_best_match_language(self):
|
||||
# Here we test that we are actually invoking language negotiation
|
||||
# by webop and also that the default locale always available is en-US
|
||||
request = wsgi.Request.blank('/')
|
||||
gettextutils.get_available_languages = mock.MagicMock()
|
||||
gettextutils.get_available_languages.return_value = ['known-language',
|
||||
'es', 'zh']
|
||||
request.headers['Accept-Language'] = 'known-language'
|
||||
language = request.best_match_language()
|
||||
self.assertEqual(language, 'known-language')
|
||||
request.headers['Accept-Language'] = 'unknown-language'
|
||||
language = request.best_match_language()
|
||||
self.assertEqual(language, 'en_US')
|
||||
|
||||
|
||||
class ResourceTestCase(base.BaseTestCase):
|
||||
|
||||
def test_unmapped_neutron_error_with_json(self):
|
||||
msg = u'\u7f51\u7edc'
|
||||
|
||||
@ -136,6 +152,29 @@ class ResourceTestCase(base.BaseTestCase):
|
||||
self.assertEqual(wsgi.XMLDeserializer().deserialize(res.body),
|
||||
expected_res)
|
||||
|
||||
@mock.patch('neutron.openstack.common.gettextutils.Message.data',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_unmapped_neutron_error_localized(self, mock_translation):
|
||||
gettextutils.install('blaa', lazy=True)
|
||||
msg_translation = 'Translated error'
|
||||
mock_translation.return_value = msg_translation
|
||||
msg = _('Unmapped error')
|
||||
|
||||
class TestException(q_exc.NeutronException):
|
||||
message = msg
|
||||
|
||||
controller = mock.MagicMock()
|
||||
controller.test.side_effect = TestException()
|
||||
resource = webtest.TestApp(wsgi_resource.Resource(controller))
|
||||
|
||||
environ = {'wsgiorg.routing_args': (None, {'action': 'test',
|
||||
'format': 'json'})}
|
||||
|
||||
res = resource.get('', extra_environ=environ, expect_errors=True)
|
||||
self.assertEqual(res.status_int, exc.HTTPInternalServerError.code)
|
||||
self.assertIn(msg_translation,
|
||||
str(wsgi.JSONDeserializer().deserialize(res.body)))
|
||||
|
||||
def test_mapped_neutron_error_with_json(self):
|
||||
msg = u'\u7f51\u7edc'
|
||||
|
||||
@ -176,6 +215,31 @@ class ResourceTestCase(base.BaseTestCase):
|
||||
self.assertEqual(wsgi.XMLDeserializer().deserialize(res.body),
|
||||
expected_res)
|
||||
|
||||
@mock.patch('neutron.openstack.common.gettextutils.Message.data',
|
||||
new_callable=mock.PropertyMock)
|
||||
def test_mapped_neutron_error_localized(self, mock_translation):
|
||||
gettextutils.install('blaa', lazy=True)
|
||||
msg_translation = 'Translated error'
|
||||
mock_translation.return_value = msg_translation
|
||||
msg = _('Unmapped error')
|
||||
|
||||
class TestException(q_exc.NeutronException):
|
||||
message = msg
|
||||
|
||||
controller = mock.MagicMock()
|
||||
controller.test.side_effect = TestException()
|
||||
faults = {TestException: exc.HTTPGatewayTimeout}
|
||||
resource = webtest.TestApp(wsgi_resource.Resource(controller,
|
||||
faults=faults))
|
||||
|
||||
environ = {'wsgiorg.routing_args': (None, {'action': 'test',
|
||||
'format': 'json'})}
|
||||
|
||||
res = resource.get('', extra_environ=environ, expect_errors=True)
|
||||
self.assertEqual(res.status_int, exc.HTTPGatewayTimeout.code)
|
||||
self.assertIn(msg_translation,
|
||||
str(wsgi.JSONDeserializer().deserialize(res.body)))
|
||||
|
||||
def test_http_error(self):
|
||||
controller = mock.MagicMock()
|
||||
controller.test.side_effect = exc.HTTPGatewayTimeout()
|
||||
|
@ -37,6 +37,7 @@ import webob.exc
|
||||
from neutron.common import constants
|
||||
from neutron.common import exceptions as exception
|
||||
from neutron import context
|
||||
from neutron.openstack.common import gettextutils
|
||||
from neutron.openstack.common import jsonutils
|
||||
from neutron.openstack.common import log as logging
|
||||
|
||||
@ -299,6 +300,12 @@ class Request(webob.Request):
|
||||
return _type
|
||||
return None
|
||||
|
||||
def best_match_language(self):
|
||||
"""Determine language for returned response."""
|
||||
all_languages = gettextutils.get_available_languages('neutron')
|
||||
return self.accept_language.best_match(all_languages,
|
||||
default_match='en_US')
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
if 'neutron.context' not in self.environ:
|
||||
|
Loading…
x
Reference in New Issue
Block a user