]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Enable localizable REST API responses via the Accept-Language header
authorLuis A. Garcia <luis@linux.vnet.ibm.com>
Thu, 18 Jul 2013 00:49:49 +0000 (00:49 +0000)
committerLuis A. Garcia <luis@linux.vnet.ibm.com>
Sat, 3 Aug 2013 00:19:17 +0000 (00:19 +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 possible to use the resolved language to
translate exception messages to the user requested language and
return that translation from the API.

The patch removes individually imported _() so they don't replace the
one installed service-wide.

Also, it adds the ability to fully re-create a remote error
with the same kwargs with which it was originally created, so that we
can translate it and show it to the user.

Partially implements bp user-locale-api.

Change-Id: I63edc8463836bfff257daa8a2c66ed5d3a444254

17 files changed:
bin/heat-api
bin/heat-api-cfn
bin/heat-api-cloudwatch
bin/heat-cfn
bin/heat-engine
bin/heat-manage
heat/api/middleware/fault.py
heat/api/openstack/v1/__init__.py
heat/api/openstack/v1/events.py
heat/api/openstack/v1/stacks.py
heat/api/openstack/v1/util.py
heat/common/exception.py
heat/common/wsgi.py
heat/openstack/common/exception.py
heat/openstack/common/rpc/common.py
heat/tests/test_api_openstack_v1.py
heat/tests/test_wsgi.py

index 4ac438596c3d68fe172e0e110494fefb5d8d125f..8f58c0944e82c745146721fceff13825aaccc638 100755 (executable)
@@ -33,7 +33,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
 
 from heat.openstack.common import gettextutils
 
-gettextutils.install('heat')
+gettextutils.install('heat', lazy=True)
 
 from oslo.config import cfg
 
index 8eceb6aef4a662ad515777c4e6a6ab443543bd05..95a946ca874d1f422c57a45570679d244fbfda07 100755 (executable)
@@ -35,7 +35,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
 
 from heat.openstack.common import gettextutils
 
-gettextutils.install('heat')
+gettextutils.install('heat', lazy=True)
 
 from oslo.config import cfg
 
index 7453b77447427684a10caa898f29f93f81454a2f..155a4d48fc106a747ac308838e7c45a2561ff439 100755 (executable)
@@ -35,7 +35,7 @@ if os.path.exists(os.path.join(possible_topdir, 'heat', '__init__.py')):
 
 from heat.openstack.common import gettextutils
 
-gettextutils.install('heat')
+gettextutils.install('heat', lazy=True)
 
 from oslo.config import cfg
 
index fc31938dfdc0e9fc6b23ab6e425a8ccb9b109f31..34a5bd0d8b603f973f041f62d9eb67b3223ec6a0 100755 (executable)
@@ -41,7 +41,7 @@ scriptname = os.path.basename(sys.argv[0])
 
 from heat.openstack.common import gettextutils
 
-gettextutils.install('heat')
+gettextutils.install('heat', lazy=True)
 
 if scriptname == 'heat-boto':
     from heat.cfn_client import boto_client as heat_client
index 14e6f185148fa470e58ff28976d5149adf2a8846..36412cbaec9beb4e6f8eb3ccaabf64915b4863f1 100755 (executable)
@@ -36,7 +36,7 @@ if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'heat', '__init__.py')):
 
 from heat.openstack.common import gettextutils
 
-gettextutils.install('heat')
+gettextutils.install('heat', lazy=True)
 
 from oslo.config import cfg
 
index 95e4a5d14c51b6fc2a032cc38163a3e491ee2147..32bdc8f788ab992d0df3814a0548b4952c73c39b 100755 (executable)
@@ -25,6 +25,10 @@ POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
 if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'heat', '__init__.py')):
     sys.path.insert(0, POSSIBLE_TOPDIR)
 
+from heat.openstack.common import gettextutils
+
+gettextutils.install('heat')
+
 from heat.cmd import manage
 
 manage.main()
index 9df7ae60e5a5a93fca052e801f330860b9751516..c8e77093fe0dddef732c267dfcf9a0235de9fe43 100644 (file)
@@ -23,6 +23,7 @@ Cinder's faultwrapper
 import traceback
 import webob
 
+from heat.common import exception
 from heat.openstack.common import log as logging
 import heat.openstack.common.rpc.common as rpc_common
 
@@ -79,11 +80,18 @@ class FaultWrapper(wsgi.Middleware):
         if ex_type.endswith(rpc_common._REMOTE_POSTFIX):
             ex_type = ex_type[:-len(rpc_common._REMOTE_POSTFIX)]
 
-        message = str(ex)
-        if message.find('\n') > -1:
-            message, trace = message.split('\n', 1)
+        if isinstance(ex, exception.OpenstackException):
+            # If the exception is an OpenstackException it is going to have a
+            # translated Message object as the message, let's recreate it here
+            message = (ex.message % ex.kwargs
+                       if hasattr(ex, 'kwargs') else ex.message)
+        else:
+            message = ex.message
+
+        trace = str(ex)
+        if trace.find('\n') > -1:
+            unused, trace = trace.split('\n', 1)
         else:
-            message = str(ex)
             trace = traceback.format_exc()
 
         webob_exc = self.error_map.get(ex_type,
index a1ab038f1c000b87d2091c49fddc6af43f4f7449..f21f89b83fc256c725cf76d5ba156af51285b93d 100644 (file)
 
 import routes
 
-from heat.openstack.common import gettextutils
-
-gettextutils.install('heat')
-
 from heat.api.openstack.v1 import stacks
 from heat.api.openstack.v1 import resources
 from heat.api.openstack.v1 import events
index 81264a50e556c90107e1c4151b8393fbc460086d..b0c0bbb69d0e05950a931ae3d4bda9ccf56927a3 100644 (file)
@@ -21,7 +21,6 @@ from heat.common import wsgi
 from heat.rpc import api as engine_api
 from heat.common import identifier
 from heat.rpc import client as rpc_client
-from heat.openstack.common.gettextutils import _
 
 
 summary_keys = [
index 617f25a3fe6d52ab0d8caca26c8f194bffdd631c..a79db25a9f4e05b39d805086d5cc53c1fd61a46b 100644 (file)
@@ -29,7 +29,6 @@ from heat.rpc import client as rpc_client
 from heat.common import urlfetch
 
 from heat.openstack.common import log as logging
-from heat.openstack.common.gettextutils import _
 
 logger = logging.getLogger(__name__)
 
index 43381bfce2305c01fd621bb959b20da825f6e4d1..b6dcdc5edd5343b1beac7073e911b01385983fb8 100644 (file)
@@ -18,8 +18,6 @@ from functools import wraps
 
 from heat.common import identifier
 
-from heat.openstack.common.gettextutils import _
-
 
 def tenant_local(handler):
     '''
index eb236a5a38e462d28ca9edc5d5449bc3381e1b26..ed4fbc5c264786b0c74b618206b31053d537e080 100644 (file)
@@ -20,7 +20,6 @@
 import functools
 import urlparse
 import sys
-from heat.openstack.common.gettextutils import _
 from heat.openstack.common import exception
 
 
index f108fe68e5b8db6b7e4fa67bf292ecf8975c0cb8..a4da6b0ebb100416f836c8407e4954f2b18062ee 100644 (file)
@@ -2,6 +2,7 @@
 
 # Copyright 2010 United States Government as represented by the
 # Administrator of the National Aeronautics and Space Administration.
+# Copyright 2013 IBM Corp.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -43,8 +44,8 @@ import webob.dec
 import webob.exc
 
 from heat.common import exception
+from heat.openstack.common import gettextutils
 from heat.openstack.common import importutils
-from heat.openstack.common.gettextutils import _
 
 
 URL_LENGTH_LIMIT = 50000
@@ -431,6 +432,12 @@ class Request(webob.Request):
         else:
             return content_type
 
+    def best_match_language(self):
+        """Determine language for returned response."""
+        all_languages = gettextutils.get_available_languages('heat')
+        return self.accept_language.best_match(all_languages,
+                                               default_match='en_US')
+
 
 def is_json_content_type(request):
     if request.method == 'GET':
@@ -589,7 +596,17 @@ class Resource(object):
                                           request, **action_args)
         except TypeError as err:
             logging.error(_('Exception handling resource: %s') % str(err))
-            raise webob.exc.HTTPBadRequest()
+            msg = _('The server could not comply with the request since\r\n'
+                    'it is either malformed or otherwise incorrect.\r\n')
+            err = webob.exc.HTTPBadRequest(msg)
+            raise translate_exception(err, request.best_match_language())
+        except webob.exc.HTTPException as err:
+            logging.error(_("Returning %(code)s to user: %(explanation)s"),
+                          {'code': err.code, 'explanation': err.explanation})
+            raise translate_exception(err, request.best_match_language())
+        except Exception as err:
+            logging.error(_("Unexpected error occurred serving API: %s") % err)
+            raise translate_exception(err, request.best_match_language())
 
         # Here we support either passing in a serializer or detecting it
         # based on the content type.
@@ -653,6 +670,26 @@ class Resource(object):
         return args
 
 
+def translate_exception(exc, locale):
+    """Translates all translatable elements of the given exception."""
+    exc.message = gettextutils.get_localized_message(exc.message, locale)
+    if isinstance(exc, webob.exc.HTTPError):
+        # If the explanation is not a Message, that means that the
+        # explanation is the default, generic and not translatable explanation
+        # from webop.exc. Since the explanation is the error shown when the
+        # exception is converted to a response, let's actually swap it with
+        # message, since message is what gets passed in at construction time
+        # in the API
+        if not isinstance(exc.explanation, gettextutils.Message):
+            exc.explanation = exc.message
+            exc.detail = ''
+        else:
+            exc.explanation = \
+                gettextutils.get_localized_message(exc.explanation, locale)
+            exc.detail = gettextutils.get_localized_message(exc.detail, locale)
+    return exc
+
+
 class BasePasteFactory(object):
 
     """A base class for paste app and filter factories.
index ca1195a3e1278fa6b43b83425b5e4c900e2da12d..9ba9fc14489e6af1d5bd109d43fbf8a52d4f368a 100644 (file)
@@ -21,8 +21,6 @@ Exceptions common to OpenStack projects
 
 import logging
 
-from heat.openstack.common.gettextutils import _
-
 _FATAL_EXCEPTION_FORMAT_ERRORS = False
 
 
@@ -120,6 +118,7 @@ class OpenstackException(Exception):
 
     def __init__(self, **kwargs):
         try:
+            self.kwargs = kwargs
             self._error_string = self.message % kwargs
 
         except Exception as e:
index 31ecbdf3d2edd83eaf88dfcbd71e7be436c40536..3651611247cb1d37fe81d791a804d4f0ea3c092b 100644 (file)
@@ -24,7 +24,6 @@ import traceback
 from oslo.config import cfg
 import six
 
-from heat.openstack.common.gettextutils import _
 from heat.openstack.common import importutils
 from heat.openstack.common import jsonutils
 from heat.openstack.common import local
index db0275a28bc653f66ca14fcda717d28736867a83..12be4f89c7a66fce027af379cd64de8966783c90 100644 (file)
@@ -47,11 +47,15 @@ def request_with_middleware(middleware, func, req, *args, **kwargs):
     return resp
 
 
-def remote_error(ex_type, message=''):
-    """convert rpc original exception to the one with _Remote suffix."""
-
-    # NOTE(jianingy): this function helps simulate the real world exceptions
+def to_remote_error(error):
+    """Converts the given exception to the one with the _Remote suffix.
 
+    This is how RPC exceptions are recreated on the caller's side, so
+    this helps better simulate how the exception mechanism actually works.
+    """
+    ex_type = type(error)
+    kwargs = error.kwargs if hasattr(error, 'kwargs') else {}
+    message = error.message
     module = ex_type().__class__.__module__
     str_override = lambda self: "%s\n<Traceback>" % message
     new_ex_type = type(ex_type.__name__ + rpc_common._REMOTE_POSTFIX,
@@ -59,7 +63,7 @@ def remote_error(ex_type, message=''):
                        {'__str__': str_override, '__unicode__': str_override})
 
     new_ex_type.__module__ = '%s%s' % (module, rpc_common._REMOTE_POSTFIX)
-    return new_ex_type()
+    return new_ex_type(**kwargs)
 
 
 class InstantiationDataTest(HeatTestCase):
@@ -398,7 +402,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                   'method': 'list_stacks',
                   'args': {},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(AttributeError))
+                 None).AndRaise(to_remote_error(AttributeError()))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -418,7 +422,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                   'method': 'list_stacks',
                   'args': {},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(Exception))
+                 None).AndRaise(to_remote_error(Exception()))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -513,6 +517,8 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._post('/stacks', json.dumps(body))
 
+        unknown_parameter = heat_exc.UnknownUserParameter(key='a')
+        missing_parameter = heat_exc.UserParameterMissing(key='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -523,7 +529,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(AttributeError))
+                 None).AndRaise(to_remote_error(AttributeError()))
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'create_stack',
@@ -533,7 +539,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.UnknownUserParameter))
+                 None).AndRaise(to_remote_error(unknown_parameter))
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'create_stack',
@@ -543,7 +549,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.UserParameterMissing))
+                 None).AndRaise(to_remote_error(missing_parameter))
         self.m.ReplayAll()
         resp = request_with_middleware(fault.FaultWrapper,
                                        self.controller.create,
@@ -579,6 +585,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._post('/stacks', json.dumps(body))
 
+        error = heat_exc.StackExists(stack_name='s')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -589,7 +596,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackExists))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -612,6 +619,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._post('/stacks', json.dumps(body))
 
+        error = heat_exc.StackValidationFailed(message='')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -622,7 +630,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackValidationFailed))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -678,13 +686,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get('/stacks/%(stack_name)s' % locals())
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'identify_stack',
                   'args': {'stack_name': stack_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -727,13 +736,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get('/stacks/%(stack_name)s/resources' % locals())
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'identify_stack',
                   'args': {'stack_name': stack_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -820,13 +830,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'show_stack',
                   'args': {'stack_identity': dict(identity)},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -844,13 +855,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
 
+        error = heat_exc.InvalidTenant(target='a', actual='b')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'show_stack',
                   'args': {'stack_identity': dict(identity)},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.InvalidTenant))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -887,15 +899,15 @@ class StackControllerTest(ControllerTest, HeatTestCase):
     def test_get_template_err_notfound(self):
         identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
         req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity)
-        template = {u'Foo': u'bar'}
 
+        error = error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'get_template',
                   'args': {'stack_identity': dict(identity)},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
 
         self.m.ReplayAll()
 
@@ -958,6 +970,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
         req = self._put('/stacks/%(stack_name)s/%(stack_id)s' % identity,
                         json.dumps(body))
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -968,7 +981,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'files': {},
                            'args': {'timeout_mins': 30}},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1021,6 +1034,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
         req = self._delete('/stacks/%(stack_name)s/%(stack_id)s' % identity)
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         # Engine returns None when delete successful
         rpc.call(req.context, self.topic,
@@ -1028,7 +1042,7 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                   'method': 'delete_stack',
                   'args': {'stack_identity': dict(identity)},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1122,13 +1136,14 @@ class StackControllerTest(ControllerTest, HeatTestCase):
                            'AWS::EC2::EIP',
                            'AWS::EC2::EIPAssociation']
 
+        error = heat_exc.ServerError(body='')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'list_resource_types',
                   'args': {},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.ServerError))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1158,13 +1173,15 @@ class StackControllerTest(ControllerTest, HeatTestCase):
 
     def test_generate_template_not_found(self):
         req = self._get('/resource_types/NOT_FOUND/template')
+
+        error = heat_exc.ResourceTypeNotFound(type_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'generate_template',
                   'args': {'type_name': 'NOT_FOUND'},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.ResourceTypeNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
         resp = request_with_middleware(fault.FaultWrapper,
                                        self.controller.generate_template,
@@ -1267,13 +1284,14 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(stack_identity._tenant_path() + '/resources')
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'list_stack_resources',
                   'args': {'stack_identity': stack_identity},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1354,6 +1372,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(res_identity._tenant_path())
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -1361,7 +1380,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
                   'args': {'stack_identity': stack_identity,
                            'resource_name': res_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1384,6 +1403,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(res_identity._tenant_path())
 
+        error = heat_exc.ResourceNotFound(stack_name='a', resource_name='b')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -1391,7 +1411,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
                   'args': {'stack_identity': stack_identity,
                            'resource_name': res_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.ResourceNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1414,6 +1434,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(res_identity._tenant_path())
 
+        error = heat_exc.ResourceNotAvailable(resource_name='')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -1421,7 +1442,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
                   'args': {'stack_identity': stack_identity,
                            'resource_name': res_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.ResourceNotAvailable))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1488,6 +1509,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(res_identity._tenant_path() + '/metadata')
 
+        error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -1495,7 +1517,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
                   'args': {'stack_identity': stack_identity,
                            'resource_name': res_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1518,6 +1540,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(res_identity._tenant_path() + '/metadata')
 
+        error = heat_exc.ResourceNotFound(stack_name='a', resource_name='b')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
@@ -1525,7 +1548,7 @@ class ResourceControllerTest(ControllerTest, HeatTestCase):
                   'args': {'stack_identity': stack_identity,
                            'resource_name': res_name},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.ResourceNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1699,13 +1722,14 @@ class EventControllerTest(ControllerTest, HeatTestCase):
 
         req = self._get(stack_identity._tenant_path() + '/events')
 
+        error = error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'list_events',
                   'args': {'stack_identity': stack_identity},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -1943,13 +1967,14 @@ class EventControllerTest(ControllerTest, HeatTestCase):
         req = self._get(stack_identity._tenant_path() +
                         '/resources/' + res_name + '/events/' + event_id)
 
+        error = error = heat_exc.StackNotFound(stack_name='a')
         self.m.StubOutWithMock(rpc, 'call')
         rpc.call(req.context, self.topic,
                  {'namespace': None,
                   'method': 'list_events',
                   'args': {'stack_identity': stack_identity},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(heat_exc.StackNotFound))
+                 None).AndRaise(to_remote_error(error))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
@@ -2380,7 +2405,7 @@ class ActionControllerTest(ControllerTest, HeatTestCase):
                   'method': 'stack_suspend',
                   'args': {'stack_identity': stack_identity},
                   'version': self.api_version},
-                 None).AndRaise(remote_error(AttributeError))
+                 None).AndRaise(to_remote_error(AttributeError()))
         self.m.ReplayAll()
 
         resp = request_with_middleware(fault.FaultWrapper,
index e6e1130278fb83fc2fe04e0450cb18603c7d1b67..ab26f2547fb8beb546233166b7a0a84f7e16d598 100644 (file)
@@ -17,6 +17,7 @@
 
 
 import datetime
+import stubout
 import webob
 
 from heat.common import exception
@@ -26,6 +27,10 @@ from heat.tests.common import HeatTestCase
 
 class RequestTest(HeatTestCase):
 
+    def setUp(self):
+        self.stubs = stubout.StubOutForTesting()
+        super(RequestTest, self).setUp()
+
     def test_content_type_missing(self):
         request = wsgi.Request.blank('/tests/123')
         self.assertRaises(exception.InvalidContentType,
@@ -74,9 +79,28 @@ class RequestTest(HeatTestCase):
         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 webop 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 ResourceTest(HeatTestCase):
 
+    def setUp(self):
+        self.stubs = stubout.StubOutForTesting()
+        super(ResourceTest, self).setUp()
+
     def test_get_action_args(self):
         env = {
             'wsgiorg.routing_args': [
@@ -160,6 +184,34 @@ class ResourceTest(HeatTestCase):
                                  None)
         self.assertRaises(webob.exc.HTTPBadRequest, resource, request)
 
+    def test_resource_call_error_handle_localized(self):
+        class Controller(object):
+            def delete(self, req, identity):
+                return (req, identity)
+
+        actions = {'action': 'delete', 'id': 12, 'body': 'data'}
+        env = {'wsgiorg.routing_args': [None, actions]}
+        request = wsgi.Request.blank('/tests/123', environ=env)
+        request.body = '{"foo" : "value"}'
+        message_es = "No Encontrado"
+        translated_ex = webob.exc.HTTPBadRequest(message_es)
+
+        resource = wsgi.Resource(Controller(),
+                                 wsgi.JSONRequestDeserializer(),
+                                 None)
+
+        def fake_translate_exception(ex, locale):
+            return translated_ex
+
+        self.stubs.SmartSet(wsgi,
+                            'translate_exception', fake_translate_exception)
+
+        try:
+            resource(request)
+        except webob.exc.HTTPBadRequest as e:
+            self.assertEquals(message_es, e.message)
+        self.m.VerifyAll()
+
 
 class JSONResponseSerializerTest(HeatTestCase):