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:
Steven Dake 2014-11-21 11:33:38 -07:00
parent 4ada29ece9
commit 0bf17e8a39
1 changed files with 222 additions and 0 deletions

222
magnum/common/exception.py Normal file
View 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")