Replace metaclass registry with explicit opt-in registry from oslo

This patch replaces Nova's magical metaclass object registry with the
one from oslo_versionedobjects.

Most of the changes here are related to test objects which previously
(and annoyingly) were automatically registered. It takes a more explicit
action to register these now, but also means we don't have to worry
about the version hash tracking picking up such objects.

There is a change to nova.exception here to copy the readonly exception
from the library, which is purely a tactic to reduce the amount of stuff
we have to change in this patch. The next patch will remove that and do
the necessary test conversions.

Related to blueprint use-oslo-objects

Change-Id: If3465f995dd05d2c01f01469b3ad9166c8362e8c
This commit is contained in:
Dan Smith 2015-06-03 10:25:38 -07:00
parent 3a037abdb0
commit d95c05a8fd
8 changed files with 133 additions and 254 deletions

View File

@ -28,6 +28,7 @@ import sys
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils from oslo_utils import excutils
from oslo_versionedobjects import exception as ovo_exc
import six import six
import webob.exc import webob.exc
@ -1509,8 +1510,9 @@ class IncompatibleObjectVersion(NovaException):
'maximum supported version is: %(supported)s') 'maximum supported version is: %(supported)s')
class ReadOnlyFieldError(NovaException): # FIXME(danms): Remove this and convert existing object tests to catch
msg_fmt = _('Cannot modify readonly field %(field)s') # the oslo_versionedobjects exception themselves
ReadOnlyFieldError = ovo_exc.ReadOnlyFieldError
class ObjectActionError(NovaException): class ObjectActionError(NovaException):

View File

@ -14,7 +14,6 @@
"""Nova common internal object model""" """Nova common internal object model"""
import collections
import contextlib import contextlib
import copy import copy
import datetime import datetime
@ -42,115 +41,16 @@ LOG = logging.getLogger('object')
def get_attrname(name): def get_attrname(name):
"""Return the mangled name of the attribute's underlying storage.""" """Return the mangled name of the attribute's underlying storage."""
return '_' + name # FIXME(danms): This is just until we use o.vo's class properties
# and object base.
return '_obj_' + name
def make_class_properties(cls): class NovaObjectRegistry(ovoo_base.VersionedObjectRegistry):
# NOTE(danms/comstud): Inherit fields from super classes. def registration_hook(self, cls, index):
# mro() returns the current class first and returns 'object' last, so # NOTE(danms): Set the *latest* version of this class
# those can be skipped. Also be careful to not overwrite any fields newest = self._registry._obj_classes[cls.obj_name()][0]
# that already exist. And make sure each cls has its own copy of setattr(objects, cls.obj_name(), newest)
# fields and that it is not sharing the dict with a super class.
cls.fields = dict(cls.fields)
for supercls in cls.mro()[1:-1]:
if not hasattr(supercls, 'fields'):
continue
for name, field in supercls.fields.items():
if name not in cls.fields:
cls.fields[name] = field
for name, field in six.iteritems(cls.fields):
if not isinstance(field, obj_fields.Field):
raise exception.ObjectFieldInvalid(
field=name, objname=cls.obj_name())
def getter(self, name=name):
attrname = get_attrname(name)
if not hasattr(self, attrname):
self.obj_load_attr(name)
return getattr(self, attrname)
def setter(self, value, name=name, field=field):
attrname = get_attrname(name)
field_value = field.coerce(self, name, value)
if field.read_only and hasattr(self, attrname):
# Note(yjiang5): _from_db_object() may iterate
# every field and write, no exception in such situation.
if getattr(self, attrname) != field_value:
raise exception.ReadOnlyFieldError(field=name)
else:
return
self._changed_fields.add(name)
try:
return setattr(self, attrname, field_value)
except Exception:
attr = "%s.%s" % (self.obj_name(), name)
LOG.exception(_LE('Error setting %(attr)s'), {'attr': attr})
raise
def deleter(self, name=name):
attrname = get_attrname(name)
if not hasattr(self, attrname):
raise AttributeError('No such attribute `%s' % name)
delattr(self, get_attrname(name))
setattr(cls, name, property(getter, setter, deleter))
# NOTE(danms): This is transitional to get the registration decorator
# on everything before we make a cut over
class NovaObjectRegistry(object):
classes = []
@classmethod
def register(cls, obj_cls):
cls.classes.append(obj_cls.obj_name())
return obj_cls
class NovaObjectMetaclass(type):
"""Metaclass that allows tracking of object classes."""
# NOTE(danms): This is what controls whether object operations are
# remoted. If this is not None, use it to remote things over RPC.
indirection_api = None
def __init__(cls, names, bases, dict_):
if not hasattr(cls, '_obj_classes'):
# This means this is a base class using the metaclass. I.e.,
# the 'NovaObject' class.
cls._obj_classes = collections.defaultdict(list)
return
def _vers_tuple(obj):
return tuple([int(x) for x in obj.VERSION.split(".")])
# Add the subclass to NovaObject._obj_classes. If the
# same version already exists, replace it. Otherwise,
# keep the list with newest version first.
make_class_properties(cls)
obj_name = cls.obj_name()
for i, obj in enumerate(cls._obj_classes[obj_name]):
if cls.VERSION == obj.VERSION:
cls._obj_classes[obj_name][i] = cls
# Update nova.objects with this newer class.
setattr(objects, obj_name, cls)
break
if _vers_tuple(cls) > _vers_tuple(obj):
# Insert before.
cls._obj_classes[obj_name].insert(i, cls)
if i == 0:
# Later version than we've seen before. Update
# nova.objects.
setattr(objects, obj_name, cls)
break
else:
cls._obj_classes[obj_name].append(cls)
# Either this is the first time we've seen the object or it's
# an older version than anything we'e seen. Update nova.objects
# only if it's the first time we've seen this object name.
if not hasattr(objects, obj_name):
setattr(objects, obj_name, cls)
# These are decorators that mark an object's method as remotable. # These are decorators that mark an object's method as remotable.
@ -220,7 +120,6 @@ def remotable(fn):
return wrapper return wrapper
@six.add_metaclass(NovaObjectMetaclass)
class NovaObject(object): class NovaObject(object):
"""Base class and object factory. """Base class and object factory.
@ -279,6 +178,9 @@ class NovaObject(object):
# since they were not added until version 1.2. # since they were not added until version 1.2.
obj_relationships = {} obj_relationships = {}
# Temporary until we inherit from o.vo.base.VersionedObject
indirection_api = None
def __init__(self, context=None, **kwargs): def __init__(self, context=None, **kwargs):
self._changed_fields = set() self._changed_fields = set()
self._context = context self._context = context
@ -304,7 +206,7 @@ class NovaObject(object):
@classmethod @classmethod
def obj_class_from_name(cls, objname, objver): def obj_class_from_name(cls, objname, objver):
"""Returns a class from the registry based on a name and version.""" """Returns a class from the registry based on a name and version."""
if objname not in cls._obj_classes: if objname not in NovaObjectRegistry.obj_classes():
LOG.error(_LE('Unable to instantiate unregistered object type ' LOG.error(_LE('Unable to instantiate unregistered object type '
'%(objtype)s'), dict(objtype=objname)) '%(objtype)s'), dict(objtype=objname))
raise exception.UnsupportedObjectError(objtype=objname) raise exception.UnsupportedObjectError(objtype=objname)
@ -315,7 +217,8 @@ class NovaObject(object):
# once below. # once below.
compatible_match = None compatible_match = None
for objclass in cls._obj_classes[objname]: obj_classes = NovaObjectRegistry.obj_classes()
for objclass in obj_classes[objname]:
if objclass.VERSION == objver: if objclass.VERSION == objver:
return objclass return objclass
if (not compatible_match and if (not compatible_match and
@ -326,7 +229,7 @@ class NovaObject(object):
return compatible_match return compatible_match
# As mentioned above, latest version is always first in the list. # As mentioned above, latest version is always first in the list.
latest_ver = cls._obj_classes[objname][0].VERSION latest_ver = obj_classes[objname][0].VERSION
raise exception.IncompatibleObjectVersion(objname=objname, raise exception.IncompatibleObjectVersion(objname=objname,
objver=objver, objver=objver,
supported=latest_ver) supported=latest_ver)

View File

@ -233,7 +233,7 @@ class TestCase(testtools.TestCase):
# registry. # registry.
objects_base.NovaObject.indirection_api = None objects_base.NovaObject.indirection_api = None
self._base_test_obj_backup = copy.copy( self._base_test_obj_backup = copy.copy(
objects_base.NovaObject._obj_classes) objects_base.NovaObjectRegistry._registry._obj_classes)
self.addCleanup(self._restore_obj_registry) self.addCleanup(self._restore_obj_registry)
# NOTE(mnaser): All calls to utils.is_neutron() are cached in # NOTE(mnaser): All calls to utils.is_neutron() are cached in
@ -251,7 +251,8 @@ class TestCase(testtools.TestCase):
self.useFixture(nova_fixtures.PoisonFunctions()) self.useFixture(nova_fixtures.PoisonFunctions())
def _restore_obj_registry(self): def _restore_obj_registry(self):
objects_base.NovaObject._obj_classes = self._base_test_obj_backup objects_base.NovaObjectRegistry._registry._obj_classes = \
self._base_test_obj_backup
def _clear_attrs(self): def _clear_attrs(self):
# Delete attributes that don't start with _ so they don't pin # Delete attributes that don't start with _ so they don't pin

View File

@ -296,6 +296,8 @@ class CellsMessageClassesTestCase(test.TestCase):
""" """
fields = {'test': objects_fields.StringField()} fields = {'test': objects_fields.StringField()}
objects_base.NovaObjectRegistry.register(CellsMsgingTestObject)
test_obj = CellsMsgingTestObject() test_obj = CellsMsgingTestObject()
test_obj.test = 'meow' test_obj.test = 'meow'

View File

@ -420,6 +420,8 @@ class ConductorTestCase(_BaseTestCase, test.TestCase):
else: else:
return 'test' return 'test'
obj_base.NovaObjectRegistry.register(TestObject)
obj = TestObject() obj = TestObject()
# NOTE(danms): After a trip over RPC, any tuple will be a list, # NOTE(danms): After a trip over RPC, any tuple will be a list,
# so use a list here to make sure we can handle it # so use a list here to make sure we can handle it
@ -456,6 +458,8 @@ class ConductorTestCase(_BaseTestCase, test.TestCase):
self.dict['foo'] = 'bar' self.dict['foo'] = 'bar'
self.obj_reset_changes() self.obj_reset_changes()
obj_base.NovaObjectRegistry.register(TestObject)
obj = TestObject() obj = TestObject()
obj.dict = {} obj.dict = {}
obj.obj_reset_changes() obj.obj_reset_changes()

View File

@ -725,6 +725,9 @@ class TestObject(TestField):
class OtherTestableObject(obj_base.NovaObject): class OtherTestableObject(obj_base.NovaObject):
pass pass
obj_base.NovaObjectRegistry.register(TestableObject)
obj_base.NovaObjectRegistry.register(OtherTestableObject)
test_inst = TestableObject() test_inst = TestableObject()
self._test_cls = TestableObject self._test_cls = TestableObject
self.field = fields.Field(fields.Object('TestableObject')) self.field = fields.Field(fields.Object('TestableObject'))

View File

@ -199,10 +199,10 @@ class _TestInstanceObject(object):
).AndReturn(fake_inst2) ).AndReturn(fake_inst2)
self.mox.ReplayAll() self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(self.context, fake_uuid) inst = instance.Instance.get_by_uuid(self.context, fake_uuid)
self.assertFalse(hasattr(inst, '_metadata')) self.assertFalse(hasattr(inst, '_obj_metadata'))
meta = inst.metadata meta = inst.metadata
self.assertEqual(meta, {'foo': 'bar'}) self.assertEqual(meta, {'foo': 'bar'})
self.assertTrue(hasattr(inst, '_metadata')) self.assertTrue(hasattr(inst, '_obj_metadata'))
# Make sure we don't run load again # Make sure we don't run load again
meta2 = inst.metadata meta2 = inst.metadata
self.assertEqual(meta2, {'foo': 'bar'}) self.assertEqual(meta2, {'foo': 'bar'})
@ -1330,6 +1330,8 @@ class TestRemoteInstanceObject(test_objects._RemoteTest,
class OldInstance(objects.Instance): class OldInstance(objects.Instance):
VERSION = '1.17' VERSION = '1.17'
base.NovaObjectRegistry.register(OldInstance)
inst = OldInstance.get_by_uuid(self.context, inst.uuid) inst = OldInstance.get_by_uuid(self.context, inst.uuid)
self.assertFalse(inst.obj_attr_is_set('system_metadata')) self.assertFalse(inst.obj_attr_is_set('system_metadata'))
self.assertEqual('bar', inst.system_metadata['foo']) self.assertEqual('bar', inst.system_metadata['foo'])
@ -1348,6 +1350,8 @@ class TestRemoteInstanceObject(test_objects._RemoteTest,
class OldInstance(objects.Instance): class OldInstance(objects.Instance):
VERSION = '1.17' VERSION = '1.17'
base.NovaObjectRegistry.register(OldInstance)
inst = OldInstance.get_by_uuid(self.context, inst.uuid, inst = OldInstance.get_by_uuid(self.context, inst.uuid,
expected_attrs=['system_metadata']) expected_attrs=['system_metadata'])
self.assertTrue(inst.obj_attr_is_set('system_metadata')) self.assertTrue(inst.obj_attr_is_set('system_metadata'))

View File

@ -21,19 +21,20 @@ import inspect
import os import os
import pprint import pprint
import fixtures
import mock import mock
from oslo_log import log from oslo_log import log
from oslo_utils import timeutils from oslo_utils import timeutils
from oslo_versionedobjects import exception as ovo_exc
from oslo_versionedobjects import fixture
import six import six
from testtools import matchers from testtools import matchers
from nova.conductor import rpcapi as conductor_rpcapi
from nova import context from nova import context
from nova import exception from nova import exception
from nova import objects from nova import objects
from nova.objects import base from nova.objects import base
from nova.objects import fields from nova.objects import fields
from nova import rpc
from nova import test from nova import test
from nova.tests import fixtures as nova_fixtures from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import fake_notifier from nova.tests.unit import fake_notifier
@ -122,6 +123,10 @@ class MyObjDiffVers(MyObj):
class MyObj2(object): class MyObj2(object):
fields = {
'bar': fields.StringField(),
}
@classmethod @classmethod
def obj_name(cls): def obj_name(cls):
return 'MyObj' return 'MyObj'
@ -136,78 +141,15 @@ class RandomMixInWithNoFields(object):
pass pass
@base.NovaObjectRegistry.register_if(False)
class TestSubclassedObject(RandomMixInWithNoFields, MyObj): class TestSubclassedObject(RandomMixInWithNoFields, MyObj):
fields = {'new_field': fields.StringField()} fields = {'new_field': fields.StringField()}
class TestMetaclass(test.NoDBTestCase):
def test_obj_tracking(self):
@six.add_metaclass(base.NovaObjectMetaclass)
class NewBaseClass(object):
VERSION = '1.0'
fields = {}
@classmethod
def obj_name(cls):
return cls.__name__
class Fake1TestObj1(NewBaseClass):
@classmethod
def obj_name(cls):
return 'fake1'
class Fake1TestObj2(Fake1TestObj1):
pass
class Fake1TestObj3(Fake1TestObj1):
VERSION = '1.1'
class Fake2TestObj1(NewBaseClass):
@classmethod
def obj_name(cls):
return 'fake2'
class Fake1TestObj4(Fake1TestObj3):
VERSION = '1.2'
class Fake2TestObj2(Fake2TestObj1):
VERSION = '1.1'
class Fake1TestObj5(Fake1TestObj1):
VERSION = '1.1'
# Newest versions first in the list. Duplicate versions take the
# newest object.
expected = {'fake1': [Fake1TestObj4, Fake1TestObj5, Fake1TestObj2],
'fake2': [Fake2TestObj2, Fake2TestObj1]}
self.assertEqual(expected, NewBaseClass._obj_classes)
# The following should work, also.
self.assertEqual(expected, Fake1TestObj1._obj_classes)
self.assertEqual(expected, Fake1TestObj2._obj_classes)
self.assertEqual(expected, Fake1TestObj3._obj_classes)
self.assertEqual(expected, Fake1TestObj4._obj_classes)
self.assertEqual(expected, Fake1TestObj5._obj_classes)
self.assertEqual(expected, Fake2TestObj1._obj_classes)
self.assertEqual(expected, Fake2TestObj2._obj_classes)
def test_field_checking(self):
def create_class(field):
class TestField(base.NovaObject):
VERSION = '1.5'
fields = {'foo': field()}
return TestField
create_class(fields.IPV4AndV6AddressField)
self.assertRaises(exception.ObjectFieldInvalid,
create_class, fields.IPV4AndV6Address)
self.assertRaises(exception.ObjectFieldInvalid,
create_class, int)
class TestObjToPrimitive(test.NoDBTestCase): class TestObjToPrimitive(test.NoDBTestCase):
def test_obj_to_primitive_list(self): def test_obj_to_primitive_list(self):
@base.NovaObjectRegistry.register_if(False)
class MyObjElement(base.NovaObject): class MyObjElement(base.NovaObject):
fields = {'foo': fields.IntegerField()} fields = {'foo': fields.IntegerField()}
@ -215,6 +157,7 @@ class TestObjToPrimitive(test.NoDBTestCase):
super(MyObjElement, self).__init__() super(MyObjElement, self).__init__()
self.foo = foo self.foo = foo
@base.NovaObjectRegistry.register_if(False)
class MyList(base.ObjectListBase, base.NovaObject): class MyList(base.ObjectListBase, base.NovaObject):
fields = {'objects': fields.ListOfObjectsField('MyObjElement')} fields = {'objects': fields.ListOfObjectsField('MyObjElement')}
@ -224,11 +167,14 @@ class TestObjToPrimitive(test.NoDBTestCase):
[x['foo'] for x in base.obj_to_primitive(mylist)]) [x['foo'] for x in base.obj_to_primitive(mylist)])
def test_obj_to_primitive_dict(self): def test_obj_to_primitive_dict(self):
base.NovaObjectRegistry.register(MyObj)
myobj = MyObj(foo=1, bar='foo') myobj = MyObj(foo=1, bar='foo')
self.assertEqual({'foo': 1, 'bar': 'foo'}, self.assertEqual({'foo': 1, 'bar': 'foo'},
base.obj_to_primitive(myobj)) base.obj_to_primitive(myobj))
def test_obj_to_primitive_recursive(self): def test_obj_to_primitive_recursive(self):
base.NovaObjectRegistry.register(MyObj)
class MyList(base.ObjectListBase, base.NovaObject): class MyList(base.ObjectListBase, base.NovaObject):
fields = {'objects': fields.ListOfObjectsField('MyObj')} fields = {'objects': fields.ListOfObjectsField('MyObj')}
@ -239,6 +185,7 @@ class TestObjToPrimitive(test.NoDBTestCase):
base.obj_to_primitive(mylist)) base.obj_to_primitive(mylist))
def test_obj_to_primitive_with_ip_addr(self): def test_obj_to_primitive_with_ip_addr(self):
@base.NovaObjectRegistry.register_if(False)
class TestObject(base.NovaObject): class TestObject(base.NovaObject):
fields = {'addr': fields.IPAddressField(), fields = {'addr': fields.IPAddressField(),
'cidr': fields.IPNetworkField()} 'cidr': fields.IPNetworkField()}
@ -316,6 +263,12 @@ class _BaseTestCase(test.TestCase):
fake_notifier.stub_notifier(self.stubs) fake_notifier.stub_notifier(self.stubs)
self.addCleanup(fake_notifier.reset) self.addCleanup(fake_notifier.reset)
# NOTE(danms): register these here instead of at import time
# so that they're not always present
base.NovaObjectRegistry.register(MyObj)
base.NovaObjectRegistry.register(MyObjDiffVers)
base.NovaObjectRegistry.register(MyOwnedObject)
def compare_obj(self, obj, db_obj, subs=None, allow_missing=None, def compare_obj(self, obj, db_obj, subs=None, allow_missing=None,
comparators=None): comparators=None):
compare_obj(self, obj, db_obj, subs=subs, allow_missing=allow_missing, compare_obj(self, obj, db_obj, subs=subs, allow_missing=allow_missing,
@ -356,51 +309,57 @@ def things_temporarily_local():
base.NovaObject.indirection_api = _api base.NovaObject.indirection_api = _api
class FakeIndirectionHack(fixture.FakeIndirectionAPI):
def object_action(self, context, objinst, objmethod, args, kwargs):
objinst = self._ser.deserialize_entity(
context, self._ser.serialize_entity(
context, objinst))
objmethod = six.text_type(objmethod)
args = self._ser.deserialize_entity(
None, self._ser.serialize_entity(None, args))
kwargs = self._ser.deserialize_entity(
None, self._ser.serialize_entity(None, kwargs))
original = objinst.obj_clone()
with mock.patch('nova.objects.base.NovaObject.'
'indirection_api', new=None):
result = getattr(objinst, objmethod)(*args, **kwargs)
updates = self._get_changes(original, objinst)
updates['obj_what_changed'] = objinst.obj_what_changed()
return updates, result
def object_class_action(self, context, objname, objmethod, objver,
args, kwargs):
objname = six.text_type(objname)
objmethod = six.text_type(objmethod)
objver = six.text_type(objver)
args = self._ser.deserialize_entity(
None, self._ser.serialize_entity(None, args))
kwargs = self._ser.deserialize_entity(
None, self._ser.serialize_entity(None, kwargs))
cls = base.NovaObject.obj_class_from_name(objname, objver)
with mock.patch('nova.objects.base.NovaObject.'
'indirection_api', new=None):
result = getattr(cls, objmethod)(context, *args, **kwargs)
return (base.NovaObject.obj_from_primitive(
result.obj_to_primitive(target_version=objver),
context=context)
if isinstance(result, base.NovaObject) else result)
class IndirectionFixture(fixtures.Fixture):
def setUp(self):
super(IndirectionFixture, self).setUp()
ser = base.NovaObjectSerializer()
self.indirection_api = FakeIndirectionHack(serializer=ser)
self.useFixture(fixtures.MonkeyPatch(
'nova.objects.base.NovaObject.indirection_api',
self.indirection_api))
class _RemoteTest(_BaseTestCase): class _RemoteTest(_BaseTestCase):
def _testable_conductor(self):
self.conductor_service = self.start_service(
'conductor', manager='nova.conductor.manager.ConductorManager')
self.remote_object_calls = list()
orig_object_class_action = \
self.conductor_service.manager.object_class_action
orig_object_action = \
self.conductor_service.manager.object_action
def fake_object_class_action(*args, **kwargs):
self.remote_object_calls.append((kwargs.get('objname'),
kwargs.get('objmethod')))
with things_temporarily_local():
result = orig_object_class_action(*args, **kwargs)
return (base.NovaObject.obj_from_primitive(result, context=args[0])
if isinstance(result, base.NovaObject) else result)
self.stubs.Set(self.conductor_service.manager, 'object_class_action',
fake_object_class_action)
def fake_object_action(*args, **kwargs):
self.remote_object_calls.append((kwargs.get('objinst'),
kwargs.get('objmethod')))
with things_temporarily_local():
result = orig_object_action(*args, **kwargs)
return result
self.stubs.Set(self.conductor_service.manager, 'object_action',
fake_object_action)
# Things are remoted by default in this session
self.useFixture(nova_fixtures.IndirectionAPIFixture(
conductor_rpcapi.ConductorAPI()))
# To make sure local and remote contexts match
self.stubs.Set(rpc.RequestContextSerializer,
'serialize_context',
lambda s, c: c)
self.stubs.Set(rpc.RequestContextSerializer,
'deserialize_context',
lambda s, c: c)
def setUp(self): def setUp(self):
super(_RemoteTest, self).setUp() super(_RemoteTest, self).setUp()
self._testable_conductor() self.useFixture(IndirectionFixture())
class _TestObject(object): class _TestObject(object):
@ -494,6 +453,7 @@ class _TestObject(object):
self.assertEqual(obj.bar, 'loaded!') self.assertEqual(obj.bar, 'loaded!')
def test_load_in_base(self): def test_load_in_base(self):
@base.NovaObjectRegistry.register_if(False)
class Foo(base.NovaObject): class Foo(base.NovaObject):
fields = {'foobar': fields.IntegerField()} fields = {'foobar': fields.IntegerField()}
obj = Foo() obj = Foo()
@ -586,6 +546,7 @@ class _TestObject(object):
self.assertIsInstance(obj.rel_object, MyOwnedObject) self.assertIsInstance(obj.rel_object, MyOwnedObject)
def test_changed_with_sub_object(self): def test_changed_with_sub_object(self):
@base.NovaObjectRegistry.register_if(False)
class ParentObject(base.NovaObject): class ParentObject(base.NovaObject):
fields = {'foo': fields.IntegerField(), fields = {'foo': fields.IntegerField(),
'bar': fields.ObjectField('MyObj'), 'bar': fields.ObjectField('MyObj'),
@ -734,6 +695,7 @@ class _TestObject(object):
self.assertEqual({}, obj.obj_get_changes()) self.assertEqual({}, obj.obj_get_changes())
def test_obj_fields(self): def test_obj_fields(self):
@base.NovaObjectRegistry.register_if(False)
class TestObj(base.NovaObject): class TestObj(base.NovaObject):
fields = {'foo': fields.IntegerField()} fields = {'foo': fields.IntegerField()}
obj_extra_fields = ['bar'] obj_extra_fields = ['bar']
@ -754,7 +716,7 @@ class _TestObject(object):
def test_obj_read_only(self): def test_obj_read_only(self):
obj = MyObj(context=self.context, foo=123, bar='abc') obj = MyObj(context=self.context, foo=123, bar='abc')
obj.readonly = 1 obj.readonly = 1
self.assertRaises(exception.ReadOnlyFieldError, setattr, self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
obj, 'readonly', 2) obj, 'readonly', 2)
def test_obj_mutable_default(self): def test_obj_mutable_default(self):
@ -853,12 +815,14 @@ class _TestObject(object):
obj.obj_make_compatible, {}, '1.0') obj.obj_make_compatible, {}, '1.0')
def test_obj_make_compatible_doesnt_skip_falsey_sub_objects(self): def test_obj_make_compatible_doesnt_skip_falsey_sub_objects(self):
@base.NovaObjectRegistry.register_if(False)
class MyList(base.ObjectListBase, base.NovaObject): class MyList(base.ObjectListBase, base.NovaObject):
VERSION = '1.2' VERSION = '1.2'
fields = {'objects': fields.ListOfObjectsField('MyObjElement')} fields = {'objects': fields.ListOfObjectsField('MyObjElement')}
mylist = MyList(objects=[]) mylist = MyList(objects=[])
@base.NovaObjectRegistry.register_if(False)
class MyOwner(base.NovaObject): class MyOwner(base.NovaObject):
VERSION = '1.2' VERSION = '1.2'
fields = {'mylist': fields.ObjectField('MyList')} fields = {'mylist': fields.ObjectField('MyList')}
@ -978,6 +942,8 @@ class TestObjectSerializer(_BaseTestCase):
class MyTestObj(MyObj): class MyTestObj(MyObj):
VERSION = my_version VERSION = my_version
base.NovaObjectRegistry.register(MyTestObj)
obj = MyTestObj() obj = MyTestObj()
obj.VERSION = obj_version obj.VERSION = obj_version
primitive = obj.obj_to_primitive() primitive = obj.obj_to_primitive()
@ -1135,8 +1101,6 @@ object_data = {
'KeyPairList': '1.2-60f984184dc5a8eba6e34e20cbabef04', 'KeyPairList': '1.2-60f984184dc5a8eba6e34e20cbabef04',
'Migration': '1.2-331b1f37d0b20b932614181b9832c860', 'Migration': '1.2-331b1f37d0b20b932614181b9832c860',
'MigrationList': '1.2-5e79c0693d7ebe4e9ac03b5db11ab243', 'MigrationList': '1.2-5e79c0693d7ebe4e9ac03b5db11ab243',
'MyObj': '1.6-ee7b607402fbfb3390a92ab7199e0d88',
'MyOwnedObject': '1.0-fec853730bd02d54cc32771dd67f08a0',
'NUMACell': '1.2-74fc993ac5c83005e76e34e8487f1c05', 'NUMACell': '1.2-74fc993ac5c83005e76e34e8487f1c05',
'NUMAPagesTopology': '1.0-c71d86317283266dc8364c149155e48e', 'NUMAPagesTopology': '1.0-c71d86317283266dc8364c149155e48e',
'NUMATopology': '1.2-c63fad38be73b6afd04715c9c1b29220', 'NUMATopology': '1.2-c63fad38be73b6afd04715c9c1b29220',
@ -1160,7 +1124,6 @@ object_data = {
'ServiceList': '1.10-2f49ab65571c0edcbf623f664da612c0', 'ServiceList': '1.10-2f49ab65571c0edcbf623f664da612c0',
'Tag': '1.0-616bf44af4a22e853c17b37a758ec73e', 'Tag': '1.0-616bf44af4a22e853c17b37a758ec73e',
'TagList': '1.0-e16d65894484b7530b720792ffbbbd02', 'TagList': '1.0-e16d65894484b7530b720792ffbbbd02',
'TestSubclassedObject': '1.6-716fc8b481c9374f7e222de03ba0a621',
'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee', 'VirtCPUFeature': '1.0-3310718d8c72309259a6e39bdefe83ee',
'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3', 'VirtCPUModel': '1.0-6a5cc9f322729fc70ddc6733bacd57d3',
'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563', 'VirtCPUTopology': '1.0-fc694de72e20298f7c6bab1083fd4563',
@ -1191,12 +1154,10 @@ object_relationships = {
'InstanceNUMACell': {'VirtCPUTopology': '1.0'}, 'InstanceNUMACell': {'VirtCPUTopology': '1.0'},
'InstanceNUMATopology': {'InstanceNUMACell': '1.2'}, 'InstanceNUMATopology': {'InstanceNUMACell': '1.2'},
'InstancePCIRequests': {'InstancePCIRequest': '1.1'}, 'InstancePCIRequests': {'InstancePCIRequest': '1.1'},
'MyObj': {'MyOwnedObject': '1.0'},
'NUMACell': {'NUMAPagesTopology': '1.0'}, 'NUMACell': {'NUMAPagesTopology': '1.0'},
'NUMATopology': {'NUMACell': '1.2'}, 'NUMATopology': {'NUMACell': '1.2'},
'SecurityGroupRule': {'SecurityGroup': '1.1'}, 'SecurityGroupRule': {'SecurityGroup': '1.1'},
'Service': {'ComputeNode': '1.11'}, 'Service': {'ComputeNode': '1.11'},
'TestSubclassedObject': {'MyOwnedObject': '1.0'},
'VirtCPUModel': {'VirtCPUFeature': '1.0', 'VirtCPUTopology': '1.0'}, 'VirtCPUModel': {'VirtCPUFeature': '1.0', 'VirtCPUTopology': '1.0'},
} }
@ -1218,7 +1179,8 @@ class TestObjectVersions(test.NoDBTestCase):
return None return None
def _get_fingerprint(self, obj_name): def _get_fingerprint(self, obj_name):
obj_class = base.NovaObject._obj_classes[obj_name][0] obj_classes = base.NovaObjectRegistry.obj_classes()
obj_class = obj_classes[obj_name][0]
fields = obj_class.fields.items() fields = obj_class.fields.items()
fields.sort() fields.sort()
methods = [] methods = []
@ -1246,7 +1208,8 @@ class TestObjectVersions(test.NoDBTestCase):
def test_versions(self): def test_versions(self):
fingerprints = {} fingerprints = {}
for obj_name in base.NovaObject._obj_classes: obj_classes = base.NovaObjectRegistry.obj_classes()
for obj_name in obj_classes:
fingerprints[obj_name] = self._get_fingerprint(obj_name) fingerprints[obj_name] = self._get_fingerprint(obj_name)
if os.getenv('GENERATE_HASHES'): if os.getenv('GENERATE_HASHES'):
@ -1269,15 +1232,6 @@ class TestObjectVersions(test.NoDBTestCase):
'versions have been bumped, and then update their ' 'versions have been bumped, and then update their '
'hashes here.') 'hashes here.')
def test_registry_matches_metaclass(self):
reference = set(object_data.keys())
actual = set(base.NovaObjectRegistry.classes)
test_objects = set(['MyObj', 'MyOwnedObject', 'TestSubclassedObject'])
# NOTE(danms): In the new registry, we don't implicitly track test
# objects, so make sure that the difference between the metaclass and
# the opt-in registry is the set of test objects.
self.assertEqual(test_objects, reference.symmetric_difference(actual))
def _get_object_field_name(self, field): def _get_object_field_name(self, field):
if isinstance(field._type, fields.Object): if isinstance(field._type, fields.Object):
return field._type._obj_name return field._type._obj_name
@ -1290,6 +1244,7 @@ class TestObjectVersions(test.NoDBTestCase):
if obj_name in tree: if obj_name in tree:
return return
obj_classes = base.NovaObjectRegistry.obj_classes()
for name, field in obj_class.fields.items(): for name, field in obj_class.fields.items():
# Notes(yjiang5): ObjectListBase should be covered by # Notes(yjiang5): ObjectListBase should be covered by
# child_versions test # child_versions test
@ -1298,15 +1253,16 @@ class TestObjectVersions(test.NoDBTestCase):
continue continue
sub_obj_name = self._get_object_field_name(field) sub_obj_name = self._get_object_field_name(field)
if sub_obj_name: if sub_obj_name:
sub_obj_class = base.NovaObject._obj_classes[sub_obj_name][0] sub_obj_class = obj_classes[sub_obj_name][0]
self._build_tree(tree, sub_obj_class) self._build_tree(tree, sub_obj_class)
tree.setdefault(obj_name, {}) tree.setdefault(obj_name, {})
tree[obj_name][sub_obj_name] = sub_obj_class.VERSION tree[obj_name][sub_obj_name] = sub_obj_class.VERSION
def test_relationships(self): def test_relationships(self):
tree = {} tree = {}
for obj_name in base.NovaObject._obj_classes.keys(): obj_classes = base.NovaObjectRegistry.obj_classes()
self._build_tree(tree, base.NovaObject._obj_classes[obj_name][0]) for obj_name in obj_classes.keys():
self._build_tree(tree, obj_classes[obj_name][0])
stored = set([(x, str(y)) for x, y in object_relationships.items()]) stored = set([(x, str(y)) for x, y in object_relationships.items()])
computed = set([(x, str(y)) for x, y in tree.items()]) computed = set([(x, str(y)) for x, y in tree.items()])
@ -1329,8 +1285,9 @@ class TestObjectVersions(test.NoDBTestCase):
# This doesn't actually test the data conversions, but it at least # This doesn't actually test the data conversions, but it at least
# makes sure the method doesn't blow up on something basic like # makes sure the method doesn't blow up on something basic like
# expecting the wrong version format. # expecting the wrong version format.
for obj_name in base.NovaObject._obj_classes: obj_classes = base.NovaObjectRegistry.obj_classes()
obj_class = base.NovaObject._obj_classes[obj_name][0] for obj_name in obj_classes:
obj_class = obj_classes[obj_name][0]
version = utils.convert_version_to_tuple(obj_class.VERSION) version = utils.convert_version_to_tuple(obj_class.VERSION)
for n in range(version[1]): for n in range(version[1]):
test_version = '%d.%d' % (version[0], n) test_version = '%d.%d' % (version[0], n)
@ -1340,10 +1297,11 @@ class TestObjectVersions(test.NoDBTestCase):
def _get_obj_to_test(self, obj_class): def _get_obj_to_test(self, obj_class):
obj = obj_class() obj = obj_class()
obj_classes = base.NovaObjectRegistry.obj_classes()
for fname, ftype in obj.fields.items(): for fname, ftype in obj.fields.items():
if isinstance(ftype, fields.ObjectField): if isinstance(ftype, fields.ObjectField):
fobjname = ftype.AUTO_TYPE._obj_name fobjname = ftype.AUTO_TYPE._obj_name
fobjcls = base.NovaObject._obj_classes[fobjname][0] fobjcls = obj_classes[fobjname][0]
setattr(obj, fname, self._get_obj_to_test(fobjcls)) setattr(obj, fname, self._get_obj_to_test(fobjcls))
elif isinstance(ftype, fields.ListOfObjectsField): elif isinstance(ftype, fields.ListOfObjectsField):
# FIXME(danms): This will result in no tests for this # FIXME(danms): This will result in no tests for this
@ -1385,8 +1343,9 @@ class TestObjectVersions(test.NoDBTestCase):
# This doesn't actually test the data conversions, but it at least # This doesn't actually test the data conversions, but it at least
# makes sure the method doesn't blow up on something basic like # makes sure the method doesn't blow up on something basic like
# expecting the wrong version format. # expecting the wrong version format.
for obj_name in base.NovaObject._obj_classes: obj_classes = base.NovaObjectRegistry.obj_classes()
obj_class = base.NovaObject._obj_classes[obj_name][0] for obj_name in obj_classes:
obj_class = obj_classes[obj_name][0]
if 'tests.unit' in obj_class.__module__: if 'tests.unit' in obj_class.__module__:
# NOTE(danms): Skip test objects. When we move to # NOTE(danms): Skip test objects. When we move to
# oslo.versionedobjects, we won't have to do this # oslo.versionedobjects, we won't have to do this
@ -1407,8 +1366,9 @@ class TestObjectVersions(test.NoDBTestCase):
# This doesn't actually test the data conversions, but it at least # This doesn't actually test the data conversions, but it at least
# makes sure the method doesn't blow up on something basic like # makes sure the method doesn't blow up on something basic like
# expecting the wrong version format. # expecting the wrong version format.
for obj_name in base.NovaObject._obj_classes: obj_classes = base.NovaObjectRegistry.obj_classes()
obj_class = base.NovaObject._obj_classes[obj_name][0] for obj_name in obj_classes:
obj_class = obj_classes[obj_name][0]
for field, versions in obj_class.obj_relationships.items(): for field, versions in obj_class.obj_relationships.items():
last_my_version = (0, 0) last_my_version = (0, 0)
last_child_version = (0, 0) last_child_version = (0, 0)