Add exception.py
This adds exception.py. Only one example exception is defined. More will come as the code base fills out. The main need for this are the pecan and keystone exception wrappers. Change-Id: I8c9d1c11d81610eb124e569e056d64436df68396
This commit is contained in:
parent
4ada29ece9
commit
0bf17e8a39
222
magnum/common/exception.py
Normal file
222
magnum/common/exception.py
Normal file
@ -0,0 +1,222 @@
|
||||
# Copyright 2013 - Red Hat, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Magnum base exception handling.
|
||||
|
||||
Includes decorator for re-raising Magnum-type exceptions.
|
||||
|
||||
"""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from keystoneclient import exceptions as keystone_exceptions
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import excutils
|
||||
import pecan
|
||||
import six
|
||||
import wsme
|
||||
|
||||
from magnum.common import safe_utils
|
||||
from magnum.openstack.common._i18n import _
|
||||
from magnum.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
exc_log_opts = [
|
||||
cfg.BoolOpt('fatal_exception_format_errors',
|
||||
default=False,
|
||||
help='make exception message format errors fatal')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(exc_log_opts)
|
||||
|
||||
|
||||
def wrap_exception(notifier=None, publisher_id=None, event_type=None,
|
||||
level=None):
|
||||
"""This decorator wraps a method to catch any exceptions.
|
||||
|
||||
It logs the exception as well as optionally sending
|
||||
it to the notification system.
|
||||
"""
|
||||
def inner(f):
|
||||
def wrapped(self, context, *args, **kw):
|
||||
# Don't store self or context in the payload, it now seems to
|
||||
# contain confidential information.
|
||||
try:
|
||||
return f(self, context, *args, **kw)
|
||||
except Exception as e:
|
||||
with excutils.save_and_reraise_exception():
|
||||
if notifier:
|
||||
call_dict = safe_utils.getcallargs(f, *args, **kw)
|
||||
payload = dict(exception=e,
|
||||
private=dict(args=call_dict)
|
||||
)
|
||||
|
||||
# Use a temp vars so we don't shadow
|
||||
# our outer definitions.
|
||||
temp_level = level
|
||||
if not temp_level:
|
||||
temp_level = notifier.ERROR
|
||||
|
||||
temp_type = event_type
|
||||
if not temp_type:
|
||||
# If f has multiple decorators, they must use
|
||||
# functools.wraps to ensure the name is
|
||||
# propagated.
|
||||
temp_type = f.__name__
|
||||
|
||||
notifier.notify(context, publisher_id, temp_type,
|
||||
temp_level, payload)
|
||||
|
||||
return functools.wraps(f)(wrapped)
|
||||
return inner
|
||||
|
||||
|
||||
OBFUSCATED_MSG = _('Your request could not be handled '
|
||||
'because of a problem in the server. '
|
||||
'Error Correlation id is: %s')
|
||||
|
||||
|
||||
def wrap_controller_exception(func, func_server_error, func_client_error):
|
||||
"""This decorator wraps controllers methods to handle exceptions:
|
||||
|
||||
- if an unhandled Exception or a MagnumException with an error code >=500
|
||||
is catched, raise a http 5xx ClientSideError and correlates it with a log
|
||||
message
|
||||
|
||||
- if a MagnumException is catched and its error code is <500, raise a http
|
||||
4xx and logs the excp in debug mode
|
||||
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kw):
|
||||
try:
|
||||
return func(*args, **kw)
|
||||
except Exception as excp:
|
||||
if isinstance(excp, MagnumException):
|
||||
http_error_code = excp.code
|
||||
else:
|
||||
http_error_code = 500
|
||||
|
||||
if http_error_code >= 500:
|
||||
# log the error message with its associated
|
||||
# correlation id
|
||||
log_correlation_id = str(uuid.uuid4())
|
||||
LOG.error("%s:%s", log_correlation_id, str(excp))
|
||||
# raise a client error with an obfuscated message
|
||||
func_server_error(log_correlation_id, http_error_code)
|
||||
else:
|
||||
# raise a client error the original message
|
||||
LOG.debug(excp)
|
||||
func_client_error(excp, http_error_code)
|
||||
return wrapped
|
||||
|
||||
|
||||
def wrap_wsme_controller_exception(func):
|
||||
"""This decorator wraps wsme controllers to handle exceptions."""
|
||||
def _func_server_error(log_correlation_id, status_code):
|
||||
raise wsme.exc.ClientSideError(
|
||||
six.text_type(OBFUSCATED_MSG % log_correlation_id), status_code)
|
||||
|
||||
def _func_client_error(excp, status_code):
|
||||
raise wsme.exc.ClientSideError(six.text_type(excp), status_code)
|
||||
|
||||
return wrap_controller_exception(func,
|
||||
_func_server_error,
|
||||
_func_client_error)
|
||||
|
||||
|
||||
def wrap_pecan_controller_exception(func):
|
||||
"""This decorator wraps pecan controllers to handle exceptions."""
|
||||
def _func_server_error(log_correlation_id, status_code):
|
||||
pecan.response.status = status_code
|
||||
pecan.response.text = six.text_type(OBFUSCATED_MSG %
|
||||
log_correlation_id)
|
||||
|
||||
def _func_client_error(excp, status_code):
|
||||
pecan.response.status = status_code
|
||||
pecan.response.text = six.text_type(excp)
|
||||
pecan.response.content_type = None
|
||||
|
||||
return wrap_controller_exception(func,
|
||||
_func_server_error,
|
||||
_func_client_error)
|
||||
|
||||
|
||||
def wrap_keystone_exception(func):
|
||||
"""Wrap keystone exceptions and throw Magnum specific exceptions."""
|
||||
@functools.wraps(func)
|
||||
def wrapped(*args, **kw):
|
||||
try:
|
||||
return func(*args, **kw)
|
||||
except keystone_exceptions.AuthorizationFailure:
|
||||
raise AuthorizationFailure(
|
||||
client=func.__name__, message="reason: %s" % sys.exc_info()[1])
|
||||
except keystone_exceptions.ClientException:
|
||||
raise AuthorizationFailure(
|
||||
client=func.__name__,
|
||||
message="unexpected keystone client error occurred: %s"
|
||||
% sys.exc_info()[1])
|
||||
return wrapped
|
||||
|
||||
|
||||
class MagnumException(Exception):
|
||||
"""Base Magnum Exception
|
||||
|
||||
To correctly use this class, inherit from it and define
|
||||
a 'msg_fmt' property. That msg_fmt will get printf'd
|
||||
with the keyword arguments provided to the constructor.
|
||||
|
||||
"""
|
||||
message = _("An unknown exception occurred.")
|
||||
code = 500
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
if CONF.fatal_exception_format_errors:
|
||||
assert isinstance(self.msg_fmt, six.text_type)
|
||||
|
||||
try:
|
||||
self.message = self.msg_fmt % kwargs
|
||||
except KeyError:
|
||||
# kwargs doesn't match a variable in the message
|
||||
# log the issue and the kwargs
|
||||
LOG.exception(_('Exception in string format operation'),
|
||||
extra=dict(
|
||||
private=dict(
|
||||
msg=self.msg_fmt,
|
||||
args=kwargs
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if CONF.fatal_exception_format_errors:
|
||||
raise
|
||||
|
||||
def __str__(self):
|
||||
if six.PY3:
|
||||
return self.message
|
||||
return self.message.encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return self.message
|
||||
|
||||
|
||||
class AuthorizationFailure(MagnumException):
|
||||
msg_fmt = _("%(client)s connection failed. %(message)s")
|
Loading…
x
Reference in New Issue
Block a user