]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add support for API message localization
authorLuis A. Garcia <luis@linux.vnet.ibm.com>
Mon, 8 Jul 2013 23:11:05 +0000 (23:11 +0000)
committerLuis A. Garcia <luis@linux.vnet.ibm.com>
Fri, 9 Aug 2013 04:57:36 +0000 (04:57 +0000)
Add support for doing language resolution for a request, based on the
Accept-Language HTTP header.

Using the lazy gettext functionality from oslo gettextutils, it is now
possible to use the resolved language to translate an exception message
to the user requested language and return that translation from the API.

Partially implements bp user-locale-api

Change-Id: Ib2c8360372996d53b50542df54a52d92b07295ca

bin/cinder-all
bin/cinder-api
cinder/api/openstack/wsgi.py
cinder/tests/api/middleware/test_faults.py
cinder/tests/api/openstack/test_wsgi.py
cinder/utils.py
cinder/wsgi.py

index 8e19ab5f48941ad026c84367623131ab5362f4fa..33ae0ea849bbcb2a30f23ff2755ad009140fecb2 100755 (executable)
@@ -44,7 +44,7 @@ if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")):
     sys.path.insert(0, possible_topdir)
 
 from cinder.openstack.common import gettextutils
-gettextutils.install('cinder')
+gettextutils.install('cinder', lazy=True)
 
 from cinder.common import config  # Need to register global_opts
 from cinder.openstack.common import log as logging
index 7005d24cecf3d7e547d99673f105da3dde1feef3..7c296c6bfd6afd44fb6bcb2e29b2254723996d6c 100755 (executable)
@@ -24,7 +24,6 @@
 # eventlet is updated/released to fix the root issue
 
 import eventlet
-
 eventlet.monkey_patch()
 
 import os
@@ -39,7 +38,7 @@ if os.path.exists(os.path.join(possible_topdir, "cinder", "__init__.py")):
     sys.path.insert(0, possible_topdir)
 
 from cinder.openstack.common import gettextutils
-gettextutils.install('cinder')
+gettextutils.install('cinder', lazy=True)
 
 from cinder.common import config  # Need to register global_opts
 from cinder.openstack.common import log as logging
index a2101c1961b707013f6d1f26fe144aaf703e68fe..89a4356ae417966477a7c59dc7bcab2d531ef6a7 100644 (file)
@@ -1,6 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
 # Copyright 2011 OpenStack LLC.
+# Copyright 2013 IBM Corp.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -21,6 +22,7 @@ import time
 import webob
 
 from cinder import exception
+from cinder.openstack.common import gettextutils
 from cinder.openstack.common import jsonutils
 from cinder.openstack.common import log as logging
 from cinder import utils
@@ -101,6 +103,12 @@ class Request(webob.Request):
 
         return content_type
 
+    def best_match_language(self):
+        """Determines best available locale from the Accept-Language header."""
+        all_languages = gettextutils.get_available_languages('cinder')
+        return self.accept_language.best_match(all_languages,
+                                               default_match='en_US')
+
 
 class ActionDispatcher(object):
     """Maps method name to local methods through action name."""
@@ -1069,12 +1077,15 @@ class Fault(webob.exc.HTTPException):
     def __call__(self, req):
         """Generate a WSGI response based on the exception passed to ctor."""
         # Replace the body with fault details.
+        locale = req.best_match_language()
         code = self.wrapped_exc.status_int
         fault_name = self._fault_names.get(code, "computeFault")
+        explanation = self.wrapped_exc.explanation
         fault_data = {
             fault_name: {
                 'code': code,
-                'message': self.wrapped_exc.explanation}}
+                'message': gettextutils.get_localized_message(explanation,
+                                                              locale)}}
         if code == 413:
             retry = self.wrapped_exc.headers['Retry-After']
             fault_data[fault_name]['retryAfter'] = retry
@@ -1134,13 +1145,19 @@ class OverLimitFault(webob.exc.HTTPException):
 
     @webob.dec.wsgify(RequestClass=Request)
     def __call__(self, request):
-        """
-        Return the wrapped exception with a serialized body conforming to our
-        error format.
-        """
+        """Serializes the wrapped exception conforming to our error format."""
         content_type = request.best_match_content_type()
         metadata = {"attributes": {"overLimitFault": "code"}}
 
+        def translate(msg):
+            locale = request.best_match_language()
+            return gettextutils.get_localized_message(msg, locale)
+
+        self.content['overLimitFault']['message'] = \
+            translate(self.content['overLimitFault']['message'])
+        self.content['overLimitFault']['details'] = \
+            translate(self.content['overLimitFault']['details'])
+
         xml_serializer = XMLDictSerializer(metadata, XMLNS_V1)
         serializer = {
             'application/xml': xml_serializer,
index aff1dfbcc96d9ff760bb3ccb3ae08a74cbc99659..7be3b05fc4e869235298493e3f16b17c656e88b3 100644 (file)
 
 from xml.dom import minidom
 
-import webob
 import webob.dec
 import webob.exc
 
 from cinder.api import common
 from cinder.api.openstack import wsgi
+from cinder.openstack.common import gettextutils
 from cinder.openstack.common import jsonutils
 from cinder import test
 
@@ -109,6 +109,27 @@ class TestFaults(test.TestCase):
         self.assertTrue('resizeNotAllowed' not in resp.body)
         self.assertTrue('forbidden' in resp.body)
 
+    def test_raise_localized_explanation(self):
+        params = ('blah', )
+        expl = gettextutils.Message("String with params: %s" % params, 'test')
+
+        def _mock_translation(msg, locale):
+            return "Mensaje traducido"
+
+        self.stubs.Set(gettextutils,
+                       "get_localized_message", _mock_translation)
+
+        @webob.dec.wsgify
+        def raiser(req):
+            raise wsgi.Fault(webob.exc.HTTPNotFound(explanation=expl))
+
+        req = webob.Request.blank('/.xml')
+        resp = req.get_response(raiser)
+        self.assertEqual(resp.content_type, "application/xml")
+        self.assertEqual(resp.status_int, 404)
+        self.assertTrue(("Mensaje traducido") in resp.body)
+        self.stubs.UnsetAll()
+
     def test_fault_has_status_int(self):
         """Ensure the status_int is set correctly on faults"""
         fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?'))
index 75e0fa21553421b26b9de68d8761c01355898a6e..0413a842eaabdeab8cb76935c17835d6cc020b2e 100644 (file)
@@ -87,6 +87,21 @@ class RequestTest(test.TestCase):
         result = request.best_match_content_type()
         self.assertEqual(result, "application/json")
 
+    def test_best_match_language(self):
+        # Here we test that we are actually invoking language negotiation
+        # by webob and also that the default locale always available is en-US
+        request = wsgi.Request.blank('/')
+        accepted = 'unknown-lang'
+        request.headers = {'Accept-Language': accepted}
+
+        def fake_best_match(self, offers, default_match=None):
+            return default_match
+
+        self.stubs.SmartSet(request.accept_language,
+                            'best_match', fake_best_match)
+
+        self.assertEqual(request.best_match_language(), 'en_US')
+
 
 class ActionDispatcherTest(test.TestCase):
     def test_dispatch(self):
index fa05e73e6904aa2dd054e512a4baab6c6f27b0e3..8942c273d6db188df16dbdcecb6a64041d8f7611 100644 (file)
@@ -48,6 +48,7 @@ from oslo.config import cfg
 
 from cinder import exception
 from cinder.openstack.common import excutils
+from cinder.openstack.common import gettextutils
 from cinder.openstack.common import importutils
 from cinder.openstack.common import lockutils
 from cinder.openstack.common import log as logging
@@ -659,6 +660,8 @@ def utf8(value):
     """
     if isinstance(value, unicode):
         return value.encode('utf-8')
+    elif isinstance(value, gettextutils.Message):
+        return unicode(value).encode('utf-8')
     elif isinstance(value, str):
         return value
     else:
index 098c1d3a6753898a34cebc907348128ad469de38..50e71038a1f5e1a431e9f8d4356b1f81f40f44db 100644 (file)
@@ -204,7 +204,8 @@ class Server(object):
                                         backlog=backlog)
         self._server = eventlet.spawn(self._start)
         (self._host, self._port) = self._socket.getsockname()[0:2]
-        LOG.info(_("Started %(name)s on %(_host)s:%(_port)s") % self.__dict__)
+        LOG.info(_("Started %(name)s on %(host)s:%(port)s") %
+                 {'name': self.name, 'host': self.host, 'port': self.port})
 
     @property
     def host(self):