nova/nova/exception_wrapper.py
Kevin_Zheng 2a0f2a0d27 Add full traceback to ExceptionPayload in versioned notifications
This patch adds full traceback to ExceptionPayload in versioned
notifications.

The instance fault field and instance-action REST API has already
provide the traceback to the admin users (controlable through policy)
and the notifications are also admin only things as they are emitted
to the message bus by default. So it is assumed that security is not
a bigger concern for the notification than for the REST API.

On the ML [1] post there was no objection to add new string field to the
ExceptionPayload that will hold the serialized traceback object.

[1] http://lists.openstack.org/pipermail/openstack-dev/2018-March/128105.html

Implements: blueprint add-full-traceback-to-error-notifications

Change-Id: Id587967ea4f9980c292492e2f659bf55fb037b28
2018-06-19 16:46:46 +08:00

102 lines
3.8 KiB
Python

# 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.
import functools
import inspect
import traceback
from oslo_utils import excutils
import nova.conf
from nova.notifications.objects import base
from nova.notifications.objects import exception
from nova.objects import fields
from nova import rpc
from nova import safe_utils
CONF = nova.conf.CONF
def _emit_exception_notification(notifier, context, ex, function_name, args,
source, trace_back):
_emit_legacy_exception_notification(notifier, context, ex, function_name,
args)
_emit_versioned_exception_notification(context, ex, source, trace_back)
@rpc.if_notifications_enabled
def _emit_versioned_exception_notification(context, ex, source, trace_back):
versioned_exception_payload = \
exception.ExceptionPayload.from_exc_and_traceback(ex, trace_back)
publisher = base.NotificationPublisher(host=CONF.host, source=source)
event_type = base.EventType(
object='compute',
action=fields.NotificationAction.EXCEPTION)
notification = exception.ExceptionNotification(
publisher=publisher,
event_type=event_type,
priority=fields.NotificationPriority.ERROR,
payload=versioned_exception_payload)
notification.emit(context)
def _emit_legacy_exception_notification(notifier, context, ex, function_name,
args):
payload = dict(exception=ex, args=args)
notifier.error(context, function_name, payload)
def wrap_exception(notifier=None, get_notifier=None, binary=None):
"""This decorator wraps a method to catch any exceptions that may
get thrown. It also optionally sends the exception 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:
tb = traceback.format_exc()
with excutils.save_and_reraise_exception():
if notifier or get_notifier:
call_dict = _get_call_dict(
f, self, context, *args, **kw)
function_name = f.__name__
_emit_exception_notification(
notifier or get_notifier(), context, e,
function_name, call_dict, binary, tb)
return functools.wraps(f)(wrapped)
return inner
def _get_call_dict(function, self, context, *args, **kw):
wrapped_func = safe_utils.get_wrapped_function(function)
call_dict = inspect.getcallargs(wrapped_func, self,
context, *args, **kw)
# self can't be serialized and shouldn't be in the
# payload
call_dict.pop('self', None)
# NOTE(gibi) remove context as well as it contains sensitive information
# and it can also contain circular references
call_dict.pop('context', None)
return _cleanse_dict(call_dict)
def _cleanse_dict(original):
"""Strip all admin_password, new_pass, rescue_pass keys from a dict."""
return {k: v for k, v in original.items() if "_pass" not in k}