Refactor objects into a magic registry

This adds a register hook to the objects base class that allows us to
late-import objects classes into ironic.objects. This is useful to allow
object classes to depend on other objects.

For example, this will allow us to do the Port.node_uuid munging that we
currently do in the API layer, in the objects layer instead.

This is completely taken from Nova, much thanks to comstud and dansmith
and whoever else worked on that.

Change-Id: Ic655e7af533497bda5e8e72d3bd90c27394681a7
This commit is contained in:
Jim Rollenhagen 2015-12-28 23:03:51 +00:00
parent 1109d4bd5d
commit 946ea0a515
5 changed files with 72 additions and 14 deletions

View File

@ -33,6 +33,7 @@ from ironic.common.i18n import _
from ironic.common.i18n import _LE from ironic.common.i18n import _LE
from ironic.common.i18n import _LI from ironic.common.i18n import _LI
from ironic.common import rpc from ironic.common import rpc
from ironic import objects
from ironic.objects import base as objects_base from ironic.objects import base as objects_base
@ -140,6 +141,7 @@ def prepare_service(argv=[]):
]) ])
config.parse_args(argv) config.parse_args(argv)
log.setup(CONF, 'ironic') log.setup(CONF, 'ironic')
objects.register_all()
def process_launcher(): def process_launcher():

View File

@ -12,18 +12,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from ironic.objects import chassis # NOTE(comstud): You may scratch your head as you see code that imports
from ironic.objects import conductor # this module and then accesses attributes for objects such as Node,
from ironic.objects import node # etc, yet you do not see these attributes in here. Never fear, there is
from ironic.objects import port # a little bit of magic. When objects are registered, an attribute is set
# on this module automatically, pointing to the newest/latest version of
# the object.
Chassis = chassis.Chassis def register_all():
Conductor = conductor.Conductor # NOTE(danms): You must make sure your object gets imported in this
Node = node.Node # function in order for it to be registered by services that may
Port = port.Port # need to receive it via RPC.
__import__('ironic.objects.chassis')
__all__ = (Chassis, __import__('ironic.objects.conductor')
Conductor, __import__('ironic.objects.node')
Node, __import__('ironic.objects.port')
Port)

View File

@ -14,13 +14,27 @@
"""Ironic common internal object model""" """Ironic common internal object model"""
from oslo_utils import versionutils
from oslo_versionedobjects import base as object_base from oslo_versionedobjects import base as object_base
from ironic import objects
from ironic.objects import fields as object_fields from ironic.objects import fields as object_fields
class IronicObjectRegistry(object_base.VersionedObjectRegistry): class IronicObjectRegistry(object_base.VersionedObjectRegistry):
pass def registration_hook(self, cls, index):
# NOTE(jroll): blatantly stolen from nova
# NOTE(danms): This is called when an object is registered,
# and is responsible for maintaining ironic.objects.$OBJECT
# as the highest-versioned implementation of a given object.
version = versionutils.convert_version_to_tuple(cls.VERSION)
if not hasattr(objects, cls.obj_name()):
setattr(objects, cls.obj_name(), cls)
else:
cur_version = versionutils.convert_version_to_tuple(
getattr(objects, cls.obj_name()).VERSION)
if version >= cur_version:
setattr(objects, cls.obj_name(), cls)
class IronicObject(object_base.VersionedObject): class IronicObject(object_base.VersionedObject):

View File

@ -26,4 +26,11 @@
import eventlet import eventlet
from ironic import objects
eventlet.monkey_patch(os=False) eventlet.monkey_patch(os=False)
# NOTE(comstud): Make sure we have all of the objects loaded. We do this
# at module import time, because we may be using mock decorators in our
# tests that run at import time.
objects.register_all()

View File

@ -496,3 +496,37 @@ class TestObjectSerializer(test_base.TestCase):
def test_deserialize_entity_newer_version_passes_revision(self): def test_deserialize_entity_newer_version_passes_revision(self):
"Test object with unsupported (newer) version and revision" "Test object with unsupported (newer) version and revision"
self._test_deserialize_entity_newer('1.7', '1.6.1', my_version='1.6.1') self._test_deserialize_entity_newer('1.7', '1.6.1', my_version='1.6.1')
class TestRegistry(test_base.TestCase):
@mock.patch('ironic.objects.base.objects')
def test_hook_chooses_newer_properly(self, mock_objects):
reg = base.IronicObjectRegistry()
reg.registration_hook(MyObj, 0)
class MyNewerObj(object):
VERSION = '1.123'
@classmethod
def obj_name(cls):
return 'MyObj'
self.assertEqual(MyObj, mock_objects.MyObj)
reg.registration_hook(MyNewerObj, 0)
self.assertEqual(MyNewerObj, mock_objects.MyObj)
@mock.patch('ironic.objects.base.objects')
def test_hook_keeps_newer_properly(self, mock_objects):
reg = base.IronicObjectRegistry()
reg.registration_hook(MyObj, 0)
class MyOlderObj(object):
VERSION = '1.1'
@classmethod
def obj_name(cls):
return 'MyObj'
self.assertEqual(MyObj, mock_objects.MyObj)
reg.registration_hook(MyOlderObj, 0)
self.assertEqual(MyObj, mock_objects.MyObj)