15
15
"""Ironic common internal object model"""
20
from oslo import messaging
21
23
from ironic.common import exception
24
from ironic.common.i18n import _
25
from ironic.common.i18n import _LE
22
26
from ironic.objects import utils as obj_utils
23
27
from ironic.openstack.common import context
24
28
from ironic.openstack.common import log as logging
25
from ironic.openstack.common.rpc import common as rpc_common
26
from ironic.openstack.common.rpc import serializer as rpc_serializer
29
from ironic.openstack.common import versionutils
29
32
LOG = logging.getLogger('object')
35
class NotSpecifiedSentinel:
32
39
def get_attrname(name):
33
40
"""Return the mangled name of the attribute's underlying storage."""
34
41
return '_%s' % name
37
44
def make_class_properties(cls):
38
# NOTE(danms): Inherit IronicObject's base fields only
39
cls.fields.update(IronicObject.fields)
45
# NOTE(danms/comstud): Inherit fields from super classes.
46
# mro() returns the current class first and returns 'object' last, so
47
# those can be skipped. Also be careful to not overwrite any fields
48
# that already exist. And make sure each cls has its own copy of
49
# fields and that it is not sharing the dict with a super class.
50
cls.fields = dict(cls.fields)
51
for supercls in cls.mro()[1:-1]:
52
if not hasattr(supercls, 'fields'):
54
for name, field in supercls.fields.items():
55
if name not in cls.fields:
56
cls.fields[name] = field
40
57
for name, typefn in cls.fields.iteritems():
42
59
def getter(self, name=name):
51
68
return setattr(self, get_attrname(name), typefn(value))
53
70
attr = "%s.%s" % (self.obj_name(), name)
54
LOG.exception(_('Error setting %(attr)s') %
71
LOG.exception(_LE('Error setting %(attr)s'),
86
103
def wrapper(cls, context, *args, **kwargs):
87
104
if IronicObject.indirection_api:
88
105
result = IronicObject.indirection_api.object_class_action(
89
context, cls.obj_name(), fn.__name__, cls.version,
106
context, cls.obj_name(), fn.__name__, cls.VERSION,
92
109
result = fn(cls, context, *args, **kwargs)
106
123
def wrapper(self, *args, **kwargs):
107
124
ctxt = self._context
109
if isinstance(args[0], (context.RequestContext,
110
rpc_common.CommonRpcContext)):
126
if isinstance(args[0], (context.RequestContext)):
113
129
except IndexError:
183
199
obj_extra_fields = []
201
_attr_created_at_from_primitive = obj_utils.dt_deserializer
202
_attr_updated_at_from_primitive = obj_utils.dt_deserializer
203
_attr_created_at_to_primitive = obj_utils.dt_serializer('created_at')
204
_attr_updated_at_to_primitive = obj_utils.dt_serializer('updated_at')
206
def __init__(self, context, **kwargs):
186
207
self._changed_fields = set()
208
self._context = context
190
212
def obj_name(cls):
197
219
def obj_class_from_name(cls, objname, objver):
198
220
"""Returns a class from the registry based on a name and version."""
199
221
if objname not in cls._obj_classes:
200
LOG.error(_('Unable to instantiate unregistered object type '
201
'%(objtype)s') % dict(objtype=objname))
222
LOG.error(_LE('Unable to instantiate unregistered object type '
223
'%(objtype)s'), dict(objtype=objname))
202
224
raise exception.UnsupportedObjectError(objtype=objname)
204
227
compatible_match = None
205
228
for objclass in cls._obj_classes[objname]:
206
if objclass.version == objver:
229
if objclass.VERSION == objver:
209
check_object_version(objclass.version, objver)
232
version_bits = tuple([int(x) for x in objclass.VERSION.split(".")])
234
latest = version_bits
235
elif latest < version_bits:
236
latest = version_bits
238
if versionutils.is_compatible(objver, objclass.VERSION):
210
239
compatible_match = objclass
211
except exception.IncompatibleObjectVersion:
214
241
if compatible_match:
215
242
return compatible_match
244
latest_ver = '%i.%i' % latest
217
245
raise exception.IncompatibleObjectVersion(objname=objname,
220
_attr_created_at_from_primitive = obj_utils.dt_deserializer
221
_attr_updated_at_from_primitive = obj_utils.dt_deserializer
247
supported=latest_ver)
223
249
def _attr_from_primitive(self, attribute, value):
224
250
"""Attribute deserialization dispatcher.
262
def _obj_from_primitive(cls, context, objver, primitive):
264
self.VERSION = objver
265
objdata = primitive['ironic_object.data']
266
changes = primitive.get('ironic_object.changes', [])
267
for name in self.fields:
270
self._attr_from_primitive(name, objdata[name]))
271
self._changed_fields = set([x for x in changes if x in self.fields])
236
275
def obj_from_primitive(cls, primitive, context=None):
237
276
"""Simple base-case hydration.
246
285
primitive['ironic_object.name']))
247
286
objname = primitive['ironic_object.name']
248
287
objver = primitive['ironic_object.version']
249
objdata = primitive['ironic_object.data']
250
288
objclass = cls.obj_class_from_name(objname, objver)
252
self._context = context
289
return objclass._obj_from_primitive(context, objver, primitive)
291
def __deepcopy__(self, memo):
292
"""Efficiently make a deep copy of this object."""
294
# NOTE(danms): A naive deepcopy would copy more than we need,
295
# and since we have knowledge of the volatile bits of the
296
# object, we can be smarter here. Also, nested entities within
297
# some objects may be uncopyable, so we can avoid those sorts
298
# of issues by copying only our field data.
300
nobj = self.__class__(self._context)
253
301
for name in self.fields:
256
self._attr_from_primitive(name, objdata[name]))
257
changes = primitive.get('ironic_object.changes', [])
258
self._changed_fields = set([x for x in changes if x in self.fields])
302
if self.obj_attr_is_set(name):
303
nval = copy.deepcopy(getattr(self, name), memo)
304
setattr(nobj, name, nval)
305
nobj._changed_fields = set(self._changed_fields)
261
_attr_created_at_to_primitive = obj_utils.dt_serializer('created_at')
262
_attr_updated_at_to_primitive = obj_utils.dt_serializer('updated_at')
310
return copy.deepcopy(self)
264
312
def _attr_to_primitive(self, attribute):
265
313
"""Attribute serialization dispatcher.
285
333
primitive[name] = self._attr_to_primitive(name)
286
334
obj = {'ironic_object.name': self.obj_name(),
287
335
'ironic_object.namespace': 'ironic',
288
'ironic_object.version': self.version,
336
'ironic_object.version': self.VERSION,
289
337
'ironic_object.data': primitive}
290
338
if self.obj_what_changed():
291
339
obj['ironic_object.changes'] = list(self.obj_what_changed())
331
379
self._changed_fields.clear()
381
def obj_attr_is_set(self, attrname):
382
"""Test object to see if attrname is present.
384
Returns True if the named attribute has a value set, or
385
False if not. Raises AttributeError if attrname is not
386
a valid attribute for this object.
388
if attrname not in self.obj_fields:
389
raise AttributeError(
390
_("%(objname)s object has no attribute '%(attrname)s'") %
391
{'objname': self.obj_name(), 'attrname': attrname})
392
return hasattr(self, get_attrname(attrname))
395
def obj_fields(self):
396
return self.fields.keys() + self.obj_extra_fields
333
398
# dictish syntactic sugar
334
399
def iteritems(self):
335
400
"""For backwards-compatibility with dict-based objects.
365
430
return hasattr(self, get_attrname(name))
367
def get(self, key, value=None):
432
def get(self, key, value=NotSpecifiedSentinel):
368
433
"""For backwards-compatibility with dict-based objects.
370
435
NOTE(danms): May be removed in the future.
437
if key not in self.obj_fields:
438
raise AttributeError(
439
_("'%(objclass)s' object has no attribute '%(attrname)s'") %
440
{'objclass': self.__class__, 'attrname': key})
441
if value != NotSpecifiedSentinel and not self.obj_attr_is_set(key):
374
446
def update(self, updates):
375
447
"""For backwards-compatibility with dict-base objects.
472
# This is a dictionary of my_version:child_version mappings so that
473
# we can support backleveling our contents based on the version
474
# requested of the list object.
400
477
def __iter__(self):
401
478
"""List iterator interface."""
402
479
return iter(self.objects)
408
485
def __getitem__(self, index):
409
486
"""List index access."""
410
487
if isinstance(index, slice):
411
new_obj = self.__class__()
488
new_obj = self.__class__(self._context)
412
489
new_obj.objects = self.objects[index]
413
490
# NOTE(danms): We must be mixed in with an IronicObject!
414
491
new_obj.obj_reset_changes()
415
new_obj._context = self._context
417
493
return self.objects[index]
441
517
objects.append(obj)
445
class IronicObjectSerializer(rpc_serializer.Serializer):
520
def obj_make_compatible(self, primitive, target_version):
521
primitives = primitive['objects']
522
child_target_version = self.child_versions.get(target_version, '1.0')
523
for index, item in enumerate(self.objects):
524
self.objects[index].obj_make_compatible(
525
primitives[index]['ironic_object.data'],
526
child_target_version)
527
primitives[index]['ironic_object.version'] = child_target_version
529
def obj_what_changed(self):
530
changes = set(self._changed_fields)
531
for child in self.objects:
532
if child.obj_what_changed():
533
changes.add('objects')
537
class IronicObjectSerializer(messaging.NoOpSerializer):
446
538
"""A IronicObject-aware Serializer.
448
540
This implements the Oslo Serializer interface and provides the