diff --git a/ironic/common/service.py b/ironic/common/service.py index a657dc5905..7da3f426f0 100644 --- a/ironic/common/service.py +++ b/ironic/common/service.py @@ -33,6 +33,7 @@ from ironic.common.i18n import _ from ironic.common.i18n import _LE from ironic.common.i18n import _LI from ironic.common import rpc +from ironic import objects from ironic.objects import base as objects_base @@ -140,6 +141,7 @@ def prepare_service(argv=[]): ]) config.parse_args(argv) log.setup(CONF, 'ironic') + objects.register_all() def process_launcher(): diff --git a/ironic/objects/__init__.py b/ironic/objects/__init__.py index f022421fd1..72fe9d9862 100644 --- a/ironic/objects/__init__.py +++ b/ironic/objects/__init__.py @@ -12,18 +12,19 @@ # License for the specific language governing permissions and limitations # under the License. -from ironic.objects import chassis -from ironic.objects import conductor -from ironic.objects import node -from ironic.objects import port +# NOTE(comstud): You may scratch your head as you see code that imports +# this module and then accesses attributes for objects such as Node, +# etc, yet you do not see these attributes in here. Never fear, there is +# 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 -Conductor = conductor.Conductor -Node = node.Node -Port = port.Port - -__all__ = (Chassis, - Conductor, - Node, - Port) +def register_all(): + # NOTE(danms): You must make sure your object gets imported in this + # function in order for it to be registered by services that may + # need to receive it via RPC. + __import__('ironic.objects.chassis') + __import__('ironic.objects.conductor') + __import__('ironic.objects.node') + __import__('ironic.objects.port') diff --git a/ironic/objects/base.py b/ironic/objects/base.py index f72477b35e..25d4add641 100644 --- a/ironic/objects/base.py +++ b/ironic/objects/base.py @@ -14,13 +14,27 @@ """Ironic common internal object model""" +from oslo_utils import versionutils from oslo_versionedobjects import base as object_base +from ironic import objects from ironic.objects import fields as object_fields 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): diff --git a/ironic/tests/unit/__init__.py b/ironic/tests/unit/__init__.py index 0b16a622c6..dcc2f0d687 100644 --- a/ironic/tests/unit/__init__.py +++ b/ironic/tests/unit/__init__.py @@ -26,4 +26,11 @@ import eventlet +from ironic import objects + 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() diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 0a13d16fe3..4616c6c88d 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -496,3 +496,37 @@ class TestObjectSerializer(test_base.TestCase): def test_deserialize_entity_newer_version_passes_revision(self): "Test object with unsupported (newer) version and revision" 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)