diff --git a/designate/notifications.py b/designate/notifications.py index f53c22611..c2a49fc10 100644 --- a/designate/notifications.py +++ b/designate/notifications.py @@ -152,8 +152,7 @@ class Audit(NotificationPlugin): changes = [] for arg in arglist: - if isinstance(arg, (objects.DesignateObject, - objects.OVODesignateObject)): + if isinstance(arg, objects.DesignateObject): for change in arg.obj_what_changed(): if change != 'records': old_value = arg.obj_get_original_value(change) diff --git a/designate/objects/__init__.py b/designate/objects/__init__.py index 70a0da183..c1acb072a 100644 --- a/designate/objects/__init__.py +++ b/designate/objects/__init__.py @@ -13,13 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. from designate.objects.base import DesignateObject # noqa -from designate.objects.ovo_base import DesignateObject as OVODesignateObject # noqa from designate.objects.base import ListObjectMixin # noqa -from designate.objects.ovo_base import ListObjectMixin as OVOListObjectMixin # noqa from designate.objects.base import DictObjectMixin # noqa -from designate.objects.ovo_base import DictObjectMixin as OVODictObjectMixin # noqa from designate.objects.base import PagedListObjectMixin # noqa -from designate.objects.ovo_base import PagedListObjectMixin as OVOPagedListObjectMixin # noqa from designate.objects.blacklist import Blacklist, BlacklistList # noqa from designate.objects.zone import Zone, ZoneList # noqa from designate.objects.zone_attribute import ZoneAttribute, ZoneAttributeList # noqa diff --git a/designate/objects/adapters/api_v2/base.py b/designate/objects/adapters/api_v2/base.py index 1c013f567..b5585aa9d 100644 --- a/designate/objects/adapters/api_v2/base.py +++ b/designate/objects/adapters/api_v2/base.py @@ -15,8 +15,7 @@ from six.moves.urllib import parse from oslo_config import cfg from designate.objects.adapters import base -from designate.objects import base as obj_base -from designate.objects import ovo_base as ovoobj_base +from designate.objects import base as ovoobj_base from designate import exceptions @@ -45,8 +44,7 @@ class APIv2Adapter(base.DesignateAdapter): r_list['links'] = cls._get_collection_links( list_object, kwargs['request']) # Check if we should include metadata - if isinstance(list_object, (obj_base.PagedListObjectMixin, - ovoobj_base.PagedListObjectMixin)): + if isinstance(list_object, ovoobj_base.PagedListObjectMixin): metadata = {} if list_object.total_count is not None: metadata['total_count'] = list_object.total_count diff --git a/designate/objects/adapters/api_v2/validation_error.py b/designate/objects/adapters/api_v2/validation_error.py index 64efe49e8..ceb9f343a 100644 --- a/designate/objects/adapters/api_v2/validation_error.py +++ b/designate/objects/adapters/api_v2/validation_error.py @@ -78,8 +78,7 @@ class ValidationErrorAPIv2Adapter(base.APIv2Adapter): # Check if the object is a list - lists will just have an index as a # value, ands this can't be renamed - if issubclass(obj_adapter.ADAPTER_OBJECT, - (objects.ListObjectMixin, objects.OVOListObjectMixin)): + if issubclass(obj_adapter.ADAPTER_OBJECT, objects.ListObjectMixin): obj_adapter = cls.get_object_adapter( cls.ADAPTER_FORMAT, obj_adapter.ADAPTER_OBJECT.LIST_ITEM_TYPE.obj_name()) @@ -110,7 +109,7 @@ class ValidationErrorAPIv2Adapter(base.APIv2Adapter): obj_adapter = cls.get_object_adapter( cls.ADAPTER_FORMAT, obj_cls) - object = objects.OVODesignateObject.obj_cls_from_name(obj_cls)() # noqa + object = objects.DesignateObject.obj_cls_from_name(obj_cls)() # noqa # Recurse down into this object path_segment, obj_adapter = cls._rename_path_segment( obj_adapter, object, path_segment) diff --git a/designate/objects/adapters/base.py b/designate/objects/adapters/base.py index e9292870a..569d40fea 100644 --- a/designate/objects/adapters/base.py +++ b/designate/objects/adapters/base.py @@ -60,8 +60,7 @@ class DesignateAdapter(object): @classmethod def get_object_adapter(cls, format_, object): - if isinstance(object, (objects.DesignateObject, - objects.OVODesignateObject)): + if isinstance(object, objects.DesignateObject): key = '%s:%s' % (format_, object.obj_name()) else: key = '%s:%s' % (format_, object) @@ -82,8 +81,7 @@ class DesignateAdapter(object): @classmethod def render(cls, format_, object, *args, **kwargs): - if isinstance(object, (objects.ListObjectMixin, - objects.OVOListObjectMixin)): + if isinstance(object, objects.ListObjectMixin): # type_ = 'list' return cls.get_object_adapter( format_, object)._render_list(object, *args, **kwargs) @@ -177,8 +175,7 @@ class DesignateAdapter(object): LOG.debug(output_object) try: - if isinstance(output_object, (objects.ListObjectMixin, - objects.OVOListObjectMixin)): + if isinstance(output_object, objects.ListObjectMixin): # type_ = 'list' return cls.get_object_adapter( format_, @@ -260,7 +257,7 @@ class DesignateAdapter(object): check_field, 'objname'): # (daidv): Check if field is OVO field and have a relation obj_class_name = output_object.FIELDS.get(obj_key).objname - obj_class = objects.OVODesignateObject.obj_cls_from_name( + obj_class = objects.DesignateObject.obj_cls_from_name( obj_class_name) obj = cls.get_object_adapter( cls.ADAPTER_FORMAT, obj_class_name).parse( diff --git a/designate/objects/base.py b/designate/objects/base.py index 8b0d2b968..895054795 100644 --- a/designate/objects/base.py +++ b/designate/objects/base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014 Rackspace Hosting +# Copyright (c) 2017 Fujitsu Limited # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -12,240 +12,46 @@ # 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 copy - import six -from six.moves.urllib import parse -import jsonschema from oslo_log import log as logging -from oslo_utils import timeutils - -from designate import exceptions -from designate.objects import validation_error -from designate.schema import validators -from designate.schema import format +from oslo_versionedobjects import exception +from oslo_utils import excutils from designate.i18n import _ +from designate.i18n import _LE +from oslo_versionedobjects import base +from oslo_versionedobjects.base import VersionedObjectDictCompat as DictObjectMixin # noqa + +from designate.objects import fields +from designate import exceptions LOG = logging.getLogger(__name__) -class NotSpecifiedSentinel: - pass +def _get_attrname(name): + return "_obj_{}".format(name) -def get_attrname(name): - """Return the mangled name of the attribute's underlying storage.""" - return '_obj_field_%s' % name +def get_dict_attr(klass, attr): + for klass in [klass] + klass.mro(): + if attr in klass.__dict__: + return klass.__dict__[attr] + raise AttributeError -def make_class_properties(cls): - """Build getter and setter methods for all the objects attributes""" - # Prepare an empty dict to gather the merged/final set of fields - fields = {} - - # Add each supercls's fields - for supercls in cls.mro()[1:-1]: - if not hasattr(supercls, 'FIELDS'): - continue - fields.update(supercls.FIELDS) - - # Add our fields - fields.update(cls.FIELDS) - - # Store the results - cls.FIELDS = fields - - for field in six.iterkeys(cls.FIELDS): - def getter(self, name=field): - self._obj_check_relation(name) - return getattr(self, get_attrname(name), None) - - def setter(self, value, name=field): - if (self.obj_attr_is_set(name) and value != getattr(self, name) - or not self.obj_attr_is_set(name)): - self._obj_changes.add(name) - - if (self.obj_attr_is_set(name) and value != getattr(self, name) - and name not in list(six.iterkeys( - self._obj_original_values))): - self._obj_original_values[name] = getattr(self, name) - - return setattr(self, get_attrname(name), value) - - setattr(cls, field, property(getter, setter)) - - -def _schema_ref_resolver(uri): - """ - Fetches an DesignateObject's schema from a JSON Schema Reference URI - - Sample URI: obj://ObjectName#/subpathA/subpathB - """ - obj_name = parse.urlsplit(uri).netloc - obj = DesignateObject.obj_cls_from_name(obj_name) - - return obj.obj_get_schema() - - -def make_class_validator(obj): - - schema = { - '$schema': 'http://json-schema.org/draft-04/hyper-schema', - 'title': obj.obj_name(), - 'description': 'Designate %s Object' % obj.obj_name(), - } - - if isinstance(obj, ListObjectMixin): - - schema['type'] = 'array', - schema['items'] = make_class_validator(obj.LIST_ITEM_TYPE()) - - else: - schema['type'] = 'object' - schema['additionalProperties'] = False - schema['required'] = [] - schema['properties'] = {} - - for name, properties in obj.FIELDS.items(): - if properties.get('relation', False): - if obj.obj_attr_is_set(name): - schema['properties'][name] = \ - make_class_validator(getattr(obj, name)) - else: - schema['properties'][name] = properties.get('schema', {}) - - if properties.get('required', False): - schema['required'].append(name) - - resolver = jsonschema.RefResolver.from_schema( - schema, handlers={'obj': _schema_ref_resolver}) - - obj._obj_validator = validators.Draft4Validator( - schema, resolver=resolver, format_checker=format.draft4_format_checker) - - return schema - - -class DesignateObjectMetaclass(type): - def __init__(cls, names, bases, dict_): - if not hasattr(cls, '_obj_classes'): - # This means we're working on the base DesignateObject class, - # and can skip the remaining Metaclass functionality - cls._obj_classes = {} - return - - make_class_properties(cls) - - # Add a reference to the finished class into the _obj_classes - # dictionary, allowing us to lookup classes by their name later - this - # is useful for e.g. referencing another DesignateObject in a - # validation schema. - if cls.obj_name() not in cls._obj_classes: - cls._obj_classes[cls.obj_name()] = cls - else: - raise Exception("Duplicate DesignateObject with name '%(name)s'" % - {'name': cls.obj_name()}) - - -@six.add_metaclass(DesignateObjectMetaclass) -class DesignateObject(object): - FIELDS = {} +class DesignateObject(base.VersionedObject): + OBJ_SERIAL_NAMESPACE = 'designate_object' + OBJ_PROJECT_NAMESPACE = 'designate' STRING_KEYS = [] - def _obj_check_relation(self, name): - if name in self.FIELDS and self.FIELDS[name].get('relation', False): - if not self.obj_attr_is_set(name): - raise exceptions.RelationNotLoaded(object=self, relation=name) - - @classmethod - def obj_cls_from_name(cls, name): - """Retrieves a object cls from the registry by name and returns it.""" - return cls._obj_classes[name] - - @classmethod - def from_primitive(cls, primitive): - """ - Construct an object from primitive types - - This is used while deserializing the object. - """ - objcls = cls.obj_cls_from_name(primitive['designate_object.name']) - return objcls._obj_from_primitive(primitive) - - @classmethod - def _obj_from_primitive(cls, primitive): - instance = cls() - - for field, value in primitive['designate_object.data'].items(): - if isinstance(value, dict) and 'designate_object.name' in value: - setattr(instance, field, DesignateObject.from_primitive(value)) - else: - # data typically doesn't have a schema.. - schema = cls.FIELDS[field].get("schema", None) - if schema is not None and value is not None: - if "format" in schema and schema["format"] == "date-time": - value = timeutils.parse_strtime(value) - setattr(instance, field, value) - - instance._obj_changes = set(primitive['designate_object.changes']) - instance._obj_original_values = \ - primitive['designate_object.original_values'] - - return instance - - @classmethod - def from_dict(cls, _dict): - instance = cls() - - for field, value in _dict.items(): - if (field in instance.FIELDS and - instance.FIELDS[field].get('relation', False)): - relation_cls_name = instance.FIELDS[field]['relation_cls'] - # We're dealing with a relation, we'll want to create the - # correct object type and recurse - relation_cls = cls.obj_cls_from_name(relation_cls_name) - - if isinstance(value, list): - setattr(instance, field, relation_cls.from_list(value)) - else: - setattr(instance, field, relation_cls.from_dict(value)) - - else: - setattr(instance, field, value) - - return instance - - @classmethod - def from_list(cls, _list): - raise NotImplementedError() - - @classmethod - def obj_name(cls): - """Return a canonical name for this object which will be used over - the wire and in validation schemas. - """ - return cls.__name__ - - @classmethod - def obj_get_schema(cls): - """Returns the JSON Schema for this Object.""" - return cls._obj_validator.schema - - def __init__(self, **kwargs): - self._obj_changes = set() - self._obj_original_values = dict() - - for name, value in kwargs.items(): - if name in list(six.iterkeys(self.FIELDS)): - setattr(self, name, value) - else: + def __init__(self, *args, **kwargs): + for name in kwargs: + if name not in self.fields: raise TypeError("__init__() got an unexpected keyword " "argument '%(name)s'" % {'name': name}) - - def __str__(self): - return (self._make_obj_str(self.STRING_KEYS) - % self) + super(DesignateObject, self).__init__(self, *args, **kwargs) + self._obj_original_values = dict() + self.FIELDS = self.fields @classmethod def _make_obj_str(cls, keys): @@ -255,35 +61,29 @@ class DesignateObject(object): msg += ">" return msg - def to_primitive(self): - """ - Convert the object to primitive types so that the object can be - serialized. - NOTE: Currently all the designate objects contain primitive types that - do not need special handling. If this changes we need to modify this - function. - """ - data = {} + def __str__(self): + return (self._make_obj_str(self.STRING_KEYS) + % self) - for field in six.iterkeys(self.FIELDS): - if self.obj_attr_is_set(field): - if isinstance(getattr(self, field), DesignateObject): - data[field] = getattr(self, field).to_primitive() - else: - data[field] = getattr(self, field) + def save(self, context): + pass - return { - 'designate_object.name': self.obj_name(), - 'designate_object.data': data, - 'designate_object.changes': sorted(self._obj_changes), - 'designate_object.original_values': dict(self._obj_original_values) - } + def _obj_check_relation(self, name): + if name in self.fields: + if hasattr(self.fields.get(name), 'objname'): + if not self.obj_attr_is_set(name): + raise exceptions.RelationNotLoaded( + object=self, relation=name) def to_dict(self): """Convert the object to a simple dictionary.""" data = {} - for field in six.iterkeys(self.FIELDS): + if isinstance(self, ListObjectMixin): + return { + 'objects': self.to_list() + } + for field in self.fields: if self.obj_attr_is_set(field): val = getattr(self, field) if isinstance(val, ListObjectMixin): @@ -300,85 +100,146 @@ class DesignateObject(object): for k, v in values.items(): setattr(self, k, v) - @property - def is_valid(self): - """Returns True if the Object is valid.""" + @classmethod + def from_dict(cls, _dict): + instance = cls() - make_class_validator(self) + for field, value in _dict.items(): + if (field in instance.fields and + hasattr(instance.fields.get(field), 'objname')): + relation_cls_name = instance.fields[field].objname + # We're dealing with a relation, we'll want to create the + # correct object type and recurse + relation_cls = cls.obj_class_from_name( + relation_cls_name, '1.0') - return self._obj_validator.is_valid(self.to_dict()) + if isinstance(value, list): + setattr(instance, field, relation_cls.from_list(value)) + else: + setattr(instance, field, relation_cls.from_dict(value)) - def validate(self): + else: + setattr(instance, field, value) - make_class_validator(self) + return instance - ValidationErrorList = validation_error.ValidationErrorList - ValidationError = validation_error.ValidationError + @classmethod + def from_list(cls, _list): + raise NotImplementedError() - errors = ValidationErrorList() + def __setattr__(self, name, value): + """Enforces all object attributes are private or well defined""" + if not (name[0:5] == '_obj_' + or name[0:7] == '_change' + or name == '_context' + or name in list(six.iterkeys(self.fields)) + or name == 'FIELDS' + or name == 'VERSION' + or name == 'fields'): + raise AttributeError( + "Designate object '%(type)s' has no" + "attribute '%(name)s'" % { + 'type': self.obj_name(), + 'name': name, + }) + super(DesignateObject, self).__setattr__(name, value) - try: - values = self.to_dict() - except exceptions.RelationNotLoaded as e: - e = ValidationError() - e.path = ['type'] - e.validator = 'required' - e.validator_value = [e.relation] - e.message = "'%s' is a required property" % e.relation - errors.append(e) - raise exceptions.InvalidObject( - "Provided object does not match " - "schema", errors=errors, object=self) + def __eq__(self, other): + if self.__class__ != other.__class__: + return False - LOG.debug("Validating '%(name)s' object with values: %(values)r", { - 'name': self.obj_name(), - 'values': values, - }) + return self.obj_to_primitive() == other.obj_to_primitive() - for error in self._obj_validator.iter_errors(values): - errors.append(ValidationError.from_js_error(error)) + def __ne__(self, other): + return not (self.__eq__(other)) - if len(errors) > 0: - LOG.debug( - "Error Validating '%(name)s' object with values: " - "%(values)r", { - 'name': self.obj_name(), - 'values': values, - } - ) - raise exceptions.InvalidObject( - "Provided object does not match " - "schema", errors=errors, object=self) + def __repr__(self): + return "OVO Objects" - def obj_attr_is_set(self, name): + # TODO(daidv): all of bellow functions should + # be removed when we completed migration. + def nested_sort(self, key, value): """ - Return True or False depending of if a particular attribute has had - an attribute's value explicitly set. + This function ensure that change fields list + is sorted. + :param key: + :param value: + :return: """ - return hasattr(self, get_attrname(name)) + if key == 'designate_object.changes': + return sorted(value) + if isinstance(value, list): + _list = [] + for item in value: + _list.append(self.nested_sort(None, item)) + return _list + elif isinstance(value, dict): + _dict = {} + for k, v in value.items(): + _dict[k] = self.nested_sort(k, v) + return _dict + else: + return value - def obj_what_changed(self): - """Returns a set of fields that have been modified.""" - return set(self._obj_changes) + def to_primitive(self): + return self.nested_sort(None, self.obj_to_primitive()) - def obj_get_changes(self): - """Returns a dict of changed fields and their new values.""" - changes = {} + @classmethod + def from_primitive(cls, primitive, context=None): + return cls.obj_from_primitive(primitive, context) - for key in self.obj_what_changed(): - changes[key] = getattr(self, key) + @classmethod + def obj_cls_from_name(cls, name): + return cls.obj_class_from_name(name, '1.0') - return changes + @classmethod + def obj_get_schema(cls): + return cls.to_json_schema() + + def obj_reset_changes(self, fields=None, recursive=False): + """Reset the list of fields that have been changed. + + :param fields: List of fields to reset, or "all" if None. + :param recursive: Call obj_reset_changes(recursive=True) on + any sub-objects within the list of fields + being reset. + + This is NOT "revert to previous values". + + Specifying fields on recursive resets will only be honored at the top + level. Everything below the top will reset all. + """ + if recursive: + for field in self.obj_get_changes(): + + # Ignore fields not in requested set (if applicable) + if fields and field not in fields: + continue + + # Skip any fields that are unset + if not self.obj_attr_is_set(field): + continue + + value = getattr(self, field) + + # Don't reset nulled fields + if value is None: + continue + + # Reset straight Object and ListOfObjects fields + if isinstance(self.fields[field], self.obj_fields.ObjectField): + value.obj_reset_changes(recursive=True) + elif isinstance(self.fields[field], + self.obj_fields.ListOfObjectsField): + for thing in value: + thing.obj_reset_changes(recursive=True) - def obj_reset_changes(self, fields=None): - """Reset the list of fields that have been changed.""" if fields: - self._obj_changes -= set(fields) + self._changed_fields -= set(fields) for field in fields: self._obj_original_values.pop(field, None) - else: - self._obj_changes.clear() + self._changed_fields.clear() self._obj_original_values = dict() def obj_get_original_value(self, field): @@ -390,116 +251,89 @@ class DesignateObject(object): else: raise KeyError(field) - def __setattr__(self, name, value): - """Enforces all object attributes are private or well defined""" - if name[0:5] == '_obj_' or name in list(six.iterkeys(self.FIELDS)) \ - or name == 'FIELDS': - super(DesignateObject, self).__setattr__(name, value) + @property + def obj_fields(self): + return list(self.fields.keys()) + self.obj_extra_fields - else: - raise AttributeError( - "Designate object '%(type)s' has no attribute '%(name)s'" % { - 'type': self.obj_name(), - 'name': name, - }) + @property + def obj_context(self): + return self._context - def __deepcopy__(self, memodict=None): - """ - Efficiently make a deep copy of this object. - - "Efficiently" is used here a relative term, this will be faster - than allowing python to naively deepcopy the object. - """ - - memodict = memodict or {} - - c_obj = self.__class__() - - for field in six.iterkeys(self.FIELDS): - if self.obj_attr_is_set(field): - c_field = copy.deepcopy(getattr(self, field), memodict) - setattr(c_obj, field, c_field) - - c_obj._obj_changes = set(self._obj_changes) - - return c_obj - - def __eq__(self, other): - if self.__class__ != other.__class__: + @property + def is_valid(self): + """Returns True if the Object is valid.""" + try: + self.validate() + except Exception: return False - - return self.to_primitive() == other.to_primitive() - - def __ne__(self, other): - return not(self.__eq__(other)) - - -class DictObjectMixin(object): - """ - Mixin to allow DesignateObjects to behave like dictionaries - - Eventually, this should be removed as other code is updated to use object - rather than dictionary accessors. - """ - def __getitem__(self, key): - return getattr(self, key) - - def __setitem__(self, key, value): - setattr(self, key, value) - - def __contains__(self, item): - return item in list(six.iterkeys(self.FIELDS)) - - def get(self, key, default=NotSpecifiedSentinel): - if key not in list(six.iterkeys(self.FIELDS)): - raise AttributeError("'%s' object has no attribute '%s'" % ( - self.__class__, key)) - - if default != NotSpecifiedSentinel and not self.obj_attr_is_set(key): - return default else: - return getattr(self, key) + return True - def items(self): - for field in six.iterkeys(self.FIELDS): - if self.obj_attr_is_set(field): - yield field, getattr(self, field) + def validate(self): + # NOTE(kiall, daidv): We make use of the Object registry here + # in order to avoid an impossible circular import. + ValidationErrorList = self.obj_cls_from_name('ValidationErrorList') + ValidationError = self.obj_cls_from_name('ValidationError') + self.fields = self.FIELDS + for name in self.fields: + field = self.fields[name] + if self.obj_attr_is_set(name): + value = getattr(self, name) # Check relation + if isinstance(value, ListObjectMixin): + for obj in value.objects: + obj.validate() + elif isinstance(value, DesignateObject): + value.validate() + else: + try: + field.coerce(self, name, value) # Check value + except Exception as e: + raise exceptions.InvalidObject( + "{} is invalid".format(name)) + elif not field.nullable: + # Check required is True ~ nullable is False + errors = ValidationErrorList() + e = ValidationError() + e.path = ['records', 0] + e.validator = 'required' + e.validator_value = [name] + e.message = "'%s' is a required property" % name + errors.append(e) + raise exceptions.InvalidObject( + "Provided object does not match " + "schema", errors=errors, object=self) - # Compatibility with jsonutils to_primitive(). See bug - # https://bugs.launchpad.net/designate/+bug/1481377 - iteritems = items - - def __iter__(self): - for field in six.iterkeys(self.FIELDS): - if self.obj_attr_is_set(field): - yield field, getattr(self, field) + def obj_attr_is_set(self, name): + """ + Return True or False depending of if a particular attribute has had + an attribute's value explicitly set. + """ + return hasattr(self, _get_attrname(name)) -class ListObjectMixin(object): - """Mixin to allow DesignateObjects to behave like python lists.""" - FIELDS = { - 'objects': { - 'relation': True - } - } +class ListObjectMixin(base.ObjectListBase): LIST_ITEM_TYPE = DesignateObject @classmethod - def _obj_from_primitive(cls, primitive): + def _obj_from_primitive(cls, context, objver, primitive): instance = cls() + instance.VERSION = objver + instance._context = context for field, value in primitive['designate_object.data'].items(): if field == 'objects': - instance.objects = [DesignateObject.from_primitive(v) for v in - value] + instance.objects = [ + DesignateObject.obj_from_primitive(v) for v in value] elif isinstance(value, dict) and 'designate_object.name' in value: - setattr(instance, field, DesignateObject.from_primitive(value)) + setattr(instance, field, + DesignateObject.obj_from_primitive(value)) else: setattr(instance, field, value) - instance._obj_changes = set(primitive['designate_object.changes']) + instance._obj_changes = set( + primitive.get('designate_object.changes', [])) instance._obj_original_values = \ - primitive['designate_object.original_values'] + primitive.get('designate_object.original_values', {}) return instance @@ -526,31 +360,6 @@ class ListObjectMixin(object): return list_ - def __init__(self, *args, **kwargs): - super(ListObjectMixin, self).__init__(*args, **kwargs) - if 'objects' not in kwargs: - self.objects = [] - self.obj_reset_changes(['objects']) - - def to_primitive(self): - data = {} - - for field in six.iterkeys(self.FIELDS): - if self.obj_attr_is_set(field): - if field == 'objects': - data[field] = [o.to_primitive() for o in self.objects] - elif isinstance(getattr(self, field), DesignateObject): - data[field] = getattr(self, field).to_primitive() - else: - data[field] = getattr(self, field) - - return { - 'designate_object.name': self.obj_name(), - 'designate_object.data': data, - 'designate_object.changes': list(self._obj_changes), - 'designate_object.original_values': dict(self._obj_original_values) - } - def __str__(self): return (_("<%(type)s count:'%(count)s' object:'%(list_type)s'>") % {'count': len(self), @@ -561,10 +370,6 @@ class ListObjectMixin(object): """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): @@ -610,24 +415,15 @@ class ListObjectMixin(object): """List count of value occurrences""" return self.objects.count(value) - def sort(self, key=None, reverse=False): - self.objects.sort(key=key, reverse=reverse) - - def obj_what_changed(self): - changes = set(self._obj_changes) - for item in self.objects: - if item.obj_what_changed(): - changes.add('objects') - return changes - class AttributeListObjectMixin(ListObjectMixin): """ Mixin class for "Attribute" objects. - Attribute objects are ListObjects, who's members have a "key" and "value" + Attribute objects are ListObjects, who's memebers have a "key" and "value" property, which should be exposed on the list itself as list.. """ + @classmethod def from_dict(cls, _dict): instances = cls.from_list([{'key': k, 'value': v} for k, v @@ -657,38 +453,13 @@ class PersistentObjectMixin(object): This adds the fields that we use in common for all persistent objects. """ - FIELDS = { - 'id': { - 'schema': { - 'type': 'string', - 'format': 'uuid', - }, - 'read_only': True - }, - 'created_at': { - 'schema': { - 'type': 'string', - 'format': 'date-time', - }, - 'read_only': True - }, - 'updated_at': { - 'schema': { - 'type': ['string', 'null'], - 'format': 'date-time', - }, - 'read_only': True - }, - 'version': { - 'schema': { - 'type': 'integer', - }, - 'read_only': True - } + fields = { + 'id': fields.UUIDFields(nullable=True), + 'created_at': fields.DateTimeField(nullable=True), + 'updated_at': fields.DateTimeField(nullable=True), + 'version': fields.IntegerFields(nullable=True) } - STRING_KEYS = ['id'] - class SoftDeleteObjectMixin(object): """ @@ -696,20 +467,9 @@ class SoftDeleteObjectMixin(object): This adds the fields that we use in common for all soft-deleted objects. """ - FIELDS = { - 'deleted': { - 'schema': { - 'type': ['string', 'integer'], - }, - 'read_only': True - }, - 'deleted_at': { - 'schema': { - 'type': ['string', 'null'], - 'format': 'date-time', - }, - 'read_only': True - } + fields = { + 'deleted': fields.StringFields(nullable=True), + 'deleted_at': fields.DateTimeField(nullable=True), } @@ -719,10 +479,45 @@ class PagedListObjectMixin(object): This adds fields that would populate API metadata for collections. """ - FIELDS = { - 'total_count': { - 'schema': { - 'type': ['integer'], - } - } + fields = { + 'total_count': fields.IntegerFields(nullable=True) } + + +class DesignateRegistry(base.VersionedObjectRegistry): + def registration_hook(self, cls, index): + for name, field in six.iteritems(cls.fields): + attr = get_dict_attr(cls, name) + + def getter(self, name=name): + attrname = _get_attrname(name) + self._obj_check_relation(name) + return getattr(self, attrname, None) + + 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) + # TODO(daidv): _obj_original_values shoud be removed + # after OVO migration completed. + if (self.obj_attr_is_set(name) and value != getattr(self, name) + and name not in list(six.iterkeys( + self._obj_original_values))): + self._obj_original_values[name] = getattr(self, name) + try: + return setattr(self, attrname, field_value) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception( + _LE('Error setting %{obj_name}s.%{field_name}s'), + {"obj_name": self.obj_name(), "field_name": name}) + + setattr(cls, name, property(getter, setter, attr.fdel)) diff --git a/designate/objects/blacklist.py b/designate/objects/blacklist.py index 1eac5610b..1a5eb388f 100644 --- a/designate/objects/blacklist.py +++ b/designate/objects/blacklist.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/fields.py b/designate/objects/fields.py index 53d122113..8901b1aae 100644 --- a/designate/objects/fields.py +++ b/designate/objects/fields.py @@ -165,8 +165,18 @@ class IPV4AndV6AddressField(ovoo_fields.IPV4AndV6AddressField): return str(value) -class EnumField(ovoo_fields.EnumField): - pass +class Enum(ovoo_fields.Enum): + def get_schema(self): + return { + 'enum': self._valid_values, + 'type': 'any' + } + + +class EnumField(ovoo_fields.BaseEnumField): + def __init__(self, valid_values, **kwargs): + self.AUTO_TYPE = Enum(valid_values=valid_values) + super(EnumField, self).__init__(**kwargs) class DomainField(StringFields): diff --git a/designate/objects/floating_ip.py b/designate/objects/floating_ip.py index 69def488f..5423b1a8e 100644 --- a/designate/objects/floating_ip.py +++ b/designate/objects/floating_ip.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/ovo_base.py b/designate/objects/ovo_base.py deleted file mode 100644 index b842ddb4a..000000000 --- a/designate/objects/ovo_base.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (c) 2017 Fujitsu Vietnam Ltd. -# All Rights Reserved. -# -# 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 six -from oslo_log import log as logging -from oslo_versionedobjects import exception -from oslo_utils import excutils -from oslo_versionedobjects import base -from oslo_versionedobjects.base import VersionedObjectDictCompat as DictObjectMixin # noqa - -from designate.i18n import _ -from designate.i18n import _LE -from designate.objects import fields -from designate import exceptions - -LOG = logging.getLogger(__name__) - - -def _get_attrname(name): - return "_obj_{}".format(name) - - -def get_dict_attr(klass, attr): - for klass in [klass] + klass.mro(): - if attr in klass.__dict__: - return klass.__dict__[attr] - raise AttributeError - - -class DesignateObject(base.VersionedObject): - OBJ_SERIAL_NAMESPACE = 'designate_object' - OBJ_PROJECT_NAMESPACE = 'designate' - - STRING_KEYS = [] - - def __init__(self, *args, **kwargs): - super(DesignateObject, self).__init__(self, *args, **kwargs) - self._obj_original_values = dict() - self.FIELDS = self.fields - - @classmethod - def _make_obj_str(cls, keys): - msg = "<%(name)s" % {'name': cls.obj_name()} - for key in keys: - msg += " {0}:'%({0})s'".format(key) - msg += ">" - return msg - - def __str__(self): - return (self._make_obj_str(self.STRING_KEYS) - % self) - - def save(self, context): - pass - - def _obj_check_relation(self, name): - if name in self.fields: - if hasattr(self.fields.get(name), 'objname'): - if not self.obj_attr_is_set(name): - raise exceptions.RelationNotLoaded( - object=self, relation=name) - - def to_dict(self): - """Convert the object to a simple dictionary.""" - data = {} - - for field in six.iterkeys(self.fields): - if self.obj_attr_is_set(field): - val = getattr(self, field) - if isinstance(val, ListObjectMixin): - data[field] = val.to_list() - elif isinstance(val, DesignateObject): - data[field] = val.to_dict() - else: - data[field] = val - - return data - - def update(self, values): - """Update a object's fields with the supplied key/value pairs""" - for k, v in values.items(): - setattr(self, k, v) - - @classmethod - def from_dict(cls, _dict): - instance = cls() - - for field, value in _dict.items(): - if (field in instance.fields and - hasattr(instance.fields.get(field), 'objname')): - relation_cls_name = instance.fields[field].objname - # We're dealing with a relation, we'll want to create the - # correct object type and recurse - relation_cls = cls.obj_class_from_name( - relation_cls_name, '1.0') - - if isinstance(value, list): - setattr(instance, field, relation_cls.from_list(value)) - else: - setattr(instance, field, relation_cls.from_dict(value)) - - else: - setattr(instance, field, value) - - return instance - - def __eq__(self, other): - if self.__class__ != other.__class__: - return False - - return self.obj_to_primitive() == other.obj_to_primitive() - - def __ne__(self, other): - return not (self.__eq__(other)) - - def __repr__(self): - return "OVO Objects" - - # TODO(daidv): all of bellow functions should - # be removed when we completed migration. - def to_primitive(self): - return self.obj_to_primitive() - - @classmethod - def from_primitive(cls, primitive, context=None): - return cls.obj_from_primitive(primitive, context) - - @classmethod - def obj_cls_from_name(cls, name): - return cls.obj_class_from_name(name, '1.0') - - @classmethod - def obj_get_schema(cls): - return cls.to_json_schema() - - def obj_reset_changes(self, fields=None, recursive=False): - """Reset the list of fields that have been changed. - - :param fields: List of fields to reset, or "all" if None. - :param recursive: Call obj_reset_changes(recursive=True) on - any sub-objects within the list of fields - being reset. - - This is NOT "revert to previous values". - - Specifying fields on recursive resets will only be honored at the top - level. Everything below the top will reset all. - """ - if recursive: - for field in self.obj_get_changes(): - - # Ignore fields not in requested set (if applicable) - if fields and field not in fields: - continue - - # Skip any fields that are unset - if not self.obj_attr_is_set(field): - continue - - value = getattr(self, field) - - # Don't reset nulled fields - if value is None: - continue - - # Reset straight Object and ListOfObjects fields - if isinstance(self.fields[field], self.obj_fields.ObjectField): - value.obj_reset_changes(recursive=True) - elif isinstance(self.fields[field], - self.obj_fields.ListOfObjectsField): - for thing in value: - thing.obj_reset_changes(recursive=True) - - if fields: - self._changed_fields -= set(fields) - for field in fields: - self._obj_original_values.pop(field, None) - else: - self._changed_fields.clear() - self._obj_original_values = dict() - - def obj_get_original_value(self, field): - """Returns the original value of a field.""" - if field in list(six.iterkeys(self._obj_original_values)): - return self._obj_original_values[field] - elif self.obj_attr_is_set(field): - return getattr(self, field) - else: - raise KeyError(field) - - @property - def obj_fields(self): - return list(self.fields.keys()) + self.obj_extra_fields - - @property - def obj_context(self): - return self._context - - def validate(self): - # NOTE(kiall, daidv): We make use of the Object registry here - # in order to avoid an impossible circular import. - ValidationErrorList = self.obj_cls_from_name('ValidationErrorList') - ValidationError = self.obj_cls_from_name('ValidationError') - self.fields = self.FIELDS - for name in self.fields: - field = self.fields[name] - if self.obj_attr_is_set(name): - value = getattr(self, name) # Check relation - if isinstance(value, ListObjectMixin): - for obj in value.objects: - obj.validate() - else: - try: - field.coerce(self, name, value) # Check value - except Exception as e: - raise exceptions.InvalidObject( - "{} is invalid".format(name)) - elif not field.nullable: - # Check required is True ~ nullable is False - errors = ValidationErrorList() - e = ValidationError() - e.path = ['records', 0] - e.validator = 'required' - e.validator_value = [name] - e.message = "'%s' is a required property" % name - errors.append(e) - raise exceptions.InvalidObject( - "Provided object does not match " - "schema", errors=errors, object=self) - - -class ListObjectMixin(base.ObjectListBase): - LIST_ITEM_TYPE = DesignateObject - - @classmethod - def _obj_from_primitive(cls, context, objver, primitive): - instance = cls() - instance.VERSION = objver - instance._context = context - - for field, value in primitive['designate_object.data'].items(): - if field == 'objects': - instance.objects = [ - DesignateObject.obj_from_primitive(v) for v in value] - elif isinstance(value, dict) and 'designate_object.name' in value: - setattr(instance, field, - DesignateObject.obj_from_primitive(value)) - else: - setattr(instance, field, value) - - instance._obj_changes = set( - primitive.get('designate_object.changes', [])) - instance._obj_original_values = \ - primitive.get('designate_object.original_values', {}) - - return instance - - @classmethod - def from_list(cls, _list): - instance = cls() - - for item in _list: - instance.append(cls.LIST_ITEM_TYPE.from_dict(item)) - - return instance - - def to_list(self): - - list_ = [] - - for item in self.objects: - if isinstance(item, ListObjectMixin): - list_.append(item.to_list()) - elif isinstance(item, DesignateObject): - list_.append(item.to_dict()) - else: - list_.append(item) - - return list_ - - def __str__(self): - return (_("<%(type)s count:'%(count)s' object:'%(list_type)s'>") - % {'count': len(self), - 'type': self.LIST_ITEM_TYPE.obj_name(), - 'list_type': self.obj_name()}) - - def __iter__(self): - """List iterator interface""" - return iter(self.objects) - - def __getitem__(self, index): - """List index access""" - if isinstance(index, slice): - new_obj = self.__class__() - new_obj.objects = self.objects[index] - new_obj.obj_reset_changes() - return new_obj - return self.objects[index] - - def __setitem__(self, index, value): - """Set list index value""" - self.objects[index] = value - - def __contains__(self, value): - """List membership test""" - return value in self.objects - - def append(self, value): - """Append a value to the list""" - return self.objects.append(value) - - def extend(self, values): - """Extend the list by appending all the items in the given list""" - return self.objects.extend(values) - - def pop(self, index): - """Pop a value from the list""" - return self.objects.pop(index) - - def insert(self, index, value): - """Insert a value into the list at the given index""" - return self.objects.insert(index, value) - - def remove(self, value): - """Remove a value from the list""" - return self.objects.remove(value) - - def index(self, value): - """List index of value""" - return self.objects.index(value) - - def count(self, value): - """List count of value occurrences""" - return self.objects.count(value) - - -class AttributeListObjectMixin(ListObjectMixin): - """ - Mixin class for "Attribute" objects. - - Attribute objects are ListObjects, who's memebers have a "key" and "value" - property, which should be exposed on the list itself as list.. - """ - - @classmethod - def from_dict(cls, _dict): - instances = cls.from_list([{'key': k, 'value': v} for k, v - in _dict.items()]) - - return cls.from_list(instances) - - def to_dict(self): - data = {} - - for item in self.objects: - data[item.key] = item.value - - return data - - def get(self, key, default=None): - for obj in self.objects: - if obj.key == key: - return obj.value - - return default - - -class PersistentObjectMixin(object): - """ - Mixin class for Persistent objects. - - This adds the fields that we use in common for all persistent objects. - """ - fields = { - 'id': fields.UUIDFields(nullable=True), - 'created_at': fields.DateTimeField(nullable=True), - 'updated_at': fields.DateTimeField(nullable=True), - 'version': fields.IntegerFields(nullable=True) - } - - -class SoftDeleteObjectMixin(object): - """ - Mixin class for Soft-Deleted objects. - - This adds the fields that we use in common for all soft-deleted objects. - """ - fields = { - 'deleted': fields.StringFields(nullable=True), - 'deleted_at': fields.DateTimeField(nullable=True), - } - - -class PagedListObjectMixin(object): - """ - Mixin class for List objects. - - This adds fields that would populate API metadata for collections. - """ - fields = { - 'total_count': fields.IntegerFields(nullable=True) - } - - -class DesignateRegistry(base.VersionedObjectRegistry): - def registration_hook(self, cls, index): - for name, field in six.iteritems(cls.fields): - attr = get_dict_attr(cls, name) - - def getter(self, name=name): - attrname = _get_attrname(name) - self._obj_check_relation(name) - return getattr(self, attrname, None) - - 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) - # TODO(daidv): _obj_original_values shoud be removed - # after OVO migration completed. - if (self.obj_attr_is_set(name) and value != getattr(self, name) - and name not in list(six.iterkeys( - self._obj_original_values))): - self._obj_original_values[name] = getattr(self, name) - try: - return setattr(self, attrname, field_value) - except Exception: - with excutils.save_and_reraise_exception(): - attr = "%s.%s" % (self.obj_name(), name) - LOG.exception(_LE('Error setting %(attr)s'), - {'attr': attr}) - - setattr(cls, name, property(getter, setter, attr.fdel)) diff --git a/designate/objects/pool.py b/designate/objects/pool.py index 4dc12dc23..d9dfb0894 100644 --- a/designate/objects/pool.py +++ b/designate/objects/pool.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. from designate import utils -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_also_notify.py b/designate/objects/pool_also_notify.py index 8e5c18025..5dc0fb675 100644 --- a/designate/objects/pool_also_notify.py +++ b/designate/objects/pool_also_notify.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_attribute.py b/designate/objects/pool_attribute.py index 9b2b70ad3..aae8701e5 100644 --- a/designate/objects/pool_attribute.py +++ b/designate/objects/pool_attribute.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_manager_status.py b/designate/objects/pool_manager_status.py index 5373782b4..f3bca78ad 100644 --- a/designate/objects/pool_manager_status.py +++ b/designate/objects/pool_manager_status.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_nameserver.py b/designate/objects/pool_nameserver.py index e11151efb..4fea96ff7 100644 --- a/designate/objects/pool_nameserver.py +++ b/designate/objects/pool_nameserver.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_ns_record.py b/designate/objects/pool_ns_record.py index c6b1954b7..8a3b8cb3a 100644 --- a/designate/objects/pool_ns_record.py +++ b/designate/objects/pool_ns_record.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_target.py b/designate/objects/pool_target.py index 8d8b50290..a0d9fd9db 100644 --- a/designate/objects/pool_target.py +++ b/designate/objects/pool_target.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_target_master.py b/designate/objects/pool_target_master.py index 957aff747..309df9bbb 100644 --- a/designate/objects/pool_target_master.py +++ b/designate/objects/pool_target_master.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/pool_target_option.py b/designate/objects/pool_target_option.py index b349b7ea6..7fc782ac9 100644 --- a/designate/objects/pool_target_option.py +++ b/designate/objects/pool_target_option.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/quota.py b/designate/objects/quota.py index dcc989040..0ad30094f 100644 --- a/designate/objects/quota.py +++ b/designate/objects/quota.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/record.py b/designate/objects/record.py index 327ba8e3c..6c9698ffd 100644 --- a/designate/objects/record.py +++ b/designate/objects/record.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/recordset.py b/designate/objects/recordset.py index a95e752fe..44b16053f 100755 --- a/designate/objects/recordset.py +++ b/designate/objects/recordset.py @@ -22,7 +22,7 @@ from oslo_versionedobjects import exception as ovo_exc from designate import exceptions from designate import utils -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields from designate.objects.validation_error import ValidationError from designate.objects.validation_error import ValidationErrorList diff --git a/designate/objects/rrdata_a.py b/designate/objects/rrdata_a.py index 228ad88e4..0b1673648 100644 --- a/designate/objects/rrdata_a.py +++ b/designate/objects/rrdata_a.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_aaaa.py b/designate/objects/rrdata_aaaa.py index 397d801fc..c33cee4cb 100644 --- a/designate/objects/rrdata_aaaa.py +++ b/designate/objects/rrdata_aaaa.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_cname.py b/designate/objects/rrdata_cname.py index 0d74b2d3e..9c796f431 100644 --- a/designate/objects/rrdata_cname.py +++ b/designate/objects/rrdata_cname.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_mx.py b/designate/objects/rrdata_mx.py index 0c897f069..d43c38005 100644 --- a/designate/objects/rrdata_mx.py +++ b/designate/objects/rrdata_mx.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_ns.py b/designate/objects/rrdata_ns.py index e37da6ae8..a3c5cf38a 100644 --- a/designate/objects/rrdata_ns.py +++ b/designate/objects/rrdata_ns.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_ptr.py b/designate/objects/rrdata_ptr.py index df575807b..9b3e419a0 100644 --- a/designate/objects/rrdata_ptr.py +++ b/designate/objects/rrdata_ptr.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_soa.py b/designate/objects/rrdata_soa.py index 5f2a57c97..b2219b2a1 100644 --- a/designate/objects/rrdata_soa.py +++ b/designate/objects/rrdata_soa.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_spf.py b/designate/objects/rrdata_spf.py index 377b90f41..1202bc77f 100644 --- a/designate/objects/rrdata_spf.py +++ b/designate/objects/rrdata_spf.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_srv.py b/designate/objects/rrdata_srv.py index d90742c86..20eb842d9 100644 --- a/designate/objects/rrdata_srv.py +++ b/designate/objects/rrdata_srv.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_sshfp.py b/designate/objects/rrdata_sshfp.py index 316d76e3e..1c66df54b 100644 --- a/designate/objects/rrdata_sshfp.py +++ b/designate/objects/rrdata_sshfp.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/rrdata_txt.py b/designate/objects/rrdata_txt.py index 8ed3dd882..d22581f8a 100644 --- a/designate/objects/rrdata_txt.py +++ b/designate/objects/rrdata_txt.py @@ -14,7 +14,7 @@ # under the License. from designate.objects.record import Record from designate.objects.record import RecordList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/server.py b/designate/objects/server.py index d3b4b536d..6d4d5677f 100644 --- a/designate/objects/server.py +++ b/designate/objects/server.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/service_status.py b/designate/objects/service_status.py index 303e0e600..e54a403f2 100644 --- a/designate/objects/service_status.py +++ b/designate/objects/service_status.py @@ -11,7 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/tenant.py b/designate/objects/tenant.py index 99fc22629..7ae25e1b9 100644 --- a/designate/objects/tenant.py +++ b/designate/objects/tenant.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/tld.py b/designate/objects/tld.py index dbcc7fd80..8cf41f6ac 100644 --- a/designate/objects/tld.py +++ b/designate/objects/tld.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/tsigkey.py b/designate/objects/tsigkey.py index 81a752367..cc418568a 100644 --- a/designate/objects/tsigkey.py +++ b/designate/objects/tsigkey.py @@ -12,7 +12,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/validation_error.py b/designate/objects/validation_error.py index 900df3a70..0abf52369 100644 --- a/designate/objects/validation_error.py +++ b/designate/objects/validation_error.py @@ -11,7 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone.py b/designate/objects/zone.py index 47f1de9ca..3b9e54dc6 100644 --- a/designate/objects/zone.py +++ b/designate/objects/zone.py @@ -16,7 +16,7 @@ from designate import utils from designate import exceptions from designate.objects.validation_error import ValidationError from designate.objects.validation_error import ValidationErrorList -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_attribute.py b/designate/objects/zone_attribute.py index 7e87d33d7..406696524 100644 --- a/designate/objects/zone_attribute.py +++ b/designate/objects/zone_attribute.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_export.py b/designate/objects/zone_export.py index 9c1651bb0..f7901b289 100644 --- a/designate/objects/zone_export.py +++ b/designate/objects/zone_export.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_import.py b/designate/objects/zone_import.py index 7b7b1babf..e816e6b5a 100644 --- a/designate/objects/zone_import.py +++ b/designate/objects/zone_import.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_master.py b/designate/objects/zone_master.py index 7b90d6c46..804b82f15 100644 --- a/designate/objects/zone_master.py +++ b/designate/objects/zone_master.py @@ -16,7 +16,7 @@ from oslo_versionedobjects import base as ovoo_base from designate import utils -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_transfer_accept.py b/designate/objects/zone_transfer_accept.py index d2b15d800..567f66b28 100644 --- a/designate/objects/zone_transfer_accept.py +++ b/designate/objects/zone_transfer_accept.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/objects/zone_transfer_request.py b/designate/objects/zone_transfer_request.py index 9fff1a112..06540161c 100644 --- a/designate/objects/zone_transfer_request.py +++ b/designate/objects/zone_transfer_request.py @@ -13,7 +13,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -from designate.objects import ovo_base as base +from designate.objects import base from designate.objects import fields diff --git a/designate/rpc.py b/designate/rpc.py index f923e7f1c..e1ed5d8ea 100644 --- a/designate/rpc.py +++ b/designate/rpc.py @@ -131,10 +131,7 @@ class DesignateObjectSerializer(messaging.NoOpSerializer): def deserialize_entity(self, context, entity): if isinstance(entity, dict) and 'designate_object.name' in entity: - if 'designate_object.version' in entity: - entity = objects.OVODesignateObject.from_primitive(entity) - else: - entity = objects.DesignateObject.from_primitive(entity) + entity = objects.DesignateObject.from_primitive(entity) elif isinstance(entity, (tuple, list, set)): entity = self._process_iterable(context, self.deserialize_entity, entity) diff --git a/designate/tests/unit/test_objects/test_adapters.py b/designate/tests/unit/test_objects/test_adapters.py index 00b52fdd1..31dc375c0 100644 --- a/designate/tests/unit/test_objects/test_adapters.py +++ b/designate/tests/unit/test_objects/test_adapters.py @@ -21,6 +21,7 @@ import oslotest.base from oslo_utils import timeutils from designate import objects +from designate.objects import base from designate.objects import adapters @@ -37,6 +38,7 @@ class DesignateTestAdapter(adapters.DesignateAdapter): } +@base.DesignateRegistry.register class DesignateTestPersistentObject( objects.DesignateObject, objects.base.PersistentObjectMixin): pass diff --git a/designate/tests/unit/test_objects/test_base.py b/designate/tests/unit/test_objects/test_base.py index ed80dc42f..ec4843071 100644 --- a/designate/tests/unit/test_objects/test_base.py +++ b/designate/tests/unit/test_objects/test_base.py @@ -16,7 +16,6 @@ from operator import attrgetter import copy -import unittest from oslo_log import log as logging import mock @@ -26,49 +25,42 @@ import testtools from designate import exceptions from designate import objects +from designate.objects import base +from designate.objects import fields LOG = logging.getLogger(__name__) +@base.DesignateRegistry.register class TestObject(objects.DesignateObject): - FIELDS = { - 'id': {}, - 'name': {}, - 'nested': { - 'relation': True, - 'relation_cls': 'TestObject', - }, - 'nested_list': { - 'relation': True, - 'relation_cls': 'TestObjectList', - }, + fields = { + 'id': fields.AnyField(nullable=True), + 'name': fields.AnyField(nullable=True), + 'nested': fields.ObjectFields('TestObject', nullable=True), + 'nested_list': fields.ObjectFields('TestObjectList', nullable=True), } -class TestObjectDict(objects.DictObjectMixin, TestObject): +@base.DesignateRegistry.register +class TestObjectDict(TestObject, objects.DictObjectMixin): pass +@base.DesignateRegistry.register class TestObjectList(objects.ListObjectMixin, objects.DesignateObject): LIST_ITEM_TYPE = TestObject + fields = { + 'objects': fields.ListOfObjectsField('TestObject'), + } + +@base.DesignateRegistry.register class TestValidatableObject(objects.DesignateObject): - FIELDS = { - 'id': { - 'schema': { - 'type': 'string', - 'format': 'uuid', - }, - 'required': True, - }, - 'nested': { - 'relation': True, - 'relation_cls': 'TestValidatableObject', - 'schema': { - '$ref': 'obj://TestValidatableObject#/' - } - } + fields = { + 'id': fields.UUIDFields(), + 'nested': fields.ObjectFields('TestValidatableObject', + nullable=True), } @@ -87,16 +79,17 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): primitive = { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID', + 'id': 1, }, 'designate_object.changes': [], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } obj = objects.DesignateObject.from_primitive(primitive) # Validate it has been thawed correctly - self.assertEqual('MyID', obj.id) + self.assertEqual(1, obj.id) # Ensure the ID field has a value self.assertTrue(obj.obj_attr_is_set('id')) @@ -111,33 +104,35 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): primitive = { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID', + 'id': 1, 'nested': { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID-Nested', + 'id': 2, }, 'designate_object.changes': [], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } }, 'designate_object.changes': [], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } obj = objects.DesignateObject.from_primitive(primitive) # Validate it has been thawed correctly - self.assertEqual('MyID', obj.id) - self.assertEqual('MyID-Nested', obj.nested.id) + self.assertEqual(1, obj.id) + self.assertEqual(2, obj.nested.id) def test_from_dict(self): obj = TestObject.from_dict({ - 'id': 'MyID', + 'id': 1, }) # Validate it has been thawed correctly - self.assertEqual('MyID', obj.id) + self.assertEqual(1, obj.id) # Ensure the ID field has a value self.assertTrue(obj.obj_attr_is_set('id')) @@ -150,15 +145,15 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): def test_from_dict_recursive(self): obj = TestObject.from_dict({ - 'id': 'MyID', + 'id': 1, 'nested': { - 'id': 'MyID-Nested', + 'id': 2, }, }) # Validate it has been thawed correctly - self.assertEqual('MyID', obj.id) - self.assertEqual('MyID-Nested', obj.nested.id) + self.assertEqual(1, obj.id) + self.assertEqual(2, obj.nested.id) # Ensure the changes list has two entries, one for the id field and the # other for the nested field @@ -169,18 +164,18 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): def test_from_dict_nested_list(self): obj = TestObject.from_dict({ - 'id': 'MyID', + 'id': 1, 'nested_list': [{ - 'id': 'MyID-Nested1', + 'id': 2, }, { - 'id': 'MyID-Nested2', + 'id': 3, }], }) # Validate it has been thawed correctly - self.assertEqual('MyID', obj.id) - self.assertEqual('MyID-Nested1', obj.nested_list[0].id) - self.assertEqual('MyID-Nested2', obj.nested_list[1].id) + self.assertEqual(1, obj.id) + self.assertEqual(2, obj.nested_list[0].id) + self.assertEqual(3, obj.nested_list[1].id) # Ensure the changes list has two entries, one for the id field and the # other for the nested field @@ -190,36 +185,6 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): with testtools.ExpectedException(NotImplementedError): TestObject.from_list([]) - def test_get_schema(self): - obj = TestValidatableObject() - obj.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' - obj.validate() - self.assertTrue(hasattr(obj, '_obj_validator')) - - expected = { - 'description': 'Designate TestValidatableObject Object', - 'title': 'TestValidatableObject', 'required': ['id'], - 'additionalProperties': False, - '$schema': 'http://json-schema.org/draft-04/hyper-schema', - 'type': 'object', - 'properties': { - 'id': { - 'type': 'string', 'format': 'uuid' - } - } - } - schema = obj._obj_validator.schema - self.assertEqual(expected, schema) - - with testtools.ExpectedException(AttributeError): # bug - schema = obj.obj_get_schema() - - @unittest.expectedFailure # bug - def test__schema_ref_resolver(self): - from designate.objects import base - base._schema_ref_resolver( - 'obj://TestValidatableObject#/subpathA/subpathB') - def test_init_invalid(self): with testtools.ExpectedException(TypeError): TestObject(extra_field='Fail') @@ -242,8 +207,8 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): def test_setattr(self): obj = TestObject() - obj.id = 'MyID' - self.assertEqual('MyID', obj.id) + obj.id = 1 + self.assertEqual(1, obj.id) self.assertEqual(1, len(obj.obj_what_changed())) obj.name = 'MyName' @@ -257,17 +222,18 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): obj.badthing = 'demons' def test_to_primitive(self): - obj = TestObject(id='MyID') + obj = TestObject(id=1) # Ensure only the id attribute is returned primitive = obj.to_primitive() expected = { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID', + 'id': 1, }, 'designate_object.changes': ['id'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } self.assertEqual(expected, primitive) @@ -279,44 +245,47 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): expected = { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID', + 'id': 1, 'name': None, }, 'designate_object.changes': ['id', 'name'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } self.assertEqual(expected, primitive) def test_to_primitive_recursive(self): - obj = TestObject(id='MyID', nested=TestObject(id='MyID-Nested')) + obj = TestObject(id=1, nested=TestObject(id=2)) # Ensure only the id attribute is returned primitive = obj.to_primitive() expected = { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID', + 'id': 1, 'nested': { 'designate_object.name': 'TestObject', 'designate_object.data': { - 'id': 'MyID-Nested', + 'id': 2, }, 'designate_object.changes': ['id'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } }, 'designate_object.changes': ['id', 'nested'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } self.assertEqual(expected, primitive) def test_to_dict(self): - obj = TestObject(id='MyID') + obj = TestObject(id=1) # Ensure only the id attribute is returned dict_ = obj.to_dict() expected = { - 'id': 'MyID', + 'id': 1, } self.assertEqual(expected, dict_) @@ -326,68 +295,36 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): # Ensure both the id and name attributes are returned dict_ = obj.to_dict() expected = { - 'id': 'MyID', + 'id': 1, 'name': None, } self.assertEqual(expected, dict_) def test_to_dict_recursive(self): - obj = TestObject(id='MyID', nested=TestObject(id='MyID-Nested')) + obj = TestObject(id=1, nested=TestObject(id=2)) # Ensure only the id attribute is returned dict_ = obj.to_dict() expected = { - 'id': 'MyID', + 'id': 1, 'nested': { - 'id': 'MyID-Nested', + 'id': 2, }, } self.assertEqual(expected, dict_) def test_update(self): - obj = TestObject(id='MyID', name='test') + obj = TestObject(id=1, name='test') obj.update({'id': 'new_id', 'name': 'new_name'}) self.assertEqual('new_id', obj.id) self.assertEqual('new_name', obj.name) def test_update_unexpected_attribute(self): - obj = TestObject(id='MyID', name='test') + obj = TestObject(id=1, name='test') with testtools.ExpectedException(AttributeError): obj.update({'id': 'new_id', 'new_key': 3}) - def test_is_valid(self): - obj = TestValidatableObject(id='MyID') - - # ID should be a UUID, So - Not Valid. - self.assertFalse(obj.is_valid) - - # Correct the ID field - obj.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' - - # ID is now a UUID, So - Valid. - self.assertTrue(obj.is_valid) - - def test_is_valid_recursive(self): - obj = TestValidatableObject( - id='MyID', - nested=TestValidatableObject(id='MyID')) - - # ID should be a UUID, So - Not Valid. - self.assertFalse(obj.is_valid) - - # Correct the outer objects ID field - obj.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' - - # Outer ID is now a UUID, Nested ID is Not. So - Invalid. - self.assertFalse(obj.is_valid) - - # Correct the nested objects ID field - obj.nested.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' - - # Outer and Nested IDs are now UUIDs. So - Valid. - self.assertTrue(obj.is_valid) - def test_validate(self): obj = TestValidatableObject() @@ -395,45 +332,29 @@ class DesignateObjectTest(oslotest.base.BaseTestCase): with testtools.ExpectedException(exceptions.InvalidObject): obj.validate() - # Set the ID field to an invalid value - obj.id = 'MyID' - - # ID is now set, but to an invalid value, still invalid - with testtools.ExpectedException(exceptions.InvalidObject): - obj.validate() + with testtools.ExpectedException(ValueError): + obj.id = 'MyID' # Set the ID field to a valid value obj.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' obj.validate() def test_validate_recursive(self): + with testtools.ExpectedException(ValueError): + TestValidatableObject( + id='MyID', + nested=TestValidatableObject(id='MyID')) + + with testtools.ExpectedException(ValueError): + TestValidatableObject( + id='ffded5c4-e4f6-4e02-a175-48e13c5c12a0', + nested=TestValidatableObject( + id='MyID')) + obj = TestValidatableObject( - id='MyID', - nested=TestValidatableObject(id='MyID')) - - # ID should be a UUID, So - Invalid. - with testtools.ExpectedException(exceptions.InvalidObject): - obj.validate() - - # Correct the outer objects ID field - obj.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' - - # Outer ID is now set, Inner ID is not, still invalid. - e = self.assertRaises(exceptions.InvalidObject, obj.validate) - - # Ensure we have exactly one error and fetch it - self.assertEqual(1, len(e.errors)) - error = e.errors.pop(0) - - # Ensure the format validator has triggered the failure. - self.assertEqual('format', error.validator) - - # Ensure the nested ID field has triggered the failure. - # For some reason testtools turns lists into deques :/ - self.assertEqual(error.path, ['nested', 'id']) - - # Set the Nested ID field to a valid value - obj.nested.id = 'ffded5c4-e4f6-4e02-a175-48e13c5c12a0' + id='ffded5c4-e4f6-4e02-a175-48e13c5c12a0', + nested=TestValidatableObject( + id='ffded5c4-e4f6-4e02-a175-48e13c5c12a0')) obj.validate() def test_obj_attr_is_set(self): @@ -577,11 +498,11 @@ class DictObjectMixinTest(oslotest.base.BaseTestCase): def test_cast_to_dict(self): # Create an object obj = TestObjectDict() - obj.id = "My ID" + obj.id = 1 obj.name = "My Name" expected = { - 'id': 'My ID', + 'id': 1, 'name': 'My Name', } @@ -613,7 +534,7 @@ class DictObjectMixinTest(oslotest.base.BaseTestCase): def test_get_default(self): obj = TestObjectDict(name='n') - v = obj.get('name', default='default') + v = obj.get('name', value='default') self.assertEqual('n', v) def test_get_default_with_patch(self): @@ -621,7 +542,7 @@ class DictObjectMixinTest(oslotest.base.BaseTestCase): fname = 'designate.objects.base.DesignateObject.obj_attr_is_set' with mock.patch(fname) as attr_is_set: attr_is_set.return_value = False - v = obj.get('name', default='default') + v = obj.get('name', value='default') self.assertEqual('default', v) def test_iteritems(self): @@ -648,15 +569,18 @@ class ListObjectMixinTest(oslotest.base.BaseTestCase): {'designate_object.changes': ['id'], 'designate_object.data': {'id': 'One'}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}, {'designate_object.changes': ['id'], 'designate_object.data': {'id': 'Two'}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}, ], }, 'designate_object.changes': ['objects'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0', } obj = objects.DesignateObject.from_primitive(primitive) @@ -701,15 +625,18 @@ class ListObjectMixinTest(oslotest.base.BaseTestCase): {'designate_object.changes': ['id'], 'designate_object.data': {'id': 'One'}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}, {'designate_object.changes': ['id'], 'designate_object.data': {'id': 'Two'}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}, ], }, 'designate_object.changes': ['objects'], - 'designate_object.original_values': {}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0' } self.assertEqual(expected, primitive) @@ -718,7 +645,7 @@ class ListObjectMixinTest(oslotest.base.BaseTestCase): obj_one = TestObject() obj_two = TestObject() obj_two.id = "Two" - obj_one.id = obj_two + obj_one.nested = obj_two # Create a ListObject obj = TestObjectList(objects=[obj_one, obj_two]) @@ -729,19 +656,26 @@ class ListObjectMixinTest(oslotest.base.BaseTestCase): 'designate_object.changes': ['objects'], 'designate_object.data': { 'objects': [ - {'designate_object.changes': ['id'], - 'designate_object.data': {'id': - {'designate_object.changes': ['id'], - 'designate_object.data': {'id': 'Two'}, - 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}}, + {'designate_object.changes': ['nested'], + 'designate_object.data': {'nested': + { + 'designate_object.changes': [ + 'id'], + 'designate_object.data': { + 'id': 'Two'}, + 'designate_object.name': 'TestObject', + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}, {'designate_object.changes': ['id'], 'designate_object.data': {'id': 'Two'}, 'designate_object.name': 'TestObject', - 'designate_object.original_values': {}}]}, - 'designate_object.original_values': {}} + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'}]}, + 'designate_object.namespace': 'designate', + 'designate_object.version': '1.0'} self.assertEqual(expected, primitive) @@ -879,21 +813,12 @@ class ListObjectMixinTest(oslotest.base.BaseTestCase): self.assertEqual([obj_one, obj_two, obj_three], obj.objects) - def test_to_dict(self): - # Create a ListObject containing a DesignateObject - obj_one = objects.DesignateObject() - obj = TestObjectList(objects=obj_one) - - dict_ = obj.to_dict() - expected = {'objects': {}} - self.assertEqual(expected, dict_) - def test_to_dict_list_mixin(self): # Create a ListObject containing an ObjectList - obj = TestObjectList(objects=TestObjectList()) + obj = TestObjectList(objects=[TestObject()]) dict_ = obj.to_dict() - expected = {'objects': []} + expected = {'objects': [{}]} self.assertEqual(expected, dict_) def test_to_list(self): diff --git a/designate/tests/unit/test_objects/test_recordset.py b/designate/tests/unit/test_objects/test_recordset.py index 9ae9114e0..ba72a7f52 100644 --- a/designate/tests/unit/test_objects/test_recordset.py +++ b/designate/tests/unit/test_objects/test_recordset.py @@ -37,17 +37,6 @@ def debug(*a, **kw): LOG.debug("%s: %s", k, repr(kw[k])) -class TestRecordSet(objects.RecordSet): - FIELDS = { - 'id': {}, - 'name': {}, - 'records': { - 'relation': True, - 'relation_cls': 'RecordList', - }, - } - - def create_test_recordset(): rs = objects.RecordSet( name='www.example.org.',