Add support for API message localization

Add support for doing language resolution for a request,
based on the Accept-Language HTTP header. For example,
an HTTP client can receive API messages in Chinese even
if the locale language of the server is English.

The underscore (_) method is initialized so that it returns
openstack.common.gettextutils.Message objects. The locale
of these Message objects is set when exceptions with which
they are associated are raised in the context of an HTTP
request.

docImpact
Partially implements bp i18n-messages

Signed-off-by: Fei Long Wang <flwang@cn.ibm.com>
Signed-off-by: John Warren <jswarren@us.ibm.com>

Change-Id: I352cda57fe119022c59c6c813b5c8053765b2d3c
This commit is contained in:
Fei Long Wang 2014-01-24 11:06:29 +08:00
parent bfddf66ec4
commit f70b72b78b
21 changed files with 260 additions and 110 deletions

View File

@ -1066,7 +1066,7 @@ class ImageDeserializer(wsgi.JSONRequestDeserializer):
except exception.InvalidParameterValue as e:
msg = unicode(e)
LOG.warn(msg, exc_info=True)
raise HTTPBadRequest(explanation=msg, request=request)
raise HTTPBadRequest(explanation=e.msg, request=request)
image_meta = result['image_meta']
image_meta = validate_image_meta(request, image_meta)

View File

@ -15,6 +15,7 @@
# under the License.
from oslo.config import cfg
import six
import webob.exc
from glance.api import policy
@ -94,13 +95,11 @@ class Controller(controller.BaseController):
registry.delete_member(req.context, image_id, id)
self._update_store_acls(req, image_id)
except exception.NotFound as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@ -152,17 +151,14 @@ class Controller(controller.BaseController):
registry.add_member(req.context, image_id, id, can_share)
self._update_store_acls(req, image_id)
except exception.Invalid as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPBadRequest(explanation=msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.NotFound as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@ -190,17 +186,14 @@ class Controller(controller.BaseController):
registry.replace_members(req.context, image_id, body)
self._update_store_acls(req, image_id)
except exception.Invalid as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPBadRequest(explanation=msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.NotFound as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
return webob.exc.HTTPNoContent()
@ -220,13 +213,11 @@ class Controller(controller.BaseController):
try:
members = registry.get_member_images(req.context, id)
except exception.NotFound as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPNotFound(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
msg = "%s" % e
LOG.debug(msg)
raise webob.exc.HTTPForbidden(msg)
LOG.debug(six.text_type(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
return dict(shared_images=members)
def _update_store_acls(self, req, image_id):

View File

@ -92,7 +92,7 @@ class ImageDataController(object):
except exception.InvalidImageStatusTransition as e:
msg = unicode(e)
LOG.debug(msg)
raise webob.exc.HTTPConflict(explanation=msg, request=req)
raise webob.exc.HTTPConflict(explanation=e.msg, request=req)
except exception.Forbidden as e:
msg = (_("Not allowed to upload image data for image %s") %
@ -101,7 +101,7 @@ class ImageDataController(object):
raise webob.exc.HTTPForbidden(explanation=msg, request=req)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.StorageFull as e:
msg = _("Image storage media is full: %s") % e
@ -149,11 +149,11 @@ class ImageDataController(object):
if not image.locations:
raise exception.ImageDataNotFound()
except exception.ImageDataNotFound as e:
raise webob.exc.HTTPNoContent(explanation=unicode(e))
raise webob.exc.HTTPNoContent(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
return image
@ -162,8 +162,8 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
def upload(self, request):
try:
request.get_content_type(('application/octet-stream',))
except exception.InvalidContentType:
raise webob.exc.HTTPUnsupportedMediaType()
except exception.InvalidContentType as e:
raise webob.exc.HTTPUnsupportedMediaType(explanation=e.msg)
image_size = request.content_length or None
return {'size': image_size, 'data': request.body_file}
@ -178,7 +178,7 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
# an iterator very strange
response.app_iter = iter(image.get_data())
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
#NOTE(saschpe): "response.app_iter = ..." currently resets Content-MD5
# (https://github.com/Pylons/webob/issues/86), so it should be set
# afterwards for the time being.

View File

@ -67,13 +67,13 @@ class ImageMembersController(object):
return new_member
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.Duplicate as e:
raise webob.exc.HTTPConflict(explanation=unicode(e))
raise webob.exc.HTTPConflict(explanation=e.msg)
except exception.ImageMemberLimitExceeded as e:
raise webob.exc.HTTPRequestEntityTooLarge(explanation=unicode(e))
raise webob.exc.HTTPRequestEntityTooLarge(explanation=e.msg)
@utils.mutating
def update(self, req, image_id, member_id, status):
@ -100,9 +100,9 @@ class ImageMembersController(object):
member_repo.save(member)
return member
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except ValueError as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
@ -132,9 +132,9 @@ class ImageMembersController(object):
members.append(member)
return dict(members=members)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
def show(self, req, image_id, member_id):
"""
@ -157,7 +157,7 @@ class ImageMembersController(object):
member = member_repo.get(member_id)
return member
except (exception.NotFound, exception.Forbidden) as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
@utils.mutating
def delete(self, req, image_id, member_id):
@ -173,9 +173,9 @@ class ImageMembersController(object):
member_repo.remove(member)
return webob.Response(body='', status=204)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
class RequestDeserializer(wsgi.JSONRequestDeserializer):

View File

@ -43,11 +43,11 @@ class Controller(object):
image.tags.add(tag_value)
image_repo.save(image)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.ImageTagLimitExceeded as e:
raise webob.exc.HTTPRequestEntityTooLarge(explanation=unicode(e))
raise webob.exc.HTTPRequestEntityTooLarge(explanation=e.msg)
@utils.mutating
def delete(self, req, image_id, tag_value):
@ -59,9 +59,9 @@ class Controller(object):
image.tags.remove(tag_value)
image_repo.save(image)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
class ResponseSerializer(wsgi.JSONResponseSerializer):

View File

@ -60,15 +60,15 @@ class ImagesController(object):
tags=tags, **image)
image_repo.add(image)
except exception.DuplicateLocation as dup:
raise webob.exc.HTTPBadRequest(explanation=unicode(dup))
raise webob.exc.HTTPBadRequest(explanation=dup.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.InvalidParameterValue as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.LimitExceeded as e:
LOG.info(unicode(e))
raise webob.exc.HTTPRequestEntityTooLarge(
explanation=unicode(e), request=req, content_type='text/plain')
explanation=e.msg, request=req, content_type='text/plain')
return image
@ -93,9 +93,9 @@ class ImagesController(object):
result['next_marker'] = images[-1].image_id
except (exception.NotFound, exception.InvalidSortKey,
exception.InvalidFilterRangeValue) as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
result['images'] = images
return result
@ -104,9 +104,9 @@ class ImagesController(object):
try:
return image_repo.get(image_id)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
@utils.mutating
def update(self, req, image_id, changes):
@ -123,11 +123,11 @@ class ImagesController(object):
if changes:
image_repo.save(image)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.InvalidParameterValue as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.StorageQuotaFull as e:
msg = (_("Denying attempt to upload image because it exceeds the ."
"quota: %s") % e)
@ -137,7 +137,7 @@ class ImagesController(object):
except exception.LimitExceeded as e:
LOG.info(unicode(e))
raise webob.exc.HTTPRequestEntityTooLarge(
explanation=unicode(e), request=req, content_type='text/plain')
explanation=e.msg, request=req, content_type='text/plain')
return image
@ -192,7 +192,7 @@ class ImagesController(object):
image.delete()
image_repo.remove(image)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
msg = (_("Failed to find image %(image_id)s to delete") %
{'image_id': image_id})
@ -228,7 +228,7 @@ class ImagesController(object):
if image.status == 'queued':
image.status = 'active'
except (exception.BadStoreUri, exception.DuplicateLocation) as bse:
raise webob.exc.HTTPBadRequest(explanation=unicode(bse))
raise webob.exc.HTTPBadRequest(explanation=bse.msg)
except ValueError as ve: # update image status failed.
raise webob.exc.HTTPBadRequest(explanation=unicode(ve))
@ -243,7 +243,7 @@ class ImagesController(object):
if image.status == 'queued':
image.status = 'active'
except (exception.BadStoreUri, exception.DuplicateLocation) as bse:
raise webob.exc.HTTPBadRequest(explanation=unicode(bse))
raise webob.exc.HTTPBadRequest(explanation=bse.msg)
except ValueError as ve: # update image status failed.
raise webob.exc.HTTPBadRequest(explanation=unicode(ve))
@ -301,7 +301,7 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
try:
self.schema.validate(body)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
image = {}
properties = body
tags = properties.pop('tags', None)
@ -418,7 +418,7 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
try:
self.schema.validate(partial_image)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
def _validate_path(self, op, path):
path_root = path[0]
@ -595,7 +595,7 @@ class ResponseSerializer(wsgi.JSONResponseSerializer):
image_view['schema'] = '/v2/schemas/image'
image_view = self.schema.filter(image_view) # domain
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
return image_view
def create(self, response, image):

View File

@ -66,7 +66,7 @@ class TasksController(object):
msg = (_("Forbidden to create task. Reason: %(reason)s")
% {'reason': unicode(e)})
LOG.info(msg)
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
result = {'task': new_task, 'task_details': new_task_details}
return result
@ -94,10 +94,10 @@ class TasksController(object):
except (exception.NotFound, exception.InvalidSortKey,
exception.InvalidFilterRangeValue) as e:
LOG.info(unicode(e))
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
except exception.Forbidden as e:
LOG.info(unicode(e))
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
result['tasks'] = tasks
return result
@ -109,12 +109,12 @@ class TasksController(object):
msg = (_("Failed to find task %(task_id)s. Reason: %(reason)s") %
{'task_id': task_id, 'reason': unicode(e)})
LOG.info(msg)
raise webob.exc.HTTPNotFound(explanation=unicode(e))
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Forbidden as e:
msg = (_("Forbidden to get task %(task_id)s. Reason: %(reason)s") %
{'task_id': task_id, 'reason': unicode(e)})
LOG.info(msg)
raise webob.exc.HTTPForbidden(explanation=unicode(e))
raise webob.exc.HTTPForbidden(explanation=e.msg)
result = {'task': task, 'task_details': task_details}
return result
@ -197,7 +197,7 @@ class RequestDeserializer(wsgi.JSONRequestDeserializer):
try:
self.schema.validate(body)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=unicode(e))
raise webob.exc.HTTPBadRequest(explanation=e.msg)
task = {}
properties = body
for key in self._required_properties:

View File

@ -14,4 +14,4 @@
# under the License.
from glance.openstack.common import gettextutils
gettextutils.install('glance')
gettextutils.install('glance', lazy=True)

View File

@ -25,6 +25,8 @@ import eventlet
import os
import sys
import six
# Monkey patch socket, time, select, threads
eventlet.patcher.monkey_patch(all=False, socket=True, time=True,
select=True, thread=True)
@ -45,7 +47,7 @@ import glance.store
def fail(returncode, e):
sys.stderr.write("ERROR: %s\n" % e)
sys.stderr.write("ERROR: %s\n" % six.text_type(e))
sys.exit(returncode)

View File

@ -16,6 +16,7 @@
"""Glance exception subclasses"""
import six
import six.moves.urllib.parse as urlparse
_FATAL_EXCEPTION_FORMAT_ERRORS = False
@ -40,16 +41,23 @@ class GlanceException(Exception):
if not message:
message = self.message
try:
message = message % kwargs
if kwargs:
message = message % kwargs
except Exception:
if _FATAL_EXCEPTION_FORMAT_ERRORS:
raise
else:
# at least get the core message out if something happened
pass
self.msg = message
super(GlanceException, self).__init__(message)
def __unicode__(self):
# NOTE(flwang): By default, self.msg is an instance of Message, which
# can't be converted by str(). Based on the definition of
# __unicode__, it should return unicode always.
return six.text_type(self.msg)
class MissingCredentialError(GlanceException):
message = _("Missing required credential: %(required)s")

View File

@ -178,7 +178,7 @@ class Controller(object):
if self.raise_exc:
raise
cls, val = e.__class__, str(e)
cls, val = e.__class__, six.text_type(e)
msg = (_("RPC Call Error: %(val)s\n%(tb)s") %
dict(val=val, tb=traceback.format_exc()))
LOG.error(msg)
@ -188,7 +188,7 @@ class Controller(object):
module = cls.__module__
if module not in CONF.allowed_rpc_exception_modules:
cls = exception.RPCError
val = str(exception.RPCError(cls=cls, val=val))
val = six.text_type(exception.RPCError(cls=cls, val=val))
cls_path = "%s.%s" % (cls.__module__, cls.__name__)
result = {"_error": {"cls": cls_path, "val": val}}

View File

@ -1,6 +1,7 @@
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# Copyright 2010 OpenStack Foundation
# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -41,6 +42,7 @@ import webob.exc
from glance.common import exception
from glance.common import utils
from glance.openstack.common import gettextutils
from glance.openstack.common import jsonutils
import glance.openstack.common.log as logging
@ -512,6 +514,17 @@ class Request(webob.Request):
else:
return content_type
def best_match_language(self):
"""Determines best available locale from the Accept-Language header.
:returns: the best language match or None if the 'Accept-Language'
header was not available in the request.
"""
if not self.accept_language:
return None
langs = gettextutils.get_available_languages('glance')
return self.accept_language.best_match(langs)
class JSONRequestDeserializer(object):
def has_body(self, request):
@ -563,6 +576,23 @@ class JSONResponseSerializer(object):
response.body = self.to_json(result)
def translate_exception(req, e):
"""Translates all translatable elements of the given exception."""
# The RequestClass attribute in the webob.dec.wsgify decorator
# does not guarantee that the request object will be a particular
# type; this check is therefore necessary.
if not hasattr(req, "best_match_language"):
return e
locale = req.best_match_language()
if isinstance(e, webob.exc.HTTPError):
e.explanation = gettextutils.translate(e.explanation, locale)
e.detail = gettextutils.translate(e.detail, locale)
return e
class Resource(object):
"""
WSGI app that handles (de)serialization and controller dispatch.
@ -599,17 +629,22 @@ class Resource(object):
action_args = self.get_action_args(request.environ)
action = action_args.pop('action', None)
deserialized_request = self.dispatch(self.deserializer,
action, request)
action_args.update(deserialized_request)
try:
deserialized_request = self.dispatch(self.deserializer,
action, request)
action_args.update(deserialized_request)
action_result = self.dispatch(self.controller, action,
request, **action_args)
except webob.exc.WSGIHTTPException as e:
exc_info = sys.exc_info()
raise translate_exception(request, e), None, exc_info[2]
action_result = self.dispatch(self.controller, action,
request, **action_args)
try:
response = webob.Response(request=request)
self.dispatch(self.serializer, action, response, action_result)
return response
except webob.exc.WSGIHTTPException as e:
return translate_exception(request, e)
except webob.exc.HTTPException as e:
return e
# return unserializable result (typically a webob exc)

View File

@ -59,6 +59,7 @@ import stat
import time
from oslo.config import cfg
import six
import xattr
from glance.common import exception
@ -281,13 +282,14 @@ class Driver(base.Driver):
os.unlink(self.get_image_filepath(image_id, 'queue'))
def rollback(e):
set_attr('error', "%s" % e)
set_attr('error', six.text_type(e))
invalid_path = self.get_image_filepath(image_id, 'invalid')
LOG.debug(_("Fetch of cache file failed (%(e)s), rolling back by "
"moving '%(incomplete_path)s' to "
"'%(invalid_path)s'"),
{'e': e, 'incomplete_path': incomplete_path,
{'e': six.text_type(e),
'incomplete_path': incomplete_path,
'invalid_path': invalid_path})
os.rename(incomplete_path, invalid_path)

View File

@ -14,9 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import warnings
from oslo.config import cfg
from oslo import messaging
import webob
@ -70,7 +67,7 @@ class Notifier(object):
if CONF.notifier_strategy != 'default':
msg = _("notifier_strategy was deprecated in "
"favor of `notification_driver`")
warnings.warn(msg, DeprecationWarning)
LOG.warn(msg)
# NOTE(flaper87): Use this to keep backwards
# compatibility. We'll try to get an oslo.messaging

View File

@ -14,6 +14,7 @@
# under the License.
import jsonschema
import six
from glance.common import exception
@ -31,7 +32,8 @@ class Schema(object):
try:
jsonschema.validate(obj, self.raw())
except jsonschema.ValidationError as e:
raise exception.InvalidObject(schema=self.name, reason=str(e))
raise exception.InvalidObject(schema=self.name,
reason=six.text_type(e))
def filter(self, obj):
filtered = {}

View File

@ -18,6 +18,7 @@ import copy
import sys
from oslo.config import cfg
import six
from glance.common import exception
from glance.common import utils
@ -305,7 +306,7 @@ def safe_delete_from_backend(context, uri, image_id, **kwargs):
msg = _('Failed to delete image %s in store from URI')
LOG.warn(msg % image_id)
except exception.StoreDeleteNotSupported as e:
LOG.warn(str(e))
LOG.warn(six.text_type(e))
except UnsupportedBackend:
exc_type = sys.exc_info()[0].__name__
msg = (_('Failed to delete image %(image_id)s from store '
@ -369,8 +370,8 @@ def store_add_to_backend(image_id, data, size, store):
if not isinstance(metadata, dict):
msg = (_("The storage driver %(store)s returned invalid metadata "
"%(metadata)s. This must be a dictionary type") %
{'store': store,
'metadata': metadata})
{'store': six.text_type(store),
'metadata': six.text_type(metadata)})
LOG.error(msg)
raise BackendException(msg)
try:
@ -378,9 +379,9 @@ def store_add_to_backend(image_id, data, size, store):
except BackendException as e:
e_msg = (_("A bad metadata structure was returned from the "
"%(store)s storage driver: %(metadata)s. %(error)s.") %
{'store': store,
'metadata': metadata,
'error': e})
{'store': six.text_type(store),
'metadata': six.text_type(metadata),
'error': six.text_type(e)})
LOG.error(e_msg)
raise BackendException(e_msg)
return (location, size, checksum, metadata)
@ -725,7 +726,7 @@ class ImageProxy(glance.domain.proxy.Image):
except Exception as e:
LOG.warn(_('Get image %(id)s data failed: '
'%(err)s.') % {'id': self.image.image_id,
'err': e})
'err': six.text_type(e)})
err = e
# tried all locations
LOG.error(_('Glance tried all locations to get data for image %s '

View File

@ -22,6 +22,7 @@ import hashlib
import os
from oslo.config import cfg
import six
import six.moves.urllib.parse as urlparse
from glance.common import exception
@ -279,19 +280,19 @@ class Store(glance.store.base.Store):
'used: %(error)s An empty dictionary will be '
'returned to the client.') %
{'file': CONF.filesystem_store_metadata_file,
'error': str(bee)})
'error': six.text_type(bee)})
return {}
except IOError as ioe:
LOG.error(_('The path for the metadata file %(file)s could not be '
'opened: %(error)s An empty dictionary will be '
'returned to the client.') %
{'file': CONF.filesystem_store_metadata_file,
'error': ioe})
'error': six.text_type(ioe)})
return {}
except Exception as ex:
LOG.exception(_('An error occurred processing the storage systems '
'meta data file: %s. An empty dictionary will be '
'returned to the client.') % str(ex))
'returned to the client.') % six.text_type(ex))
return {}
def get(self, location):

View File

@ -82,7 +82,8 @@ def register_scheme_map(scheme_map):
"""
for (k, v) in scheme_map.items():
if k not in SCHEME_TO_CLS_MAP:
LOG.debug("Registering scheme %s with %s", k, v)
LOG.debug(_("Registering scheme %(k)s with %(v)s") % {'k': k,
'v': v})
SCHEME_TO_CLS_MAP[k] = v

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from glance.common import exception
from glance.tests import utils as test_utils
@ -40,3 +42,7 @@ class GlanceExceptionTestCase(test_utils.BaseTestCase):
self.assertTrue('test: 500' in
unicode(exception.GlanceException('test: %(code)s',
code=500)))
def test_non_unicode_error_msg(self):
exc = exception.GlanceException(str('test'))
self.assertIsInstance(six.text_type(exc), six.text_type)

View File

@ -1,4 +1,5 @@
# Copyright 2010-2011 OpenStack Foundation
# Copyright 2014 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -16,19 +17,42 @@
import datetime
import socket
from babel import localedata
import eventlet.patcher
import fixtures
import gettext
import mock
import webob
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
from glance.openstack.common import gettextutils
from glance.tests import utils as test_utils
class RequestTest(test_utils.BaseTestCase):
def _set_expected_languages(self, all_locales=[], avail_locales=None):
# Override localedata.locale_identifiers to return some locales.
def returns_some_locales(*args, **kwargs):
return all_locales
self.stubs.Set(localedata, 'locale_identifiers', returns_some_locales)
# Override gettext.find to return other than None for some languages.
def fake_gettext_find(lang_id, *args, **kwargs):
found_ret = '/glance/%s/LC_MESSAGES/glance.mo' % lang_id
if avail_locales is None:
# All locales are available.
return found_ret
languages = kwargs['languages']
if languages[0] in avail_locales:
return found_ret
return None
self.stubs.Set(gettext, 'find', fake_gettext_find)
def test_content_type_missing(self):
request = wsgi.Request.blank('/tests/123')
self.assertRaises(exception.InvalidContentType,
@ -77,6 +101,50 @@ class RequestTest(test_utils.BaseTestCase):
result = request.best_match_content_type()
self.assertEqual(result, "application/json")
def test_language_accept_default(self):
request = wsgi.Request.blank('/tests/123')
request.headers["Accept-Language"] = "zz-ZZ,zz;q=0.8"
result = request.best_match_language()
self.assertIsNone(result)
def test_language_accept_none(self):
request = wsgi.Request.blank('/tests/123')
result = request.best_match_language()
self.assertIsNone(result)
def test_best_match_language_expected(self):
# If Accept-Language is a supported language, best_match_language()
# returns it.
self._set_expected_languages(all_locales=['it'])
req = wsgi.Request.blank('/', headers={'Accept-Language': 'it'})
self.assertEqual('it', req.best_match_language())
def test_request_match_language_unexpected(self):
# If Accept-Language is a language we do not support,
# best_match_language() returns None.
self._set_expected_languages(all_locales=['it'])
req = wsgi.Request.blank('/', headers={'Accept-Language': 'zh'})
self.assertIsNone(req.best_match_language())
@mock.patch.object(webob.acceptparse.AcceptLanguage, 'best_match')
def test_best_match_language_unknown(self, mock_best_match):
# Test that we are actually invoking language negotiation by webop
request = wsgi.Request.blank('/')
accepted = 'unknown-lang'
request.headers = {'Accept-Language': accepted}
mock_best_match.return_value = None
self.assertIsNone(request.best_match_language())
# If Accept-Language is missing or empty, match should be None
request.headers = {'Accept-Language': ''}
self.assertIsNone(request.best_match_language())
request.headers.pop('Accept-Language')
self.assertIsNone(request.best_match_language())
class ResourceTest(test_utils.BaseTestCase):
@ -171,6 +239,42 @@ class ResourceTest(test_utils.BaseTestCase):
self.assertIsInstance(response, webob.exc.HTTPForbidden)
self.assertEqual(response.status_code, 403)
@mock.patch.object(wsgi, 'translate_exception')
def test_resource_call_error_handle_localized(self,
mock_translate_exception):
class Controller(object):
def delete(self, req, identity):
raise webob.exc.HTTPBadRequest(explanation='Not Found')
actions = {'action': 'delete', 'identity': 12}
env = {'wsgiorg.routing_args': [None, actions]}
request = wsgi.Request.blank('/tests/123', environ=env)
message_es = 'No Encontrado'
resource = wsgi.Resource(Controller(),
wsgi.JSONRequestDeserializer(),
None)
translated_exc = webob.exc.HTTPBadRequest(message_es)
mock_translate_exception.return_value = translated_exc
e = self.assertRaises(webob.exc.HTTPBadRequest,
resource, request)
self.assertEqual(message_es, str(e))
@mock.patch.object(webob.acceptparse.AcceptLanguage, 'best_match')
@mock.patch.object(gettextutils, 'translate')
def test_translate_exception(self, mock_translate, mock_best_match):
mock_translate.return_value = 'No Encontrado'
mock_best_match.return_value = 'de'
req = wsgi.Request.blank('/tests/123')
req.headers["Accept-Language"] = "de"
e = webob.exc.HTTPNotFound(explanation='Not Found')
e = wsgi.translate_exception(req, e)
self.assertEqual('No Encontrado', e.explanation)
class JSONResponseSerializerTest(test_utils.BaseTestCase):

View File

@ -424,7 +424,7 @@ class SwiftTests(object):
except BackendException as e:
exception_caught = True
self.assertTrue("container noexist does not exist "
"in Swift" in str(e))
"in Swift" in six.text_type(e))
self.assertTrue(exception_caught)
self.assertEqual(SWIFT_PUT_OBJECT_CALLS, 0)