Merge "Enable localizable REST API responses via the Accept-Language header"

This commit is contained in:
Jenkins 2013-08-08 02:46:03 +00:00 committed by Gerrit Code Review
commit 5cb5d7b097
4 changed files with 101 additions and 0 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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: