diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 13f6af44525d..406ddf7489c2 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 IBM Corp. # Copyright 2011 OpenStack Foundation # All Rights Reserved. # @@ -25,6 +26,7 @@ import webob from nova.api.openstack import xmlutil from nova import exception +from nova.openstack.common import gettextutils from nova.openstack.common.gettextutils import _ from nova.openstack.common import jsonutils from nova.openstack.common import log as logging @@ -176,6 +178,12 @@ class Request(webob.Request): return content_type + def best_match_language(self): + """Determine language for returned response.""" + return self.accept_language.best_match( + gettextutils.get_available_languages('nova'), + default_match='en_US') + class ActionDispatcher(object): """Maps method name to local methods through action name.""" @@ -925,7 +933,7 @@ class Resource(wsgi.Application): "%(body)s") % {'action': action, 'body': unicode(body, 'utf-8')} LOG.debug(msg) - LOG.debug(_("Calling method %s") % meth) + LOG.debug(_("Calling method %s") % str(meth)) # Now, deserialize the request body... try: @@ -1180,6 +1188,8 @@ class Fault(webob.exc.HTTPException): @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): """Generate a WSGI response based on the exception passed to ctor.""" + + user_locale = req.best_match_language() # Replace the body with fault details. code = self.wrapped_exc.status_int fault_name = self._fault_names.get(code, "computeFault") @@ -1187,6 +1197,8 @@ class Fault(webob.exc.HTTPException): LOG.debug(_("Returning %(code)s to user: %(explanation)s"), {'code': code, 'explanation': explanation}) + explanation = gettextutils.get_localized_message(explanation, + user_locale) fault_data = { fault_name: { 'code': code, @@ -1250,9 +1262,19 @@ class RateLimitFault(webob.exc.HTTPException): Return the wrapped exception with a serialized body conforming to our error format. """ + user_locale = request.best_match_language() content_type = request.best_match_content_type() metadata = {"attributes": {"overLimit": ["code", "retryAfter"]}} + self.content['overLimit']['message'] = \ + gettextutils.get_localized_message( + self.content['overLimit']['message'], + user_locale) + self.content['overLimit']['details'] = \ + gettextutils.get_localized_message( + self.content['overLimit']['details'], + user_locale) + xml_serializer = XMLDictSerializer(metadata, XMLNS_V11) serializer = { 'application/xml': xml_serializer, diff --git a/nova/cmd/__init__.py b/nova/cmd/__init__.py index 87edf9b4b957..a0d4dc38d908 100644 --- a/nova/cmd/__init__.py +++ b/nova/cmd/__init__.py @@ -32,3 +32,6 @@ os.environ['EVENTLET_NO_GREENDNS'] = 'yes' import eventlet eventlet.monkey_patch(os=False) + +from nova.openstack.common import gettextutils +gettextutils.enable_lazy() diff --git a/nova/compute/manager.py b/nova/compute/manager.py index d1be4b45f6ae..126e50796fee 100755 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -911,7 +911,7 @@ class ComputeManager(manager.SchedulerDependentManager): info = extra_usage_info.copy() if not msg: msg = "" - info['message'] = msg + info['message'] = unicode(msg) self._notify_about_instance_usage(context, instance, type_, extra_usage_info=info, **kwargs) diff --git a/nova/test.py b/nova/test.py index f7c68e7b9f31..2f98d6374ff8 100644 --- a/nova/test.py +++ b/nova/test.py @@ -27,6 +27,7 @@ import eventlet eventlet.monkey_patch(os=False) import copy +import gettext import os import shutil import sys @@ -191,6 +192,17 @@ class MoxStubout(fixtures.Fixture): self.addCleanup(self.mox.VerifyAll) +class TranslationFixture(fixtures.Fixture): + """Use gettext NullTranslation objects in tests.""" + + def setUp(self): + super(TranslationFixture, self).setUp() + nulltrans = gettext.NullTranslations() + gettext_fixture = fixtures.MonkeyPatch('gettext.translation', + lambda *x, **y: nulltrans) + self.gettext_patcher = self.useFixture(gettext_fixture) + + class TestingException(Exception): pass @@ -216,6 +228,7 @@ class TestCase(testtools.TestCase): self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) self.useFixture(fixtures.NestedTempfile()) self.useFixture(fixtures.TempHomeDir()) + self.useFixture(TranslationFixture()) if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or os.environ.get('OS_STDOUT_CAPTURE') == '1'): diff --git a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py index abd25401f9a9..a781c6bf2b5f 100644 --- a/nova/tests/api/openstack/compute/contrib/test_floating_ips.py +++ b/nova/tests/api/openstack/compute/contrib/test_floating_ips.py @@ -17,7 +17,6 @@ import uuid from lxml import etree -import testtools import webob from nova.api.openstack.compute.contrib import floating_ips @@ -306,9 +305,10 @@ class FloatingIpTest(test.TestCase): self.stubs.Set(network.api.API, "allocate_floating_ip", fake_allocate) req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ips') - with testtools.ExpectedException(webob.exc.HTTPNotFound, - 'No more floating ips'): - self.controller.create(req) + ex = self.assertRaises(webob.exc.HTTPNotFound, + self.controller.create, req) + + self.assertIn('No more floating ips', ex.explanation) def test_floating_ip_allocate_no_free_ips_pool(self): def fake_allocate(*args, **kwargs): @@ -317,10 +317,11 @@ class FloatingIpTest(test.TestCase): self.stubs.Set(network.api.API, "allocate_floating_ip", fake_allocate) req = fakes.HTTPRequest.blank('/v2/fake/os-floating-ips') - with testtools.ExpectedException( - webob.exc.HTTPNotFound, - 'No more floating ips in pool non_existant_pool'): - self.controller.create(req, {'pool': 'non_existant_pool'}) + ex = self.assertRaises(webob.exc.HTTPNotFound, + self.controller.create, req, {'pool': 'non_existant_pool'}) + + self.assertIn('No more floating ips in pool non_existant_pool', + ex.explanation) def test_floating_ip_allocate(self): def fake1(*args, **kwargs): diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d12d6bfc6cd6..3d789848a3e3 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -699,3 +699,8 @@ def stub_snapshot_get_all(self, context): def stub_bdm_get_all_by_instance(context, instance_uuid): return [{'source_type': 'volume', 'volume_id': 'volume_id1'}, {'source_type': 'volume', 'volume_id': 'volume_id2'}] + + +def fake_get_available_languages(domain): + existing_translations = ['en_GB', 'en_AU', 'de', 'zh_CN', 'en_US'] + return existing_translations diff --git a/nova/tests/api/openstack/test_faults.py b/nova/tests/api/openstack/test_faults.py index 879afde387d7..89ff61a63176 100644 --- a/nova/tests/api/openstack/test_faults.py +++ b/nova/tests/api/openstack/test_faults.py @@ -1,5 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 +# Copyright 2013 IBM Corp. # Copyright 2010 OpenStack Foundation # All Rights Reserved. # @@ -23,6 +24,7 @@ import webob.exc from nova.api.openstack import common from nova.api.openstack import wsgi +from nova.openstack.common import gettextutils from nova.openstack.common import jsonutils from nova import test @@ -138,6 +140,22 @@ class TestFaults(test.TestCase): self.assertTrue('resizeNotAllowed' not in resp.body) self.assertTrue('forbidden' in resp.body) + def test_raise_localize_explanation(self): + msgid = "String with params: %s" + params = ('blah', ) + lazy_gettext = gettextutils._ + expl = lazy_gettext(msgid) % params + + @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((msgid % params) in resp.body) + def test_fault_has_status_int(self): # Ensure the status_int is set correctly on faults. fault = wsgi.Fault(webob.exc.HTTPBadRequest(explanation='what?')) diff --git a/nova/tests/api/openstack/test_wsgi.py b/nova/tests/api/openstack/test_wsgi.py index 41a80a65c414..08812e44ee39 100644 --- a/nova/tests/api/openstack/test_wsgi.py +++ b/nova/tests/api/openstack/test_wsgi.py @@ -5,6 +5,7 @@ import webob from nova.api.openstack import wsgi from nova import exception +from nova.openstack.common import gettextutils from nova import test from nova.tests.api.openstack import fakes from nova.tests import utils @@ -97,6 +98,53 @@ class RequestTest(test.TestCase): 'uuid1': instances[1], 'uuid2': instances[2]}) + def test_from_request(self): + self.stubs.Set(gettextutils, 'get_available_languages', + fakes.fake_get_available_languages) + + request = wsgi.Request.blank('/') + accepted = 'bogus;q=1.1, en-gb;q=0.7,en-us,en;q=.5,*;q=.7' + request.headers = {'Accept-Language': accepted} + self.assertEqual(request.best_match_language(), 'en_US') + + def test_asterisk(self): + # asterisk should match first available if there + # are not any other available matches + self.stubs.Set(gettextutils, 'get_available_languages', + fakes.fake_get_available_languages) + + request = wsgi.Request.blank('/') + accepted = '*,es;q=.5' + request.headers = {'Accept-Language': accepted} + self.assertEqual(request.best_match_language(), 'en_GB') + + def test_prefix(self): + self.stubs.Set(gettextutils, 'get_available_languages', + fakes.fake_get_available_languages) + + request = wsgi.Request.blank('/') + accepted = 'zh' + request.headers = {'Accept-Language': accepted} + self.assertEqual(request.best_match_language(), 'zh_CN') + + def test_secondary(self): + self.stubs.Set(gettextutils, 'get_available_languages', + fakes.fake_get_available_languages) + + request = wsgi.Request.blank('/') + accepted = 'nn,en-gb;q=.5' + request.headers = {'Accept-Language': accepted} + self.assertEqual(request.best_match_language(), 'en_GB') + + def test_none_found(self): + self.stubs.Set(gettextutils, 'get_available_languages', + fakes.fake_get_available_languages) + + request = wsgi.Request.blank('/') + accepted = 'nb-no' + request.headers = {'Accept-Language': accepted} + self.assertEqual(request.best_match_language(), 'en_US') + class ActionDispatcherTest(test.TestCase): def test_dispatch(self): diff --git a/nova/tests/api/test_auth.py b/nova/tests/api/test_auth.py index 6091e19c1cb4..f73bc0c617b8 100644 --- a/nova/tests/api/test_auth.py +++ b/nova/tests/api/test_auth.py @@ -78,9 +78,9 @@ class TestKeystoneMiddlewareRoles(test.TestCase): context = req.environ['nova.context'] if "knight" in context.roles and "bad" not in context.roles: - return webob.Response(status=_("200 Role Match")) + return webob.Response(status="200 Role Match") elif context.roles == ['']: - return webob.Response(status=_("200 No Roles")) + return webob.Response(status="200 No Roles") else: raise webob.exc.HTTPBadRequest(_("unexpected role header")) diff --git a/nova/utils.py b/nova/utils.py index 2bfa9495f73a..79aa19ae362e 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -43,6 +43,7 @@ from oslo.config import cfg from nova import exception from nova.openstack.common import excutils +from nova.openstack.common import gettextutils from nova.openstack.common.gettextutils import _ from nova.openstack.common import importutils from nova.openstack.common import lockutils @@ -476,6 +477,8 @@ def utf8(value): """ if isinstance(value, unicode): return value.encode('utf-8') + elif isinstance(value, gettextutils.Message): + return unicode(value).encode('utf-8') assert isinstance(value, str) return value diff --git a/nova/virt/driver.py b/nova/virt/driver.py index c69224c4c102..3ba3b802d77b 100755 --- a/nova/virt/driver.py +++ b/nova/virt/driver.py @@ -1004,7 +1004,7 @@ class ComputeDriver(object): """ if not self._compute_event_callback: - LOG.debug(_("Discarding event %s") % event) + LOG.debug(_("Discarding event %s") % str(event)) return if not isinstance(event, virtevent.Event): @@ -1012,7 +1012,7 @@ class ComputeDriver(object): _("Event must be an instance of nova.virt.event.Event")) try: - LOG.debug(_("Emitting event %s") % event) + LOG.debug(_("Emitting event %s") % str(event)) self._compute_event_callback(event) except Exception as ex: LOG.error(_("Exception dispatching event %(event)s: %(ex)s"), diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 78643cee674b..255d5064831c 100755 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -583,7 +583,8 @@ class LibvirtDriver(driver.ComputeDriver): self._wrapped_conn = wrapped_conn try: - LOG.debug(_("Registering for lifecycle events %s") % self) + LOG.debug(_("Registering for lifecycle events %s") % + str(self)) wrapped_conn.domainEventRegisterAny( None, libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE, @@ -595,8 +596,8 @@ class LibvirtDriver(driver.ComputeDriver): if self.has_min_version(MIN_LIBVIRT_CLOSE_CALLBACK_VERSION): try: - LOG.debug(_("Registering for connection events: %s") - % self) + LOG.debug(_("Registering for connection events: %s") % + str(self)) wrapped_conn.registerCloseCallback( self._close_callback, None) except libvirt.libvirtError: