Commit 741eda43 authored by Zuul's avatar Zuul Committed by Gerrit Code Review

Merge "Migrate object to OVO"

parents 1e31659f 0eb9627c
......@@ -265,6 +265,9 @@ class Service(service.RPCService, service.Service):
def _is_valid_zone_name(self, context, zone_name):
# Validate zone name length
if zone_name is None:
raise exceptions.InvalidObject
if len(zone_name) > cfg.CONF['service:central'].max_zone_name_len:
raise exceptions.InvalidZoneName('Name too long')
......@@ -311,6 +314,9 @@ class Service(service.RPCService, service.Service):
return True
def _is_valid_recordset_name(self, context, zone, recordset_name):
if recordset_name is None:
raise exceptions.InvalidObject
if not recordset_name.endswith('.'):
raise ValueError('Please supply a FQDN')
......
......@@ -151,7 +151,8 @@ class Audit(NotificationPlugin):
changes = []
for arg in arglist:
if isinstance(arg, objects.DesignateObject):
if isinstance(arg, (objects.DesignateObject,
objects.OVODesignateObject)):
for change in arg.obj_what_changed():
if change != 'records':
old_value = arg.obj_get_original_value(change)
......
......@@ -13,9 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
from designate.objects.base import DesignateObject # noqa
from designate.objects.base import DictObjectMixin # 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
......
......@@ -16,6 +16,7 @@ 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 import exceptions
......@@ -44,7 +45,8 @@ 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):
if isinstance(list_object, (obj_base.PagedListObjectMixin,
ovoobj_base.PagedListObjectMixin)):
metadata = {}
if list_object.total_count is not None:
metadata['total_count'] = list_object.total_count
......
......@@ -78,7 +78,8 @@ 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):
if issubclass(obj_adapter.ADAPTER_OBJECT,
(objects.ListObjectMixin, objects.OVOListObjectMixin)):
obj_adapter = cls.get_object_adapter(
cls.ADAPTER_FORMAT,
obj_adapter.ADAPTER_OBJECT.LIST_ITEM_TYPE.obj_name())
......@@ -90,8 +91,8 @@ class ValidationErrorAPIv2Adapter(base.APIv2Adapter):
'fields', {}).items():
# Check if this field as actually a nested object
if object.FIELDS.get(path_segment, {}).get('relation', False):
field = object.FIELDS.get(path_segment, {})
if isinstance(field, dict) and field.get('relation'):
obj_cls = object.FIELDS.get(path_segment).get('relation_cls')
obj_adapter = cls.get_object_adapter(
cls.ADAPTER_FORMAT,
......@@ -104,6 +105,18 @@ class ValidationErrorAPIv2Adapter(base.APIv2Adapter):
# No need to continue the loop
break
elif hasattr(field, 'objname'):
obj_cls = field.objname
obj_adapter = cls.get_object_adapter(
cls.ADAPTER_FORMAT, obj_cls)
object = objects.OVODesignateObject.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)
# No need to continue the loop
break
if not isinstance(
value.get(
......
......@@ -15,6 +15,7 @@ import datetime
from oslo_log import log
import six
from oslo_versionedobjects import fields
from designate import objects
from designate import utils
......@@ -25,7 +26,6 @@ LOG = log.getLogger(__name__)
class DesignateObjectAdapterMetaclass(type):
def __init__(cls, names, bases, dict_):
if not hasattr(cls, '_adapter_classes'):
cls._adapter_classes = {}
......@@ -60,7 +60,8 @@ class DesignateAdapter(object):
@classmethod
def get_object_adapter(cls, format_, object):
if isinstance(object, objects.DesignateObject):
if isinstance(object, (objects.DesignateObject,
objects.OVODesignateObject)):
key = '%s:%s' % (format_, object.obj_name())
else:
key = '%s:%s' % (format_, object)
......@@ -81,7 +82,8 @@ class DesignateAdapter(object):
@classmethod
def render(cls, format_, object, *args, **kwargs):
if isinstance(object, objects.ListObjectMixin):
if isinstance(object, (objects.ListObjectMixin,
objects.OVOListObjectMixin)):
# type_ = 'list'
return cls.get_object_adapter(
format_, object)._render_list(object, *args, **kwargs)
......@@ -98,11 +100,16 @@ class DesignateAdapter(object):
def _is_datetime_field(object, key):
field = object.FIELDS.get(key, {})
return field.get('schema', {}).get('format', '') == 'date-time'
if isinstance(field, fields.Field):
# TODO(daidv): If we change to use DateTimeField or STL
# we should change this to exact object
return isinstance(field, fields.DateTimeField)
else:
return field.get('schema', {}).get('format', '') == 'date-time'
def _format_datetime_field(obj):
return datetime.datetime.strftime(
obj, utils.DATETIME_FORMAT)
obj, utils.DATETIME_FORMAT)
# The dict we will return to be rendered to JSON / output format
r_obj = {}
......@@ -121,14 +128,21 @@ class DesignateAdapter(object):
obj_key = key
# Check if this item is a relation (another DesignateObject that
# will need to be converted itself
if object.FIELDS.get(obj_key, {}).get('relation'):
field = object.FIELDS.get(obj_key, {})
if isinstance(field, dict) and field.get('relation'):
# Get a adapter for the nested object
# Get the class the object is and get its adapter, then set
# the item in the dict to the output
r_obj[key] = cls.get_object_adapter(
cls.ADAPTER_FORMAT,
object.FIELDS[obj_key].get('relation_cls')).render(
cls.ADAPTER_FORMAT, obj, *args, **kwargs)
cls.ADAPTER_FORMAT, obj, *args, **kwargs)
elif hasattr(field, 'objname'):
# Add by daidv: Check if field is OVO field and have a relation
r_obj[key] = cls.get_object_adapter(
cls.ADAPTER_FORMAT,
field.objname).render(
cls.ADAPTER_FORMAT, obj, *args, **kwargs)
elif _is_datetime_field(object, obj_key) and obj is not None:
# So, we now have a datetime object to render correctly
# see bug #1579844
......@@ -160,28 +174,30 @@ class DesignateAdapter(object):
LOG.debug("Creating %s object with values %r" %
(output_object.obj_name(), values))
LOG.debug(output_object)
try:
if isinstance(output_object, objects.ListObjectMixin):
if isinstance(output_object, (objects.ListObjectMixin,
objects.OVOListObjectMixin)):
# type_ = 'list'
return cls.get_object_adapter(
format_,
output_object)._parse_list(
values, output_object, *args, **kwargs)
values, output_object, *args, **kwargs)
else:
# type_ = 'object'
return cls.get_object_adapter(
format_,
output_object)._parse_object(
values, output_object, *args, **kwargs)
values, output_object, *args, **kwargs)
except TypeError as e:
LOG.exception(_LE("TypeError creating %(name)s with values"
" %(values)r") %
{"name": output_object.obj_name(), "values": values})
error_message = (u'Provided object is not valid. '
u'Got a TypeError with message {}'.format(
six.text_type(e)))
u'Got a TypeError with message {}'.format(
six.text_type(e)))
raise exceptions.InvalidObject(error_message)
except AttributeError as e:
......@@ -189,8 +205,8 @@ class DesignateAdapter(object):
"with values %(values)r") %
{"name": output_object.obj_name(), "values": values})
error_message = (u'Provided object is not valid. '
u'Got an AttributeError with message {}'.format(
six.text_type(e)))
u'Got an AttributeError with message {}'.format(
six.text_type(e)))
raise exceptions.InvalidObject(error_message)
except exceptions.InvalidObject:
......@@ -204,8 +220,8 @@ class DesignateAdapter(object):
"values %(values)r") %
{"name": output_object.obj_name(), "values": values})
error_message = (u'Provided object is not valid. '
u'Got a {} error with message {}'.format(
type(e).__name__, six.text_type(e)))
u'Got a {} error with message {}'.format(
type(e).__name__, six.text_type(e)))
raise exceptions.InvalidObject(error_message)
@classmethod
......@@ -229,7 +245,7 @@ class DesignateAdapter(object):
# initially set (eg zone name)
if cls.MODIFICATIONS['fields'][key].get('immutable', False):
if getattr(output_object, obj_key, False) and \
getattr(output_object, obj_key) != value:
getattr(output_object, obj_key) != value:
error_keys.append(key)
break
# Is this field a read only field
......@@ -239,8 +255,19 @@ class DesignateAdapter(object):
break
# Check if the key is a nested object
if output_object.FIELDS.get(obj_key, {}).get(
'relation', False):
check_field = output_object.FIELDS.get(obj_key, {})
if isinstance(check_field, fields.Field) and hasattr(
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_name)
obj = cls.get_object_adapter(
cls.ADAPTER_FORMAT, obj_class_name).parse(
value, obj_class())
setattr(output_object, obj_key, obj)
elif not isinstance(check_field, fields.Field)\
and check_field.get('relation', False):
# Get the right class name
obj_class_name = output_object.FIELDS.get(
obj_key, {}).get('relation_cls')
......@@ -252,7 +279,7 @@ class DesignateAdapter(object):
obj = \
cls.get_object_adapter(
cls.ADAPTER_FORMAT, obj_class_name).parse(
value, obj_class())
value, obj_class())
# Set the object on the main object
setattr(output_object, obj_key, obj)
else:
......@@ -285,7 +312,7 @@ class DesignateAdapter(object):
# We need to do `get_object_adapter` as we need a new
# instance of the Adapter
output_object.LIST_ITEM_TYPE()).parse(
item, output_object.LIST_ITEM_TYPE()))
item, output_object.LIST_ITEM_TYPE()))
# Return the filled list
return output_object
# Copyright 2015 Red Hat, Inc.
# 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 re
import uuid
from oslo_versionedobjects import fields as ovoo_fields
from oslo_versionedobjects.fields import DateTimeField # noqa
class IntegerField(ovoo_fields.IntegerField):
pass
class BooleanField(ovoo_fields.BooleanField):
pass
class PolymorphicObject(ovoo_fields.Object):
def coerce(self, obj, attr, value):
if hasattr(value, '__bases__'):
check_value = value.__bases__[0]
super(PolymorphicObject, self).coerce(obj, attr, check_value)
return value
class PolymorphicObjectField(ovoo_fields.AutoTypedField):
def __init__(self, objtype, subclasses=False, **kwargs):
self.AUTO_TYPE = PolymorphicObject(objtype, subclasses)
self.objname = objtype
super(PolymorphicObjectField, self).__init__(**kwargs)
class PolymorphicListOfObjectsField(ovoo_fields.AutoTypedField):
def __init__(self, objtype, subclasses=False, **kwargs):
self.AUTO_TYPE = ovoo_fields.List(
PolymorphicObject(objtype, subclasses))
self.objname = objtype
super(PolymorphicListOfObjectsField, self).__init__(**kwargs)
class ListOfObjectsField(ovoo_fields.ListOfObjectsField):
pass
class ObjectFields(ovoo_fields.ObjectField):
def __init__(self, objtype, subclasses=False, relation=False, **kwargs):
self.AUTO_TYPE = ovoo_fields.List(
ovoo_fields.Object(objtype, subclasses))
self.objname = objtype
super(ObjectFields, self).__init__(objtype, **kwargs)
self.relation = relation
class IntegerFields(IntegerField):
def __init__(self, nullable=False, default=ovoo_fields.UnspecifiedDefault,
read_only=False, minimum=0, maximum=None):
super(IntegerFields, self).__init__(nullable=nullable,
default=default,
read_only=read_only)
self.min = minimum
self.max = maximum
def coerce(self, obj, attr, value):
value = super(IntegerFields, self).coerce(obj, attr, value)
if value is None:
return value
if value < self.min:
# return self.min
raise ValueError('Value must be >= {} for field {}'.format(
self.min, attr)
)
if self.max and value > self.max:
raise ValueError('Value too high for %s' % attr)
return value
class StringFields(ovoo_fields.StringField):
RE_HOSTNAME = r'^(?!.{255,})(?:(?:^\*|(?!\-)[A-Za-z0-9_\-]{1,63})(?<!\-)\.)+\Z' # noqa
RE_ZONENAME = r'^(?!.{255,})(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)\.)+\Z'
RE_SRV_HOST_NAME = r'^(?:(?!\-)(?:\_[A-Za-z0-9_\-]{1,63}\.){2})(?!.{255,})'\
r'(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)\.)+\Z'
RE_SSHFP_FINGERPRINT = r'^([0-9A-Fa-f]{10,40}|[0-9A-Fa-f]{64})\Z'
RE_TLDNAME = r'^(?!.{255,})(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-))' \
r'(?:\.(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)))*\Z'
def __init__(self, nullable=False, read_only=False,
default=ovoo_fields.UnspecifiedDefault, description='',
maxLength=None):
super(StringFields, self).__init__(nullable=nullable, default=default,
read_only=read_only)
self.description = description
self.maxLength = maxLength
def coerce(self, obj, attr, value):
if value is None:
return self._null(obj, attr)
else:
value = super(StringFields, self).coerce(obj, attr, value)
if self.maxLength and len(value) > self.maxLength:
raise ValueError('Value too long for %s' % attr)
return value
class UUID(ovoo_fields.UUID):
def coerce(self, obj, attr, value):
try:
value = int(value)
uuid.UUID(int=value)
except ValueError:
uuid.UUID(hex=value)
return str(value)
class UUIDFields(ovoo_fields.AutoTypedField):
AUTO_TYPE = UUID()
class DateTimeField(DateTimeField):
def __init__(self, tzinfo_aware=False, **kwargs):
super(DateTimeField, self).__init__(tzinfo_aware, **kwargs)
class ObjectField(ovoo_fields.ObjectField):
pass
class IPV4AddressField(ovoo_fields.IPV4AddressField):
def coerce(self, obj, attr, value):
value = super(IPV4AddressField, self).coerce(obj, attr, value)
# we use this field as a string, not need a netaddr.IPAdress
# as oslo.versionedobjects is using
return str(value)
class IPV6AddressField(ovoo_fields.IPV6AddressField):
def coerce(self, obj, attr, value):
value = super(IPV6AddressField, self).coerce(obj, attr, value)
# we use this field as a string, not need a netaddr.IPAdress
# as oslo.versionedobjects is using
return str(value)
class IPV4AndV6AddressField(ovoo_fields.IPV4AndV6AddressField):
def coerce(self, obj, attr, value):
value = super(IPV4AndV6AddressField, self).coerce(obj, attr, value)
# we use this field as a string, not need a netaddr.IPAdress
# as oslo.versionedobjects is using
return str(value)
class EnumField(ovoo_fields.EnumField):
pass
class DomainField(StringFields):
def __init__(self, **kwargs):
super(DomainField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(DomainField, self).coerce(obj, attr, value)
domain = value.split('.')
for host in domain:
if len(host) > 63:
raise ValueError("Host %s is too long" % host)
if not value.endswith('.'):
raise ValueError("Domain %s is not end with a dot" % value)
if not re.match(self.RE_ZONENAME, value):
raise ValueError("Domain %s is not match" % value)
return value
class EmailField(StringFields):
def __init__(self, **kwargs):
super(EmailField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(EmailField, self).coerce(obj, attr, value)
if value.count('@') != 1:
raise ValueError("%s is not an email" % value)
email = value.replace('@', '.')
if not re.match(self.RE_ZONENAME, "%s." % email):
raise ValueError("Email %s is not match" % value)
return value
class HostField(StringFields):
def __init__(self, **kwargs):
super(HostField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(HostField, self).coerce(obj, attr, value)
hostname = value.split('.')
for host in hostname:
if len(host) > 63:
raise ValueError("Host %s is too long" % host)
if value.endswith('.') is False:
raise ValueError("Host name %s is not end with a dot" % value)
if not re.match(self.RE_HOSTNAME, value):
raise ValueError("Host name %s is not match" % value)
return value
class SRVField(StringFields):
def __init__(self, **kwargs):
super(SRVField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(SRVField, self).coerce(obj, attr, value)
srvtype = value.split('.')
for host in srvtype:
if len(host) > 63:
raise ValueError("Host %s is too long" % host)
if value.endswith('.') is False:
raise ValueError("Host name %s is not end with a dot" % value)
if not re.match(self.RE_SRV_HOST_NAME, value):
raise ValueError("Host name %s is not a SRV record" % value)
return value
class TxtField(StringFields):
def __init__(self, **kwargs):
super(TxtField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(TxtField, self).coerce(obj, attr, value)
if value.endswith('\\'):
raise ValueError("Do NOT put '\\' into end of TXT record")
return value
class Sshfp(StringFields):
def __init__(self, **kwargs):
super(Sshfp, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(Sshfp, self).coerce(obj, attr, value)
if not re.match(self.RE_SSHFP_FINGERPRINT, "%s" % value):
raise ValueError("Host name %s is not a SSHFP record" % value)
return value
class TldField(StringFields):
def __init__(self, **kwargs):
super(TldField, self).__init__(**kwargs)
def coerce(self, obj, attr, value):
value = super(TldField, self).coerce(obj, attr, value)
if not re.match(self.RE_TLDNAME, value):
raise ValueError("%s is not an TLD" % value)
return value
class Any(ovoo_fields.FieldType):
@staticmethod
def coerce(obj, attr, value):
return value
class AnyField(ovoo_fields.AutoTypedField):
AUTO_TYPE = Any()
class BaseObject(ovoo_fields.FieldType):
@staticmethod
def coerce(obj, attr, value):
if isinstance(value, object):
return value
else:
raise ValueError("BaseObject valid values are not valid")
class BaseObjectField(ovoo_fields.AutoTypedField):
AUTO_TYPE = BaseObject()
This diff is collapsed.
......@@ -12,117 +12,48 @@
# 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 base
from designate.objects import ovo_base as base
from designate.objects import fields
class Record(base.DictObjectMixin, base.PersistentObjectMixin,
base.DesignateObject):
# TODO(kiall): `hash` is an implementation detail of our SQLA driver,
# so we should remove it.
FIELDS = {
'shard': {
'schema': {
'type': 'integer',
'minimum': 0,
'maximum': 4095
}
},
'data': {},
'zone_id': {
'schema': {
'type': 'string',
'format': 'uuid',
},
},
'managed': {
'schema': {
'type': 'boolean'
}
},
'managed_resource_type': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'managed_resource_id': {
'schema': {
'type': ['string', 'null'],
'format': 'uuid',
},
},
'managed_plugin_name': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'managed_plugin_type': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'hash': {
'schema': {
'type': 'string',
'maxLength': 32
},
},
'description': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'status': {
'schema': {
'type': 'string',
'enum': ['ACTIVE', 'PENDING', 'ERROR',
'DELETED', 'SUCCESS', 'NO_ZONE']
},
},
'tenant_id': {
'schema': {
'type': 'string',
},
},
'recordset_id': {
'schema': {
'type': 'string',
'format': 'uuid',
},
},
'managed_tenant_id': {
'schema': {
'type': ['string', 'null'],
}
},
'managed_resource_region': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'managed_extra': {
'schema': {
'type': ['string', 'null'],
'maxLength': 160
},
},
'action': {
'schema': {
'type': 'string',
'enum': ['CREATE', 'DELETE', 'UPDATE', 'NONE'],
},
},
'serial': {
'schema': {
'type': 'integer',
'minimum': 1,
'maximum': 4294967295,
},
},
@base.DesignateRegistry.register
class Record(base.DesignateObject, base.PersistentObjectMixin,
base.DictObjectMixin):
def __init__(self, *args, **kwargs):
super(Record, self).__init__(*args, **kwargs)
fields = {
'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095),
'data': fields.AnyField(nullable=True),
'zone_id': fields.UUIDFields(nullable=True),
'managed': fields.BooleanField(nullable=True),
'managed_resource_type': fields.StringFields(nullable=True,
maxLength=160),