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
This commit is contained in:
Kevin_Zheng
2018-04-25 11:17:14 +08:00
parent 8863b501b7
commit 2a0f2a0d27
20 changed files with 188 additions and 77 deletions
@@ -5,11 +5,12 @@
"exception": "AggregateNameExists", "exception": "AggregateNameExists",
"exception_message": "Aggregate versioned_exc_aggregate already exists.", "exception_message": "Aggregate versioned_exc_aggregate already exists.",
"function_name": "_aggregate_create_in_db", "function_name": "_aggregate_create_in_db",
"module_name": "nova.objects.aggregate" "module_name": "nova.objects.aggregate",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
}, },
"priority": "ERROR", "priority": "ERROR",
"publisher_id": "nova-api:fake-mini" "publisher_id": "nova-api:fake-mini"
@@ -8,11 +8,12 @@
"exception": "FlavorDiskTooSmall", "exception": "FlavorDiskTooSmall",
"exception_message": "The created instance's disk would be too small.", "exception_message": "The created instance's disk would be too small.",
"function_name": "_build_resources", "function_name": "_build_resources",
"module_name": "nova.tests.functional.notification_sample_tests.test_instance" "module_name": "nova.tests.functional.notification_sample_tests.test_instance",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
}, },
"ip_addresses": [], "ip_addresses": [],
"launched_at": null, "launched_at": null,
@@ -9,11 +9,12 @@
"exception": "InterfaceAttachFailed", "exception": "InterfaceAttachFailed",
"exception_message": "dummy", "exception_message": "dummy",
"function_name": "_unsuccessful_attach_interface", "function_name": "_unsuccessful_attach_interface",
"module_name": "nova.tests.functional.notification_sample_tests.test_instance" "module_name": "nova.tests.functional.notification_sample_tests.test_instance",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
} }
} }
}, },
@@ -8,11 +8,12 @@
"exception": "UnsupportedVirtType", "exception": "UnsupportedVirtType",
"exception_message": "Virtualization type 'FakeVirt' is not supported by this compute driver", "exception_message": "Virtualization type 'FakeVirt' is not supported by this compute driver",
"function_name": "_hard_reboot", "function_name": "_hard_reboot",
"module_name": "nova.tests.functional.notification_sample_tests.test_instance" "module_name": "nova.tests.functional.notification_sample_tests.test_instance",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
}, },
"task_state":"reboot_started_hard" "task_state":"reboot_started_hard"
} }
@@ -9,9 +9,10 @@
"module_name": "nova.tests.functional.notification_sample_tests.test_instance", "module_name": "nova.tests.functional.notification_sample_tests.test_instance",
"exception_message": "Virtual Interface creation failed", "exception_message": "Virtual Interface creation failed",
"function_name": "_virtual_interface_create_failed", "function_name": "_virtual_interface_create_failed",
"exception": "VirtualInterfaceCreateException" "exception": "VirtualInterfaceCreateException",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.version": "1.0", "nova_object.version": "1.1",
"nova_object.namespace": "nova" "nova_object.namespace": "nova"
}, },
"architecture": null, "architecture": null,
@@ -8,11 +8,12 @@
"exception":"FlavorDiskTooSmall", "exception":"FlavorDiskTooSmall",
"exception_message":"The created instance's disk would be too small.", "exception_message":"The created instance's disk would be too small.",
"function_name":"_build_resources", "function_name":"_build_resources",
"module_name":"nova.tests.functional.notification_sample_tests.test_instance" "module_name":"nova.tests.functional.notification_sample_tests.test_instance",
"traceback":"Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name":"ExceptionPayload", "nova_object.name":"ExceptionPayload",
"nova_object.namespace":"nova", "nova_object.namespace":"nova",
"nova_object.version":"1.0" "nova_object.version":"1.1"
}, },
"block_devices": [], "block_devices": [],
"task_state": "resize_prep" "task_state": "resize_prep"
@@ -8,11 +8,12 @@
"exception": "CinderConnectionFailed", "exception": "CinderConnectionFailed",
"exception_message": "Connection to cinder host failed: Connection timed out", "exception_message": "Connection to cinder host failed: Connection timed out",
"function_name": "attach_volume", "function_name": "attach_volume",
"module_name": "nova.tests.functional.notification_sample_tests.test_instance" "module_name": "nova.tests.functional.notification_sample_tests.test_instance",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
} }
} }
}, },
@@ -10,11 +10,12 @@
"exception": "TypeError", "exception": "TypeError",
"exception_message": "'tuple' object does not support item assignment", "exception_message": "'tuple' object does not support item assignment",
"function_name": "_init_volume_connection", "function_name": "_init_volume_connection",
"module_name": "nova.compute.manager" "module_name": "nova.compute.manager",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
}, },
"nova_object.name": "ExceptionPayload", "nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova", "nova_object.namespace": "nova",
"nova_object.version": "1.0" "nova_object.version": "1.1"
} }
} }
}, },
+31 -15
View File
@@ -2017,18 +2017,20 @@ class ComputeManager(manager.Manager):
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
except exception.ComputeResourcesUnavailable as e: except exception.ComputeResourcesUnavailable as e:
LOG.debug(e.format_message(), instance=instance) LOG.debug(e.format_message(), instance=instance)
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
raise exception.RescheduledException( raise exception.RescheduledException(
instance_uuid=instance.uuid, reason=e.format_message()) instance_uuid=instance.uuid, reason=e.format_message())
except exception.BuildAbortException as e: except exception.BuildAbortException as e:
@@ -2036,20 +2038,22 @@ class ComputeManager(manager.Manager):
LOG.debug(e.format_message(), instance=instance) LOG.debug(e.format_message(), instance=instance)
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
except (exception.FixedIpLimitExceeded, except (exception.FixedIpLimitExceeded,
exception.NoMoreNetworks, exception.NoMoreFixedIps) as e: exception.NoMoreNetworks, exception.NoMoreFixedIps) as e:
LOG.warning('No more network or fixed IP to be allocated', LOG.warning('No more network or fixed IP to be allocated',
instance=instance) instance=instance)
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
msg = _('Failed to allocate the network(s) with error %s, ' msg = _('Failed to allocate the network(s) with error %s, '
'not rescheduling.') % e.format_message() 'not rescheduling.') % e.format_message()
raise exception.BuildAbortException(instance_uuid=instance.uuid, raise exception.BuildAbortException(instance_uuid=instance.uuid,
@@ -2062,10 +2066,11 @@ class ComputeManager(manager.Manager):
instance=instance) instance=instance)
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
msg = _('Failed to allocate the network(s), not rescheduling.') msg = _('Failed to allocate the network(s), not rescheduling.')
raise exception.BuildAbortException(instance_uuid=instance.uuid, raise exception.BuildAbortException(instance_uuid=instance.uuid,
reason=msg) reason=msg)
@@ -2084,19 +2089,21 @@ class ComputeManager(manager.Manager):
exception.RequestedVRamTooHigh) as e: exception.RequestedVRamTooHigh) as e:
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
raise exception.BuildAbortException(instance_uuid=instance.uuid, raise exception.BuildAbortException(instance_uuid=instance.uuid,
reason=e.format_message()) reason=e.format_message())
except Exception as e: except Exception as e:
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
raise exception.RescheduledException( raise exception.RescheduledException(
instance_uuid=instance.uuid, reason=six.text_type(e)) instance_uuid=instance.uuid, reason=six.text_type(e))
@@ -2128,10 +2135,11 @@ class ComputeManager(manager.Manager):
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'create.error', fault=e) 'create.error', fault=e)
tb = traceback.format_exc()
compute_utils.notify_about_instance_create( compute_utils.notify_about_instance_create(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=e, phase=fields.NotificationPhase.ERROR, exception=e,
bdms=block_device_mapping) bdms=block_device_mapping, tb=tb)
self._update_scheduler_instance_info(context, instance) self._update_scheduler_instance_info(context, instance)
self._notify_about_instance_usage(context, instance, 'create.end', self._notify_about_instance_usage(context, instance, 'create.end',
@@ -2775,11 +2783,13 @@ class ComputeManager(manager.Manager):
block_device_info=new_block_device_info) block_device_info=new_block_device_info)
def _notify_instance_rebuild_error(self, context, instance, error, bdms): def _notify_instance_rebuild_error(self, context, instance, error, bdms):
tb = traceback.format_exc()
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'rebuild.error', fault=error) 'rebuild.error', fault=error)
compute_utils.notify_about_instance_rebuild( compute_utils.notify_about_instance_rebuild(
context, instance, self.host, context, instance, self.host,
phase=fields.NotificationPhase.ERROR, exception=error, bdms=bdms) phase=fields.NotificationPhase.ERROR, exception=error, bdms=bdms,
tb=tb)
@messaging.expected_exceptions(exception.PreserveEphemeralNotSupported) @messaging.expected_exceptions(exception.PreserveEphemeralNotSupported)
@wrap_exception() @wrap_exception()
@@ -3229,11 +3239,12 @@ class ComputeManager(manager.Manager):
instance, error, exc_info) instance, error, exc_info)
self._notify_about_instance_usage(context, instance, self._notify_about_instance_usage(context, instance,
'reboot.error', fault=error) 'reboot.error', fault=error)
tb = traceback.format_exc()
compute_utils.notify_about_instance_action( compute_utils.notify_about_instance_action(
context, instance, self.host, context, instance, self.host,
action=fields.NotificationAction.REBOOT, action=fields.NotificationAction.REBOOT,
phase=fields.NotificationPhase.ERROR, phase=fields.NotificationPhase.ERROR,
exception=error, bdms=bdms exception=error, bdms=bdms, tb=tb
) )
ctxt.reraise = False ctxt.reraise = False
else: else:
@@ -4202,7 +4213,8 @@ class ComputeManager(manager.Manager):
context, instance, self.host, context, instance, self.host,
action=fields.NotificationAction.RESIZE, action=fields.NotificationAction.RESIZE,
phase=fields.NotificationPhase.ERROR, phase=fields.NotificationPhase.ERROR,
exception=error) exception=error,
tb=','.join(traceback.format_exception(*exc_info)))
if rescheduled: if rescheduled:
self._log_original_error(exc_info, instance_uuid) self._log_original_error(exc_info, instance_uuid)
compute_utils.add_instance_fault_from_exc(context, compute_utils.add_instance_fault_from_exc(context,
@@ -4213,7 +4225,8 @@ class ComputeManager(manager.Manager):
context, instance, self.host, context, instance, self.host,
action=fields.NotificationAction.RESIZE, action=fields.NotificationAction.RESIZE,
phase=fields.NotificationPhase.ERROR, phase=fields.NotificationPhase.ERROR,
exception=exc_info[1]) exception=exc_info[1],
tb=','.join(traceback.format_exception(*exc_info)))
else: else:
# not re-scheduling # not re-scheduling
six.reraise(*exc_info) six.reraise(*exc_info)
@@ -5360,12 +5373,13 @@ class ComputeManager(manager.Manager):
bdm['attachment_id']) bdm['attachment_id'])
else: else:
self.volume_api.unreserve_volume(context, bdm.volume_id) self.volume_api.unreserve_volume(context, bdm.volume_id)
tb = traceback.format_exc()
compute_utils.notify_about_volume_attach_detach( compute_utils.notify_about_volume_attach_detach(
context, instance, self.host, context, instance, self.host,
action=fields.NotificationAction.VOLUME_ATTACH, action=fields.NotificationAction.VOLUME_ATTACH,
phase=fields.NotificationPhase.ERROR, phase=fields.NotificationPhase.ERROR,
exception=e, exception=e,
volume_id=bdm.volume_id) volume_id=bdm.volume_id, tb=tb)
info = {'volume_id': bdm.volume_id} info = {'volume_id': bdm.volume_id}
self._notify_about_instance_usage( self._notify_about_instance_usage(
@@ -5561,10 +5575,11 @@ class ComputeManager(manager.Manager):
except Exception as ex: except Exception as ex:
failed = True failed = True
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
tb = traceback.format_exc()
compute_utils.notify_about_volume_swap( compute_utils.notify_about_volume_swap(
context, instance, self.host, context, instance, self.host,
fields.NotificationPhase.ERROR, fields.NotificationPhase.ERROR,
old_volume_id, new_volume_id, ex) old_volume_id, new_volume_id, ex, tb)
if new_cinfo: if new_cinfo:
msg = ("Failed to swap volume %(old_volume_id)s " msg = ("Failed to swap volume %(old_volume_id)s "
"for %(new_volume_id)s") "for %(new_volume_id)s")
@@ -5799,11 +5814,12 @@ class ComputeManager(manager.Manager):
LOG.warning("deallocate port %(port_id)s failed", LOG.warning("deallocate port %(port_id)s failed",
{'port_id': port_id}, instance=instance) {'port_id': port_id}, instance=instance)
tb = traceback.format_exc()
compute_utils.notify_about_instance_action( compute_utils.notify_about_instance_action(
context, instance, self.host, context, instance, self.host,
action=fields.NotificationAction.INTERFACE_ATTACH, action=fields.NotificationAction.INTERFACE_ATTACH,
phase=fields.NotificationPhase.ERROR, phase=fields.NotificationPhase.ERROR,
exception=ex) exception=ex, tb=tb)
raise exception.InterfaceAttachFailed( raise exception.InterfaceAttachFailed(
instance_uuid=instance.uuid) instance_uuid=instance.uuid)
+24 -16
View File
@@ -366,14 +366,14 @@ def notify_about_instance_usage(notifier, context, instance, event_suffix,
method(context, 'compute.instance.%s' % event_suffix, usage_info) method(context, 'compute.instance.%s' % event_suffix, usage_info)
def _get_fault_and_priority_from_exc(exception): def _get_fault_and_priority_from_exc_and_tb(exception, tb):
fault = None fault = None
priority = fields.NotificationPriority.INFO priority = fields.NotificationPriority.INFO
if exception: if exception:
priority = fields.NotificationPriority.ERROR priority = fields.NotificationPriority.ERROR
fault = notification_exception.ExceptionPayload.from_exception( fault = notification_exception.ExceptionPayload.from_exc_and_traceback(
exception) exception, tb)
return fault, priority return fault, priority
@@ -381,7 +381,7 @@ def _get_fault_and_priority_from_exc(exception):
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_instance_action(context, instance, host, action, phase=None, def notify_about_instance_action(context, instance, host, action, phase=None,
source=fields.NotificationSource.COMPUTE, source=fields.NotificationSource.COMPUTE,
exception=None, bdms=None): exception=None, bdms=None, tb=None):
"""Send versioned notification about the action made on the instance """Send versioned notification about the action made on the instance
:param instance: the instance which the action performed on :param instance: the instance which the action performed on
:param host: the host emitting the notification :param host: the host emitting the notification
@@ -391,8 +391,9 @@ def notify_about_instance_action(context, instance, host, action, phase=None,
:param exception: the thrown exception (used in error notifications) :param exception: the thrown exception (used in error notifications)
:param bdms: BlockDeviceMappingList object for the instance. If it is not :param bdms: BlockDeviceMappingList object for the instance. If it is not
provided then we will load it from the db if so configured provided then we will load it from the db if so configured
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceActionPayload( payload = instance_notification.InstanceActionPayload(
context=context, context=context,
instance=instance, instance=instance,
@@ -413,7 +414,7 @@ def notify_about_instance_action(context, instance, host, action, phase=None,
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_instance_create(context, instance, host, phase=None, def notify_about_instance_create(context, instance, host, phase=None,
exception=None, bdms=None): exception=None, bdms=None, tb=None):
"""Send versioned notification about instance creation """Send versioned notification about instance creation
:param context: the request context :param context: the request context
@@ -423,8 +424,9 @@ def notify_about_instance_create(context, instance, host, phase=None,
:param exception: the thrown exception (used in error notifications) :param exception: the thrown exception (used in error notifications)
:param bdms: BlockDeviceMappingList object for the instance. If it is not :param bdms: BlockDeviceMappingList object for the instance. If it is not
provided then we will load it from the db if so configured provided then we will load it from the db if so configured
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceCreatePayload( payload = instance_notification.InstanceCreatePayload(
context=context, context=context,
instance=instance, instance=instance,
@@ -445,7 +447,7 @@ def notify_about_instance_create(context, instance, host, phase=None,
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_volume_attach_detach(context, instance, host, action, phase, def notify_about_volume_attach_detach(context, instance, host, action, phase,
volume_id=None, exception=None): volume_id=None, exception=None, tb=None):
"""Send versioned notification about the action made on the instance """Send versioned notification about the action made on the instance
:param instance: the instance which the action performed on :param instance: the instance which the action performed on
:param host: the host emitting the notification :param host: the host emitting the notification
@@ -453,8 +455,9 @@ def notify_about_volume_attach_detach(context, instance, host, action, phase,
:param phase: the phase of the action :param phase: the phase of the action
:param volume_id: id of the volume will be attached :param volume_id: id of the volume will be attached
:param exception: the thrown exception (used in error notifications) :param exception: the thrown exception (used in error notifications)
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceActionVolumePayload( payload = instance_notification.InstanceActionVolumePayload(
context=context, context=context,
instance=instance, instance=instance,
@@ -474,8 +477,9 @@ def notify_about_volume_attach_detach(context, instance, host, action, phase,
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_instance_rescue_action( def notify_about_instance_rescue_action(context, instance, host,
context, instance, host, rescue_image_ref, phase=None, exception=None): rescue_image_ref, phase=None,
exception=None, tb=None):
"""Send versioned notification about the action made on the instance """Send versioned notification about the action made on the instance
:param instance: the instance which the action performed on :param instance: the instance which the action performed on
@@ -483,8 +487,9 @@ def notify_about_instance_rescue_action(
:param rescue_image_ref: the rescue image ref :param rescue_image_ref: the rescue image ref
:param phase: the phase of the action :param phase: the phase of the action
:param exception: the thrown exception (used in error notifications) :param exception: the thrown exception (used in error notifications)
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceActionRescuePayload( payload = instance_notification.InstanceActionRescuePayload(
context=context, context=context,
instance=instance, instance=instance,
@@ -528,7 +533,8 @@ def notify_about_keypair_action(context, keypair, action, phase):
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_volume_swap(context, instance, host, phase, def notify_about_volume_swap(context, instance, host, phase,
old_volume_id, new_volume_id, exception=None): old_volume_id, new_volume_id, exception=None,
tb=None):
"""Send versioned notification about the volume swap action """Send versioned notification about the volume swap action
on the instance on the instance
@@ -539,8 +545,9 @@ def notify_about_volume_swap(context, instance, host, phase,
:param old_volume_id: the ID of the volume that is copied from and detached :param old_volume_id: the ID of the volume that is copied from and detached
:param new_volume_id: the ID of the volume that is copied to and attached :param new_volume_id: the ID of the volume that is copied to and attached
:param exception: an exception :param exception: an exception
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceActionVolumeSwapPayload( payload = instance_notification.InstanceActionVolumeSwapPayload(
context=context, context=context,
instance=instance, instance=instance,
@@ -717,7 +724,7 @@ def notify_about_server_group_add_member(context, group_id):
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def notify_about_instance_rebuild(context, instance, host, phase=None, def notify_about_instance_rebuild(context, instance, host, phase=None,
exception=None, bdms=None): exception=None, bdms=None, tb=None):
"""Send versioned notification about instance rebuild """Send versioned notification about instance rebuild
:param instance: the instance which the action performed on :param instance: the instance which the action performed on
@@ -726,8 +733,9 @@ def notify_about_instance_rebuild(context, instance, host, phase=None,
:param exception: the thrown exception (used in error notifications) :param exception: the thrown exception (used in error notifications)
:param bdms: BlockDeviceMappingList object for the instance. If it is not :param bdms: BlockDeviceMappingList object for the instance. If it is not
provided then we will load it from the db if so configured provided then we will load it from the db if so configured
:param tb: the traceback (used in error notifications)
""" """
fault, priority = _get_fault_and_priority_from_exc(exception) fault, priority = _get_fault_and_priority_from_exc_and_tb(exception, tb)
payload = instance_notification.InstanceActionRebuildPayload( payload = instance_notification.InstanceActionRebuildPayload(
context=context, context=context,
instance=instance, instance=instance,
+8 -5
View File
@@ -12,6 +12,7 @@
import functools import functools
import inspect import inspect
import traceback
from oslo_utils import excutils from oslo_utils import excutils
@@ -27,15 +28,16 @@ CONF = nova.conf.CONF
def _emit_exception_notification(notifier, context, ex, function_name, args, def _emit_exception_notification(notifier, context, ex, function_name, args,
source): source, trace_back):
_emit_legacy_exception_notification(notifier, context, ex, function_name, _emit_legacy_exception_notification(notifier, context, ex, function_name,
args) args)
_emit_versioned_exception_notification(context, ex, source) _emit_versioned_exception_notification(context, ex, source, trace_back)
@rpc.if_notifications_enabled @rpc.if_notifications_enabled
def _emit_versioned_exception_notification(context, ex, source): def _emit_versioned_exception_notification(context, ex, source, trace_back):
versioned_exception_payload = exception.ExceptionPayload.from_exception(ex) versioned_exception_payload = \
exception.ExceptionPayload.from_exc_and_traceback(ex, trace_back)
publisher = base.NotificationPublisher(host=CONF.host, source=source) publisher = base.NotificationPublisher(host=CONF.host, source=source)
event_type = base.EventType( event_type = base.EventType(
object='compute', object='compute',
@@ -66,6 +68,7 @@ def wrap_exception(notifier=None, get_notifier=None, binary=None):
try: try:
return f(self, context, *args, **kw) return f(self, context, *args, **kw)
except Exception as e: except Exception as e:
tb = traceback.format_exc()
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
if notifier or get_notifier: if notifier or get_notifier:
call_dict = _get_call_dict( call_dict = _get_call_dict(
@@ -73,7 +76,7 @@ def wrap_exception(notifier=None, get_notifier=None, binary=None):
function_name = f.__name__ function_name = f.__name__
_emit_exception_notification( _emit_exception_notification(
notifier or get_notifier(), context, e, notifier or get_notifier(), context, e,
function_name, call_dict, binary) function_name, call_dict, binary, tb)
return functools.wraps(f)(wrapped) return functools.wraps(f)(wrapped)
return inner return inner
+9 -5
View File
@@ -21,24 +21,27 @@ from nova.objects import fields
@nova_base.NovaObjectRegistry.register_notification @nova_base.NovaObjectRegistry.register_notification
class ExceptionPayload(base.NotificationPayloadBase): class ExceptionPayload(base.NotificationPayloadBase):
# Version 1.0: Initial version # Version 1.0: Initial version
VERSION = '1.0' # Version 1.1: Add traceback field to ExceptionPayload
VERSION = '1.1'
fields = { fields = {
'module_name': fields.StringField(), 'module_name': fields.StringField(),
'function_name': fields.StringField(), 'function_name': fields.StringField(),
'exception': fields.StringField(), 'exception': fields.StringField(),
'exception_message': fields.StringField() 'exception_message': fields.StringField(),
'traceback': fields.StringField()
} }
def __init__(self, module_name, function_name, exception, def __init__(self, module_name, function_name, exception,
exception_message): exception_message, traceback):
super(ExceptionPayload, self).__init__() super(ExceptionPayload, self).__init__()
self.module_name = module_name self.module_name = module_name
self.function_name = function_name self.function_name = function_name
self.exception = exception self.exception = exception
self.exception_message = exception_message self.exception_message = exception_message
self.traceback = traceback
@classmethod @classmethod
def from_exception(cls, fault): def from_exc_and_traceback(cls, fault, traceback):
trace = inspect.trace()[-1] trace = inspect.trace()[-1]
# TODO(gibi): apply strutils.mask_password on exception_message and # TODO(gibi): apply strutils.mask_password on exception_message and
# consider emitting the exception_message only if the safe flag is # consider emitting the exception_message only if the safe flag is
@@ -49,7 +52,8 @@ class ExceptionPayload(base.NotificationPayloadBase):
function_name=trace[3], function_name=trace[3],
module_name=module_name, module_name=module_name,
exception=fault.__class__.__name__, exception=fault.__class__.__name__,
exception_message=six.text_type(fault)) exception_message=six.text_type(fault),
traceback=traceback)
@base.notification_sample('compute-exception.json') @base.notification_sample('compute-exception.json')
@@ -34,6 +34,11 @@ class TestExceptionNotificationSample(
self.admin_api.api_post, 'os-aggregates', post) self.admin_api.api_post, 'os-aggregates', post)
self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS)) self.assertEqual(4, len(fake_notifier.VERSIONED_NOTIFICATIONS))
traceback = fake_notifier.VERSIONED_NOTIFICATIONS[3][
'payload']['nova_object.data']['traceback']
self.assertIn('AggregateNameExists', traceback)
self._verify_notification( self._verify_notification(
'compute-exception', 'compute-exception',
replacements={
'traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[3]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[3])
@@ -367,6 +367,10 @@ class TestInstanceNotificationSample(
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS)) self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
tb = fake_notifier.VERSIONED_NOTIFICATIONS[1]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn('raise exception.FlavorDiskTooSmall()', tb)
self._verify_notification( self._verify_notification(
'instance-create-start', 'instance-create-start',
replacements={ replacements={
@@ -377,7 +381,8 @@ class TestInstanceNotificationSample(
'instance-create-error', 'instance-create-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id']}, 'uuid': server['id'],
'fault.traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
def test_instance_exists_usage_audit(self): def test_instance_exists_usage_audit(self):
@@ -910,10 +915,16 @@ class TestInstanceNotificationSample(
fake_notifier.VERSIONED_NOTIFICATIONS) fake_notifier.VERSIONED_NOTIFICATIONS)
# Note(gibi): There is also an instance.exists notification emitted # Note(gibi): There is also an instance.exists notification emitted
# during the rescheduling # during the rescheduling
tb = fake_notifier.VERSIONED_NOTIFICATIONS[2]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("raise exception.FlavorDiskTooSmall()", tb)
self._verify_notification('instance-resize-error', self._verify_notification('instance-resize-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id'] 'uuid': server['id'],
'fault.traceback': self.ANY
}, },
actual=fake_notifier.VERSIONED_NOTIFICATIONS[2]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
@@ -970,10 +981,16 @@ class TestInstanceNotificationSample(
self.assertEqual(5, len(fake_notifier.VERSIONED_NOTIFICATIONS), self.assertEqual(5, len(fake_notifier.VERSIONED_NOTIFICATIONS),
'Unexpected number of notifications: %s' % 'Unexpected number of notifications: %s' %
fake_notifier.VERSIONED_NOTIFICATIONS) fake_notifier.VERSIONED_NOTIFICATIONS)
tb = fake_notifier.VERSIONED_NOTIFICATIONS[2]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("raise exception.FlavorDiskTooSmall()", tb)
self._verify_notification('instance-resize-error', self._verify_notification('instance-resize-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id'] 'uuid': server['id'],
'fault.traceback': self.ANY
}, },
actual=fake_notifier.VERSIONED_NOTIFICATIONS[2]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[2])
@@ -1146,12 +1163,18 @@ class TestInstanceNotificationSample(
self._wait_for_state_change(self.api, server, expected_status='ERROR') self._wait_for_state_change(self.api, server, expected_status='ERROR')
notification = self._get_notifications('instance.rebuild.error') notification = self._get_notifications('instance.rebuild.error')
self.assertEqual(1, len(notification)) self.assertEqual(1, len(notification))
tb = fake_notifier.VERSIONED_NOTIFICATIONS[0]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn('raise exception.VirtualInterfaceCreateException()', tb)
self._verify_notification( self._verify_notification(
'instance-rebuild-error', 'instance-rebuild-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id'], 'uuid': server['id'],
'trusted_image_certificates': None}, 'trusted_image_certificates': None,
'fault.traceback': self.ANY},
actual=notification[0]) actual=notification[0])
def _test_restore_server(self, server): def _test_restore_server(self, server):
@@ -1207,6 +1230,11 @@ class TestInstanceNotificationSample(
self._wait_for_notification('instance.reboot.start') self._wait_for_notification('instance.reboot.start')
self._wait_for_notification('instance.reboot.error') self._wait_for_notification('instance.reboot.error')
self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS)) self.assertEqual(2, len(fake_notifier.VERSIONED_NOTIFICATIONS))
tb = fake_notifier.VERSIONED_NOTIFICATIONS[1]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("raise exception.UnsupportedVirtType", tb)
self._verify_notification( self._verify_notification(
'instance-reboot-start', 'instance-reboot-start',
replacements={ replacements={
@@ -1217,7 +1245,8 @@ class TestInstanceNotificationSample(
'instance-reboot-error', 'instance-reboot-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id']}, 'uuid': server['id'],
'fault.traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
def _detach_volume_from_server(self, server, volume_id): def _detach_volume_from_server(self, server, volume_id):
@@ -1312,12 +1341,21 @@ class TestInstanceNotificationSample(
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id']}, 'uuid': server['id']},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[5]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[5])
tb1 = fake_notifier.VERSIONED_NOTIFICATIONS[6]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("_swap_volume", tb1)
tb2 = fake_notifier.VERSIONED_NOTIFICATIONS[7]['payload'][
'nova_object.data']['traceback']
self.assertIn("_swap_volume", tb2)
self._verify_notification( self._verify_notification(
'instance-volume_swap-error', 'instance-volume_swap-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'block_devices': block_devices, 'block_devices': block_devices,
'uuid': server['id']}, 'uuid': server['id'],
'fault.traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[6]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[6])
def test_resize_confirm_server(self): def test_resize_confirm_server(self):
@@ -1525,13 +1563,19 @@ class TestInstanceNotificationSample(
'volume_id': self.cinder.SWAP_NEW_VOL, 'volume_id': self.cinder.SWAP_NEW_VOL,
'uuid': server['id']}, 'uuid': server['id']},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
tb = fake_notifier.VERSIONED_NOTIFICATIONS[1]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("CinderConnectionFailed:", tb)
self._verify_notification( self._verify_notification(
'instance-volume_attach-error', 'instance-volume_attach-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'block_devices': block_devices, 'block_devices': block_devices,
'volume_id': self.cinder.SWAP_NEW_VOL, 'volume_id': self.cinder.SWAP_NEW_VOL,
'uuid': server['id']}, 'uuid': server['id'],
'fault.traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
@mock.patch('nova.volume.cinder.API.attachment_update') @mock.patch('nova.volume.cinder.API.attachment_update')
@@ -1605,11 +1649,17 @@ class TestInstanceNotificationSample(
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id']}, 'uuid': server['id']},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[0]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[0])
tb = fake_notifier.VERSIONED_NOTIFICATIONS[1]['payload'][
'nova_object.data']['fault']['nova_object.data']['traceback']
self.assertIn("raise exception.InterfaceAttachFailed", tb)
self._verify_notification( self._verify_notification(
'instance-interface_attach-error', 'instance-interface_attach-error',
replacements={ replacements={
'reservation_id': server['reservation_id'], 'reservation_id': server['reservation_id'],
'uuid': server['id']}, 'uuid': server['id'],
'fault.traceback': self.ANY},
actual=fake_notifier.VERSIONED_NOTIFICATIONS[1]) actual=fake_notifier.VERSIONED_NOTIFICATIONS[1])
+7 -5
View File
@@ -428,7 +428,8 @@ class ComputeVolumeTestCase(BaseTestCase):
mock.call(self.context, instance, 'fake-mini', mock.call(self.context, instance, 'fake-mini',
action='volume_attach', phase='error', action='volume_attach', phase='error',
volume_id=uuids.volume_id, volume_id=uuids.volume_id,
exception=expected_exception), exception=expected_exception,
tb=mock.ANY),
]) ])
mock_event.assert_called_once_with( mock_event.assert_called_once_with(
self.context, 'compute_attach_volume', CONF.host, self.context, 'compute_attach_volume', CONF.host,
@@ -468,7 +469,8 @@ class ComputeVolumeTestCase(BaseTestCase):
mock.call(self.context, instance, 'fake-mini', mock.call(self.context, instance, 'fake-mini',
action='volume_attach', phase='error', action='volume_attach', phase='error',
volume_id=uuids.volume_id, volume_id=uuids.volume_id,
exception=expected_exception), exception=expected_exception,
tb=mock.ANY),
]) ])
@mock.patch.object(compute_utils, 'EventReporter') @mock.patch.object(compute_utils, 'EventReporter')
@@ -3182,7 +3184,7 @@ class ComputeTestCase(BaseTestCase,
notify_action_call_list.append( notify_action_call_list.append(
mock.call(econtext, instance, 'fake-mini', mock.call(econtext, instance, 'fake-mini',
action='reboot', phase='error', exception=fault, action='reboot', phase='error', exception=fault,
bdms=bdms)) bdms=bdms, tb=mock.ANY))
notify_call_list.append(mock.call(econtext, instance, notify_call_list.append(mock.call(econtext, instance,
'reboot.end')) 'reboot.end'))
notify_action_call_list.append( notify_action_call_list.append(
@@ -10253,7 +10255,7 @@ class ComputeAPITestCase(BaseTestCase):
mock.call(self.context, instance, self.compute.host, mock.call(self.context, instance, self.compute.host,
action='interface_attach', action='interface_attach',
exception=mock_attach.side_effect, exception=mock_attach.side_effect,
phase='error')]) phase='error', tb=mock.ANY)])
@mock.patch.object(compute_utils, 'notify_about_instance_action') @mock.patch.object(compute_utils, 'notify_about_instance_action')
def test_detach_interface(self, mock_notify): def test_detach_interface(self, mock_notify):
@@ -12529,7 +12531,7 @@ class ComputeRescheduleResizeOrReraiseTestCase(BaseTestCase):
task_states.RESIZE_PREP, exc_info, host_list=None) task_states.RESIZE_PREP, exc_info, host_list=None)
mock_notify.assert_called_once_with( mock_notify.assert_called_once_with(
self.context, instance, 'fake-mini', action='resize', self.context, instance, 'fake-mini', action='resize',
phase='error', exception=mock_res.side_effect) phase='error', exception=mock_res.side_effect, tb=mock.ANY)
@mock.patch.object(compute_manager.ComputeManager, "_reschedule") @mock.patch.object(compute_manager.ComputeManager, "_reschedule")
def test_reschedule_false(self, mock_res): def test_reschedule_false(self, mock_res):
+4 -4
View File
@@ -2031,7 +2031,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
self.compute.host, self.compute.host,
fields.NotificationPhase.ERROR, fields.NotificationPhase.ERROR,
uuids.old_volume, uuids.new_volume, uuids.old_volume, uuids.new_volume,
test.MatchType(expected_exception)) test.MatchType(expected_exception), mock.ANY)
else: else:
self.compute.swap_volume(self.context, uuids.old_volume, self.compute.swap_volume(self.context, uuids.old_volume,
uuids.new_volume, instance1, None) uuids.new_volume, instance1, None)
@@ -3987,7 +3987,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
) )
mock_notify.assert_called_once_with( mock_notify.assert_called_once_with(
mock.ANY, instance, 'fake-mini', phase='error', exception=exc, mock.ANY, instance, 'fake-mini', phase='error', exception=exc,
bdms=None) bdms=None, tb=mock.ANY)
def test_rebuild_deleting(self): def test_rebuild_deleting(self):
instance = fake_instance.fake_instance_obj(self.context) instance = fake_instance.fake_instance_obj(self.context)
@@ -4115,7 +4115,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase):
elevated_context, instance, 'fake-node', node_type='destination') elevated_context, instance, 'fake-node', node_type='destination')
mock_notify.assert_called_once_with( mock_notify.assert_called_once_with(
elevated_context, instance, 'fake-mini', bdms=None, exception=exc, elevated_context, instance, 'fake-mini', bdms=None, exception=exc,
phase='error') phase='error', tb=mock.ANY)
def test_rebuild_node_not_updated_if_not_recreate(self): def test_rebuild_node_not_updated_if_not_recreate(self):
node = uuidutils.generate_uuid() # ironic node uuid node = uuidutils.generate_uuid() # ironic node uuid
@@ -5567,7 +5567,7 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock.call(self.context, self.instance, 'fake-mini', mock.call(self.context, self.instance, 'fake-mini',
phase='start', bdms=[]), phase='start', bdms=[]),
mock.call(self.context, self.instance, 'fake-mini', mock.call(self.context, self.instance, 'fake-mini',
phase='error', exception=exc, bdms=[])]) phase='error', exception=exc, bdms=[], tb=mock.ANY)])
save.assert_has_calls([ save.assert_has_calls([
mock.call(), mock.call(),
@@ -18,6 +18,7 @@
import copy import copy
import string import string
import traceback
import mock import mock
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
@@ -733,10 +734,11 @@ class UsageInfoTestCase(test.TestCase):
# To get exception trace, raise and catch an exception # To get exception trace, raise and catch an exception
raise test.TestingException('Volume swap error.') raise test.TestingException('Volume swap error.')
except Exception as ex: except Exception as ex:
tb = traceback.format_exc()
compute_utils.notify_about_volume_swap( compute_utils.notify_about_volume_swap(
self.context, instance, 'fake-compute', self.context, instance, 'fake-compute',
fields.NotificationPhase.ERROR, fields.NotificationPhase.ERROR,
uuids.old_volume_id, uuids.new_volume_id, ex) uuids.old_volume_id, uuids.new_volume_id, ex, tb)
self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1) self.assertEqual(len(fake_notifier.VERSIONED_NOTIFICATIONS), 1)
notification = fake_notifier.VERSIONED_NOTIFICATIONS[0] notification = fake_notifier.VERSIONED_NOTIFICATIONS[0]
@@ -776,6 +778,8 @@ class UsageInfoTestCase(test.TestCase):
exception_payload['function_name']) exception_payload['function_name'])
self.assertEqual('nova.tests.unit.compute.test_compute_utils', self.assertEqual('nova.tests.unit.compute.test_compute_utils',
exception_payload['module_name']) exception_payload['module_name'])
self.assertIn('test_notify_about_volume_swap_with_error',
exception_payload['traceback'])
def test_notify_about_instance_rescue_action(self): def test_notify_about_instance_rescue_action(self):
instance = create_instance(self.context) instance = create_instance(self.context)
@@ -371,7 +371,7 @@ notification_object_data = {
'BlockDevicePayload': '1.0-29751e1b6d41b1454e36768a1e764df8', 'BlockDevicePayload': '1.0-29751e1b6d41b1454e36768a1e764df8',
'EventType': '1.10-2a6ab743d767837366ab1aec387d7c3b', 'EventType': '1.10-2a6ab743d767837366ab1aec387d7c3b',
'ExceptionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'ExceptionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'ExceptionPayload': '1.0-27db46ee34cd97e39f2643ed92ad0cc5', 'ExceptionPayload': '1.1-6c43008bd81885a63bc7f7c629f0793b',
'FlavorNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'FlavorNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
'FlavorPayload': '1.4-2e7011b8b4e59167fe8b7a0a81f0d452', 'FlavorPayload': '1.4-2e7011b8b4e59167fe8b7a0a81f0d452',
'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56', 'InstanceActionNotification': '1.0-a73147b93b520ff0061865849d3dfa56',
+2 -1
View File
@@ -104,7 +104,7 @@ class WrapExceptionTestCase(test.NoDBTestCase):
payload = notification['payload'] payload = notification['payload']
self.assertEqual('ExceptionPayload', payload['nova_object.name']) self.assertEqual('ExceptionPayload', payload['nova_object.name'])
self.assertEqual('1.0', payload['nova_object.version']) self.assertEqual('1.1', payload['nova_object.version'])
payload = payload['nova_object.data'] payload = payload['nova_object.data']
self.assertEqual('TestingException', payload['exception']) self.assertEqual('TestingException', payload['exception'])
@@ -112,6 +112,7 @@ class WrapExceptionTestCase(test.NoDBTestCase):
self.assertEqual('bad_function_exception', payload['function_name']) self.assertEqual('bad_function_exception', payload['function_name'])
self.assertEqual('nova.tests.unit.test_exception', self.assertEqual('nova.tests.unit.test_exception',
payload['module_name']) payload['module_name'])
self.assertIn('bad_function_exception', payload['traceback'])
@mock.patch('nova.rpc.NOTIFIER') @mock.patch('nova.rpc.NOTIFIER')
@mock.patch('nova.notifications.objects.exception.' @mock.patch('nova.notifications.objects.exception.'
@@ -0,0 +1,10 @@
---
features:
- |
A new ``traceback`` field has been added to each versioned instance
notification. In an error notification this field contains the full
traceback string of the exception which caused the error notification.
See the `notification dev ref`_ for the sample file of
``instance.create.error`` as an example.
.. _notification dev ref: https://docs.openstack.org/nova/latest/reference/notifications.html#existing-versioned-notifications