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 _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():

View File

@ -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')

View File

@ -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):

View File

@ -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()

View File

@ -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)