~ubuntu-branches/ubuntu/utopic/ironic/utopic

« back to all changes in this revision

Viewing changes to ironic/objects/base.py

  • Committer: Package Import Robot
  • Author(s): Adam Gandelman, Adam Gandelman, James Page
  • Date: 2014-09-30 10:44:08 UTC
  • mfrom: (1.2.1) (6.1.1 utopic-proposed)
  • Revision ID: package-import@ubuntu.com-20140930104408-k3z2yaikfvmpkptd
Tags: 2014.2~rc1-0ubuntu1
[ Adam Gandelman ]
* New upstream release.
* debian/patches/set_logdir.patch: Renamed to set_config_defaults.patch,
  also set default sqlite db connection.
* debian/control: Refreshed dependencies for Juno, wrap-and-sort.
* debian/ironic-common.install: Added ironic-nova-bm-migrate binary.
* debian/ironic-common.postinst: Create the default sqlite database if
  configured to use it and it does not exist.
* debian/pydist-overrides: Add pysendfile.
* debian/ironic_sudoers: Add rootwrap.conf (LP: #1185019).

[ James Page ]
* d/rules,control: Increase test verbosity using subunit.

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
"""Ironic common internal object model"""
16
16
 
17
17
import collections
 
18
import copy
18
19
 
 
20
from oslo import messaging
19
21
import six
20
22
 
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
27
30
 
28
31
 
29
32
LOG = logging.getLogger('object')
30
33
 
31
34
 
 
35
class NotSpecifiedSentinel:
 
36
    pass
 
37
 
 
38
 
32
39
def get_attrname(name):
33
40
    """Return the mangled name of the attribute's underlying storage."""
34
41
    return '_%s' % name
35
42
 
36
43
 
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'):
 
53
            continue
 
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():
41
58
 
42
59
        def getter(self, name=name):
51
68
                return setattr(self, get_attrname(name), typefn(value))
52
69
            except Exception:
53
70
                attr = "%s.%s" % (self.obj_name(), name)
54
 
                LOG.exception(_('Error setting %(attr)s') %
 
71
                LOG.exception(_LE('Error setting %(attr)s'),
55
72
                              {'attr': attr})
56
73
                raise
57
74
 
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,
90
107
                args, kwargs)
91
108
        else:
92
109
            result = fn(cls, context, *args, **kwargs)
106
123
    def wrapper(self, *args, **kwargs):
107
124
        ctxt = self._context
108
125
        try:
109
 
            if isinstance(args[0], (context.RequestContext,
110
 
                                    rpc_common.CommonRpcContext)):
 
126
            if isinstance(args[0], (context.RequestContext)):
111
127
                ctxt = args[0]
112
128
                args = args[1:]
113
129
        except IndexError:
164
180
    """
165
181
 
166
182
    # Version of this object (see rules above check_object_version())
167
 
    version = '1.0'
 
183
    VERSION = '1.0'
168
184
 
169
185
    # The fields present in this object as key:typefn pairs. For example:
170
186
    #
182
198
        }
183
199
    obj_extra_fields = []
184
200
 
185
 
    def __init__(self):
 
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')
 
205
 
 
206
    def __init__(self, context, **kwargs):
186
207
        self._changed_fields = set()
187
 
        self._context = None
 
208
        self._context = context
 
209
        self.update(kwargs)
188
210
 
189
211
    @classmethod
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)
203
225
 
 
226
        latest = None
204
227
        compatible_match = None
205
228
        for objclass in cls._obj_classes[objname]:
206
 
            if objclass.version == objver:
 
229
            if objclass.VERSION == objver:
207
230
                return objclass
208
 
            try:
209
 
                check_object_version(objclass.version, objver)
 
231
 
 
232
            version_bits = tuple([int(x) for x in objclass.VERSION.split(".")])
 
233
            if latest is None:
 
234
                latest = version_bits
 
235
            elif latest < version_bits:
 
236
                latest = version_bits
 
237
 
 
238
            if versionutils.is_compatible(objver, objclass.VERSION):
210
239
                compatible_match = objclass
211
 
            except exception.IncompatibleObjectVersion:
212
 
                pass
213
240
 
214
241
        if compatible_match:
215
242
            return compatible_match
216
243
 
 
244
        latest_ver = '%i.%i' % latest
217
245
        raise exception.IncompatibleObjectVersion(objname=objname,
218
 
                                                  objver=objver)
219
 
 
220
 
    _attr_created_at_from_primitive = obj_utils.dt_deserializer
221
 
    _attr_updated_at_from_primitive = obj_utils.dt_deserializer
 
246
                                                  objver=objver,
 
247
                                                  supported=latest_ver)
222
248
 
223
249
    def _attr_from_primitive(self, attribute, value):
224
250
        """Attribute deserialization dispatcher.
233
259
        return value
234
260
 
235
261
    @classmethod
 
262
    def _obj_from_primitive(cls, context, objver, primitive):
 
263
        self = cls(context)
 
264
        self.VERSION = objver
 
265
        objdata = primitive['ironic_object.data']
 
266
        changes = primitive.get('ironic_object.changes', [])
 
267
        for name in self.fields:
 
268
            if name in objdata:
 
269
                setattr(self, name,
 
270
                        self._attr_from_primitive(name, objdata[name]))
 
271
        self._changed_fields = set([x for x in changes if x in self.fields])
 
272
        return self
 
273
 
 
274
    @classmethod
236
275
    def obj_from_primitive(cls, primitive, context=None):
237
276
        """Simple base-case hydration.
238
277
 
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)
251
 
        self = objclass()
252
 
        self._context = context
 
289
        return objclass._obj_from_primitive(context, objver, primitive)
 
290
 
 
291
    def __deepcopy__(self, memo):
 
292
        """Efficiently make a deep copy of this object."""
 
293
 
 
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.
 
299
 
 
300
        nobj = self.__class__(self._context)
253
301
        for name in self.fields:
254
 
            if name in objdata:
255
 
                setattr(self, name,
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])
259
 
        return self
 
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)
 
306
        return nobj
260
307
 
261
 
    _attr_created_at_to_primitive = obj_utils.dt_serializer('created_at')
262
 
    _attr_updated_at_to_primitive = obj_utils.dt_serializer('updated_at')
 
308
    def obj_clone(self):
 
309
        """Create a copy."""
 
310
        return copy.deepcopy(self)
263
311
 
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())
330
378
        else:
331
379
            self._changed_fields.clear()
332
380
 
 
381
    def obj_attr_is_set(self, attrname):
 
382
        """Test object to see if attrname is present.
 
383
 
 
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.
 
387
        """
 
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))
 
393
 
 
394
    @property
 
395
    def obj_fields(self):
 
396
        return self.fields.keys() + self.obj_extra_fields
 
397
 
333
398
    # dictish syntactic sugar
334
399
    def iteritems(self):
335
400
        """For backwards-compatibility with dict-based objects.
364
429
        """
365
430
        return hasattr(self, get_attrname(name))
366
431
 
367
 
    def get(self, key, value=None):
 
432
    def get(self, key, value=NotSpecifiedSentinel):
368
433
        """For backwards-compatibility with dict-based objects.
369
434
 
370
435
        NOTE(danms): May be removed in the future.
371
436
        """
372
 
        return self[key]
 
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):
 
442
            return value
 
443
        else:
 
444
            return self[key]
373
445
 
374
446
    def update(self, updates):
375
447
        """For backwards-compatibility with dict-base objects.
397
469
        'objects': list,
398
470
        }
399
471
 
 
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.
 
475
    child_versions = {}
 
476
 
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
416
492
            return new_obj
417
493
        return self.objects[index]
418
494
 
441
517
            objects.append(obj)
442
518
        return objects
443
519
 
444
 
 
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
 
528
 
 
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')
 
534
        return changes
 
535
 
 
536
 
 
537
class IronicObjectSerializer(messaging.NoOpSerializer):
446
538
    """A IronicObject-aware Serializer.
447
539
 
448
540
    This implements the Oslo Serializer interface and provides the