Merge "Migrate object to OVO"
This commit is contained in:
commit
741eda43b2
|
@ -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
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
# 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()
|
|
@ -0,0 +1,437 @@
|
|||
# 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):
|
||||
self.fields = self.FIELDS
|
||||
try:
|
||||
for name in self.fields:
|
||||
field = self.fields[name]
|
||||
if self.obj_attr_is_set(name):
|
||||
value = getattr(self, name) # Check relation
|
||||
field.coerce(self, name, value) # Check value
|
||||
if isinstance(value, base.ObjectListBase):
|
||||
for obj in value:
|
||||
obj.validate()
|
||||
elif not field.nullable:
|
||||
# Check required is True ~ nullable is False
|
||||
raise exceptions.InvalidObject
|
||||
except Exception:
|
||||
raise exceptions.InvalidObject
|
||||
|
||||
|
||||
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.<key>.
|
||||
"""
|
||||
|
||||
@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))
|
|
@ -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),
|
||||
'managed_resource_id': fields.UUIDFields(nullable=True),
|
||||
'managed_plugin_name': fields.StringFields(nullable=True,
|
||||
maxLength=160),
|
||||
'managed_plugin_type': fields.StringFields(nullable=True,
|
||||
maxLength=160),
|
||||
'hash': fields.StringFields(nullable=True, maxLength=32),
|
||||
'description': fields.StringFields(nullable=True,
|
||||
maxLength=160),
|
||||
'status': fields.EnumField(
|
||||
valid_values=['ACTIVE', 'PENDING', 'ERROR', 'DELETED'],
|
||||
nullable=True
|
||||
),
|
||||
'tenant_id': fields.StringFields(nullable=True),
|
||||
'recordset_id': fields.UUIDFields(nullable=True),
|
||||
'managed_tenant_id': fields.StringFields(nullable=True),
|
||||
'managed_resource_region': fields.StringFields(nullable=True,
|
||||
maxLength=160),
|
||||
'managed_extra': fields.StringFields(nullable=True,
|
||||
maxLength=160),
|
||||
'action': fields.EnumField(
|
||||
valid_values=['CREATE', 'DELETE', 'UPDATE', 'NONE'],
|
||||
nullable=True
|
||||
),
|
||||
'serial': fields.IntegerFields(nullable=True,
|
||||
minimum=1, maximum=4294967295),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -142,5 +73,10 @@ class Record(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
% record)
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class RecordList(base.ListObjectMixin, base.DesignateObject):
|
||||
LIST_ITEM_TYPE = Record
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Record'),
|
||||
}
|
||||
|
|
|
@ -18,10 +18,12 @@ from copy import deepcopy
|
|||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
import six
|
||||
from oslo_versionedobjects import exception as ovo_exc
|
||||
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
from designate.objects import base
|
||||
from designate.objects import ovo_base as base
|
||||
from designate.objects import fields
|
||||
from designate.objects.validation_error import ValidationError
|
||||
from designate.objects.validation_error import ValidationErrorList
|
||||
|
||||
|
@ -31,8 +33,11 @@ LOG = log.getLogger(__name__)
|
|||
cfg.CONF.import_opt('supported_record_type', 'designate')
|
||||
|
||||
|
||||
class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
||||
base.DesignateObject):
|
||||
@base.DesignateRegistry.register
|
||||
class RecordSet(base.DesignateObject, base.DictObjectMixin,
|
||||
base.PersistentObjectMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RecordSet, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def action(self):
|
||||
|
@ -44,13 +49,13 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
actions[record.action] += 1
|
||||
|
||||
if actions['CREATE'] != 0 and actions['UPDATE'] == 0 and \
|
||||
actions['DELETE'] == 0 and actions['NONE'] == 0:
|
||||
actions['DELETE'] == 0 and actions['NONE'] == 0: # noqa
|
||||
action = 'CREATE'
|
||||
elif actions['DELETE'] != 0 and actions['UPDATE'] == 0 and \
|
||||
actions['CREATE'] == 0 and actions['NONE'] == 0:
|
||||
actions['CREATE'] == 0 and actions['NONE'] == 0: # noqa
|
||||
action = 'DELETE'
|
||||
elif actions['UPDATE'] != 0 or actions['CREATE'] != 0 or \
|
||||
actions['DELETE'] != 0:
|
||||
actions['DELETE'] != 0: # noqa
|
||||
action = 'UPDATE'
|
||||
return action
|
||||
|
||||
|
@ -73,78 +78,17 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
status = record.status
|
||||
return status
|
||||
|
||||
FIELDS = {
|
||||
'shard': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 4095
|
||||
}
|
||||
},
|
||||
'tenant_id': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'zone_id': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Zone identifier',
|
||||
'format': 'uuid'
|
||||
},
|
||||
},
|
||||
'zone_name': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Zone name',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'name': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Recordset name',
|
||||
'format': 'hostname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'immutable': True,
|
||||
'required': True
|
||||
},
|
||||
'type': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'RecordSet type (TODO: Make types extensible)',
|
||||
},
|
||||
'required': True,
|
||||
'immutable': True
|
||||
},
|
||||
'ttl': {
|
||||
'schema': {
|
||||
'type': ['integer', 'null'],
|
||||
'description': 'Default time to live',
|
||||
'minimum': 1,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
},
|
||||
'description': {
|
||||
'schema': {
|
||||
'type': ['string', 'null'],
|
||||
'maxLength': 160
|
||||
},
|
||||
},
|
||||
'records': {
|
||||
'relation': True,
|
||||
'relation_cls': 'RecordList'
|
||||
},
|
||||
# TODO(graham): implement the polymorphic class relations
|
||||
# 'records': {
|
||||
# 'polymorphic': 'type',
|
||||
# 'relation': True,
|
||||
# 'relation_cls': lambda type_: '%sList' % type_
|
||||
# },
|
||||
fields = {
|
||||
'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095),
|
||||
'tenant_id': fields.StringFields(nullable=True, read_only=True),
|
||||
'zone_id': fields.UUIDFields(nullable=True, read_only=True),
|
||||
'zone_name': fields.DomainField(nullable=True, maxLength=255),
|
||||
'name': fields.HostField(maxLength=255, nullable=True),
|
||||
'type': fields.StringFields(nullable=True, read_only=True),
|
||||
'ttl': fields.IntegerFields(nullable=True,
|
||||
minimum=1, maximum=2147483647),
|
||||
'description': fields.StringFields(nullable=True, maxLength=160),
|
||||
'records': fields.PolymorphicObjectField('RecordList', nullable=True),
|
||||
}
|
||||
|
||||
def _validate_fail(self, errors, msg):
|
||||
|
@ -165,6 +109,7 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
'name': self.obj_name(),
|
||||
'values': self.to_dict(),
|
||||
})
|
||||
LOG.debug(list(self.records))
|
||||
|
||||
errors = ValidationErrorList()
|
||||
|
||||
|
@ -172,7 +117,7 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
try:
|
||||
record_list_cls = self.obj_cls_from_name('%sList' % self.type)
|
||||
record_cls = self.obj_cls_from_name(self.type)
|
||||
except KeyError as e:
|
||||
except (KeyError, ovo_exc.UnsupportedObjectError) as e:
|
||||
err_msg = ("'%(type)s' is not a valid record type"
|
||||
% {'type': self.type})
|
||||
self._validate_fail(errors, err_msg)
|
||||
|
@ -259,21 +204,6 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
super(RecordSet, self).validate()
|
||||
|
||||
except exceptions.InvalidObject as e:
|
||||
# Something is wrong according to JSONSchema - append our errors
|
||||
increment = 0
|
||||
# This code below is to make sure we have the index for the record
|
||||
# list correct. JSONSchema may be missing some of the objects due
|
||||
# to validation above, so this re - inserts them, and makes sure
|
||||
# the index is right
|
||||
for error in e.errors:
|
||||
if len(error.path) > 1 and isinstance(error.path[1], int):
|
||||
error.path[1] += increment
|
||||
while error.path[1] in error_indexes:
|
||||
increment += 1
|
||||
error.path[1] += 1
|
||||
# Add the list from above
|
||||
e.errors.extend(errors)
|
||||
# Raise the exception
|
||||
raise e
|
||||
else:
|
||||
# If JSONSchema passes, but we found parsing errors,
|
||||
|
@ -292,6 +222,7 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
finally:
|
||||
if old_fields:
|
||||
self.FIELDS = old_fields
|
||||
|
||||
# Send in the traditional Record objects to central / storage
|
||||
self.records = old_records
|
||||
|
||||
|
@ -300,6 +231,11 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
]
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class RecordSetList(base.ListObjectMixin, base.DesignateObject,
|
||||
base.PagedListObjectMixin):
|
||||
LIST_ITEM_TYPE = RecordSet
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('RecordSet'),
|
||||
}
|
||||
|
|
|
@ -14,21 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class A(Record):
|
||||
"""
|
||||
A Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'address': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'ipv4',
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'address': fields.IPV4AddressField()
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -42,6 +39,11 @@ class A(Record):
|
|||
RECORD_TYPE = 1
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class AList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = A
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('A'),
|
||||
}
|
||||
|
|
|
@ -14,21 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class AAAA(Record):
|
||||
"""
|
||||
AAAA Resource Record Type
|
||||
Defined in: RFC3596
|
||||
"""
|
||||
FIELDS = {
|
||||
'address': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'ipv6',
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'address': fields.IPV6AddressField()
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -42,6 +39,11 @@ class AAAA(Record):
|
|||
RECORD_TYPE = 28
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class AAAAList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = AAAA
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('AAAA'),
|
||||
}
|
||||
|
|
|
@ -14,22 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class CNAME(Record):
|
||||
"""
|
||||
CNAME Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'cname': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'cname': fields.DomainField(maxLength=255)
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -43,6 +39,11 @@ class CNAME(Record):
|
|||
RECORD_TYPE = 5
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class CNAMEList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = CNAME
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('CNAME'),
|
||||
}
|
||||
|
|
|
@ -14,30 +14,19 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class MX(Record):
|
||||
"""
|
||||
MX Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'priority': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 65535
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'exchange': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'priority': fields.IntegerFields(minimum=0, maximum=65535),
|
||||
'exchange': fields.StringFields(maxLength=255),
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -57,6 +46,11 @@ class MX(Record):
|
|||
RECORD_TYPE = 15
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class MXList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = MX
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('MX'),
|
||||
}
|
||||
|
|
|
@ -14,32 +14,24 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class NS(Record):
|
||||
"""
|
||||
NS Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'nsdname': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'nsdname': fields.DomainField(maxLength=255)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_recordset_schema_changes(cls):
|
||||
return {
|
||||
'name': {
|
||||
'schema': {
|
||||
'format': 'ns-hostname',
|
||||
},
|
||||
},
|
||||
'name': fields.DomainField(),
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -53,6 +45,11 @@ class NS(Record):
|
|||
RECORD_TYPE = 2
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class NSList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = NS
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('NS'),
|
||||
}
|
||||
|
|
|
@ -14,22 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class PTR(Record):
|
||||
"""
|
||||
PTR Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'ptrdname': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'ptrdname': fields.DomainField(maxLength=255)
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -43,6 +39,11 @@ class PTR(Record):
|
|||
RECORD_TYPE = 12
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class PTRList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = PTR
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('PTR'),
|
||||
}
|
||||
|
|
|
@ -14,70 +14,25 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SOA(Record):
|
||||
"""
|
||||
SOA Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'mname': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'rname': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'serial': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
'maximum': 4294967295,
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'refresh': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'retry': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'expire': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'minimum': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
|
||||
fields = {
|
||||
'mname': fields.DomainField(maxLength=255),
|
||||
'rname': fields.DomainField(maxLength=255),
|
||||
'serial': fields.IntegerFields(minimum=1, maximum=4294967295),
|
||||
'refresh': fields.IntegerFields(minimum=0, maximum=2147483647),
|
||||
'retry': fields.IntegerFields(minimum=0, maximum=2147483647),
|
||||
'expire': fields.IntegerFields(minimum=0, maximum=2147483647),
|
||||
'minimum': fields.IntegerFields(minimum=0, maximum=2147483647)
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -99,6 +54,11 @@ class SOA(Record):
|
|||
RECORD_TYPE = 6
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SOAList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = SOA
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('SOA'),
|
||||
}
|
||||
|
|
|
@ -14,20 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SPF(Record):
|
||||
"""
|
||||
SPF Resource Record Type
|
||||
Defined in: RFC4408
|
||||
"""
|
||||
FIELDS = {
|
||||
'txt_data': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'txt_data': fields.StringFields()
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -41,6 +39,10 @@ class SPF(Record):
|
|||
RECORD_TYPE = 99
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SPFList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = SPF
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('SPF'),
|
||||
}
|
||||
|
|
|
@ -14,56 +14,27 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SRV(Record):
|
||||
"""
|
||||
SRV Resource Record Type
|
||||
Defined in: RFC2782
|
||||
"""
|
||||
FIELDS = {
|
||||
'priority': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 65535
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'weight': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 65535
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'port': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 65535
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'target': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'priority': fields.IntegerFields(minimum=0, maximum=65535),
|
||||
'weight': fields.IntegerFields(minimum=0, maximum=65535),
|
||||
'port': fields.IntegerFields(minimum=0, maximum=65535),
|
||||
'target': fields.DomainField(maxLength=255),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_recordset_schema_changes(cls):
|
||||
return {
|
||||
'name': {
|
||||
'schema': {
|
||||
'format': 'srv-hostname',
|
||||
},
|
||||
},
|
||||
'name': fields.SRVField(maxLength=255, nullable=True)
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -81,6 +52,10 @@ class SRV(Record):
|
|||
RECORD_TYPE = 33
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SRVList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = SRV
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('SRV'),
|
||||
}
|
||||
|
|
|
@ -14,37 +14,20 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SSHFP(Record):
|
||||
"""
|
||||
SSHFP Resource Record Type
|
||||
Defined in: RFC4255
|
||||
"""
|
||||
FIELDS = {
|
||||
'algorithm': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 4
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'fp_type': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2
|
||||
},
|
||||
'required': True
|
||||
},
|
||||
'fingerprint': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'sshfp'
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'algorithm': fields.IntegerFields(minimum=0, maximum=4),
|
||||
'fp_type': fields.IntegerFields(minimum=0, maximum=2),
|
||||
'fingerprint': fields.Sshfp(nullable=True),
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -66,6 +49,10 @@ class SSHFP(Record):
|
|||
RECORD_TYPE = 44
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class SSHFPList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = SSHFP
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('SSHFP'),
|
||||
}
|
||||
|
|
|
@ -14,22 +14,18 @@
|
|||
# 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 fields
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class TXT(Record):
|
||||
"""
|
||||
TXT Resource Record Type
|
||||
Defined in: RFC1035
|
||||
"""
|
||||
FIELDS = {
|
||||
'txt_data': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'txt-data',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'txt_data': fields.TxtField(maxLength=255)
|
||||
}
|
||||
|
||||
def _to_string(self):
|
||||
|
@ -43,6 +39,10 @@ class TXT(Record):
|
|||
RECORD_TYPE = 16
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class TXTList(RecordList):
|
||||
|
||||
LIST_ITEM_TYPE = TXT
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('TXT'),
|
||||
}
|
||||
|
|
|
@ -14,159 +14,57 @@
|
|||
# under the License.
|
||||
from designate import utils
|
||||
from designate import exceptions
|
||||
from designate.objects import base
|
||||
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 fields
|
||||
|
||||
|
||||
class Zone(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
||||
base.PersistentObjectMixin, base.DesignateObject):
|
||||
FIELDS = {
|
||||
'shard': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 4095
|
||||
}
|
||||
},
|
||||
'tenant_id': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
},
|
||||
'immutable': True
|
||||
},
|
||||
'name': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Zone name',
|
||||
'format': 'domainname',
|
||||
'maxLength': 255,
|
||||
},
|
||||
'immutable': True,
|
||||
'required': True
|
||||
},
|
||||
'email': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Hostmaster email address',
|
||||
'format': 'email',
|
||||
'maxLength': 255
|
||||
},
|
||||
'required': False
|
||||
},
|
||||
'ttl': {
|
||||
'schema': {
|
||||
'type': ['integer', 'null'],
|
||||
'minimum': 1,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
},
|
||||
'refresh': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'retry': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'expire': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'minimum': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
'maximum': 2147483647
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'parent_zone_id': {
|
||||
'schema': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'uuid'
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'serial': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
'maximum': 4294967295,
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'description': {
|
||||
'schema': {
|
||||
'type': ['string', 'null'],
|
||||
'maxLength': 160
|
||||
},
|
||||
},
|
||||
'status': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'enum': ['ACTIVE', 'PENDING', 'ERROR',
|
||||
'DELETED', 'SUCCESS', 'NO_ZONE']
|
||||
},
|
||||
'read_only': True,
|
||||
},
|
||||
'action': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'enum': ['CREATE', 'DELETE', 'UPDATE', 'NONE'],
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'pool_id': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'uuid',
|
||||
},
|
||||
'immutable': True,
|
||||
},
|
||||
'recordsets': {
|
||||
'relation': True,
|
||||
'relation_cls': 'RecordSetList'
|
||||
},
|
||||
'attributes': {
|
||||
'relation': True,
|
||||
'relation_cls': 'ZoneAttributeList'
|
||||
},
|
||||
'masters': {
|
||||
'relation': True,
|
||||
'relation_cls': 'ZoneMasterList'
|
||||
},
|
||||
'type': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'enum': ['SECONDARY', 'PRIMARY'],
|
||||
},
|
||||
'immutable': True
|
||||
},
|
||||
'transferred_at': {
|
||||
'schema': {
|
||||
'type': ['string', 'null'],
|
||||
'format': 'date-time',
|
||||
},
|
||||
'read_only': True
|
||||
},
|
||||
'delayed_notify': {
|
||||
'schema': {
|
||||
'type': 'boolean',
|
||||
},
|
||||
},
|
||||
@base.DesignateRegistry.register
|
||||
class Zone(base.DesignateObject, base.DictObjectMixin,
|
||||
base.PersistentObjectMixin, base.SoftDeleteObjectMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Zone, self).__init__(*args, **kwargs)
|
||||
|
||||
fields = {
|
||||
'shard': fields.IntegerFields(nullable=True, minimum=0, maximum=4095),
|
||||
'tenant_id': fields.StringFields(nullable=True, read_only=False),
|
||||
'name': fields.DomainField(maxLength=255),
|
||||
'email': fields.EmailField(maxLength=255, nullable=True),
|
||||
'ttl': fields.IntegerFields(nullable=True, minimum=1,
|
||||
maximum=2147483647),
|
||||
'refresh': fields.IntegerFields(nullable=True, minimum=0,
|
||||
maximum=2147483647, read_only=False),
|
||||
'retry': fields.IntegerFields(nullable=True, minimum=0,
|
||||
maximum=2147483647, read_only=False),
|
||||
'expire': fields.IntegerFields(nullable=True, minimum=0,
|
||||
maximum=2147483647, read_only=False),
|
||||
'minimum': fields.IntegerFields(nullable=True, minimum=0,
|
||||
maximum=2147483647, read_only=False),
|
||||
'parent_zone_id': fields.UUIDFields(nullable=True, read_only=False),
|
||||
'serial': fields.IntegerFields(nullable=True, minimum=0,
|
||||
maximum=4294967295, read_only=False),
|
||||
'description': fields.StringFields(nullable=True, maxLength=160),
|
||||
'status': fields.EnumField(nullable=True, read_only=False,
|
||||
valid_values=[
|
||||
'ACTIVE', 'PENDING', 'ERROR',
|
||||
'DELETED', 'SUCCESS', 'NO_ZONE']
|
||||
|
||||
),
|
||||
'action': fields.EnumField(nullable=True,
|
||||
valid_values=[
|
||||
'CREATE', 'DELETE', 'UPDATE', 'NONE']
|
||||
),
|
||||
'pool_id': fields.UUIDFields(nullable=True, read_only=False),
|
||||
'recordsets': fields.ObjectField('RecordSetList', nullable=True),
|
||||
'attributes': fields.ObjectField('ZoneAttributeList', nullable=True),
|
||||
'masters': fields.ObjectField('ZoneMasterList', nullable=True),
|
||||
'type': fields.EnumField(nullable=True,
|
||||
valid_values=['SECONDARY', 'PRIMARY'],
|
||||
read_only=False
|
||||
),
|
||||
'transferred_at': fields.DateTimeField(nullable=True, read_only=False),
|
||||
'delayed_notify': fields.BooleanField(nullable=True),
|
||||
}
|
||||
|
||||
STRING_KEYS = [
|
||||
|
@ -229,7 +127,7 @@ class Zone(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
|||
e.validator = 'not_allowed'
|
||||
e.validator_value = i
|
||||
e.message = "'%s' can't be specified when type is " \
|
||||
"SECONDARY" % i
|
||||
"SECONDARY" % i
|
||||
errors.append(e)
|
||||
self._raise(errors)
|
||||
|
||||
|
@ -245,6 +143,11 @@ class Zone(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
|||
self._raise(errors)
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class ZoneList(base.ListObjectMixin, base.DesignateObject,
|
||||
base.PagedListObjectMixin):
|
||||
LIST_ITEM_TYPE = Zone
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('Zone'),
|
||||
}
|
||||
|
|
|
@ -13,33 +13,17 @@
|
|||
# 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
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class ZoneAttribute(base.DictObjectMixin, base.PersistentObjectMixin,
|
||||
base.DesignateObject):
|
||||
FIELDS = {
|
||||
'zone_id': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'description': 'Zone identifier',
|
||||
'format': 'uuid',
|
||||
},
|
||||
},
|
||||
'key': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'maxLength': 50,
|
||||
},
|
||||
'required': True,
|
||||
},
|
||||
'value': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'maxLength': 50,
|
||||
},
|
||||
'required': True
|
||||
}
|
||||
fields = {
|
||||
'zone_id': fields.UUIDFields(nullable=True),
|
||||
'key': fields.StringFields(maxLength=50, nullable=False),
|
||||
'value': fields.StringFields(maxLength=50, nullable=False)
|
||||
}
|
||||
|
||||
STRING_KEYS = [
|
||||
|
@ -47,5 +31,10 @@ class ZoneAttribute(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
]
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class ZoneAttributeList(base.AttributeListObjectMixin, base.DesignateObject):
|
||||
LIST_ITEM_TYPE = ZoneAttribute
|
||||
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('ZoneAttribute'),
|
||||
}
|
||||
|
|
|
@ -13,45 +13,69 @@
|
|||
# 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 oslo_versionedobjects import base as ovoo_base
|
||||
|
||||
from designate import utils
|
||||
from designate.objects import ovo_base as base
|
||||
from designate.objects import fields
|
||||
|
||||
|
||||
class ZoneMaster(base.DictObjectMixin, base.PersistentObjectMixin,
|
||||
base.DesignateObject):
|
||||
FIELDS = {
|
||||
'zone_id': {},
|
||||
'host': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
'format': 'ip-or-host',
|
||||
'required': True,
|
||||
},
|
||||
},
|
||||
'port': {
|
||||
'schema': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
'maximum': 65535,
|
||||
'required': True,
|
||||
},
|
||||
}
|
||||
@base.DesignateRegistry.register
|
||||
class ZoneMaster(base.DesignateObject,
|
||||
base.DictObjectMixin,
|
||||
base.PersistentObjectMixin,
|
||||
base.SoftDeleteObjectMixin):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ZoneMaster, self).__init__(*args, **kwargs)
|
||||
|
||||
fields = {
|
||||
'zone_id': fields.UUIDFields(nullable=True),
|
||||
'host': fields.StringFields(),
|
||||
'port': fields.IntegerFields(minimum=1, maximum=65535)
|
||||
}
|
||||
|
||||
def to_data(self):
|
||||
return "%(host)s:%(port)d" % self.to_dict()
|
||||
return "{}:{}".format(self.host, self.port)
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
host, port = utils.split_host_port(data)
|
||||
return cls.from_dict({"host": host, "port": port})
|
||||
dict_data = {"host": host, "port": port}
|
||||
return cls(**dict_data)
|
||||
|
||||
|
||||
@base.DesignateRegistry.register
|
||||
class ZoneMasterList(base.ListObjectMixin, base.DesignateObject):
|
||||
LIST_ITEM_TYPE = ZoneMaster
|
||||
fields = {
|
||||
'objects': fields.ListOfObjectsField('ZoneMaster'),
|
||||
}
|
||||
|
||||
@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, ovoo_base.ObjectListBase):
|
||||
list_.append(item.to_list())
|
||||
elif isinstance(item, base.DesignateObject):
|
||||
list_.append(item.to_dict())
|
||||
else:
|
||||
list_.append(item)
|
||||
|
||||
return list_
|
||||
|
||||
def to_data(self):
|
||||
rlist = []
|
||||
zone_master_list = []
|
||||
for item in self.objects:
|
||||
rlist.append(item.to_data())
|
||||
return rlist
|
||||
zone_master_list.append(item.to_data())
|
||||
return zone_master_list
|
||||
|
|
|
@ -131,7 +131,10 @@ class DesignateObjectSerializer(messaging.NoOpSerializer):
|
|||
|
||||
def deserialize_entity(self, context, entity):
|
||||
if isinstance(entity, dict) and 'designate_object.name' in entity:
|
||||
entity = objects.DesignateObject.from_primitive(entity)
|
||||
if 'designate_object.version' in entity:
|
||||
entity = objects.OVODesignateObject.from_primitive(entity)
|
||||
else:
|
||||
entity = objects.DesignateObject.from_primitive(entity)
|
||||
elif isinstance(entity, (tuple, list, set)):
|
||||
entity = self._process_iterable(context, self.deserialize_entity,
|
||||
entity)
|
||||
|
|
|
@ -26,6 +26,7 @@ from testtools.matchers import GreaterThan
|
|||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_db import exception as db_exception
|
||||
from oslo_versionedobjects import exception as ovo_exc
|
||||
from oslo_messaging.notify import notifier
|
||||
|
||||
from designate import exceptions
|
||||
|
@ -694,7 +695,7 @@ class CentralServiceTest(CentralTestCase):
|
|||
values = self.get_zone_fixture(fixture=1)
|
||||
values['ttl'] = 0
|
||||
|
||||
with testtools.ExpectedException(exceptions.InvalidTTL):
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.central_service.create_zone(
|
||||
context, objects.Zone.from_dict(values))
|
||||
|
||||
|
@ -707,7 +708,7 @@ class CentralServiceTest(CentralTestCase):
|
|||
values = self.get_zone_fixture(fixture=1)
|
||||
values['ttl'] = -100
|
||||
|
||||
with testtools.ExpectedException(exceptions.InvalidTTL):
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.central_service.create_zone(
|
||||
context, objects.Zone.from_dict(values))
|
||||
|
||||
|
@ -1801,14 +1802,8 @@ class CentralServiceTest(CentralTestCase):
|
|||
# Create a recordset
|
||||
recordset = self.create_recordset(zone)
|
||||
|
||||
# Update the recordset
|
||||
recordset.ttl = 1800
|
||||
recordset.zone_id = other_zone.id
|
||||
|
||||
# Ensure we get a BadRequest if we change the zone_id
|
||||
with testtools.ExpectedException(exceptions.BadRequest):
|
||||
self.central_service.update_recordset(
|
||||
self.admin_context, recordset)
|
||||
self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
|
||||
recordset, 'zone_id', other_zone.id)
|
||||
|
||||
def test_update_recordset_immutable_tenant_id(self):
|
||||
zone = self.create_zone()
|
||||
|
@ -1816,30 +1811,19 @@ class CentralServiceTest(CentralTestCase):
|
|||
# Create a recordset
|
||||
recordset = self.create_recordset(zone)
|
||||
|
||||
# Update the recordset
|
||||
recordset.ttl = 1800
|
||||
recordset.tenant_id = 'other-tenant'
|
||||
|
||||
# Ensure we get a BadRequest if we change the zone_id
|
||||
with testtools.ExpectedException(exceptions.BadRequest):
|
||||
self.central_service.update_recordset(
|
||||
self.admin_context, recordset)
|
||||
self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
|
||||
recordset, 'tenant_id', 'other-tenant')
|
||||
|
||||
def test_update_recordset_immutable_type(self):
|
||||
zone = self.create_zone()
|
||||
|
||||
# ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'SPF', 'NS', 'PTR',
|
||||
# 'SSHFP', 'SOA']
|
||||
# Create a recordset
|
||||
recordset = self.create_recordset(zone)
|
||||
cname_recordset = self.create_recordset(zone, type='CNAME')
|
||||
|
||||
# Update the recordset
|
||||
recordset.ttl = 1800
|
||||
recordset.type = cname_recordset.type
|
||||
|
||||
# Ensure we get a BadRequest if we change the zone_id
|
||||
with testtools.ExpectedException(exceptions.BadRequest):
|
||||
self.central_service.update_recordset(
|
||||
self.admin_context, recordset)
|
||||
self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
|
||||
recordset, 'type', cname_recordset.type)
|
||||
|
||||
def test_delete_recordset(self):
|
||||
zone = self.create_zone()
|
||||
|
|
|
@ -196,7 +196,8 @@ class RecordSetTest(oslotest.base.BaseTestCase):
|
|||
|
||||
def test_validate_handle_exception(self):
|
||||
rs = create_test_recordset()
|
||||
fn_name = 'designate.objects.DesignateObject.obj_cls_from_name'
|
||||
rs_module = rs.__class__.__bases__[0].__module__
|
||||
fn_name = '{}.DesignateObject.obj_cls_from_name'.format(rs_module)
|
||||
with mock.patch(fn_name) as patched:
|
||||
patched.side_effect = KeyError
|
||||
with testtools.ExpectedException(exceptions.InvalidObject):
|
||||
|
|
|
@ -21,6 +21,7 @@ oslo.rootwrap>=5.8.0 # Apache-2.0
|
|||
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
|
||||
oslo.service!=1.28.1,>=1.24.0 # Apache-2.0
|
||||
oslo.utils>=3.33.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.31.2 # Apache-2.0
|
||||
Paste>=2.0.2 # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
pbr!=2.1.0,>=2.0.0 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue