diff --git a/nova/objects/instance_action.py b/nova/objects/instance_action.py index a84735f1927c..88ab7eb53987 100644 --- a/nova/objects/instance_action.py +++ b/nova/objects/instance_action.py @@ -14,6 +14,8 @@ import traceback +import six + from nova import db from nova.objects import base from nova.objects import fields @@ -115,7 +117,7 @@ def serialize_args(fn): exc_tb = kwargs.get('exc_tb') if exc_val is not None: kwargs['exc_val'] = str(exc_val) - if not isinstance(exc_tb, str) and exc_tb is not None: + if not isinstance(exc_tb, six.string_types) and exc_tb is not None: kwargs['exc_tb'] = ''.join(traceback.format_tb(exc_tb)) # NOTE(danms): We wrap a descriptor, so use that protocol return fn.__get__(None, cls)(*args, **kwargs) diff --git a/nova/tests/objects/test_instance_action.py b/nova/tests/objects/test_instance_action.py index c9e8109516a2..7611ced6900e 100644 --- a/nova/tests/objects/test_instance_action.py +++ b/nova/tests/objects/test_instance_action.py @@ -267,6 +267,7 @@ class _TestInstanceActionEventObject(object): @mock.patch.object(traceback, 'format_tb') @mock.patch.object(db, 'action_event_finish') def test_event_finish_with_failure_legacy(self, mock_finish, mock_tb): + # Tests that exc_tb is serialized when it's not a string type. mock_tb.return_value = 'fake-tb' timeutils.set_time_override(override_time=NOW) test_class = instance_action.InstanceActionEvent @@ -275,9 +276,28 @@ class _TestInstanceActionEventObject(object): expected_packed_values['finish_time'] = timeutils.utcnow() mock_finish.return_value = fake_event + fake_tb = mock.sentinel.fake_tb event = test_class.event_finish_with_failure( self.context, 'fake-uuid', 'fake-event', exc_val='val', - exc_tb=mock.sentinel.fake_tb, want_result=True) + exc_tb=fake_tb, want_result=True) + mock_finish.assert_called_once_with(self.context, + expected_packed_values) + self.compare_obj(event, fake_event) + mock_tb.assert_called_once_with(fake_tb) + + @mock.patch.object(db, 'action_event_finish') + def test_event_finish_with_failure_legacy_unicode(self, mock_finish): + # Tests that traceback.format_tb is not called when exc_tb is unicode. + timeutils.set_time_override(override_time=NOW) + test_class = instance_action.InstanceActionEvent + expected_packed_values = test_class.pack_action_event_finish( + self.context, 'fake-uuid', 'fake-event', 'val', unicode('fake-tb')) + expected_packed_values['finish_time'] = timeutils.utcnow() + + mock_finish.return_value = fake_event + event = test_class.event_finish_with_failure( + self.context, 'fake-uuid', 'fake-event', exc_val='val', + exc_tb=unicode('fake-tb'), want_result=True) mock_finish.assert_called_once_with(self.context, expected_packed_values) self.compare_obj(event, fake_event) @@ -285,6 +305,8 @@ class _TestInstanceActionEventObject(object): @mock.patch.object(traceback, 'format_tb') @mock.patch.object(db, 'action_event_finish') def test_event_finish_with_failure_no_result(self, mock_finish, mock_tb): + # Tests that traceback.format_tb is not called when exc_tb is a str + # and want_result is False, so no event should come back. mock_tb.return_value = 'fake-tb' timeutils.set_time_override(override_time=NOW) test_class = instance_action.InstanceActionEvent @@ -299,6 +321,7 @@ class _TestInstanceActionEventObject(object): mock_finish.assert_called_once_with(self.context, expected_packed_values) self.assertIsNone(event) + self.assertFalse(mock_tb.called) @mock.patch.object(db, 'action_events_get') def test_get_by_action(self, mock_get):