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
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):
"""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
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.
{'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.
with obj.obj_as_admin():
obj.save()
-
"""
if self._context is None:
raise exception.OrphanedObjectError(method='obj_as_admin',
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):
+++ /dev/null
-# 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)
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
LOG = logging.getLogger(__name__)
+@base.CinderObjectRegistry.register
class Snapshot(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat):
# Version 1.0: Initial version
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'))
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:
# 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:
self.obj_reset_changes(['metadata'])
+@base.CinderObjectRegistry.register
class SnapshotList(base.ObjectListBase, base.CinderObject):
VERSION = '1.0'
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
LOG = logging.getLogger(__name__)
+@base.CinderObjectRegistry.register
class Volume(base.CinderPersistentObject, base.CinderObject,
base.CinderObjectDictCompat):
# Version 1.0: Initial version
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'
# 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):
# 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):
+++ /dev/null
-# 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))
+++ /dev/null
-# 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)
# 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',
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'})
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',
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):
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,
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,
# 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):
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 {})
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,
@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):
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
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:
" %(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"),
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(
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
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