Use 'Exception.__traceback__' for versioned notifications

The 'inspect.trace()' function is expected to be called within the
context of an exception handler. The 'from_exc_and_traceback' class
method of the 'nova.notification.objects.exception.ExceptionPayload'
class uses this to get information about a provided exception, however,
there are cases where this is called from outside of an exception
handler. In these cases, we see an 'IndexError' since we can't get the
last frame of a non-existent stacktrace. The solution to this is to
fallback to using the traceback embedded in the exception. This is a bit
lossy when decorators are involved but for all other cases this will
give us the same information. This also allows us to avoid passing a
traceback argument to the function since we have it to hand already.

Change-Id: I404ca316b1bf2a963106cd34e927934befbd9b12
Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
Closes-Bug: #1881455
This commit is contained in:
Stephen Finucane
2020-06-04 12:13:56 +01:00
parent 03b00ae02f
commit 125df26bf9
13 changed files with 159 additions and 185 deletions

View File

@@ -12,14 +12,12 @@
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.notifications.objects import exception as exception_obj
from nova.objects import fields
from nova import rpc
from nova import safe_utils
@@ -27,26 +25,28 @@ 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)
def _emit_exception_notification(
notifier, context, exception, function_name, args, source,
):
_emit_legacy_exception_notification(
notifier, context, exception, function_name, args)
_emit_versioned_exception_notification(context, exception, source)
@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)
def _emit_versioned_exception_notification(context, exception, source):
payload = exception_obj.ExceptionPayload.from_exception(exception)
publisher = base.NotificationPublisher(host=CONF.host, source=source)
event_type = base.EventType(
object='compute',
action=fields.NotificationAction.EXCEPTION)
notification = exception.ExceptionNotification(
object='compute',
action=fields.NotificationAction.EXCEPTION,
)
notification = exception_obj.ExceptionNotification(
publisher=publisher,
event_type=event_type,
priority=fields.NotificationPriority.ERROR,
payload=versioned_exception_payload)
payload=payload,
)
notification.emit(context)
@@ -67,16 +67,15 @@ def wrap_exception(notifier=None, get_notifier=None, binary=None):
# contain confidential information.
try:
return f(self, context, *args, **kw)
except Exception as e:
tb = traceback.format_exc()
except Exception as 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)
notifier or get_notifier(), context, exc,
function_name, call_dict, binary)
return functools.wraps(f)(wrapped)
return inner