Allow to load a subset of object fields from DB

Currently, the logic contained inside the _from_db_object base ironic
object method is simply assignment of values in the DB object to the
oslo versioned object. This might not be that simple in case of nested
objects, for example in case of tags contained inside the node. This
change adds a possibility to select which fields to set as usual by
passing them as fields argument to _from_db_object method. Other fields
are loaded in the new _set_from_db_object method.

Change-Id: Ib2ca1acc52b9ba05297976f2c7ad67a63e07dbde
This commit is contained in:
Vladyslav Drok
2017-06-20 19:08:28 +03:00
parent cbedc8268c
commit 7960fdd2bc
2 changed files with 52 additions and 5 deletions

View File

@@ -143,8 +143,19 @@ class IronicObject(object_base.VersionedObject):
return self.__class__.VERSION
def _set_from_db_object(self, context, db_object, fields=None):
"""Sets object fields.
:param context: security context
:param db_object: A DB entity of the object
:param fields: list of fields to set on obj from values from db_object.
"""
fields = fields or self.fields
for field in fields:
self[field] = db_object[field]
@staticmethod
def _from_db_object(context, obj, db_object):
def _from_db_object(context, obj, db_object, fields=None):
"""Converts a database entity to a formal object.
This always converts the database entity to the latest version
@@ -156,6 +167,7 @@ class IronicObject(object_base.VersionedObject):
:param context: security context
:param obj: An object of the class.
:param db_object: A DB entity of the object
:param fields: list of fields to set on obj from values from db_object.
:return: The object of the class with the database entity added
:raises: ovo_exception.IncompatibleObjectVersion
"""
@@ -183,8 +195,7 @@ class IronicObject(object_base.VersionedObject):
objname=objname, objver=db_version,
supported=obj.__class__.VERSION)
for field in obj.fields:
obj[field] = db_object[field]
obj._set_from_db_object(context, db_object, fields)
obj._context = context

View File

@@ -38,8 +38,16 @@ class MyObj(base.IronicObject, object_base.VersionedObjectDictCompat):
fields = {'foo': fields.IntegerField(),
'bar': fields.StringField(),
'missing': fields.StringField(),
# nested object added as string for simplicity
'nested_object': fields.StringField(),
}
def _set_from_db_object(self, context, db_object, fields=None):
fields = set(fields or self.fields) - {'nested_object'}
super(MyObj, self)._set_from_db_object(context, db_object, fields)
# some special manipulation with nested_object here
self['nested_object'] = db_object.get('nested_object', '') + 'test'
def obj_load_attr(self, attrname):
if attrname == 'version':
setattr(self, attrname, None)
@@ -332,7 +340,7 @@ class _TestObject(object):
def test_object_inheritance(self):
base_fields = list(base.IronicObject.fields)
myobj_fields = ['foo', 'bar', 'missing'] + base_fields
myobj_fields = ['foo', 'bar', 'missing', 'nested_object'] + base_fields
myobj3_fields = ['new_field']
self.assertTrue(issubclass(TestSubclassedObject, MyObj))
self.assertEqual(len(myobj_fields), len(MyObj.fields))
@@ -503,6 +511,34 @@ class _TestObject(object):
self.assertEqual('test', obj.bar)
self.assertEqual('foo', obj.missing)
@mock.patch('ironic.common.release_mappings.RELEASE_MAPPING',
autospec=True)
def test__from_db_object_no_version_subset_of_fields(self,
mock_release_mapping):
# DB doesn't have version; get it from mapping
mock_release_mapping.__getitem__.return_value = {
'objects': {
'MyObj': '1.5',
}
}
obj = MyObj(self.context)
dbobj = {'created_at': timeutils.utcnow(),
'updated_at': timeutils.utcnow(),
'version': None,
'foo': 123, 'bar': 'test', 'missing': '',
'nested_object': 'test'}
# Mock obj_load_attr as this is what is called if an attribute that we
# try to access is not yet set. For all ironic objects it's a noop,
# we've implemented it in MyObj purely for testing
with mock.patch.object(obj, 'obj_load_attr'):
MyObj._from_db_object(self.context, obj, dbobj,
fields=['foo', 'bar'])
self.assertEqual(obj.__class__.VERSION, obj.VERSION)
self.assertEqual(123, obj.foo)
self.assertEqual('test', obj.bar)
self.assertRaises(AttributeError, getattr, obj, 'missing')
self.assertEqual('testtest', obj.nested_object)
@mock.patch('ironic.common.release_mappings.RELEASE_MAPPING',
autospec=True)
def test__from_db_object_map_version_bad(self, mock_release_mapping):
@@ -626,7 +662,7 @@ class TestObject(_LocalTest, _TestObject):
# The fingerprint values should only be changed if there is a version bump.
expected_object_fingerprints = {
'Node': '1.21-52674c214141cf3e09f8688bfed54577',
'MyObj': '1.5-4f5efe8f0fcaf182bbe1c7fe3ba858db',
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
'Port': '1.7-898a47921f4a1f53fcdddd4eeb179e0b',
'Portgroup': '1.3-71923a81a86743b313b190f5c675e258',