Save and re-raise exception

When an exception is raised, we do "some stuff" and re-raise that
original exception. This uses oslo_util's
excutils.save_and_reraise_exception to do this, to handle the case
where "some stuff" might cause the exception context to be cleared
(which would have resulted in None being attempted to be re-raised).

Change-Id: I91234797ab71399f6f9bac3a3ab35a6eeb2a87f3
This commit is contained in:
Ruby Loo
2015-07-30 13:51:08 +00:00
parent a196c717e6
commit 576dead9ff
3 changed files with 62 additions and 18 deletions

View File

@@ -20,6 +20,7 @@ import copy
import logging import logging
import oslo_messaging as messaging import oslo_messaging as messaging
from oslo_utils import excutils
import six import six
from oslo_versionedobjects._i18n import _, _LE from oslo_versionedobjects._i18n import _, _LE
@@ -80,9 +81,10 @@ def _make_class_properties(cls):
try: try:
return setattr(self, attrname, field_value) return setattr(self, attrname, field_value)
except Exception: except Exception:
attr = "%s.%s" % (self.obj_name(), name) with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error setting %(attr)s'), {'attr': attr}) attr = "%s.%s" % (self.obj_name(), name)
raise LOG.exception(_LE('Error setting %(attr)s'),
{'attr': attr})
def deleter(self, name=name): def deleter(self, name=name):
attrname = _get_attrname(name) attrname = _get_attrname(name)
@@ -839,21 +841,24 @@ class VersionedObjectSerializer(messaging.NoOpSerializer):
return self.OBJ_BASE_CLASS.obj_from_primitive( return self.OBJ_BASE_CLASS.obj_from_primitive(
objprim, context=context) objprim, context=context)
except exception.IncompatibleObjectVersion: except exception.IncompatibleObjectVersion:
verkey = '%s.version' % self.OBJ_BASE_CLASS.OBJ_SERIAL_NAMESPACE with excutils.save_and_reraise_exception(reraise=False) as ctxt:
objver = objprim[verkey] verkey = \
if objver.count('.') == 2: '%s.version' % self.OBJ_BASE_CLASS.OBJ_SERIAL_NAMESPACE
# NOTE(danms): For our purposes, the .z part of the version objver = objprim[verkey]
# should be safe to accept without requiring a backport if objver.count('.') == 2:
objprim[verkey] = \ # NOTE(danms): For our purposes, the .z part of the version
'.'.join(objver.split('.')[:2]) # should be safe to accept without requiring a backport
return self._process_object(context, objprim) objprim[verkey] = \
namekey = '%s.name' % self.OBJ_BASE_CLASS.OBJ_SERIAL_NAMESPACE '.'.join(objver.split('.')[:2])
objname = objprim[namekey] return self._process_object(context, objprim)
supported = VersionedObjectRegistry.obj_classes().get(objname, []) namekey = '%s.name' % self.OBJ_BASE_CLASS.OBJ_SERIAL_NAMESPACE
if self.OBJ_BASE_CLASS.indirection_api and supported: objname = objprim[namekey]
return self._do_backport(context, objprim, supported[0]) supported = VersionedObjectRegistry.obj_classes().get(objname,
else: [])
raise if self.OBJ_BASE_CLASS.indirection_api and supported:
return self._do_backport(context, objprim, supported[0])
else:
ctxt.reraise = True
def _process_iterable(self, context, action_fn, values): def _process_iterable(self, context, action_fn, values):
"""Process an iterable, taking an action on each value. """Process an iterable, taking an action on each value.

View File

@@ -15,6 +15,7 @@
import datetime import datetime
import iso8601 import iso8601
import mock
import six import six
from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import base as obj_base
@@ -470,6 +471,36 @@ class TestListOfSetsOfIntegers(TestField):
self.assertEqual('[set([1,2])]', self.field.stringify([set([1, 2])])) self.assertEqual('[set([1,2])]', self.field.stringify([set([1, 2])]))
class TestLocalMethods(test.TestCase):
@mock.patch.object(obj_base.LOG, 'exception')
def test__make_class_properties_setter_value_error(self, mock_log):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'intfield': fields.IntegerField(),
}
self.assertRaises(ValueError, AnObject, intfield='badvalue')
self.assertFalse(mock_log.called)
@mock.patch.object(obj_base.LOG, 'exception')
def test__make_class_properties_setter_setattr_fails(self, mock_log):
@obj_base.VersionedObjectRegistry.register
class AnObject(obj_base.VersionedObject):
fields = {
'intfield': fields.IntegerField(),
}
# We want the setattr() call in _make_class_properties.setter() to
# raise an exception
with mock.patch.object(obj_base, '_get_attrname') as mock_attr:
mock_attr.return_value = '__class__'
self.assertRaises(TypeError, AnObject, intfield=2)
mock_attr.assert_called_once_with('intfield')
mock_log.assert_called_once_with(mock.ANY,
{'attr': 'AnObject.intfield'})
class TestObject(TestField): class TestObject(TestField):
def setUp(self): def setUp(self):
super(TestObject, self).setUp() super(TestObject, self).setUp()

View File

@@ -1590,6 +1590,14 @@ class TestObjectSerializer(_BaseTestCase):
# .0 of the object. # .0 of the object.
self.assertEqual('1.6', obj.VERSION) self.assertEqual('1.6', obj.VERSION)
def test_deserialize_entity_newer_version_no_indirection(self):
ser = base.VersionedObjectSerializer()
obj = MyObj()
obj.VERSION = '1.25'
primitive = obj.obj_to_primitive()
self.assertRaises(exception.IncompatibleObjectVersion,
ser.deserialize_entity, self.context, primitive)
def _test_nested_backport(self, old): def _test_nested_backport(self, old):
@base.VersionedObjectRegistry.register @base.VersionedObjectRegistry.register
class Parent(base.VersionedObject): class Parent(base.VersionedObject):