Skip to content

Commit

Permalink
Merge "Replace metaclass registry with explicit opt-in registry from …
Browse files Browse the repository at this point in the history
…oslo"
  • Loading branch information
Jenkins authored and openstack-gerrit committed Jun 15, 2015
2 parents 93811b5 + d95c05a commit 07c93c8
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 253 deletions.
6 changes: 4 additions & 2 deletions nova/exception.py
Expand Up @@ -28,6 +28,7 @@
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_versionedobjects import exception as ovo_exc
import six
import webob.exc

Expand Down Expand Up @@ -1510,8 +1511,9 @@ class IncompatibleObjectVersion(NovaException):
'maximum supported version is: %(supported)s')


class ReadOnlyFieldError(NovaException):
msg_fmt = _('Cannot modify readonly field %(field)s')
# FIXME(danms): Remove this and convert existing object tests to catch
# the oslo_versionedobjects exception themselves
ReadOnlyFieldError = ovo_exc.ReadOnlyFieldError


class ObjectActionError(NovaException):
Expand Down
127 changes: 15 additions & 112 deletions nova/objects/base.py
Expand Up @@ -14,7 +14,6 @@

"""Nova common internal object model"""

import collections
import contextlib
import copy
import datetime
Expand Down Expand Up @@ -42,115 +41,16 @@

def get_attrname(name):
"""Return the mangled name of the attribute's underlying storage."""
return '_' + name


def make_class_properties(cls):
# NOTE(danms/comstud): Inherit fields from super classes.
# mro() returns the current class first and returns 'object' last, so
# those can be skipped. Also be careful to not overwrite any fields
# that already exist. And make sure each cls has its own copy of
# 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

# FIXME(danms): This is just until we use o.vo's class properties
# and object base.
return '_obj_' + name

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)
class NovaObjectRegistry(ovoo_base.VersionedObjectRegistry):
def registration_hook(self, cls, index):
# NOTE(danms): Set the *latest* version of this class
newest = self._registry._obj_classes[cls.obj_name()][0]
setattr(objects, cls.obj_name(), newest)


# These are decorators that mark an object's method as remotable.
Expand Down Expand Up @@ -220,7 +120,6 @@ def wrapper(self, *args, **kwargs):
return wrapper


@six.add_metaclass(NovaObjectMetaclass)
class NovaObject(object):
"""Base class and object factory.
Expand Down Expand Up @@ -279,6 +178,9 @@ class NovaObject(object):
# since they were not added until version 1.2.
obj_relationships = {}

# Temporary until we inherit from o.vo.base.VersionedObject
indirection_api = None

def __init__(self, context=None, **kwargs):
self._changed_fields = set()
self._context = context
Expand All @@ -304,7 +206,7 @@ def obj_name(cls):
@classmethod
def obj_class_from_name(cls, objname, objver):
"""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 '
'%(objtype)s'), dict(objtype=objname))
raise exception.UnsupportedObjectError(objtype=objname)
Expand All @@ -315,7 +217,8 @@ def obj_class_from_name(cls, objname, objver):
# once below.
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:
return objclass
if (not compatible_match and
Expand All @@ -326,7 +229,7 @@ def obj_class_from_name(cls, objname, objver):
return compatible_match

# 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,
objver=objver,
supported=latest_ver)
Expand Down
5 changes: 3 additions & 2 deletions nova/test.py
Expand Up @@ -233,7 +233,7 @@ def setUp(self):
# registry.
objects_base.NovaObject.indirection_api = None
self._base_test_obj_backup = copy.copy(
objects_base.NovaObject._obj_classes)
objects_base.NovaObjectRegistry._registry._obj_classes)
self.addCleanup(self._restore_obj_registry)

# NOTE(mnaser): All calls to utils.is_neutron() are cached in
Expand All @@ -251,7 +251,8 @@ def setUp(self):
self.useFixture(nova_fixtures.PoisonFunctions())

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):
# Delete attributes that don't start with _ so they don't pin
Expand Down
2 changes: 2 additions & 0 deletions nova/tests/unit/cells/test_cells_messaging.py
Expand Up @@ -295,6 +295,8 @@ class CellsMsgingTestObject(objects_base.NovaObject):
"""
fields = {'test': objects_fields.StringField()}

objects_base.NovaObjectRegistry.register(CellsMsgingTestObject)

test_obj = CellsMsgingTestObject()
test_obj.test = 'meow'

Expand Down
4 changes: 4 additions & 0 deletions nova/tests/unit/conductor/test_conductor.py
Expand Up @@ -420,6 +420,8 @@ def bar(cls, context, raise_exception=False):
else:
return 'test'

obj_base.NovaObjectRegistry.register(TestObject)

obj = TestObject()
# 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
Expand Down Expand Up @@ -456,6 +458,8 @@ def touch_dict(self):
self.dict['foo'] = 'bar'
self.obj_reset_changes()

obj_base.NovaObjectRegistry.register(TestObject)

obj = TestObject()
obj.dict = {}
obj.obj_reset_changes()
Expand Down
3 changes: 3 additions & 0 deletions nova/tests/unit/objects/test_fields.py
Expand Up @@ -725,6 +725,9 @@ def __eq__(self, value):
class OtherTestableObject(obj_base.NovaObject):
pass

obj_base.NovaObjectRegistry.register(TestableObject)
obj_base.NovaObjectRegistry.register(OtherTestableObject)

test_inst = TestableObject()
self._test_cls = TestableObject
self.field = fields.Field(fields.Object('TestableObject'))
Expand Down
8 changes: 6 additions & 2 deletions nova/tests/unit/objects/test_instance.py
Expand Up @@ -199,10 +199,10 @@ def test_load(self):
).AndReturn(fake_inst2)
self.mox.ReplayAll()
inst = instance.Instance.get_by_uuid(self.context, fake_uuid)
self.assertFalse(hasattr(inst, '_metadata'))
self.assertFalse(hasattr(inst, '_obj_metadata'))
meta = inst.metadata
self.assertEqual(meta, {'foo': 'bar'})
self.assertTrue(hasattr(inst, '_metadata'))
self.assertTrue(hasattr(inst, '_obj_metadata'))
# Make sure we don't run load again
meta2 = inst.metadata
self.assertEqual(meta2, {'foo': 'bar'})
Expand Down Expand Up @@ -1351,6 +1351,8 @@ def test_flavor_shows_up_in_lazy_loaded_sysmeta_for_old_instance(self):
class OldInstance(objects.Instance):
VERSION = '1.17'

base.NovaObjectRegistry.register(OldInstance)

inst = OldInstance.get_by_uuid(self.context, inst.uuid)
self.assertFalse(inst.obj_attr_is_set('system_metadata'))
self.assertEqual('bar', inst.system_metadata['foo'])
Expand All @@ -1369,6 +1371,8 @@ def test_flavor_shows_up_in_sysmeta_for_old_instance(self):
class OldInstance(objects.Instance):
VERSION = '1.17'

base.NovaObjectRegistry.register(OldInstance)

inst = OldInstance.get_by_uuid(self.context, inst.uuid,
expected_attrs=['system_metadata'])
self.assertTrue(inst.obj_attr_is_set('system_metadata'))
Expand Down

0 comments on commit 07c93c8

Please sign in to comment.