From: Thang Pham Date: Sun, 1 Mar 2015 22:46:02 +0000 (-0500) Subject: Switch to oslo_versionedobjects X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=4f63f7ff48c365e6cd1fe64630c92dafec41f0ce;p=openstack-build%2Fcinder-build.git Switch to oslo_versionedobjects oslo_versionedobjects was not made available until very late in the Kilo cycle (i.e. near the end of kilo-3). In order to make progress on cinder objects, a fork of nova objects was made, so that proper trial and testing could be done. The following patch makes the switch to use oslo_versionedobjects. Implements: blueprint cinder-objects Change-Id: I883f387c8247e8d79da82016a624cef2180cde88 --- diff --git a/cinder/exception.py b/cinder/exception.py index 5ceab0990..5f29fe485 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -26,6 +26,7 @@ import sys from oslo_config import cfg from oslo_log import log as logging +from oslo_versionedobjects import exception as obj_exc import six import webob.exc @@ -663,28 +664,12 @@ class EvaluatorParseException(Exception): message = _("Error during evaluator parsing: %(reason)s") -class ObjectActionError(CinderException): - msg_fmt = _('Object action %(action)s failed because: %(reason)s') - - -class ObjectFieldInvalid(CinderException): - msg_fmt = _('Field %(field)s of %(objname)s is not an instance of Field') - - -class UnsupportedObjectError(CinderException): - msg_fmt = _('Unsupported object type %(objtype)s') - - -class OrphanedObjectError(CinderException): - msg_fmt = _('Cannot call %(method)s on orphaned %(objtype)s object') - - -class IncompatibleObjectVersion(CinderException): - msg_fmt = _('Version %(objver)s of %(objname)s is not supported') - - -class ReadOnlyFieldError(CinderException): - msg_fmt = _('Cannot modify readonly field %(field)s') +UnsupportedObjectError = obj_exc.UnsupportedObjectError +OrphanedObjectError = obj_exc.OrphanedObjectError +IncompatibleObjectVersion = obj_exc.IncompatibleObjectVersion +ReadOnlyFieldError = obj_exc.ReadOnlyFieldError +ObjectActionError = obj_exc.ObjectActionError +ObjectFieldInvalid = obj_exc.ObjectFieldInvalid class VolumeGroupNotFound(CinderException): diff --git a/cinder/objects/base.py b/cinder/objects/base.py index 946f10c8e..dc984e44c 100644 --- a/cinder/objects/base.py +++ b/cinder/objects/base.py @@ -14,550 +14,42 @@ """Cinder common internal object model""" -import collections import contextlib -import copy import datetime import functools import traceback -import netaddr from oslo_log import log as logging -import oslo_messaging as messaging from oslo_utils import timeutils +from oslo_versionedobjects import base +from oslo_versionedobjects import fields import six -from cinder import context from cinder import exception -from cinder.i18n import _, _LE from cinder import objects -from cinder.objects import fields -from cinder.openstack.common import versionutils -from cinder import utils LOG = logging.getLogger('object') +remotable = base.remotable +remotable_classmethod = base.remotable_classmethod +obj_make_list = base.obj_make_list -class NotSpecifiedSentinel(object): - pass - - -def get_attrname(name): - """Return the mangled name of the attribute's underlying storage.""" - return '_' + name - - -def make_class_properties(cls): - # NOTE(danms/comstud): Inherit fields from super classes. - # mro() returns the current class first and returns 'object' last, so - # those can be skipped. Also be careful to not overwrite any fields - # that already exist. And make sure each cls has its own copy of - # fields and that it is not sharing the dict with a super class. - cls.fields = dict(cls.fields) - for supercls in cls.mro()[1:-1]: - if not hasattr(supercls, 'fields'): - continue - for name, field in supercls.fields.items(): - if name not in cls.fields: - cls.fields[name] = field - for name, field in cls.fields.iteritems(): - if not isinstance(field, fields.Field): - raise exception.ObjectFieldInvalid( - field=name, objname=cls.obj_name()) - - def getter(self, name=name): - attrname = get_attrname(name) - if not hasattr(self, attrname): - self.obj_load_attr(name) - return getattr(self, attrname) - - def setter(self, value, name=name, field=field): - attrname = get_attrname(name) - field_value = field.coerce(self, name, value) - if field.read_only and hasattr(self, attrname): - # Note(yjiang5): _from_db_object() may iterate - # every field and write, no exception in such situation. - if getattr(self, attrname) != field_value: - raise exception.ReadOnlyFieldError(field=name) - else: - return - - self._changed_fields.add(name) - try: - return setattr(self, attrname, field_value) - except Exception: - attr = "%s.%s" % (self.obj_name(), name) - LOG.exception(_LE('Error setting %(attr)s'), {'attr': attr}) - raise - - setattr(cls, name, property(getter, setter)) - - -class CinderObjectMetaclass(type): - """Metaclass that allows tracking of object classes.""" - - # NOTE(danms): This is what controls whether object operations are - # remoted. If this is not None, use it to remote things over RPC. - indirection_api = None - - def __init__(cls, names, bases, dict_): - if not hasattr(cls, '_obj_classes'): - # This means this is a base class using the metaclass. I.e., - # the 'CinderObject' class. - cls._obj_classes = collections.defaultdict(list) - return - - def _vers_tuple(obj): - return tuple([int(x) for x in obj.VERSION.split(".")]) - - # Add the subclass to CinderObject._obj_classes. If the - # same version already exists, replace it. Otherwise, - # keep the list with newest version first. - make_class_properties(cls) - obj_name = cls.obj_name() - for i, obj in enumerate(cls._obj_classes[obj_name]): - if cls.VERSION == obj.VERSION: - cls._obj_classes[obj_name][i] = cls - # Update cinder.objects with this newer class. - setattr(objects, obj_name, cls) - break - if _vers_tuple(cls) > _vers_tuple(obj): - # Insert before. - cls._obj_classes[obj_name].insert(i, cls) - if i == 0: - # Later version than we've seen before. Update - # cinder.objects. - setattr(objects, obj_name, cls) - break - else: - cls._obj_classes[obj_name].append(cls) - # Either this is the first time we've seen the object or it's - # an older version than anything we'e seen. Update cinder.objects - # only if it's the first time we've seen this object name. - if not hasattr(objects, obj_name): - setattr(objects, obj_name, cls) - - -# These are decorators that mark an object's method as remotable. -# If the metaclass is configured to forward object methods to an -# indirection service, these will result in making an RPC call -# instead of directly calling the implementation in the object. Instead, -# the object implementation on the remote end will perform the -# requested action and the result will be returned here. -def remotable_classmethod(fn): - """Decorator for remotable classmethods.""" - @functools.wraps(fn) - def wrapper(cls, context, *args, **kwargs): - if CinderObject.indirection_api: - result = CinderObject.indirection_api.object_class_action( - context, cls.obj_name(), fn.__name__, cls.VERSION, - args, kwargs) - else: - result = fn(cls, context, *args, **kwargs) - if isinstance(result, CinderObject): - result._context = context - return result - - # NOTE(danms): Make this discoverable - wrapper.remotable = True - wrapper.original_fn = fn - return classmethod(wrapper) - - -# See comment above for remotable_classmethod() -# -# Note that this will use either the provided context, or the one -# stashed in the object. If neither are present, the object is -# "orphaned" and remotable methods cannot be called. -def remotable(fn): - """Decorator for remotable object methods.""" - @functools.wraps(fn) - def wrapper(self, *args, **kwargs): - ctxt = self._context - try: - if isinstance(args[0], (context.RequestContext)): - ctxt = args[0] - args = args[1:] - except IndexError: - pass - if ctxt is None: - raise exception.OrphanedObjectError(method=fn.__name__, - objtype=self.obj_name()) - # Force this to be set if it wasn't before. - self._context = ctxt - if CinderObject.indirection_api: - updates, result = CinderObject.indirection_api.object_action( - ctxt, self, fn.__name__, args, kwargs) - for key, value in updates.iteritems(): - if key in self.fields: - field = self.fields[key] - # NOTE(ndipanov): Since CinderObjectSerializer will have - # deserialized any object fields into objects already, - # we do not try to deserialize them again here. - if isinstance(value, CinderObject): - self[key] = value - else: - self[key] = field.from_primitive(self, key, value) - self.obj_reset_changes() - self._changed_fields = set(updates.get('obj_what_changed', [])) - return result - else: - return fn(self, ctxt, *args, **kwargs) - - wrapper.remotable = True - wrapper.original_fn = fn - return wrapper - - -@six.add_metaclass(CinderObjectMetaclass) -class CinderObject(object): - """Base class and object factory. - - This forms the base of all objects that can be remoted or instantiated - via RPC. Simply defining a class that inherits from this base class - will make it remotely instantiatable. Objects should implement the - necessary "get" classmethod routines as well as "save" object methods - as appropriate. - """ - - # Object versioning rules - # - # Each service has its set of objects, each with a version attached. When - # a client attempts to call an object method, the server checks to see if - # the version of that object matches (in a compatible way) its object - # implementation. If so, cool, and if not, fail. - # - # This version is allowed to have three parts, X.Y.Z, where the .Z element - # is reserved for stable branch backports. The .Z is ignored for the - # purposes of triggering a backport, which means anything changed under - # a .Z must be additive and non-destructive such that a node that knows - # about X.Y can consider X.Y.Z equivalent. - VERSION = '1.0' - - # The fields present in this object as key:field pairs. For example: - # - # fields = { 'foo': fields.IntegerField(), - # 'bar': fields.StringField(), - # } - fields = {} - obj_extra_fields = [] +class CinderObjectRegistry(base.VersionedObjectRegistry): + def registration_hook(self, cls, index): + setattr(objects, cls.obj_name(), cls) - # Table of sub-object versioning information - # - # This contains a list of version mappings, by the field name of - # the subobject. The mappings must be in order of oldest to - # newest, and are tuples of (my_version, subobject_version). A - # request to backport this object to $my_version will cause the - # subobject to be backported to $subobject_version. - # - # obj_relationships = { - # 'subobject1': [('1.2', '1.1'), ('1.4', '1.2')], - # 'subobject2': [('1.2', '1.0')], - # } - # - # In the above example: - # - # - If we are asked to backport our object to version 1.3, - # subobject1 will be backported to version 1.1, since it was - # bumped to version 1.2 when our version was 1.4. - # - If we are asked to backport our object to version 1.5, - # no changes will be made to subobject1 or subobject2, since - # they have not changed since version 1.4. - # - If we are asked to backlevel our object to version 1.1, we - # will remove both subobject1 and subobject2 from the primitive, - # since they were not added until version 1.2. - obj_relationships = {} - - def __init__(self, context=None, **kwargs): - self._changed_fields = set() - self._context = context - for key in kwargs.keys(): - setattr(self, key, kwargs[key]) - - def __repr__(self): - return '%s(%s)' % ( - self.obj_name(), - ','.join(['%s=%s' % (name, - (self.obj_attr_is_set(name) and - field.stringify(getattr(self, name)) or - '')) - for name, field in sorted(self.fields.items())])) - - @classmethod - def obj_name(cls): - """Return a canonical name for this object. - - The canonical name will be used over the wire for remote hydration. - """ - return cls.__name__ - - @classmethod - def obj_class_from_name(cls, objname, objver): - """Returns a class from the registry based on a name and version.""" - if objname not in cls._obj_classes: - LOG.error(_LE('Unable to instantiate unregistered object type ' - '%(objtype)s'), dict(objtype=objname)) - raise exception.UnsupportedObjectError(objtype=objname) - - # NOTE(comstud): If there's not an exact match, return the highest - # compatible version. The objects stored in the class are sorted - # such that highest version is first, so only set compatible_match - # once below. - compatible_match = None - - for objclass in cls._obj_classes[objname]: - if objclass.VERSION == objver: - return objclass - if (not compatible_match and - versionutils.is_compatible(objver, objclass.VERSION)): - compatible_match = objclass - - if compatible_match: - return compatible_match - - # As mentioned above, latest version is always first in the list. - latest_ver = cls._obj_classes[objname][0].VERSION - raise exception.IncompatibleObjectVersion(objname=objname, - objver=objver, - supported=latest_ver) - - @classmethod - def _obj_from_primitive(cls, context, objver, primitive): - self = cls() - self._context = context - self.VERSION = objver - objdata = primitive['cinder_object.data'] - changes = primitive.get('cinder_object.changes', []) - for name, field in self.fields.items(): - if name in objdata: - setattr(self, name, field.from_primitive(self, name, - objdata[name])) - self._changed_fields = set([x for x in changes if x in self.fields]) - return self - - @classmethod - def obj_from_primitive(cls, primitive, context=None): - """Object field-by-field hydration.""" - if primitive['cinder_object.namespace'] != 'cinder': - # NOTE(danms): We don't do anything with this now, but it's - # there for "the future" - raise exception.UnsupportedObjectError( - objtype='%s.%s' % (primitive['cinder_object.namespace'], - primitive['cinder_object.name'])) - objname = primitive['cinder_object.name'] - objver = primitive['cinder_object.version'] - objclass = cls.obj_class_from_name(objname, objver) - return objclass._obj_from_primitive(context, objver, primitive) - - def __deepcopy__(self, memo): - """Efficiently make a deep copy of this object.""" - - # NOTE(danms): A naive deepcopy would copy more than we need, - # and since we have knowledge of the volatile bits of the - # object, we can be smarter here. Also, nested entities within - # some objects may be uncopyable, so we can avoid those sorts - # of issues by copying only our field data. - - nobj = self.__class__() - nobj._context = self._context - for name in self.fields: - if self.obj_attr_is_set(name): - nval = copy.deepcopy(getattr(self, name), memo) - setattr(nobj, name, nval) - nobj._changed_fields = set(self._changed_fields) - return nobj - - def obj_clone(self): - """Create a copy.""" - return copy.deepcopy(self) - - def _obj_make_obj_compatible(self, primitive, target_version, field): - """Backlevel a sub-object based on our versioning rules. - - This is responsible for backporting objects contained within - this object's primitive according to a set of rules we - maintain about version dependencies between objects. This - requires that the obj_relationships table in this object is - correct and up-to-date. - - :param:primitive: The primitive version of this object - :param:target_version: The version string requested for this object - :param:field: The name of the field in this object containing the - sub-object to be backported - """ - - def _do_backport(to_version): - obj = getattr(self, field) - if not obj: - return - if isinstance(obj, CinderObject): - obj.obj_make_compatible( - primitive[field]['cinder_object.data'], - to_version) - primitive[field]['cinder_object.version'] = to_version - elif isinstance(obj, list): - for i, element in enumerate(obj): - element.obj_make_compatible( - primitive[field][i]['cinder_object.data'], - to_version) - primitive[field][i]['cinder_object.version'] = to_version - - target_version = utils.convert_version_to_tuple(target_version) - for index, versions in enumerate(self.obj_relationships[field]): - my_version, child_version = versions - my_version = utils.convert_version_to_tuple(my_version) - if target_version < my_version: - if index == 0: - # We're backporting to a version from before this - # subobject was added: delete it from the primitive. - del primitive[field] - else: - # We're in the gap between index-1 and index, so - # backport to the older version - last_child_version = \ - self.obj_relationships[field][index - 1][1] - _do_backport(last_child_version) - return - elif target_version == my_version: - # This is the first mapping that satisfies the - # target_version request: backport the object. - _do_backport(child_version) - return - - def obj_make_compatible(self, primitive, target_version): - """Make an object representation compatible with a target version. - - This is responsible for taking the primitive representation of - an object and making it suitable for the given target_version. - This may mean converting the format of object attributes, removing - attributes that have been added since the target version, etc. In - general: - - - If a new version of an object adds a field, this routine - should remove it for older versions. - - If a new version changed or restricted the format of a field, this - should convert it back to something a client knowing only of the - older version will tolerate. - - If an object that this object depends on is bumped, then this - object should also take a version bump. Then, this routine should - backlevel the dependent object (by calling its obj_make_compatible()) - if the requested version of this object is older than the version - where the new dependent object was added. - - :param:primitive: The result of self.obj_to_primitive() - :param:target_version: The version string requested by the recipient - of the object - :raises: cinder.exception.UnsupportedObjectError if conversion - is not possible for some reason - """ - for key, field in self.fields.items(): - if not isinstance(field, (fields.ObjectField, - fields.ListOfObjectsField)): - continue - if not self.obj_attr_is_set(key): - continue - if key not in self.obj_relationships: - # NOTE(danms): This is really a coding error and shouldn't - # happen unless we miss something - raise exception.ObjectActionError( - action='obj_make_compatible', - reason='No rule for %s' % key) - self._obj_make_obj_compatible(primitive, target_version, key) - - def obj_to_primitive(self, target_version=None): - """Simple base-case dehydration. - - This calls to_primitive() for each item in fields. - """ - primitive = dict() - for name, field in self.fields.items(): - if self.obj_attr_is_set(name): - primitive[name] = field.to_primitive(self, name, - getattr(self, name)) - if target_version: - self.obj_make_compatible(primitive, target_version) - obj = {'cinder_object.name': self.obj_name(), - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': target_version or self.VERSION, - 'cinder_object.data': primitive} - if self.obj_what_changed(): - obj['cinder_object.changes'] = sorted(self.obj_what_changed()) - return obj - - def obj_set_defaults(self, *attrs): - if not attrs: - attrs = [name for name, field in self.fields.items() - if field.default != fields.UnspecifiedDefault] - - for attr in attrs: - default = self.fields[attr].default - if default is fields.UnspecifiedDefault: - raise exception.ObjectActionError( - action='set_defaults', - reason='No default set for field %s' % attr) - setattr(self, attr, default) - - def obj_load_attr(self, attrname): - """Load an additional attribute from the real object.""" - raise NotImplementedError( - _("Cannot load '%s' in the base class") % attrname) - - def save(self, context): - """Save the changed fields back to the store. - - This is optional for subclasses, but is presented here in the base - class for consistency among those that do. - """ - raise NotImplementedError('Cannot save anything in the base class') - def obj_what_changed(self): - """Returns a set of fields that have been modified.""" - changes = set(self._changed_fields) - for field in self.fields: - if (self.obj_attr_is_set(field) and - isinstance(getattr(self, field), CinderObject) and - getattr(self, field).obj_what_changed()): - changes.add(field) - return changes +@CinderObjectRegistry.register +class CinderObject(base.VersionedObject): + # NOTE(thangp): OBJ_PROJECT_NAMESPACE needs to be set so that nova, + # cinder, and other objects can exist on the same bus and be distinguished + # from one another. + OBJ_PROJECT_NAMESPACE = 'cinder' - def obj_get_changes(self): - """Returns a dict of changed fields and their new values.""" - changes = {} - for key in self.obj_what_changed(): - changes[key] = getattr(self, key) - return changes - def obj_reset_changes(self, fields=None): - """Reset the list of fields that have been changed. - - Note that this is NOT "revert to previous values" - """ - if fields: - self._changed_fields -= set(fields) - else: - self._changed_fields.clear() - - def obj_attr_is_set(self, attrname): - """Test object to see if attrname is present. - - Returns True if the named attribute has a value set, or - False if not. Raises AttributeError if attrname is not - a valid attribute for this object. - """ - if attrname not in self.obj_fields: - raise AttributeError( - _("%(objname)s object has no attribute '%(attrname)s'") % - {'objname': self.obj_name(), 'attrname': attrname}) - return hasattr(self, get_attrname(attrname)) - - @property - def obj_fields(self): - return self.fields.keys() + self.obj_extra_fields - - -class CinderObjectDictCompat(object): - """Mix-in to provide dictionary key access compat +class CinderObjectDictCompat(base.VersionedObjectDictCompat): + """Mix-in to provide dictionary key access compat. If an object needs to support attribute access using dictionary items instead of object attributes, inherit @@ -568,44 +60,7 @@ class CinderObjectDictCompat(object): NOTE(berrange) This class will eventually be deleted. """ - # dictish syntactic sugar - def iteritems(self): - """For backwards-compatibility with dict-based objects. - - NOTE(danms): May be removed in the future. - """ - for name in self.obj_fields: - if (self.obj_attr_is_set(name) or - name in self.obj_extra_fields): - yield name, getattr(self, name) - - items = lambda self: list(self.iteritems()) - - def __getitem__(self, name): - """For backwards-compatibility with dict-based objects. - - NOTE(danms): May be removed in the future. - """ - return getattr(self, name) - - def __setitem__(self, name, value): - """For backwards-compatibility with dict-based objects. - - NOTE(danms): May be removed in the future. - """ - setattr(self, name, value) - - def __contains__(self, name): - """For backwards-compatibility with dict-based objects. - - NOTE(danms): May be removed in the future. - """ - try: - return self.obj_attr_is_set(name) - except AttributeError: - return False - - def get(self, key, value=NotSpecifiedSentinel): + def get(self, key, value=base._NotSpecifiedSentinel): """For backwards-compatibility with dict-based objects. NOTE(danms): May be removed in the future. @@ -621,19 +76,12 @@ class CinderObjectDictCompat(object): {'object_name': self.__class__.__name__, 'attribute_name': key}) return None - if value != NotSpecifiedSentinel and not self.obj_attr_is_set(key): + if (value != base._NotSpecifiedSentinel and + not self.obj_attr_is_set(key)): return value else: return getattr(self, key) - def update(self, updates): - """For backwards-compatibility with dict-base objects. - - NOTE(danms): May be removed in the future. - """ - for key, value in updates.items(): - setattr(self, key, value) - class CinderPersistentObject(object): """Mixin class for Persistent objects. @@ -656,7 +104,6 @@ class CinderPersistentObject(object): with obj.obj_as_admin(): obj.save() - """ if self._context is None: raise exception.OrphanedObjectError(method='obj_as_admin', @@ -670,185 +117,12 @@ class CinderPersistentObject(object): self._context = original_context -class ObjectListBase(object): - """Mixin class for lists of objects. - - This mixin class can be added as a base class for an object that - is implementing a list of objects. It adds a single field of 'objects', - which is the list store, and behaves like a list itself. It supports - serialization of the list of objects automatically. - """ - fields = { - 'objects': fields.ListOfObjectsField('CinderObject'), - } - - # This is a dictionary of my_version:child_version mappings so that - # we can support backleveling our contents based on the version - # requested of the list object. - child_versions = {} - - def __init__(self, *args, **kwargs): - super(ObjectListBase, self).__init__(*args, **kwargs) - if 'objects' not in kwargs: - self.objects = [] - self._changed_fields.discard('objects') - - def __iter__(self): - """List iterator interface.""" - return iter(self.objects) - - def __len__(self): - """List length.""" - return len(self.objects) - - def __getitem__(self, index): - """List index access.""" - if isinstance(index, slice): - new_obj = self.__class__() - new_obj.objects = self.objects[index] - # NOTE(danms): We must be mixed in with a CinderObject! - new_obj.obj_reset_changes() - new_obj._context = self._context - return new_obj - return self.objects[index] - - def __contains__(self, value): - """List membership test.""" - return value in self.objects - - def count(self, value): - """List count of value occurrences.""" - return self.objects.count(value) - - def index(self, value): - """List index of value.""" - return self.objects.index(value) - - def sort(self, cmp=None, key=None, reverse=False): - self.objects.sort(cmp=cmp, key=key, reverse=reverse) - - def obj_make_compatible(self, primitive, target_version): - primitives = primitive['objects'] - child_target_version = self.child_versions.get(target_version, '1.0') - for index, item in enumerate(self.objects): - self.objects[index].obj_make_compatible( - primitives[index]['cinder_object.data'], - child_target_version) - primitives[index]['cinder_object.version'] = child_target_version - - def obj_what_changed(self): - changes = set(self._changed_fields) - for child in self.objects: - if child.obj_what_changed(): - changes.add('objects') - return changes - - -class CinderObjectSerializer(messaging.NoOpSerializer): - """A CinderObject-aware Serializer. - - This implements the Oslo Serializer interface and provides the - ability to serialize and deserialize CinderObject entities. Any service - that needs to accept or return CinderObjects as arguments or result values - should pass this to its RPCClient and RPCServer objects. - """ - - def _process_object(self, context, objprim): - try: - objinst = CinderObject.obj_from_primitive(objprim, context=context) - except exception.IncompatibleObjectVersion: - objver = objprim['cinder_object.version'] - if objver.count('.') == 2: - # NOTE(danms): For our purposes, the .z part of the version - # should be safe to accept without requiring a backport - objprim['cinder_object.version'] = \ - '.'.join(objver.split('.')[:2]) - return self._process_object(context, objprim) - raise - - return objinst - - def _process_iterable(self, context, action_fn, values): - """Process an iterable, taking an action on each value. - :param:context: Request context - :param:action_fn: Action to take on each item in values - :param:values: Iterable container of things to take action on - :returns: A new container of the same type (except set) with - items from values having had action applied. - """ - iterable = values.__class__ - if issubclass(iterable, dict): - return iterable(**{k: action_fn(context, v) - for k, v in six.iteritems(values)}) - else: - # NOTE(danms): A set can't have an unhashable value inside, such as - # a dict. Convert sets to tuples, which is fine, since we can't - # send them over RPC anyway. - if iterable == set: - iterable = tuple - return iterable([action_fn(context, value) for value in values]) - - def serialize_entity(self, context, entity): - if isinstance(entity, (tuple, list, set, dict)): - entity = self._process_iterable(context, self.serialize_entity, - entity) - elif (hasattr(entity, 'obj_to_primitive') and - callable(entity.obj_to_primitive)): - entity = entity.obj_to_primitive() - return entity - - def deserialize_entity(self, context, entity): - if isinstance(entity, dict) and 'cinder_object.name' in entity: - entity = self._process_object(context, entity) - elif isinstance(entity, (tuple, list, set, dict)): - entity = self._process_iterable(context, self.deserialize_entity, - entity) - return entity - - -def obj_to_primitive(obj): - """Recursively turn an object into a python primitive. - - A CinderObject becomes a dict, and anything that implements ObjectListBase - becomes a list. - """ - if isinstance(obj, ObjectListBase): - return [obj_to_primitive(x) for x in obj] - elif isinstance(obj, CinderObject): - result = {} - for key in obj.obj_fields: - if obj.obj_attr_is_set(key) or key in obj.obj_extra_fields: - result[key] = obj_to_primitive(getattr(obj, key)) - return result - elif isinstance(obj, netaddr.IPAddress): - return str(obj) - elif isinstance(obj, netaddr.IPNetwork): - return str(obj) - else: - return obj - - -def obj_make_list(context, list_obj, item_cls, db_list, **extra_args): - """Construct an object list from a list of primitives. +class ObjectListBase(base.ObjectListBase): + pass - This calls item_cls._from_db_object() on each item of db_list, and - adds the resulting object to list_obj. - :param:context: Request contextr - :param:list_obj: An ObjectListBase object - :param:item_cls: The CinderObject class of the objects within the list - :param:db_list: The list of primitives to convert to objects - :param:extra_args: Extra arguments to pass to _from_db_object() - :returns: list_obj - """ - list_obj.objects = [] - for db_item in db_list: - item = item_cls._from_db_object(context, item_cls(), db_item, - **extra_args) - list_obj.objects.append(item) - list_obj._context = context - list_obj.obj_reset_changes() - return list_obj +class CinderObjectSerializer(base.VersionedObjectSerializer): + OBJ_BASE_CLASS = CinderObject def serialize_args(fn): diff --git a/cinder/objects/fields.py b/cinder/objects/fields.py deleted file mode 100644 index 39986dbcf..000000000 --- a/cinder/objects/fields.py +++ /dev/null @@ -1,607 +0,0 @@ -# Copyright 2015 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import abc -import datetime - -import iso8601 -import netaddr -from oslo_utils import timeutils -import six - -from cinder.i18n import _ - - -class KeyTypeError(TypeError): - def __init__(self, expected, value): - super(KeyTypeError, self).__init__( - _('Key %(key)s must be of type %(expected)s not %(actual)s' - ) % {'key': repr(value), - 'expected': expected.__name__, - 'actual': value.__class__.__name__, - }) - - -class ElementTypeError(TypeError): - def __init__(self, expected, key, value): - super(ElementTypeError, self).__init__( - _('Element %(key)s:%(val)s must be of type %(expected)s' - ' not %(actual)s' - ) % {'key': key, - 'val': repr(value), - 'expected': expected, - 'actual': value.__class__.__name__, - }) - - -@six.add_metaclass(abc.ABCMeta) -class AbstractFieldType(object): - @abc.abstractmethod - def coerce(self, obj, attr, value): - """This is called to coerce (if possible) a value on assignment. - - This method should convert the value given into the designated type, - or throw an exception if this is not possible. - - :param:obj: The CinderObject on which an attribute is being set - :param:attr: The name of the attribute being set - :param:value: The value being set - :returns: A properly-typed value - """ - pass - - @abc.abstractmethod - def from_primitive(self, obj, attr, value): - """This is called to deserialize a value. - - This method should deserialize a value from the form given by - to_primitive() to the designated type. - - :param:obj: The CinderObject on which the value is to be set - :param:attr: The name of the attribute which will hold the value - :param:value: The serialized form of the value - :returns: The natural form of the value - """ - pass - - @abc.abstractmethod - def to_primitive(self, obj, attr, value): - """This is called to serialize a value. - - This method should serialize a value to the form expected by - from_primitive(). - - :param:obj: The CinderObject on which the value is set - :param:attr: The name of the attribute holding the value - :param:value: The natural form of the value - :returns: The serialized form of the value - """ - pass - - @abc.abstractmethod - def describe(self): - """Returns a string describing the type of the field.""" - pass - - @abc.abstractmethod - def stringify(self, value): - """Returns a short stringified version of a value.""" - pass - - -class FieldType(AbstractFieldType): - @staticmethod - def coerce(obj, attr, value): - return value - - @staticmethod - def from_primitive(obj, attr, value): - return value - - @staticmethod - def to_primitive(obj, attr, value): - return value - - def describe(self): - return self.__class__.__name__ - - def stringify(self, value): - return str(value) - - -class UnspecifiedDefault(object): - pass - - -class Field(object): - def __init__(self, field_type, nullable=False, - default=UnspecifiedDefault, read_only=False): - self._type = field_type - self._nullable = nullable - self._default = default - self._read_only = read_only - - def __repr__(self): - args = { - 'nullable': self._nullable, - 'default': self._default, - } - return '%s(%s)' % (self._type.__class__.__name__, - ','.join(['%s=%s' % (k, v) - for k, v in args.items()])) - - @property - def nullable(self): - return self._nullable - - @property - def default(self): - return self._default - - @property - def read_only(self): - return self._read_only - - def _null(self, obj, attr): - if self.nullable: - return None - elif self._default != UnspecifiedDefault: - # NOTE(danms): We coerce the default value each time the field - # is set to None as our contract states that we'll let the type - # examine the object and attribute name at that time. - return self._type.coerce(obj, attr, self._default) - else: - raise ValueError(_("Field `%s' cannot be None") % attr) - - def coerce(self, obj, attr, value): - """Coerce a value to a suitable type. - - This is called any time you set a value on an object, like: - - foo.myint = 1 - - and is responsible for making sure that the value (1 here) is of - the proper type, or can be sanely converted. - - This also handles the potentially nullable or defaultable - nature of the field and calls the coerce() method on a - FieldType to actually do the coercion. - - :param:obj: The object being acted upon - :param:attr: The name of the attribute/field being set - :param:value: The value being set - :returns: The properly-typed value - """ - if value is None: - return self._null(obj, attr) - else: - return self._type.coerce(obj, attr, value) - - def from_primitive(self, obj, attr, value): - """Deserialize a value from primitive form. - - This is responsible for deserializing a value from primitive - into regular form. It calls the from_primitive() method on a - FieldType to do the actual deserialization. - - :param:obj: The object being acted upon - :param:attr: The name of the attribute/field being deserialized - :param:value: The value to be deserialized - :returns: The deserialized value - """ - if value is None: - return None - else: - return self._type.from_primitive(obj, attr, value) - - def to_primitive(self, obj, attr, value): - """Serialize a value to primitive form. - - This is responsible for serializing a value to primitive - form. It calls to_primitive() on a FieldType to do the actual - serialization. - - :param:obj: The object being acted upon - :param:attr: The name of the attribute/field being serialized - :param:value: The value to be serialized - :returns: The serialized value - """ - if value is None: - return None - else: - return self._type.to_primitive(obj, attr, value) - - def describe(self): - """Return a short string describing the type of this field.""" - name = self._type.describe() - prefix = self.nullable and 'Nullable' or '' - return prefix + name - - def stringify(self, value): - if value is None: - return 'None' - else: - return self._type.stringify(value) - - -class String(FieldType): - @staticmethod - def coerce(obj, attr, value): - # FIXME(danms): We should really try to avoid the need to do this - if isinstance(value, (six.string_types, int, long, float, - datetime.datetime)): - return six.text_type(value) - else: - raise ValueError(_('A string is required here, not %s') % - value.__class__.__name__) - - @staticmethod - def stringify(value): - return "'%s'" % value - - -class UUID(FieldType): - @staticmethod - def coerce(obj, attr, value): - # FIXME(danms): We should actually verify the UUIDness here - return six.text_type(value) - - -class Integer(FieldType): - @staticmethod - def coerce(obj, attr, value): - return int(value) - - -class Float(FieldType): - def coerce(self, obj, attr, value): - return float(value) - - -class Boolean(FieldType): - @staticmethod - def coerce(obj, attr, value): - return bool(value) - - -class DateTime(FieldType): - @staticmethod - def coerce(obj, attr, value): - if isinstance(value, six.string_types): - # NOTE(danms): Being tolerant of isotime strings here will help us - # during our objects transition - value = timeutils.parse_isotime(value) - elif not isinstance(value, datetime.datetime): - raise ValueError(_('A datetime.datetime is required here')) - - if value.utcoffset() is None: - # NOTE(danms): Legacy objects from sqlalchemy are stored in UTC, - # but are returned without a timezone attached. - # As a transitional aid, assume a tz-naive object is in UTC. - value = value.replace(tzinfo=iso8601.iso8601.Utc()) - return value - - def from_primitive(self, obj, attr, value): - return self.coerce(obj, attr, timeutils.parse_isotime(value)) - - @staticmethod - def to_primitive(obj, attr, value): - return timeutils.isotime(value) - - @staticmethod - def stringify(value): - return timeutils.isotime(value) - - -class IPAddress(FieldType): - @staticmethod - def coerce(obj, attr, value): - try: - return netaddr.IPAddress(value) - except netaddr.AddrFormatError as e: - raise ValueError(six.text_type(e)) - - def from_primitive(self, obj, attr, value): - return self.coerce(obj, attr, value) - - @staticmethod - def to_primitive(obj, attr, value): - return six.text_type(value) - - -class IPV4Address(IPAddress): - @staticmethod - def coerce(obj, attr, value): - result = IPAddress.coerce(obj, attr, value) - if result.version != 4: - raise ValueError(_('Network "%s" is not valid') % value) - return result - - -class IPV6Address(IPAddress): - @staticmethod - def coerce(obj, attr, value): - result = IPAddress.coerce(obj, attr, value) - if result.version != 6: - raise ValueError(_('Network "%s" is not valid') % value) - return result - - -class IPV4AndV6Address(IPAddress): - @staticmethod - def coerce(obj, attr, value): - result = IPAddress.coerce(obj, attr, value) - if result.version != 4 and result.version != 6: - raise ValueError(_('Network "%s" is not valid') % value) - return result - - -class IPNetwork(IPAddress): - @staticmethod - def coerce(obj, attr, value): - try: - return netaddr.IPNetwork(value) - except netaddr.AddrFormatError as e: - raise ValueError(six.text_type(e)) - - -class IPV4Network(IPNetwork): - @staticmethod - def coerce(obj, attr, value): - try: - return netaddr.IPNetwork(value, version=4) - except netaddr.AddrFormatError as e: - raise ValueError(six.text_type(e)) - - -class IPV6Network(IPNetwork): - @staticmethod - def coerce(obj, attr, value): - try: - return netaddr.IPNetwork(value, version=6) - except netaddr.AddrFormatError as e: - raise ValueError(six.text_type(e)) - - -class CompoundFieldType(FieldType): - def __init__(self, element_type, **field_args): - self._element_type = Field(element_type, **field_args) - - -class List(CompoundFieldType): - def coerce(self, obj, attr, value): - if not isinstance(value, list): - raise ValueError(_('A list is required here')) - for index, element in enumerate(list(value)): - value[index] = self._element_type.coerce( - obj, '%s[%i]' % (attr, index), element) - return value - - def to_primitive(self, obj, attr, value): - return [self._element_type.to_primitive(obj, attr, x) for x in value] - - def from_primitive(self, obj, attr, value): - return [self._element_type.from_primitive(obj, attr, x) for x in value] - - def stringify(self, value): - return '[%s]' % ( - ','.join([self._element_type.stringify(x) for x in value])) - - -class Dict(CompoundFieldType): - def coerce(self, obj, attr, value): - if not isinstance(value, dict): - raise ValueError(_('A dict is required here')) - for key, element in value.items(): - if not isinstance(key, six.string_types): - # NOTE(guohliu) In order to keep compatibility with python3 - # we need to use six.string_types rather than basestring here, - # since six.string_types is a tuple, so we need to pass the - # real type in. - raise KeyTypeError(six.string_types[0], key) - value[key] = self._element_type.coerce( - obj, '%s["%s"]' % (attr, key), element) - return value - - def to_primitive(self, obj, attr, value): - primitive = {} - for key, element in value.items(): - primitive[key] = self._element_type.to_primitive( - obj, '%s["%s"]' % (attr, key), element) - return primitive - - def from_primitive(self, obj, attr, value): - concrete = {} - for key, element in value.items(): - concrete[key] = self._element_type.from_primitive( - obj, '%s["%s"]' % (attr, key), element) - return concrete - - def stringify(self, value): - return '{%s}' % ( - ','.join(['%s=%s' % (key, self._element_type.stringify(val)) - for key, val in sorted(value.items())])) - - -class DictProxyField(object): - """Descriptor allowing us to assign pinning data as a dict of key_types. - - This allows us to have an object field that will be a dict of key_type - keys, allowing that will convert back to string-keyed dict. - - This will take care of the conversion while the dict field will make sure - that we store the raw json-serializable data on the object. - - key_type should return a type that unambiguously responds to six.text_type - so that calling key_type on it yields the same thing. - """ - def __init__(self, dict_field_name, key_type=int): - self._fld_name = dict_field_name - self._key_type = key_type - - def __get__(self, obj, obj_type=None): - if obj is None: - return self - if getattr(obj, self._fld_name) is None: - return - return {self._key_type(k): v - for k, v in six.iteritems(getattr(obj, self._fld_name))} - - def __set__(self, obj, val): - if val is None: - setattr(obj, self._fld_name, val) - else: - setattr(obj, self._fld_name, {six.text_type(k): v - for k, v in six.iteritems(val)}) - - -class Set(CompoundFieldType): - def coerce(self, obj, attr, value): - if not isinstance(value, set): - raise ValueError(_('A set is required here')) - - coerced = set() - for element in value: - coerced.add(self._element_type.coerce( - obj, '%s["%s"]' % (attr, element), element)) - return coerced - - def to_primitive(self, obj, attr, value): - return tuple( - self._element_type.to_primitive(obj, attr, x) for x in value) - - def from_primitive(self, obj, attr, value): - return set([self._element_type.from_primitive(obj, attr, x) - for x in value]) - - def stringify(self, value): - return 'set([%s])' % ( - ','.join([self._element_type.stringify(x) for x in value])) - - -class Object(FieldType): - def __init__(self, obj_name, **kwargs): - self._obj_name = obj_name - super(Object, self).__init__(**kwargs) - - def coerce(self, obj, attr, value): - try: - obj_name = value.obj_name() - except AttributeError: - obj_name = "" - - if obj_name != self._obj_name: - raise ValueError(_('An object of type %s is required here') % - self._obj_name) - return value - - @staticmethod - def to_primitive(obj, attr, value): - return value.obj_to_primitive() - - @staticmethod - def from_primitive(obj, attr, value): - # FIXME(danms): Avoid circular import from base.py - from cinder.objects import base as obj_base - # NOTE (ndipanov): If they already got hydrated by the serializer, just - # pass them back unchanged - if isinstance(value, obj_base.CinderObject): - return value - return obj_base.CinderObject.obj_from_primitive(value, obj._context) - - def describe(self): - return "Object<%s>" % self._obj_name - - def stringify(self, value): - if 'uuid' in value.fields: - ident = '(%s)' % (value.obj_attr_is_set('uuid') and value.uuid or - 'UNKNOWN') - elif 'id' in value.fields: - ident = '(%s)' % (value.obj_attr_is_set('id') and value.id or - 'UNKNOWN') - else: - ident = '' - - return '%s%s' % (self._obj_name, ident) - - -class AutoTypedField(Field): - AUTO_TYPE = None - - def __init__(self, **kwargs): - super(AutoTypedField, self).__init__(self.AUTO_TYPE, **kwargs) - - -class StringField(AutoTypedField): - AUTO_TYPE = String() - - -class UUIDField(AutoTypedField): - AUTO_TYPE = UUID() - - -class IntegerField(AutoTypedField): - AUTO_TYPE = Integer() - - -class FloatField(AutoTypedField): - AUTO_TYPE = Float() - - -class BooleanField(AutoTypedField): - AUTO_TYPE = Boolean() - - -class DateTimeField(AutoTypedField): - AUTO_TYPE = DateTime() - - -class DictOfStringsField(AutoTypedField): - AUTO_TYPE = Dict(String()) - - -class DictOfNullableStringsField(AutoTypedField): - AUTO_TYPE = Dict(String(), nullable=True) - - -class DictOfIntegersField(AutoTypedField): - AUTO_TYPE = Dict(Integer()) - - -class ListOfStringsField(AutoTypedField): - AUTO_TYPE = List(String()) - - -class SetOfIntegersField(AutoTypedField): - AUTO_TYPE = Set(Integer()) - - -class ListOfSetsOfIntegersField(AutoTypedField): - AUTO_TYPE = List(Set(Integer())) - - -class ListOfDictOfNullableStringsField(AutoTypedField): - AUTO_TYPE = List(Dict(String(), nullable=True)) - - -class ObjectField(AutoTypedField): - def __init__(self, objtype, **kwargs): - self.AUTO_TYPE = Object(objtype) - super(ObjectField, self).__init__(**kwargs) - - -class ListOfObjectsField(AutoTypedField): - def __init__(self, objtype, **kwargs): - self.AUTO_TYPE = List(Object(objtype)) - super(ListOfObjectsField, self).__init__(**kwargs) diff --git a/cinder/objects/snapshot.py b/cinder/objects/snapshot.py index cbca84342..d0ce81f61 100644 --- a/cinder/objects/snapshot.py +++ b/cinder/objects/snapshot.py @@ -14,13 +14,13 @@ from oslo_config import cfg from oslo_log import log as logging +from oslo_versionedobjects import fields from cinder import db from cinder import exception from cinder.i18n import _ from cinder import objects from cinder.objects import base -from cinder.objects import fields from cinder import utils CONF = cfg.CONF @@ -30,6 +30,7 @@ OPTIONAL_FIELDS = ['volume', 'metadata'] LOG = logging.getLogger(__name__) +@base.CinderObjectRegistry.register class Snapshot(base.CinderPersistentObject, base.CinderObject, base.CinderObjectDictCompat): # Version 1.0: Initial version @@ -129,7 +130,7 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject, expected_attrs=['metadata']) @base.remotable - def create(self, context): + def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) @@ -139,11 +140,11 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject, raise exception.ObjectActionError(action='create', reason=_('volume assigned')) - db_snapshot = db.snapshot_create(context, updates) - self._from_db_object(context, self, db_snapshot) + db_snapshot = db.snapshot_create(self._context, updates) + self._from_db_object(self._context, self, db_snapshot) @base.remotable - def save(self, context): + def save(self): updates = self.obj_get_changes() if updates: if 'volume' in updates: @@ -154,16 +155,17 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject, # Metadata items that are not specified in the # self.metadata will be deleted metadata = updates.pop('metadata', None) - self.metadata = db.snapshot_metadata_update(context, self.id, - metadata, True) + self.metadata = db.snapshot_metadata_update(self._context, + self.id, metadata, + True) - db.snapshot_update(context, self.id, updates) + db.snapshot_update(self._context, self.id, updates) self.obj_reset_changes() @base.remotable - def destroy(self, context): - db.snapshot_destroy(context, self.id) + def destroy(self): + db.snapshot_destroy(self._context, self.id) def obj_load_attr(self, attrname): if attrname not in OPTIONAL_FIELDS: @@ -191,6 +193,7 @@ class Snapshot(base.CinderPersistentObject, base.CinderObject, self.obj_reset_changes(['metadata']) +@base.CinderObjectRegistry.register class SnapshotList(base.ObjectListBase, base.CinderObject): VERSION = '1.0' diff --git a/cinder/objects/volume.py b/cinder/objects/volume.py index 6296ed190..d32aff26a 100644 --- a/cinder/objects/volume.py +++ b/cinder/objects/volume.py @@ -14,13 +14,13 @@ from oslo_config import cfg from oslo_log import log as logging +from oslo_versionedobjects import fields from cinder import db from cinder import exception from cinder.i18n import _ from cinder import objects from cinder.objects import base -from cinder.objects import fields from cinder import utils CONF = cfg.CONF @@ -28,6 +28,7 @@ OPTIONAL_FIELDS = [] LOG = logging.getLogger(__name__) +@base.CinderObjectRegistry.register class Volume(base.CinderPersistentObject, base.CinderObject, base.CinderObjectDictCompat): # Version 1.0: Initial version @@ -116,27 +117,28 @@ class Volume(base.CinderPersistentObject, base.CinderObject, return cls._from_db_object(context, cls(context), db_volume) @base.remotable - def create(self, context): + def create(self): if self.obj_attr_is_set('id'): raise exception.ObjectActionError(action='create', reason=_('already created')) updates = self.obj_get_changes() - db_volume = db.volume_create(context, updates) - self._from_db_object(context, self, db_volume) + db_volume = db.volume_create(self._context, updates) + self._from_db_object(self._context, self, db_volume) @base.remotable - def save(self, context): + def save(self): updates = self.obj_get_changes() if updates: - db.volume_update(context, self.id, updates) + db.volume_update(self._context, self.id, updates) self.obj_reset_changes() @base.remotable - def destroy(self, context): - db.volume_destroy(context, self.id) + def destroy(self): + db.volume_destroy(self._context, self.id) +@base.CinderObjectRegistry.register class VolumeList(base.ObjectListBase, base.CinderObject): VERSION = '1.0' diff --git a/cinder/tests/unit/fake_snapshot.py b/cinder/tests/unit/fake_snapshot.py index 0d37c85ed..2b395677d 100644 --- a/cinder/tests/unit/fake_snapshot.py +++ b/cinder/tests/unit/fake_snapshot.py @@ -12,8 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_versionedobjects import fields + from cinder import objects -from cinder.objects import fields def fake_db_volume(**updates): diff --git a/cinder/tests/unit/fake_volume.py b/cinder/tests/unit/fake_volume.py index 5aba19012..46af55509 100644 --- a/cinder/tests/unit/fake_volume.py +++ b/cinder/tests/unit/fake_volume.py @@ -12,8 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_versionedobjects import fields + from cinder import objects -from cinder.objects import fields def fake_db_volume(**updates): diff --git a/cinder/tests/unit/objects/test_fields.py b/cinder/tests/unit/objects/test_fields.py deleted file mode 100644 index 1edd58479..000000000 --- a/cinder/tests/unit/objects/test_fields.py +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright 2015 Red Hat, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import datetime - -import iso8601 -from oslo_utils import timeutils - -from cinder.objects import base as obj_base -from cinder.objects import fields -from cinder import test - - -class FakeFieldType(fields.FieldType): - def coerce(self, obj, attr, value): - return '*%s*' % value - - def to_primitive(self, obj, attr, value): - return '!%s!' % value - - def from_primitive(self, obj, attr, value): - return value[1:-1] - - -class TestField(test.TestCase): - def setUp(self): - super(TestField, self).setUp() - self.field = fields.Field(FakeFieldType()) - self.coerce_good_values = [('foo', '*foo*')] - self.coerce_bad_values = [] - self.to_primitive_values = [('foo', '!foo!')] - self.from_primitive_values = [('!foo!', 'foo')] - - def test_coerce_good_values(self): - for in_val, out_val in self.coerce_good_values: - self.assertEqual(out_val, self.field.coerce('obj', 'attr', in_val)) - - def test_coerce_bad_values(self): - for in_val in self.coerce_bad_values: - self.assertRaises((TypeError, ValueError), - self.field.coerce, 'obj', 'attr', in_val) - - def test_to_primitive(self): - for in_val, prim_val in self.to_primitive_values: - self.assertEqual(prim_val, self.field.to_primitive('obj', 'attr', - in_val)) - - def test_from_primitive(self): - class ObjectLikeThing(object): - _context = 'context' - - for prim_val, out_val in self.from_primitive_values: - self.assertEqual(out_val, self.field.from_primitive( - ObjectLikeThing, 'attr', prim_val)) - - def test_stringify(self): - self.assertEqual('123', self.field.stringify(123)) - - -class TestString(TestField): - def setUp(self): - super(TestField, self).setUp() - self.field = fields.StringField() - self.coerce_good_values = [('foo', 'foo'), (1, '1'), (1L, '1'), - (True, 'True')] - self.coerce_bad_values = [None] - self.to_primitive_values = self.coerce_good_values[0:1] - self.from_primitive_values = self.coerce_good_values[0:1] - - def test_stringify(self): - self.assertEqual("'123'", self.field.stringify(123)) - - -class TestInteger(TestField): - def setUp(self): - super(TestField, self).setUp() - self.field = fields.IntegerField() - self.coerce_good_values = [(1, 1), ('1', 1)] - self.coerce_bad_values = ['foo', None] - self.to_primitive_values = self.coerce_good_values[0:1] - self.from_primitive_values = self.coerce_good_values[0:1] - - -class TestFloat(TestField): - def setUp(self): - super(TestFloat, self).setUp() - self.field = fields.FloatField() - self.coerce_good_values = [(1.1, 1.1), ('1.1', 1.1)] - self.coerce_bad_values = ['foo', None] - self.to_primitive_values = self.coerce_good_values[0:1] - self.from_primitive_values = self.coerce_good_values[0:1] - - -class TestBoolean(TestField): - def setUp(self): - super(TestField, self).setUp() - self.field = fields.BooleanField() - self.coerce_good_values = [(True, True), (False, False), (1, True), - ('foo', True), (0, False), ('', False)] - self.coerce_bad_values = [] - self.to_primitive_values = self.coerce_good_values[0:2] - self.from_primitive_values = self.coerce_good_values[0:2] - - -class TestDateTime(TestField): - def setUp(self): - super(TestDateTime, self).setUp() - self.dt = datetime.datetime(1955, 11, 5, tzinfo=iso8601.iso8601.Utc()) - self.field = fields.DateTimeField() - self.coerce_good_values = [(self.dt, self.dt), - (timeutils.isotime(self.dt), self.dt)] - self.coerce_bad_values = [1, 'foo'] - self.to_primitive_values = [(self.dt, timeutils.isotime(self.dt))] - self.from_primitive_values = [(timeutils.isotime(self.dt), self.dt)] - - def test_stringify(self): - self.assertEqual( - '1955-11-05T18:00:00Z', - self.field.stringify( - datetime.datetime(1955, 11, 5, 18, 0, 0, - tzinfo=iso8601.iso8601.Utc()))) - - -class TestDict(TestField): - def setUp(self): - super(TestDict, self).setUp() - self.field = fields.Field(fields.Dict(FakeFieldType())) - self.coerce_good_values = [({'foo': 'bar'}, {'foo': '*bar*'}), - ({'foo': 1}, {'foo': '*1*'})] - self.coerce_bad_values = [{1: 'bar'}, 'foo'] - self.to_primitive_values = [({'foo': 'bar'}, {'foo': '!bar!'})] - self.from_primitive_values = [({'foo': '!bar!'}, {'foo': 'bar'})] - - def test_stringify(self): - self.assertEqual("{key=val}", self.field.stringify({'key': 'val'})) - - -class TestDictOfStrings(TestField): - def setUp(self): - super(TestDictOfStrings, self).setUp() - self.field = fields.DictOfStringsField() - self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}), - ({'foo': 1}, {'foo': '1'})] - self.coerce_bad_values = [{1: 'bar'}, {'foo': None}, 'foo'] - self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})] - self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})] - - def test_stringify(self): - self.assertEqual("{key='val'}", self.field.stringify({'key': 'val'})) - - -class TestDictOfIntegers(TestField): - def setUp(self): - super(TestDictOfIntegers, self).setUp() - self.field = fields.DictOfIntegersField() - self.coerce_good_values = [({'foo': '42'}, {'foo': 42}), - ({'foo': 4.2}, {'foo': 4})] - self.coerce_bad_values = [{1: 'bar'}, {'foo': 'boo'}, - 'foo', {'foo': None}] - self.to_primitive_values = [({'foo': 42}, {'foo': 42})] - self.from_primitive_values = [({'foo': 42}, {'foo': 42})] - - def test_stringify(self): - self.assertEqual("{key=42}", self.field.stringify({'key': 42})) - - -class TestDictOfStringsNone(TestField): - def setUp(self): - super(TestDictOfStringsNone, self).setUp() - self.field = fields.DictOfNullableStringsField() - self.coerce_good_values = [({'foo': 'bar'}, {'foo': 'bar'}), - ({'foo': 1}, {'foo': '1'}), - ({'foo': None}, {'foo': None})] - self.coerce_bad_values = [{1: 'bar'}, 'foo'] - self.to_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})] - self.from_primitive_values = [({'foo': 'bar'}, {'foo': 'bar'})] - - def test_stringify(self): - self.assertEqual("{k2=None,key='val'}", - self.field.stringify({'k2': None, - 'key': 'val'})) - - -class TestListOfDictOfNullableStringsField(TestField): - def setUp(self): - super(TestListOfDictOfNullableStringsField, self).setUp() - self.field = fields.ListOfDictOfNullableStringsField() - self.coerce_good_values = [([{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}], - [{'f': 'b', 'f1': 'b1'}, {'f2': 'b2'}]), - ([{'f': 1}, {'f1': 'b1'}], - [{'f': '1'}, {'f1': 'b1'}]), - ([{'foo': None}], [{'foo': None}])] - self.coerce_bad_values = [[{1: 'a'}], ['ham', 1], ['eggs']] - self.to_primitive_values = [([{'f': 'b'}, {'f1': 'b1'}, {'f2': None}], - [{'f': 'b'}, {'f1': 'b1'}, {'f2': None}])] - self.from_primitive_values = [([{'f': 'b'}, {'f1': 'b1'}, - {'f2': None}], - [{'f': 'b'}, {'f1': 'b1'}, - {'f2': None}])] - - def test_stringify(self): - self.assertEqual("[{f=None,f1='b1'},{f2='b2'}]", - self.field.stringify( - [{'f': None, 'f1': 'b1'}, {'f2': 'b2'}])) - - -class TestList(TestField): - def setUp(self): - super(TestList, self).setUp() - self.field = fields.Field(fields.List(FakeFieldType())) - self.coerce_good_values = [(['foo', 'bar'], ['*foo*', '*bar*'])] - self.coerce_bad_values = ['foo'] - self.to_primitive_values = [(['foo'], ['!foo!'])] - self.from_primitive_values = [(['!foo!'], ['foo'])] - - def test_stringify(self): - self.assertEqual('[123]', self.field.stringify([123])) - - -class TestListOfStrings(TestField): - def setUp(self): - super(TestListOfStrings, self).setUp() - self.field = fields.ListOfStringsField() - self.coerce_good_values = [(['foo', 'bar'], ['foo', 'bar'])] - self.coerce_bad_values = ['foo'] - self.to_primitive_values = [(['foo'], ['foo'])] - self.from_primitive_values = [(['foo'], ['foo'])] - - def test_stringify(self): - self.assertEqual("['abc']", self.field.stringify(['abc'])) - - -class TestSet(TestField): - def setUp(self): - super(TestSet, self).setUp() - self.field = fields.Field(fields.Set(FakeFieldType())) - self.coerce_good_values = [(set(['foo', 'bar']), - set(['*foo*', '*bar*']))] - self.coerce_bad_values = [['foo'], {'foo': 'bar'}] - self.to_primitive_values = [(set(['foo']), tuple(['!foo!']))] - self.from_primitive_values = [(tuple(['!foo!']), set(['foo']))] - - def test_stringify(self): - self.assertEqual('set([123])', self.field.stringify(set([123]))) - - -class TestSetOfIntegers(TestField): - def setUp(self): - super(TestSetOfIntegers, self).setUp() - self.field = fields.SetOfIntegersField() - self.coerce_good_values = [(set(['1', 2]), - set([1, 2]))] - self.coerce_bad_values = [set(['foo'])] - self.to_primitive_values = [(set([1]), tuple([1]))] - self.from_primitive_values = [(tuple([1]), set([1]))] - - def test_stringify(self): - self.assertEqual('set([1,2])', self.field.stringify(set([1, 2]))) - - -class TestListOfSetsOfIntegers(TestField): - def setUp(self): - super(TestListOfSetsOfIntegers, self).setUp() - self.field = fields.ListOfSetsOfIntegersField() - self.coerce_good_values = [([set(['1', 2]), set([3, '4'])], - [set([1, 2]), set([3, 4])])] - self.coerce_bad_values = [[set(['foo'])]] - self.to_primitive_values = [([set([1])], [tuple([1])])] - self.from_primitive_values = [([tuple([1])], [set([1])])] - - def test_stringify(self): - self.assertEqual('[set([1,2])]', self.field.stringify([set([1, 2])])) - - -class TestObject(TestField): - def setUp(self): - super(TestObject, self).setUp() - - class TestableObject(obj_base.CinderObject): - fields = { - 'uuid': fields.StringField(), - } - - def __eq__(self, value): - # NOTE(danms): Be rather lax about this equality thing to - # satisfy the assertEqual() in test_from_primitive(). We - # just want to make sure the right type of object is re-created - return value.__class__.__name__ == TestableObject.__name__ - - class OtherTestableObject(obj_base.CinderObject): - pass - - test_inst = TestableObject() - self._test_cls = TestableObject - self.field = fields.Field(fields.Object('TestableObject')) - self.coerce_good_values = [(test_inst, test_inst)] - self.coerce_bad_values = [OtherTestableObject(), 1, 'foo'] - self.to_primitive_values = [(test_inst, test_inst.obj_to_primitive())] - self.from_primitive_values = [(test_inst.obj_to_primitive(), - test_inst), (test_inst, test_inst)] - - def test_stringify(self): - obj = self._test_cls(uuid='fake-uuid') - self.assertEqual('TestableObject(fake-uuid)', - self.field.stringify(obj)) diff --git a/cinder/tests/unit/objects/test_objects.py b/cinder/tests/unit/objects/test_objects.py deleted file mode 100644 index 74f8d7977..000000000 --- a/cinder/tests/unit/objects/test_objects.py +++ /dev/null @@ -1,966 +0,0 @@ -# Copyright 2015 IBM Corp. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import contextlib -import copy -import datetime - -import mock -from oslo_serialization import jsonutils -from oslo_utils import timeutils -import six -from testtools import matchers - -from cinder import context -from cinder import exception -from cinder import objects -from cinder.objects import base -from cinder.objects import fields -from cinder import test -from cinder.tests.unit import fake_notifier - - -class MyOwnedObject(base.CinderPersistentObject, base.CinderObject): - VERSION = '1.0' - fields = {'baz': fields.Field(fields.Integer())} - - -class MyObj(base.CinderPersistentObject, base.CinderObject, - base.CinderObjectDictCompat): - VERSION = '1.6' - fields = {'foo': fields.Field(fields.Integer(), default=1), - 'bar': fields.Field(fields.String()), - 'missing': fields.Field(fields.String()), - 'readonly': fields.Field(fields.Integer(), read_only=True), - 'rel_object': fields.ObjectField('MyOwnedObject', nullable=True), - 'rel_objects': fields.ListOfObjectsField('MyOwnedObject', - nullable=True), - } - - @staticmethod - def _from_db_object(context, obj, db_obj): - self = MyObj() - self.foo = db_obj['foo'] - self.bar = db_obj['bar'] - self.missing = db_obj['missing'] - self.readonly = 1 - return self - - def obj_load_attr(self, attrname): - setattr(self, attrname, 'loaded!') - - @base.remotable_classmethod - def query(cls, context): - obj = cls(context=context, foo=1, bar='bar') - obj.obj_reset_changes() - return obj - - @base.remotable - def marco(self, context): - return 'polo' - - @base.remotable - def _update_test(self, context): - if context.project_id == 'alternate': - self.bar = 'alternate-context' - else: - self.bar = 'updated' - - @base.remotable - def save(self, context): - self.obj_reset_changes() - - @base.remotable - def refresh(self, context): - self.foo = 321 - self.bar = 'refreshed' - self.obj_reset_changes() - - @base.remotable - def modify_save_modify(self, context): - self.bar = 'meow' - self.save() - self.foo = 42 - self.rel_object = MyOwnedObject(baz=42) - - def obj_make_compatible(self, primitive, target_version): - super(MyObj, self).obj_make_compatible(primitive, target_version) - # NOTE(danms): Simulate an older version that had a different - # format for the 'bar' attribute - if target_version == '1.1' and 'bar' in primitive: - primitive['bar'] = 'old%s' % primitive['bar'] - - -class MyObjDiffVers(MyObj): - VERSION = '1.5' - - @classmethod - def obj_name(cls): - return 'MyObj' - - -class MyObj2(object): - @classmethod - def obj_name(cls): - return 'MyObj' - - @base.remotable_classmethod - def query(cls, *args, **kwargs): - pass - - -class RandomMixInWithNoFields(object): - """Used to test object inheritance using a mixin that has no fields.""" - pass - - -class TestSubclassedObject(RandomMixInWithNoFields, MyObj): - fields = {'new_field': fields.Field(fields.String())} - - -class TestMetaclass(test.TestCase): - def test_obj_tracking(self): - - @six.add_metaclass(base.CinderObjectMetaclass) - class NewBaseClass(object): - VERSION = '1.0' - fields = {} - - @classmethod - def obj_name(cls): - return cls.__name__ - - class Fake1TestObj1(NewBaseClass): - @classmethod - def obj_name(cls): - return 'fake1' - - class Fake1TestObj2(Fake1TestObj1): - pass - - class Fake1TestObj3(Fake1TestObj1): - VERSION = '1.1' - - class Fake2TestObj1(NewBaseClass): - @classmethod - def obj_name(cls): - return 'fake2' - - class Fake1TestObj4(Fake1TestObj3): - VERSION = '1.2' - - class Fake2TestObj2(Fake2TestObj1): - VERSION = '1.1' - - class Fake1TestObj5(Fake1TestObj1): - VERSION = '1.1' - - # Newest versions first in the list. Duplicate versions take the - # newest object. - expected = {'fake1': [Fake1TestObj4, Fake1TestObj5, Fake1TestObj2], - 'fake2': [Fake2TestObj2, Fake2TestObj1]} - self.assertEqual(expected, NewBaseClass._obj_classes) - # The following should work, also. - self.assertEqual(expected, Fake1TestObj1._obj_classes) - self.assertEqual(expected, Fake1TestObj2._obj_classes) - self.assertEqual(expected, Fake1TestObj3._obj_classes) - self.assertEqual(expected, Fake1TestObj4._obj_classes) - self.assertEqual(expected, Fake1TestObj5._obj_classes) - self.assertEqual(expected, Fake2TestObj1._obj_classes) - self.assertEqual(expected, Fake2TestObj2._obj_classes) - - def test_field_checking(self): - def create_class(field): - class TestField(base.CinderObject): - VERSION = '1.5' - fields = {'foo': field()} - return TestField - - create_class(fields.BooleanField) - self.assertRaises(exception.ObjectFieldInvalid, - create_class, fields.Boolean) - self.assertRaises(exception.ObjectFieldInvalid, - create_class, int) - - -class TestObjToPrimitive(test.TestCase): - - def test_obj_to_primitive_list(self): - class MyObjElement(base.CinderObject): - fields = {'foo': fields.IntegerField()} - - def __init__(self, foo): - super(MyObjElement, self).__init__() - self.foo = foo - - class MyList(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('MyObjElement')} - - mylist = MyList() - mylist.objects = [MyObjElement(1), MyObjElement(2), MyObjElement(3)] - self.assertEqual([1, 2, 3], - [x['foo'] for x in base.obj_to_primitive(mylist)]) - - def test_obj_to_primitive_dict(self): - myobj = MyObj(foo=1, bar='foo') - self.assertEqual({'foo': 1, 'bar': 'foo'}, - base.obj_to_primitive(myobj)) - - def test_obj_to_primitive_recursive(self): - class MyList(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('MyObj')} - - mylist = MyList(objects=[MyObj(), MyObj()]) - for i, value in enumerate(mylist): - value.foo = i - self.assertEqual([{'foo': 0}, {'foo': 1}], - base.obj_to_primitive(mylist)) - - -class TestObjMakeList(test.TestCase): - - def test_obj_make_list(self): - class MyList(base.ObjectListBase, base.CinderObject): - pass - - db_objs = [{'foo': 1, 'bar': 'baz', 'missing': 'banana'}, - {'foo': 2, 'bar': 'bat', 'missing': 'apple'}, - ] - mylist = base.obj_make_list('ctxt', MyList(), MyObj, db_objs) - self.assertEqual(2, len(mylist)) - self.assertEqual('ctxt', mylist._context) - for index, item in enumerate(mylist): - self.assertEqual(db_objs[index]['foo'], item.foo) - self.assertEqual(db_objs[index]['bar'], item.bar) - self.assertEqual(db_objs[index]['missing'], item.missing) - - -def compare_obj(test, obj, db_obj, subs=None, allow_missing=None, - comparators=None): - """Compare a CinderObject and a dict-like database object. - - This automatically converts TZ-aware datetimes and iterates over - the fields of the object. - - :param:test: The TestCase doing the comparison - :param:obj: The CinderObject to examine - :param:db_obj: The dict-like database object to use as reference - :param:subs: A dict of objkey=dbkey field substitutions - :param:allow_missing: A list of fields that may not be in db_obj - :param:comparators: Map of comparator functions to use for certain fields - """ - - if subs is None: - subs = {} - if allow_missing is None: - allow_missing = [] - if comparators is None: - comparators = {} - - for key in obj.fields: - if key in allow_missing and not obj.obj_attr_is_set(key): - continue - obj_val = getattr(obj, key) - db_key = subs.get(key, key) - db_val = db_obj[db_key] - if isinstance(obj_val, datetime.datetime): - obj_val = obj_val.replace(tzinfo=None) - - if key in comparators: - comparator = comparators[key] - comparator(db_val, obj_val) - else: - test.assertEqual(db_val, obj_val) - - -class _BaseTestCase(test.TestCase): - def setUp(self): - super(_BaseTestCase, self).setUp() - self.remote_object_calls = list() - self.user_id = 'fake-user' - self.project_id = 'fake-project' - self.context = context.RequestContext(self.user_id, self.project_id) - fake_notifier.stub_notifier(self.stubs) - self.addCleanup(fake_notifier.reset) - - def compare_obj(self, obj, db_obj, subs=None, allow_missing=None, - comparators=None): - compare_obj(self, obj, db_obj, subs=subs, allow_missing=allow_missing, - comparators=comparators) - - def json_comparator(self, expected, obj_val): - # json-ify an object field for comparison with its db str - # equivalent - self.assertEqual(expected, jsonutils.dumps(obj_val)) - - def str_comparator(self, expected, obj_val): - """Compare an object field to a string in the db by performing - a simple coercion on the object field value. - """ - self.assertEqual(expected, str(obj_val)) - - def assertNotIsInstance(self, obj, cls, msg=None): - """Python < v2.7 compatibility. Assert 'not isinstance(obj, cls).""" - try: - f = super(_BaseTestCase, self).assertNotIsInstance - except AttributeError: - self.assertThat(obj, - matchers.Not(matchers.IsInstance(cls)), - message=msg or '') - else: - f(obj, cls, msg=msg) - - -class _LocalTest(_BaseTestCase): - def setUp(self): - super(_LocalTest, self).setUp() - # Just in case - base.CinderObject.indirection_api = None - - def assertRemotes(self): - self.assertEqual(self.remote_object_calls, []) - - -@contextlib.contextmanager -def things_temporarily_local(): - _api = base.CinderObject.indirection_api - base.CinderObject.indirection_api = None - yield - base.CinderObject.indirection_api = _api - - -class _TestObject(object): - def test_object_attrs_in_init(self): - # Now check the test one in this file. Should be newest version - self.assertEqual('1.6', objects.MyObj.VERSION) - - def test_hydration_type_error(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.5', - 'cinder_object.data': {'foo': 'a'}} - self.assertRaises(ValueError, MyObj.obj_from_primitive, primitive) - - def test_hydration(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.5', - 'cinder_object.data': {'foo': 1}} - real_method = MyObj._obj_from_primitive - - def _obj_from_primitive(*args): - return real_method(*args) - - with mock.patch.object(MyObj, '_obj_from_primitive') as ofp: - ofp.side_effect = _obj_from_primitive - obj = MyObj.obj_from_primitive(primitive) - ofp.assert_called_once_with(None, '1.5', primitive) - self.assertEqual(obj.foo, 1) - - def test_hydration_version_different(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.2', - 'cinder_object.data': {'foo': 1}} - obj = MyObj.obj_from_primitive(primitive) - self.assertEqual(obj.foo, 1) - self.assertEqual('1.2', obj.VERSION) - - def test_hydration_bad_ns(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'foo', - 'cinder_object.version': '1.5', - 'cinder_object.data': {'foo': 1}} - self.assertRaises(exception.UnsupportedObjectError, - MyObj.obj_from_primitive, primitive) - - def test_hydration_additional_unexpected_stuff(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.5.1', - 'cinder_object.data': { - 'foo': 1, - 'unexpected_thing': 'foobar'}} - obj = MyObj.obj_from_primitive(primitive) - self.assertEqual(1, obj.foo) - self.assertFalse(hasattr(obj, 'unexpected_thing')) - # NOTE(danms): If we call obj_from_primitive() directly - # with a version containing .z, we'll get that version - # in the resulting object. In reality, when using the - # serializer, we'll get that snipped off (tested - # elsewhere) - self.assertEqual('1.5.1', obj.VERSION) - - def test_dehydration(self): - expected = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.6', - 'cinder_object.data': {'foo': 1}} - obj = MyObj(foo=1) - obj.obj_reset_changes() - self.assertEqual(obj.obj_to_primitive(), expected) - - def test_object_property(self): - obj = MyObj(foo=1) - self.assertEqual(obj.foo, 1) - - def test_object_property_type_error(self): - obj = MyObj() - - def fail(): - obj.foo = 'a' - self.assertRaises(ValueError, fail) - - def test_object_dict_syntax(self): - obj = MyObj(foo=123, bar='bar') - self.assertEqual(obj['foo'], 123) - self.assertEqual(sorted(obj.items(), key=lambda x: x[0]), - [('bar', 'bar'), ('foo', 123)]) - self.assertEqual(sorted(list(obj.iteritems()), key=lambda x: x[0]), - [('bar', 'bar'), ('foo', 123)]) - - def test_load(self): - obj = MyObj() - self.assertEqual(obj.bar, 'loaded!') - - def test_load_in_base(self): - class Foo(base.CinderObject): - fields = {'foobar': fields.Field(fields.Integer())} - obj = Foo() - with self.assertRaisesRegex(NotImplementedError, ".*foobar.*"): - obj.foobar - - def test_loaded_in_primitive(self): - obj = MyObj(foo=1) - obj.obj_reset_changes() - self.assertEqual(obj.bar, 'loaded!') - expected = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.6', - 'cinder_object.changes': ['bar'], - 'cinder_object.data': {'foo': 1, - 'bar': 'loaded!'}} - self.assertEqual(obj.obj_to_primitive(), expected) - - def test_changes_in_primitive(self): - obj = MyObj(foo=123) - self.assertEqual(obj.obj_what_changed(), set(['foo'])) - primitive = obj.obj_to_primitive() - self.assertIn('cinder_object.changes', primitive) - obj2 = MyObj.obj_from_primitive(primitive) - self.assertEqual(obj2.obj_what_changed(), set(['foo'])) - obj2.obj_reset_changes() - self.assertEqual(obj2.obj_what_changed(), set()) - - def test_obj_class_from_name(self): - obj = base.CinderObject.obj_class_from_name('MyObj', '1.5') - self.assertEqual('1.5', obj.VERSION) - - def test_obj_class_from_name_latest_compatible(self): - obj = base.CinderObject.obj_class_from_name('MyObj', '1.1') - self.assertEqual('1.6', obj.VERSION) - - def test_unknown_objtype(self): - self.assertRaises(exception.UnsupportedObjectError, - base.CinderObject.obj_class_from_name, 'foo', '1.0') - - def test_obj_class_from_name_supported_version(self): - error = None - try: - base.CinderObject.obj_class_from_name('MyObj', '1.25') - except exception.IncompatibleObjectVersion as error: - pass - - self.assertIsNotNone(error) - self.assertEqual('1.6', error.kwargs['supported']) - - def test_with_alternate_context(self): - ctxt1 = context.RequestContext('foo', 'foo') - ctxt2 = context.RequestContext('bar', 'alternate') - obj = MyObj.query(ctxt1) - obj._update_test(ctxt2) - self.assertEqual(obj.bar, 'alternate-context') - self.assertRemotes() - - def test_orphaned_object(self): - obj = MyObj.query(self.context) - obj._context = None - self.assertRaises(exception.OrphanedObjectError, - obj._update_test) - self.assertRemotes() - - def test_changed_1(self): - obj = MyObj.query(self.context) - obj.foo = 123 - self.assertEqual(obj.obj_what_changed(), set(['foo'])) - obj._update_test(self.context) - self.assertEqual(obj.obj_what_changed(), set(['foo', 'bar'])) - self.assertEqual(obj.foo, 123) - self.assertRemotes() - - def test_changed_2(self): - obj = MyObj.query(self.context) - obj.foo = 123 - self.assertEqual(obj.obj_what_changed(), set(['foo'])) - obj.save() - self.assertEqual(obj.obj_what_changed(), set([])) - self.assertEqual(obj.foo, 123) - self.assertRemotes() - - def test_changed_3(self): - obj = MyObj.query(self.context) - obj.foo = 123 - self.assertEqual(obj.obj_what_changed(), set(['foo'])) - obj.refresh() - self.assertEqual(obj.obj_what_changed(), set([])) - self.assertEqual(obj.foo, 321) - self.assertEqual(obj.bar, 'refreshed') - self.assertRemotes() - - def test_changed_4(self): - obj = MyObj.query(self.context) - obj.bar = 'something' - self.assertEqual(obj.obj_what_changed(), set(['bar'])) - obj.modify_save_modify(self.context) - self.assertEqual(obj.obj_what_changed(), set(['foo', 'rel_object'])) - self.assertEqual(obj.foo, 42) - self.assertEqual(obj.bar, 'meow') - self.assertIsInstance(obj.rel_object, MyOwnedObject) - self.assertRemotes() - - def test_changed_with_sub_object(self): - class ParentObject(base.CinderObject): - fields = {'foo': fields.IntegerField(), - 'bar': fields.ObjectField('MyObj'), - } - obj = ParentObject() - self.assertEqual(set(), obj.obj_what_changed()) - obj.foo = 1 - self.assertEqual(set(['foo']), obj.obj_what_changed()) - bar = MyObj() - obj.bar = bar - self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed()) - obj.obj_reset_changes() - self.assertEqual(set(), obj.obj_what_changed()) - bar.foo = 1 - self.assertEqual(set(['bar']), obj.obj_what_changed()) - - def test_static_result(self): - obj = MyObj.query(self.context) - self.assertEqual(obj.bar, 'bar') - result = obj.marco() - self.assertEqual(result, 'polo') - self.assertRemotes() - - def test_updates(self): - obj = MyObj.query(self.context) - self.assertEqual(obj.foo, 1) - obj._update_test() - self.assertEqual(obj.bar, 'updated') - self.assertRemotes() - - def test_base_attributes(self): - dt = datetime.datetime(1955, 11, 5) - obj = MyObj(created_at=dt, updated_at=dt, deleted_at=None, - deleted=False) - expected = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.6', - 'cinder_object.changes': - ['created_at', 'deleted', 'deleted_at', 'updated_at'], - 'cinder_object.data': - {'created_at': timeutils.isotime(dt), - 'updated_at': timeutils.isotime(dt), - 'deleted_at': None, - 'deleted': False, - } - } - self.assertEqual(obj.obj_to_primitive(), expected) - - def test_contains(self): - obj = MyObj() - self.assertNotIn('foo', obj) - obj.foo = 1 - self.assertIn('foo', obj) - self.assertNotIn('does_not_exist', obj) - - def test_obj_attr_is_set(self): - obj = MyObj(foo=1) - self.assertTrue(obj.obj_attr_is_set('foo')) - self.assertFalse(obj.obj_attr_is_set('bar')) - self.assertRaises(AttributeError, obj.obj_attr_is_set, 'bang') - - def test_get(self): - obj = MyObj(foo=1) - # Foo has value, should not get the default - self.assertEqual(1, obj.get('foo', 2)) - # Foo has value, should return the value without error - self.assertEqual(1, obj.get('foo')) - # Bar is not loaded, so we should get the default - self.assertEqual('not-loaded', obj.get('bar', 'not-loaded')) - # Bar without a default should lazy-load - self.assertEqual('loaded!', obj.get('bar')) - # Bar now has a default, but loaded value should be returned - self.assertEqual('loaded!', obj.get('bar', 'not-loaded')) - # Invalid attribute should return None - self.assertEqual(None, obj.get('nothing')) - - def test_object_inheritance(self): - base_fields = base.CinderPersistentObject.fields.keys() - myobj_fields = (['foo', 'bar', 'missing', - 'readonly', 'rel_object', 'rel_objects'] + - base_fields) - myobj3_fields = ['new_field'] - self.assertTrue(issubclass(TestSubclassedObject, MyObj)) - self.assertEqual(len(myobj_fields), len(MyObj.fields)) - self.assertEqual(set(myobj_fields), set(MyObj.fields.keys())) - self.assertEqual(len(myobj_fields) + len(myobj3_fields), - len(TestSubclassedObject.fields)) - self.assertEqual(set(myobj_fields) | set(myobj3_fields), - set(TestSubclassedObject.fields.keys())) - - def test_obj_as_admin(self): - obj = MyObj(context=self.context) - - def fake(*args, **kwargs): - self.assertTrue(obj._context.is_admin) - - with mock.patch.object(obj, 'obj_reset_changes') as mock_fn: - mock_fn.side_effect = fake - with obj.obj_as_admin(): - obj.save() - self.assertTrue(mock_fn.called) - - self.assertFalse(obj._context.is_admin) - - def test_obj_as_admin_orphaned(self): - def testme(): - obj = MyObj() - with obj.obj_as_admin(): - pass - self.assertRaises(exception.OrphanedObjectError, testme) - - def test_get_changes(self): - obj = MyObj() - self.assertEqual({}, obj.obj_get_changes()) - obj.foo = 123 - self.assertEqual({'foo': 123}, obj.obj_get_changes()) - obj.bar = 'test' - self.assertEqual({'foo': 123, 'bar': 'test'}, obj.obj_get_changes()) - obj.obj_reset_changes() - self.assertEqual({}, obj.obj_get_changes()) - - def test_obj_fields(self): - class TestObj(base.CinderObject): - fields = {'foo': fields.Field(fields.Integer())} - obj_extra_fields = ['bar'] - - @property - def bar(self): - return 'this is bar' - - obj = TestObj() - self.assertEqual(['foo', 'bar'], obj.obj_fields) - - def test_obj_constructor(self): - obj = MyObj(context=self.context, foo=123, bar='abc') - self.assertEqual(123, obj.foo) - self.assertEqual('abc', obj.bar) - self.assertEqual(set(['foo', 'bar']), obj.obj_what_changed()) - - def test_obj_read_only(self): - obj = MyObj(context=self.context, foo=123, bar='abc') - obj.readonly = 1 - self.assertRaises(exception.ReadOnlyFieldError, setattr, - obj, 'readonly', 2) - - def test_obj_repr(self): - obj = MyObj(foo=123) - self.assertEqual('MyObj(bar=,created_at=,deleted=,' - 'deleted_at=,foo=123,missing=,readonly=,' - 'rel_object=,rel_objects=,updated_at=)', - repr(obj)) - - def test_obj_make_obj_compatible(self): - subobj = MyOwnedObject(baz=1) - obj = MyObj(rel_object=subobj) - obj.obj_relationships = { - 'rel_object': [('1.5', '1.1'), ('1.7', '1.2')], - } - primitive = obj.obj_to_primitive()['cinder_object.data'] - with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: - obj._obj_make_obj_compatible(copy.copy(primitive), '1.8', - 'rel_object') - self.assertFalse(mock_compat.called) - - with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: - obj._obj_make_obj_compatible(copy.copy(primitive), - '1.7', 'rel_object') - mock_compat.assert_called_once_with( - primitive['rel_object']['cinder_object.data'], '1.2') - self.assertEqual('1.2', - primitive['rel_object']['cinder_object.version']) - - with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: - obj._obj_make_obj_compatible(copy.copy(primitive), - '1.6', 'rel_object') - mock_compat.assert_called_once_with( - primitive['rel_object']['cinder_object.data'], '1.1') - self.assertEqual('1.1', - primitive['rel_object']['cinder_object.version']) - - with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: - obj._obj_make_obj_compatible(copy.copy(primitive), '1.5', - 'rel_object') - mock_compat.assert_called_once_with( - primitive['rel_object']['cinder_object.data'], '1.1') - self.assertEqual('1.1', - primitive['rel_object']['cinder_object.version']) - - with mock.patch.object(subobj, 'obj_make_compatible') as mock_compat: - _prim = copy.copy(primitive) - obj._obj_make_obj_compatible(_prim, '1.4', 'rel_object') - self.assertFalse(mock_compat.called) - self.assertNotIn('rel_object', _prim) - - def test_obj_make_compatible_hits_sub_objects(self): - subobj = MyOwnedObject(baz=1) - obj = MyObj(foo=123, rel_object=subobj) - obj.obj_relationships = {'rel_object': [('1.0', '1.0')]} - with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat: - obj.obj_make_compatible({'rel_object': 'foo'}, '1.10') - mock_compat.assert_called_once_with({'rel_object': 'foo'}, '1.10', - 'rel_object') - - def test_obj_make_compatible_skips_unset_sub_objects(self): - obj = MyObj(foo=123) - obj.obj_relationships = {'rel_object': [('1.0', '1.0')]} - with mock.patch.object(obj, '_obj_make_obj_compatible') as mock_compat: - obj.obj_make_compatible({'rel_object': 'foo'}, '1.10') - self.assertFalse(mock_compat.called) - - def test_obj_make_compatible_complains_about_missing_rules(self): - subobj = MyOwnedObject(baz=1) - obj = MyObj(foo=123, rel_object=subobj) - obj.obj_relationships = {} - self.assertRaises(exception.ObjectActionError, - obj.obj_make_compatible, {}, '1.0') - - def test_obj_make_compatible_handles_list_of_objects(self): - subobj = MyOwnedObject(baz=1) - obj = MyObj(rel_objects=[subobj]) - obj.obj_relationships = {'rel_objects': [('1.0', '1.123')]} - - def fake_make_compat(primitive, version): - self.assertEqual('1.123', version) - self.assertIn('baz', primitive) - - with mock.patch.object(subobj, 'obj_make_compatible') as mock_mc: - mock_mc.side_effect = fake_make_compat - obj.obj_to_primitive('1.0') - self.assertTrue(mock_mc.called) - - -class TestObject(_LocalTest, _TestObject): - def test_set_defaults(self): - obj = MyObj() - obj.obj_set_defaults('foo') - self.assertTrue(obj.obj_attr_is_set('foo')) - self.assertEqual(1, obj.foo) - - def test_set_defaults_no_default(self): - obj = MyObj() - self.assertRaises(exception.ObjectActionError, - obj.obj_set_defaults, 'bar') - - def test_set_all_defaults(self): - obj = MyObj() - obj.obj_set_defaults() - self.assertEqual(set(['deleted', 'foo']), obj.obj_what_changed()) - self.assertEqual(1, obj.foo) - - -class TestObjectListBase(test.TestCase): - def test_list_like_operations(self): - class MyElement(base.CinderObject): - fields = {'foo': fields.IntegerField()} - - def __init__(self, foo): - super(MyElement, self).__init__() - self.foo = foo - - class Foo(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('MyElement')} - - objlist = Foo(context='foo', - objects=[MyElement(1), MyElement(2), MyElement(3)]) - self.assertEqual(list(objlist), objlist.objects) - self.assertEqual(len(objlist), 3) - self.assertIn(objlist.objects[0], objlist) - self.assertEqual(list(objlist[:1]), [objlist.objects[0]]) - self.assertEqual(objlist[:1]._context, 'foo') - self.assertEqual(objlist[2], objlist.objects[2]) - self.assertEqual(objlist.count(objlist.objects[0]), 1) - self.assertEqual(objlist.index(objlist.objects[1]), 1) - objlist.sort(key=lambda x: x.foo, reverse=True) - self.assertEqual([3, 2, 1], - [x.foo for x in objlist]) - - def test_serialization(self): - class Foo(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('Bar')} - - class Bar(base.CinderObject): - fields = {'foo': fields.Field(fields.String())} - - obj = Foo(objects=[]) - for i in 'abc': - bar = Bar(foo=i) - obj.objects.append(bar) - - obj2 = base.CinderObject.obj_from_primitive(obj.obj_to_primitive()) - self.assertFalse(obj is obj2) - self.assertEqual([x.foo for x in obj], - [y.foo for y in obj2]) - - def test_list_changes(self): - class Foo(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('Bar')} - - class Bar(base.CinderObject): - fields = {'foo': fields.StringField()} - - obj = Foo(objects=[]) - self.assertEqual(set(['objects']), obj.obj_what_changed()) - obj.objects.append(Bar(foo='test')) - self.assertEqual(set(['objects']), obj.obj_what_changed()) - obj.obj_reset_changes() - # This should still look dirty because the child is dirty - self.assertEqual(set(['objects']), obj.obj_what_changed()) - obj.objects[0].obj_reset_changes() - # This should now look clean because the child is clean - self.assertEqual(set(), obj.obj_what_changed()) - - def test_initialize_objects(self): - class Foo(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('Bar')} - - class Bar(base.CinderObject): - fields = {'foo': fields.StringField()} - - obj = Foo() - self.assertEqual([], obj.objects) - self.assertEqual(set(), obj.obj_what_changed()) - - def test_obj_repr(self): - class Foo(base.ObjectListBase, base.CinderObject): - fields = {'objects': fields.ListOfObjectsField('Bar')} - - class Bar(base.CinderObject): - fields = {'uuid': fields.StringField()} - - obj = Foo(objects=[Bar(uuid='fake-uuid')]) - self.assertEqual('Foo(objects=[Bar(fake-uuid)])', repr(obj)) - - -class TestObjectSerializer(_BaseTestCase): - def test_serialize_entity_primitive(self): - ser = base.CinderObjectSerializer() - for thing in (1, 'foo', [1, 2], {'foo': 'bar'}): - self.assertEqual(thing, ser.serialize_entity(None, thing)) - - def test_deserialize_entity_primitive(self): - ser = base.CinderObjectSerializer() - for thing in (1, 'foo', [1, 2], {'foo': 'bar'}): - self.assertEqual(thing, ser.deserialize_entity(None, thing)) - - def _test_deserialize_entity_newer(self, obj_version, backported_to, - my_version='1.6'): - ser = base.CinderObjectSerializer() - ser._conductor = mock.Mock() - ser._conductor.object_backport.return_value = 'backported' - - class MyTestObj(MyObj): - VERSION = my_version - - obj = MyTestObj() - obj.VERSION = obj_version - primitive = obj.obj_to_primitive() - ser.deserialize_entity(self.context, primitive) - if backported_to is None: - self.assertFalse(ser._conductor.object_backport.called) - - def test_deserialize_entity_newer_revision_does_not_backport_zero(self): - self._test_deserialize_entity_newer('1.6.0', None) - - def test_deserialize_entity_newer_revision_does_not_backport(self): - self._test_deserialize_entity_newer('1.6.1', None) - - def test_deserialize_dot_z_with_extra_stuff(self): - primitive = {'cinder_object.name': 'MyObj', - 'cinder_object.namespace': 'cinder', - 'cinder_object.version': '1.6.1', - 'cinder_object.data': { - 'foo': 1, - 'unexpected_thing': 'foobar'}} - ser = base.CinderObjectSerializer() - obj = ser.deserialize_entity(self.context, primitive) - self.assertEqual(1, obj.foo) - self.assertFalse(hasattr(obj, 'unexpected_thing')) - # NOTE(danms): The serializer is where the logic lives that - # avoids backports for cases where only a .z difference in - # the received object version is detected. As a result, we - # end up with a version of what we expected, effectively the - # .0 of the object. - self.assertEqual('1.6', obj.VERSION) - - def test_object_serialization(self): - ser = base.CinderObjectSerializer() - obj = MyObj() - primitive = ser.serialize_entity(self.context, obj) - self.assertIn('cinder_object.name', primitive) - obj2 = ser.deserialize_entity(self.context, primitive) - self.assertIsInstance(obj2, MyObj) - self.assertEqual(self.context, obj2._context) - - def test_object_serialization_iterables(self): - ser = base.CinderObjectSerializer() - obj = MyObj() - for iterable in (list, tuple, set): - thing = iterable([obj]) - primitive = ser.serialize_entity(self.context, thing) - self.assertEqual(1, len(primitive)) - for item in primitive: - self.assertNotIsInstance(item, base.CinderObject) - thing2 = ser.deserialize_entity(self.context, primitive) - self.assertEqual(1, len(thing2)) - for item in thing2: - self.assertIsInstance(item, MyObj) - # dict case - thing = {'key': obj} - primitive = ser.serialize_entity(self.context, thing) - self.assertEqual(1, len(primitive)) - for item in primitive.itervalues(): - self.assertNotIsInstance(item, base.CinderObject) - thing2 = ser.deserialize_entity(self.context, primitive) - self.assertEqual(1, len(thing2)) - for item in thing2.itervalues(): - self.assertIsInstance(item, MyObj) - - # object-action updates dict case - thing = {'foo': obj.obj_to_primitive()} - primitive = ser.serialize_entity(self.context, thing) - self.assertEqual(thing, primitive) - thing2 = ser.deserialize_entity(self.context, thing) - self.assertIsInstance(thing2['foo'], base.CinderObject) diff --git a/cinder/tests/unit/objects/test_snapshot.py b/cinder/tests/unit/objects/test_snapshot.py index 48068a890..e1a76e7f0 100644 --- a/cinder/tests/unit/objects/test_snapshot.py +++ b/cinder/tests/unit/objects/test_snapshot.py @@ -13,11 +13,11 @@ # under the License. import mock +from oslo_versionedobjects.tests import test_objects from cinder.objects import snapshot as snapshot_obj from cinder.objects import volume as volume_obj from cinder.tests.unit import fake_volume -from cinder.tests.unit.objects import test_objects fake_snapshot = { 'id': '1', @@ -68,7 +68,7 @@ class TestSnapshot(test_objects._LocalTest): snapshot = snapshot_obj.Snapshot._from_db_object( self.context, snapshot_obj.Snapshot(), fake_snapshot) snapshot.display_name = 'foobar' - snapshot.save(self.context) + snapshot.save() snapshot_update.assert_called_once_with(self.context, snapshot.id, {'display_name': 'foobar'}) @@ -84,7 +84,7 @@ class TestSnapshot(test_objects._LocalTest): self.assertEqual({'display_name': 'foobar', 'metadata': {'key1': 'value1'}}, snapshot.obj_get_changes()) - snapshot.save(self.context) + snapshot.save() snapshot_update.assert_called_once_with(self.context, snapshot.id, {'display_name': 'foobar'}) snapshot_metadata_update.assert_called_once_with(self.context, '1', @@ -128,7 +128,7 @@ class TestSnapshot(test_objects._LocalTest): class TestSnapshotList(test_objects._LocalTest): @mock.patch('cinder.db.snapshot_metadata_get', return_value={}) - @mock.patch('cinder.objects.Volume.get_by_id') + @mock.patch('cinder.objects.volume.Volume.get_by_id') @mock.patch('cinder.db.snapshot_get_all', return_value=[fake_snapshot]) def test_get_all(self, snapshot_get_all, volume_get_by_id, snapshot_metadata_get): @@ -140,7 +140,7 @@ class TestSnapshotList(test_objects._LocalTest): TestSnapshot._compare(self, fake_snapshot, snapshots[0]) @mock.patch('cinder.db.snapshot_metadata_get', return_value={}) - @mock.patch('cinder.objects.Volume.get_by_id') + @mock.patch('cinder.objects.volume.Volume.get_by_id') @mock.patch('cinder.db.snapshot_get_all_by_project', return_value=[fake_snapshot]) def test_get_all_by_project(self, get_all_by_project, volume_get_by_id, @@ -149,12 +149,12 @@ class TestSnapshotList(test_objects._LocalTest): volume_get_by_id.return_value = fake_volume_obj snapshots = snapshot_obj.SnapshotList.get_all_by_project( - self.context, self.context.project_id) + self.context, self.project_id) self.assertEqual(1, len(snapshots)) TestSnapshot._compare(self, fake_snapshot, snapshots[0]) @mock.patch('cinder.db.snapshot_metadata_get', return_value={}) - @mock.patch('cinder.objects.Volume.get_by_id') + @mock.patch('cinder.objects.volume.Volume.get_by_id') @mock.patch('cinder.db.snapshot_get_all_for_volume', return_value=[fake_snapshot]) def test_get_all_for_volume(self, get_all_for_volume, volume_get_by_id, diff --git a/cinder/tests/unit/objects/test_volume.py b/cinder/tests/unit/objects/test_volume.py index d6f407a70..6d8f2dc50 100644 --- a/cinder/tests/unit/objects/test_volume.py +++ b/cinder/tests/unit/objects/test_volume.py @@ -13,10 +13,10 @@ # under the License. import mock +from oslo_versionedobjects.tests import test_objects from cinder import objects from cinder.tests.unit import fake_volume -from cinder.tests.unit.objects import test_objects class TestVolume(test_objects._LocalTest): diff --git a/cinder/volume/api.py b/cinder/volume/api.py index 56379bfc8..b51a560d7 100644 --- a/cinder/volume/api.py +++ b/cinder/volume/api.py @@ -105,7 +105,7 @@ def check_policy(context, action, target_obj=None): if isinstance(target_obj, objects_base.CinderObject): # Turn object into dict so target.update can work - target.update(objects_base.obj_to_primitive(target_obj) or {}) + target.update(target_obj.obj_to_primitive() or {}) else: target.update(target_obj or {}) @@ -920,7 +920,7 @@ class API(base.Base): snapshot_obj = self.get_snapshot(context, snapshot['id']) snapshot_obj.status = 'deleting' - snapshot_obj.save(context) + snapshot_obj.save() volume = self.db.volume_get(context, snapshot_obj.volume_id) self.volume_rpcapi.delete_snapshot(context, snapshot_obj, @@ -931,7 +931,7 @@ class API(base.Base): @wrap_check_policy def update_snapshot(self, context, snapshot, fields): snapshot.update(fields) - snapshot.save(context) + snapshot.save() @wrap_check_policy def get_volume_metadata(self, context, volume): @@ -1078,7 +1078,7 @@ class API(base.Base): self._check_metadata_properties(_metadata) snapshot.metadata = _metadata - snapshot.save(context) + snapshot.save() # TODO(jdg): Implement an RPC call for drivers that may use this info diff --git a/cinder/volume/manager.py b/cinder/volume/manager.py index 220a9a9a1..63034a670 100644 --- a/cinder/volume/manager.py +++ b/cinder/volume/manager.py @@ -643,12 +643,12 @@ class VolumeManager(manager.SchedulerDependentManager): model_update = self.driver.create_snapshot(snapshot) if model_update: snapshot.update(model_update) - snapshot.save(context) + snapshot.save() except Exception: with excutils.save_and_reraise_exception(): snapshot.status = 'error' - snapshot.save(context) + snapshot.save() vol_ref = self.db.volume_get(context, volume_id) if vol_ref.bootable: @@ -666,12 +666,12 @@ class VolumeManager(manager.SchedulerDependentManager): " %(volume_id)s metadata"), {'volume_id': volume_id}, resource=snapshot) snapshot.status = 'error' - snapshot.save(context) + snapshot.save() raise exception.MetadataCopyFailure(reason=six.text_type(ex)) snapshot.status = 'available' snapshot.progress = '100%' - snapshot.save(context) + snapshot.save() self._notify_about_snapshot_usage(context, snapshot, "create.end") LOG.info(_LI("Create snapshot completed successfully"), @@ -682,6 +682,7 @@ class VolumeManager(manager.SchedulerDependentManager): def delete_snapshot(self, context, snapshot): """Deletes and unexports snapshot.""" context = context.elevated() + snapshot._context = context project_id = snapshot.project_id self._notify_about_snapshot_usage( @@ -731,7 +732,7 @@ class VolumeManager(manager.SchedulerDependentManager): LOG.exception(_LE("Update snapshot usages failed."), resource=snapshot) self.db.volume_glance_metadata_delete_by_snapshot(context, snapshot.id) - snapshot.destroy(context) + snapshot.destroy() self._notify_about_snapshot_usage(context, snapshot, "delete.end") # Commit the reservations diff --git a/requirements.txt b/requirements.txt index 7fa73aa09..b8a21532b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,6 +22,7 @@ oslo.middleware>=1.0.0 # Apache-2.0 oslo.rootwrap>=1.6.0 # Apache-2.0 oslo.serialization>=1.4.0 # Apache-2.0 oslo.utils>=1.4.0 # Apache-2.0 +oslo.versionedobjects>=0.1.1 osprofiler>=0.3.0 # Apache-2.0 paramiko>=1.13.0 Paste