Add DomainMaster Object to designate objects
Change-Id: I60dbe23be0d6f2ea6b6f3f9d7bd26ce2e2f7686f
This commit is contained in:
parent
d602a6421c
commit
ea4b054ac6
@ -40,6 +40,18 @@ class RelationNotLoaded(Base):
|
|||||||
error_code = 500
|
error_code = 500
|
||||||
error_type = 'relation_not_loaded'
|
error_type = 'relation_not_loaded'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
self.relation = kwargs.pop('relation', None)
|
||||||
|
|
||||||
|
super(RelationNotLoaded, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.error_message = "%(relation)s is not loaded on %(object)s" % \
|
||||||
|
{"relation": self.relation, "object": self.object.obj_name()}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.error_message
|
||||||
|
|
||||||
|
|
||||||
class AdapterNotFound(Base):
|
class AdapterNotFound(Base):
|
||||||
error_code = 500
|
error_code = 500
|
||||||
@ -318,6 +330,10 @@ class DomainNotFound(NotFound):
|
|||||||
error_type = 'domain_not_found'
|
error_type = 'domain_not_found'
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMasterNotFound(NotFound):
|
||||||
|
error_type = 'domain_master_not_found'
|
||||||
|
|
||||||
|
|
||||||
class DomainAttributeNotFound(NotFound):
|
class DomainAttributeNotFound(NotFound):
|
||||||
error_type = 'domain_attribute_not_found'
|
error_type = 'domain_attribute_not_found'
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class XFRMixin(object):
|
|||||||
"""
|
"""
|
||||||
def domain_sync(self, context, domain, servers=None):
|
def domain_sync(self, context, domain, servers=None):
|
||||||
servers = servers or domain.masters
|
servers = servers or domain.masters
|
||||||
servers = dnsutils.expand_servers(servers)
|
servers = servers.to_list()
|
||||||
|
|
||||||
timeout = cfg.CONF["service:mdns"].xfr_timeout
|
timeout = cfg.CONF["service:mdns"].xfr_timeout
|
||||||
try:
|
try:
|
||||||
|
@ -21,6 +21,7 @@ from designate.objects.base import PagedListObjectMixin # noqa
|
|||||||
from designate.objects.blacklist import Blacklist, BlacklistList # noqa
|
from designate.objects.blacklist import Blacklist, BlacklistList # noqa
|
||||||
from designate.objects.domain import Domain, DomainList # noqa
|
from designate.objects.domain import Domain, DomainList # noqa
|
||||||
from designate.objects.domain_attribute import DomainAttribute, DomainAttributeList # noqa
|
from designate.objects.domain_attribute import DomainAttribute, DomainAttributeList # noqa
|
||||||
|
from designate.objects.domain_master import DomainMaster, DomainMasterList # noqa
|
||||||
from designate.objects.floating_ip import FloatingIP, FloatingIPList # noqa
|
from designate.objects.floating_ip import FloatingIP, FloatingIPList # noqa
|
||||||
from designate.objects.pool_manager_status import PoolManagerStatus, PoolManagerStatusList # noqa
|
from designate.objects.pool_manager_status import PoolManagerStatus, PoolManagerStatusList # noqa
|
||||||
from designate.objects.pool import Pool, PoolList # noqa
|
from designate.objects.pool import Pool, PoolList # noqa
|
||||||
|
@ -16,6 +16,7 @@ from designate.objects.adapters.base import DesignateAdapter # noqa
|
|||||||
# API v2
|
# API v2
|
||||||
from designate.objects.adapters.api_v2.blacklist import BlacklistAPIv2Adapter, BlacklistListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.blacklist import BlacklistAPIv2Adapter, BlacklistListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.domain import DomainAPIv2Adapter, DomainListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.domain import DomainAPIv2Adapter, DomainListAPIv2Adapter # noqa
|
||||||
|
from designate.objects.adapters.api_v2.domain_master import DomainMasterAPIv2Adapter, DomainMasterListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.floating_ip import FloatingIPAPIv2Adapter, FloatingIPListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.floating_ip import FloatingIPAPIv2Adapter, FloatingIPListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.record import RecordAPIv2Adapter, RecordListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.record import RecordAPIv2Adapter, RecordListAPIv2Adapter # noqa
|
||||||
from designate.objects.adapters.api_v2.recordset import RecordSetAPIv2Adapter, RecordSetListAPIv2Adapter # noqa
|
from designate.objects.adapters.api_v2.recordset import RecordSetAPIv2Adapter, RecordSetListAPIv2Adapter # noqa
|
||||||
|
@ -15,7 +15,6 @@ from oslo_log import log as logging
|
|||||||
|
|
||||||
from designate.objects.adapters.api_v2 import base
|
from designate.objects.adapters.api_v2 import base
|
||||||
from designate import objects
|
from designate import objects
|
||||||
from designate import exceptions
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -64,26 +63,16 @@ class DomainAPIv2Adapter(base.APIv2Adapter):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_object(cls, values, object, *args, **kwargs):
|
def _parse_object(cls, values, object, *args, **kwargs):
|
||||||
# TODO(Graham): Remove this when
|
|
||||||
# https://bugs.launchpad.net/designate/+bug/1432842 is fixed
|
|
||||||
|
|
||||||
if 'masters' in values:
|
if 'masters' in values:
|
||||||
if isinstance(values['masters'], list):
|
|
||||||
object.set_masters(values.get('masters'))
|
object.masters = objects.adapters.DesignateAdapter.parse(
|
||||||
|
cls.ADAPTER_FORMAT,
|
||||||
|
values['masters'],
|
||||||
|
objects.DomainMasterList(),
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
del values['masters']
|
del values['masters']
|
||||||
else:
|
|
||||||
errors = objects.ValidationErrorList()
|
|
||||||
e = objects.ValidationError()
|
|
||||||
e.path = ['masters']
|
|
||||||
e.validator = 'type'
|
|
||||||
e.validator_value = ["list"]
|
|
||||||
e.message = ("'%(data)s' is not a valid list of masters"
|
|
||||||
% {'data': values['masters']})
|
|
||||||
# Add it to the list for later
|
|
||||||
errors.append(e)
|
|
||||||
raise exceptions.InvalidObject(
|
|
||||||
"Provided object does not match "
|
|
||||||
"schema", errors=errors, object=cls.ADAPTER_OBJECT())
|
|
||||||
|
|
||||||
return super(DomainAPIv2Adapter, cls)._parse_object(
|
return super(DomainAPIv2Adapter, cls)._parse_object(
|
||||||
values, object, *args, **kwargs)
|
values, object, *args, **kwargs)
|
||||||
|
92
designate/objects/adapters/api_v2/domain_master.py
Normal file
92
designate/objects/adapters/api_v2/domain_master.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from designate.objects.adapters.api_v2 import base
|
||||||
|
from designate import objects
|
||||||
|
from designate import utils
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMasterAPIv2Adapter(base.APIv2Adapter):
|
||||||
|
|
||||||
|
ADAPTER_OBJECT = objects.DomainMaster
|
||||||
|
|
||||||
|
MODIFICATIONS = {
|
||||||
|
'fields': {
|
||||||
|
'value': {
|
||||||
|
'read_only': False
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'options': {
|
||||||
|
'links': False,
|
||||||
|
'resource_name': 'domain_master',
|
||||||
|
'collection_name': 'domain_masters',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _render_object(cls, object, *arg, **kwargs):
|
||||||
|
if object.port is 53:
|
||||||
|
return object.host
|
||||||
|
else:
|
||||||
|
return "%(host)s:%(port)d" % object.to_dict()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_object(cls, value, object, *args, **kwargs):
|
||||||
|
object.host, object.port = utils.split_host_port(value)
|
||||||
|
return object
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMasterListAPIv2Adapter(base.APIv2Adapter):
|
||||||
|
|
||||||
|
ADAPTER_OBJECT = objects.DomainMasterList
|
||||||
|
|
||||||
|
MODIFICATIONS = {
|
||||||
|
'options': {
|
||||||
|
'links': False,
|
||||||
|
'resource_name': 'domain_master',
|
||||||
|
'collection_name': 'domain_masters',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _render_list(cls, list_object, *args, **kwargs):
|
||||||
|
|
||||||
|
r_list = []
|
||||||
|
|
||||||
|
for object in list_object:
|
||||||
|
r_list.append(cls.get_object_adapter(
|
||||||
|
cls.ADAPTER_FORMAT,
|
||||||
|
object).render(cls.ADAPTER_FORMAT, object, *args, **kwargs))
|
||||||
|
|
||||||
|
return r_list
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_list(cls, values, output_object, *args, **kwargs):
|
||||||
|
|
||||||
|
for value in values:
|
||||||
|
# Add the object to the list
|
||||||
|
output_object.append(
|
||||||
|
# Get the right Adapter
|
||||||
|
cls.get_object_adapter(
|
||||||
|
cls.ADAPTER_FORMAT,
|
||||||
|
# This gets the internal type of the list, and parses it
|
||||||
|
# We need to do `get_object_adapter` as we need a new
|
||||||
|
# instance of the Adapter
|
||||||
|
output_object.LIST_ITEM_TYPE()).parse(
|
||||||
|
value, output_object.LIST_ITEM_TYPE()))
|
||||||
|
|
||||||
|
# Return the filled list
|
||||||
|
return output_object
|
@ -154,7 +154,7 @@ class DesignateObject(object):
|
|||||||
def _obj_check_relation(self, name):
|
def _obj_check_relation(self, name):
|
||||||
if name in self.FIELDS and self.FIELDS[name].get('relation', False):
|
if name in self.FIELDS and self.FIELDS[name].get('relation', False):
|
||||||
if not self.obj_attr_is_set(name):
|
if not self.obj_attr_is_set(name):
|
||||||
raise exceptions.RelationNotLoaded
|
raise exceptions.RelationNotLoaded(object=self, relation=name)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def obj_cls_from_name(cls, name):
|
def obj_cls_from_name(cls, name):
|
||||||
@ -310,9 +310,21 @@ class DesignateObject(object):
|
|||||||
ValidationErrorList = self.obj_cls_from_name('ValidationErrorList')
|
ValidationErrorList = self.obj_cls_from_name('ValidationErrorList')
|
||||||
ValidationError = self.obj_cls_from_name('ValidationError')
|
ValidationError = self.obj_cls_from_name('ValidationError')
|
||||||
|
|
||||||
values = self.to_dict()
|
|
||||||
errors = ValidationErrorList()
|
errors = ValidationErrorList()
|
||||||
|
|
||||||
|
try:
|
||||||
|
values = self.to_dict()
|
||||||
|
except exceptions.RelationNotLoaded as e:
|
||||||
|
e = ValidationError()
|
||||||
|
e.path = ['type']
|
||||||
|
e.validator = 'required'
|
||||||
|
e.validator_value = [e.relation]
|
||||||
|
e.message = "'%s' is a required property" % e.relation
|
||||||
|
errors.append(e)
|
||||||
|
raise exceptions.InvalidObject(
|
||||||
|
"Provided object does not match "
|
||||||
|
"schema", errors=errors, object=self)
|
||||||
|
|
||||||
LOG.debug("Validating '%(name)s' object with values: %(values)r", {
|
LOG.debug("Validating '%(name)s' object with values: %(values)r", {
|
||||||
'name': self.obj_name(),
|
'name': self.obj_name(),
|
||||||
'values': values,
|
'values': values,
|
||||||
|
@ -17,8 +17,6 @@ from designate import exceptions
|
|||||||
from designate.objects import base
|
from designate.objects import base
|
||||||
from designate.objects.validation_error import ValidationError
|
from designate.objects.validation_error import ValidationError
|
||||||
from designate.objects.validation_error import ValidationErrorList
|
from designate.objects.validation_error import ValidationErrorList
|
||||||
from designate.objects.domain_attribute import DomainAttribute
|
|
||||||
from designate.objects.domain_attribute import DomainAttributeList
|
|
||||||
|
|
||||||
|
|
||||||
class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
||||||
@ -145,6 +143,10 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
|||||||
'relation': True,
|
'relation': True,
|
||||||
'relation_cls': 'DomainAttributeList'
|
'relation_cls': 'DomainAttributeList'
|
||||||
},
|
},
|
||||||
|
'masters': {
|
||||||
|
'relation': True,
|
||||||
|
'relation_cls': 'DomainMasterList'
|
||||||
|
},
|
||||||
'type': {
|
'type': {
|
||||||
'schema': {
|
'schema': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
@ -165,33 +167,18 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
|||||||
'id', 'type', 'name', 'pool_id', 'serial', 'action', 'status'
|
'id', 'type', 'name', 'pool_id', 'serial', 'action', 'status'
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
|
||||||
def masters(self):
|
|
||||||
if self.obj_attr_is_set('attributes'):
|
|
||||||
return [i.value for i in self.attributes if i.key == 'master']
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# TODO(ekarlso): Make this a property sette rpr Kiall's comments later.
|
|
||||||
def set_masters(self, masters):
|
|
||||||
attributes = DomainAttributeList()
|
|
||||||
|
|
||||||
for m in masters:
|
|
||||||
obj = DomainAttribute(key='master', value=m)
|
|
||||||
attributes.append(obj)
|
|
||||||
self.attributes = attributes
|
|
||||||
|
|
||||||
def get_master_by_ip(self, host):
|
def get_master_by_ip(self, host):
|
||||||
"""
|
"""
|
||||||
Utility to get the master by it's ip for this domain.
|
Utility to get the master by it's ip for this domain.
|
||||||
"""
|
"""
|
||||||
for srv in self.masters:
|
for srv in self.masters:
|
||||||
srv_host, _ = utils.split_host_port(srv)
|
srv_host, _ = utils.split_host_port(srv.to_data())
|
||||||
if host == srv_host:
|
if host == srv_host:
|
||||||
return srv
|
return srv
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
try:
|
||||||
if self.type == 'SECONDARY' and self.masters is None:
|
if self.type == 'SECONDARY' and self.masters is None:
|
||||||
errors = ValidationErrorList()
|
errors = ValidationErrorList()
|
||||||
e = ValidationError()
|
e = ValidationError()
|
||||||
@ -205,6 +192,17 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
|||||||
"schema", errors=errors, object=self)
|
"schema", errors=errors, object=self)
|
||||||
|
|
||||||
super(Domain, self).validate()
|
super(Domain, self).validate()
|
||||||
|
except exceptions.RelationNotLoaded as ex:
|
||||||
|
errors = ValidationErrorList()
|
||||||
|
e = ValidationError()
|
||||||
|
e.path = ['type']
|
||||||
|
e.validator = 'required'
|
||||||
|
e.validator_value = [ex.relation]
|
||||||
|
e.message = "'%s' is a required property" % ex.relation
|
||||||
|
errors.append(e)
|
||||||
|
raise exceptions.InvalidObject(
|
||||||
|
"Provided object does not match "
|
||||||
|
"schema", errors=errors, object=self)
|
||||||
|
|
||||||
|
|
||||||
class DomainList(base.ListObjectMixin, base.DesignateObject,
|
class DomainList(base.ListObjectMixin, base.DesignateObject,
|
||||||
|
57
designate/objects/domain_master.py
Normal file
57
designate/objects/domain_master.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Author: Endre Karlson <endre.karlson@hp.com>
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from designate.objects import base
|
||||||
|
from designate import utils
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMaster(base.DictObjectMixin, base.PersistentObjectMixin,
|
||||||
|
base.DesignateObject):
|
||||||
|
FIELDS = {
|
||||||
|
'domain_id': {},
|
||||||
|
'host': {
|
||||||
|
'schema': {
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'ip-or-host',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'port': {
|
||||||
|
'schema': {
|
||||||
|
'type': 'integer',
|
||||||
|
'minimum': 1,
|
||||||
|
'maximum': 65535,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
return "%(host)s:%(port)d" % self.to_dict()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_data(cls, data):
|
||||||
|
host, port = utils.split_host_port(data)
|
||||||
|
return cls.from_dict({"host": host, "port": port})
|
||||||
|
|
||||||
|
|
||||||
|
class DomainMasterList(base.ListObjectMixin, base.DesignateObject):
|
||||||
|
LIST_ITEM_TYPE = DomainMaster
|
||||||
|
|
||||||
|
def to_data(self):
|
||||||
|
rlist = []
|
||||||
|
for item in self.objects:
|
||||||
|
rlist.append(item.to_data())
|
||||||
|
return rlist
|
@ -102,6 +102,20 @@ def is_hostname(instance):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@draft3_format_checker.checks("ip-or-host")
|
||||||
|
@draft4_format_checker.checks("ip-or-host")
|
||||||
|
def is_ip_or_host(instance):
|
||||||
|
if not isinstance(instance, compat.str_types):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if not re.match(RE_DOMAINNAME, instance)\
|
||||||
|
and not is_ipv4(instance)\
|
||||||
|
and not is_ipv6(instance):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@draft3_format_checker.checks("domain-name")
|
@draft3_format_checker.checks("domain-name")
|
||||||
@draft4_format_checker.checks("domainname")
|
@draft4_format_checker.checks("domainname")
|
||||||
def is_domainname(instance):
|
def is_domainname(instance):
|
||||||
|
@ -232,9 +232,17 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
|
|
||||||
def _load_relations(domain):
|
def _load_relations(domain):
|
||||||
if domain.type == 'SECONDARY':
|
if domain.type == 'SECONDARY':
|
||||||
domain.attributes = self._find_domain_attributes(
|
domain.masters = self._find_domain_masters(
|
||||||
context, {'domain_id': domain.id})
|
context, {'domain_id': domain.id})
|
||||||
domain.obj_reset_changes(['attributes'])
|
else:
|
||||||
|
# This avoids an extra DB call per primary zone. This will
|
||||||
|
# always have 0 results for a PRIMARY zone.
|
||||||
|
domain.masters = objects.DomainMasterList()
|
||||||
|
|
||||||
|
domain.attributes = self._find_domain_masters(
|
||||||
|
context, {'domain_id': domain.id})
|
||||||
|
|
||||||
|
domain.obj_reset_changes(['masters', 'attributes'])
|
||||||
|
|
||||||
if one:
|
if one:
|
||||||
_load_relations(domains)
|
_load_relations(domains)
|
||||||
@ -252,7 +260,7 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
# Don't handle recordsets for now
|
# Don't handle recordsets for now
|
||||||
domain = self._create(
|
domain = self._create(
|
||||||
tables.domains, domain, exceptions.DuplicateDomain,
|
tables.domains, domain, exceptions.DuplicateDomain,
|
||||||
['attributes', 'recordsets'],
|
['attributes', 'recordsets', 'masters'],
|
||||||
extra_values=extra_values)
|
extra_values=extra_values)
|
||||||
|
|
||||||
if domain.obj_attr_is_set('attributes'):
|
if domain.obj_attr_is_set('attributes'):
|
||||||
@ -260,7 +268,12 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
self.create_domain_attribute(context, domain.id, attrib)
|
self.create_domain_attribute(context, domain.id, attrib)
|
||||||
else:
|
else:
|
||||||
domain.attributes = objects.DomainAttributeList()
|
domain.attributes = objects.DomainAttributeList()
|
||||||
domain.obj_reset_changes('attributes')
|
if domain.obj_attr_is_set('masters'):
|
||||||
|
for master in domain.masters:
|
||||||
|
self.create_domain_master(context, domain.id, master)
|
||||||
|
else:
|
||||||
|
domain.masters = objects.DomainMasterList()
|
||||||
|
domain.obj_reset_changes(['masters', 'attributes'])
|
||||||
|
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
@ -288,7 +301,7 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
updated_domain = self._update(
|
updated_domain = self._update(
|
||||||
context, tables.domains, domain, exceptions.DuplicateDomain,
|
context, tables.domains, domain, exceptions.DuplicateDomain,
|
||||||
exceptions.DomainNotFound,
|
exceptions.DomainNotFound,
|
||||||
['attributes', 'recordsets'])
|
['attributes', 'recordsets', 'masters'])
|
||||||
|
|
||||||
if domain.obj_attr_is_set('attributes'):
|
if domain.obj_attr_is_set('attributes'):
|
||||||
# Gather the Attribute ID's we have
|
# Gather the Attribute ID's we have
|
||||||
@ -330,6 +343,46 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
attr.domain_id = domain.id
|
attr.domain_id = domain.id
|
||||||
self.create_domain_attribute(context, domain.id, attr)
|
self.create_domain_attribute(context, domain.id, attr)
|
||||||
|
|
||||||
|
if domain.obj_attr_is_set('masters'):
|
||||||
|
# Gather the Attribute ID's we have
|
||||||
|
have = set([r.id for r in self._find_domain_masters(
|
||||||
|
context, {'domain_id': domain.id})])
|
||||||
|
|
||||||
|
# Prep some lists of changes
|
||||||
|
keep = set([])
|
||||||
|
create = []
|
||||||
|
update = []
|
||||||
|
|
||||||
|
# Determine what to change
|
||||||
|
for i in domain.masters:
|
||||||
|
keep.add(i.id)
|
||||||
|
try:
|
||||||
|
i.obj_get_original_value('id')
|
||||||
|
except KeyError:
|
||||||
|
create.append(i)
|
||||||
|
else:
|
||||||
|
update.append(i)
|
||||||
|
|
||||||
|
# NOTE: Since we're dealing with mutable objects, the return value
|
||||||
|
# of create/update/delete attribute is not needed.
|
||||||
|
# The original item will be mutated in place on the input
|
||||||
|
# "domain.attributes" list.
|
||||||
|
|
||||||
|
# Delete Attributes
|
||||||
|
for i_id in have - keep:
|
||||||
|
attr = self._find_domain_masters(
|
||||||
|
context, {'id': i_id}, one=True)
|
||||||
|
self.delete_domain_master(context, attr.id)
|
||||||
|
|
||||||
|
# Update Attributes
|
||||||
|
for i in update:
|
||||||
|
self.update_domain_master(context, i)
|
||||||
|
|
||||||
|
# Create Attributes
|
||||||
|
for attr in create:
|
||||||
|
attr.domain_id = domain.id
|
||||||
|
self.create_domain_master(context, domain.id, attr)
|
||||||
|
|
||||||
if domain.obj_attr_is_set('recordsets'):
|
if domain.obj_attr_is_set('recordsets'):
|
||||||
existing = self.find_recordsets(context, {'domain_id': domain.id})
|
existing = self.find_recordsets(context, {'domain_id': domain.id})
|
||||||
|
|
||||||
@ -431,6 +484,71 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
|||||||
|
|
||||||
return deleted_domain_attribute
|
return deleted_domain_attribute
|
||||||
|
|
||||||
|
# Domain master methods
|
||||||
|
def _find_domain_masters(self, context, criterion, one=False,
|
||||||
|
marker=None, limit=None, sort_key=None,
|
||||||
|
sort_dir=None):
|
||||||
|
|
||||||
|
criterion['key'] = 'master'
|
||||||
|
|
||||||
|
attribs = self._find(context, tables.domain_attributes,
|
||||||
|
objects.DomainAttribute,
|
||||||
|
objects.DomainAttributeList,
|
||||||
|
exceptions.DomainMasterNotFound,
|
||||||
|
criterion, one,
|
||||||
|
marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
masters = objects.DomainMasterList()
|
||||||
|
|
||||||
|
for attrib in attribs:
|
||||||
|
masters.append(objects.DomainMaster().from_data(attrib.value))
|
||||||
|
|
||||||
|
return masters
|
||||||
|
|
||||||
|
def create_domain_master(self, context, domain_id, domain_master):
|
||||||
|
|
||||||
|
domain_attribute = objects.DomainAttribute()
|
||||||
|
domain_attribute.domain_id = domain_id
|
||||||
|
domain_attribute.key = 'master'
|
||||||
|
domain_attribute.value = domain_master.to_data()
|
||||||
|
|
||||||
|
return self._create(tables.domain_attributes, domain_attribute,
|
||||||
|
exceptions.DuplicateDomainAttribute)
|
||||||
|
|
||||||
|
def get_domain_masters(self, context, domain_attribute_id):
|
||||||
|
return self._find_domain_masters(
|
||||||
|
context, {'id': domain_attribute_id}, one=True)
|
||||||
|
|
||||||
|
def find_domain_masters(self, context, criterion=None, marker=None,
|
||||||
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
return self._find_domain_masters(context, criterion, marker=marker,
|
||||||
|
limit=limit, sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir)
|
||||||
|
|
||||||
|
def find_domain_master(self, context, criterion):
|
||||||
|
return self._find_domain_master(context, criterion, one=True)
|
||||||
|
|
||||||
|
def update_domain_master(self, context, domain_master):
|
||||||
|
|
||||||
|
domain_attribute = objects.DomainAttribute()
|
||||||
|
domain_attribute.domain_id = domain_master.domain_id
|
||||||
|
domain_attribute.key = 'master'
|
||||||
|
domain_attribute.value = domain_master.to_data()
|
||||||
|
|
||||||
|
return self._update(context, tables.domain_attributes,
|
||||||
|
domain_attribute,
|
||||||
|
exceptions.DuplicateDomainAttribute,
|
||||||
|
exceptions.DomainAttributeNotFound)
|
||||||
|
|
||||||
|
def delete_domain_master(self, context, domain_master_id):
|
||||||
|
domain_attribute = self._find_domain_attributes(
|
||||||
|
context, {'id': domain_master_id}, one=True)
|
||||||
|
deleted_domain_attribute = self._delete(
|
||||||
|
context, tables.domain_attributes, domain_attribute,
|
||||||
|
exceptions.DomainAttributeNotFound)
|
||||||
|
|
||||||
|
return deleted_domain_attribute
|
||||||
|
|
||||||
# RecordSet Methods
|
# RecordSet Methods
|
||||||
def _find_recordsets(self, context, criterion, one=False, marker=None,
|
def _find_recordsets(self, context, criterion, one=False, marker=None,
|
||||||
limit=None, sort_key=None, sort_dir=None):
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
|
@ -518,7 +518,6 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
|||||||
# Create a zone
|
# Create a zone
|
||||||
fixture = self.get_domain_fixture('SECONDARY', 0)
|
fixture = self.get_domain_fixture('SECONDARY', 0)
|
||||||
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
||||||
fixture['attributes'] = [{"key": "master", "value": "10.0.0.10"}]
|
|
||||||
|
|
||||||
# Create a zone
|
# Create a zone
|
||||||
zone = self.create_domain(**fixture)
|
zone = self.create_domain(**fixture)
|
||||||
|
@ -121,13 +121,16 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
|||||||
|
|
||||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||||
|
|
||||||
def _get_secondary_domain(self, values=None, attributes=None):
|
def _get_secondary_domain(self, values=None, attributes=None,
|
||||||
|
masters=None):
|
||||||
attributes = attributes or []
|
attributes = attributes or []
|
||||||
|
masters = masters or [{"host": "10.0.0.1", "port": 53}]
|
||||||
fixture = self.get_domain_fixture("SECONDARY", values=values)
|
fixture = self.get_domain_fixture("SECONDARY", values=values)
|
||||||
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
||||||
|
|
||||||
domain = objects.Domain(**fixture)
|
domain = objects.Domain(**fixture)
|
||||||
domain.attributes = objects.DomainAttributeList()
|
domain.attributes = objects.DomainAttributeList().from_list(attributes)
|
||||||
|
domain.masters = objects.DomainMasterList().from_list(masters)
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
def _get_soa_answer(self, serial):
|
def _get_soa_answer(self, serial):
|
||||||
@ -145,8 +148,6 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
|||||||
|
|
||||||
master = "10.0.0.1"
|
master = "10.0.0.1"
|
||||||
domain = self._get_secondary_domain({"serial": 123})
|
domain = self._get_secondary_domain({"serial": 123})
|
||||||
domain.attributes.append(objects.DomainAttribute(
|
|
||||||
**{"key": "master", "value": master}))
|
|
||||||
|
|
||||||
# expected response is an error code NOERROR. The other fields are
|
# expected response is an error code NOERROR. The other fields are
|
||||||
# id 50048
|
# id 50048
|
||||||
@ -176,7 +177,8 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
|||||||
response = next(self.handler(request)).to_wire()
|
response = next(self.handler(request)).to_wire()
|
||||||
|
|
||||||
self.mock_tg.add_thread.assert_called_with(
|
self.mock_tg.add_thread.assert_called_with(
|
||||||
self.handler.domain_sync, self.context, domain, [master])
|
self.handler.domain_sync, self.context, domain,
|
||||||
|
[domain.masters[0]])
|
||||||
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
self.assertEqual(expected_response, binascii.b2a_hex(response))
|
||||||
|
|
||||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||||
@ -186,8 +188,6 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
|||||||
|
|
||||||
master = "10.0.0.1"
|
master = "10.0.0.1"
|
||||||
domain = self._get_secondary_domain({"serial": 123})
|
domain = self._get_secondary_domain({"serial": 123})
|
||||||
domain.attributes.append(objects.DomainAttribute(
|
|
||||||
**{"key": "master", "value": master}))
|
|
||||||
|
|
||||||
# expected response is an error code NOERROR. The other fields are
|
# expected response is an error code NOERROR. The other fields are
|
||||||
# id 50048
|
# id 50048
|
||||||
@ -226,10 +226,8 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
|||||||
# Have a domain with different master then the one where the notify
|
# Have a domain with different master then the one where the notify
|
||||||
# comes from causing it to be "ignored" as in not transferred and
|
# comes from causing it to be "ignored" as in not transferred and
|
||||||
# logged
|
# logged
|
||||||
master = "10.0.0.1"
|
|
||||||
domain = self._get_secondary_domain({"serial": 123})
|
domain = self._get_secondary_domain({"serial": 123})
|
||||||
domain.attributes.append(objects.DomainAttribute(
|
|
||||||
**{"key": "master", "value": master}))
|
|
||||||
|
|
||||||
# expected response is an error code REFUSED. The other fields are
|
# expected response is an error code REFUSED. The other fields are
|
||||||
# id 50048
|
# id 50048
|
||||||
|
@ -18,7 +18,6 @@ import unittest
|
|||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from testtools import ExpectedException as raises # with raises(...): ...
|
from testtools import ExpectedException as raises # with raises(...): ...
|
||||||
import mock
|
|
||||||
import oslotest.base
|
import oslotest.base
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
@ -42,47 +41,37 @@ class DomainTest(oslotest.base.BaseTestCase):
|
|||||||
|
|
||||||
def test_masters_none(self):
|
def test_masters_none(self):
|
||||||
domain = objects.Domain()
|
domain = objects.Domain()
|
||||||
|
with raises(exceptions.RelationNotLoaded):
|
||||||
self.assertEqual(domain.masters, None)
|
self.assertEqual(domain.masters, None)
|
||||||
|
|
||||||
def test_masters(self):
|
def test_masters(self):
|
||||||
domain = objects.Domain(
|
domain = objects.Domain(
|
||||||
attributes=objects.DomainAttributeList.from_list([
|
masters=objects.DomainMasterList.from_list([
|
||||||
objects.DomainAttribute(key='master', value='1.0.0.0')
|
{'host': '1.0.0.0', 'port': 53}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
self.assertEqual(domain.masters, ['1.0.0.0'])
|
self.assertEqual(
|
||||||
|
domain.masters.to_list(), [{'host': '1.0.0.0', 'port': 53}])
|
||||||
|
|
||||||
def test_masters_2(self):
|
def test_masters_2(self):
|
||||||
domain = objects.Domain(
|
domain = objects.Domain(
|
||||||
attributes=objects.DomainAttributeList.from_list([
|
masters=objects.DomainMasterList.from_list([
|
||||||
objects.DomainAttribute(key='master', value='1.0.0.0'),
|
{'host': '1.0.0.0'},
|
||||||
objects.DomainAttribute(key='master', value='2.0.0.0')
|
{'host': '2.0.0.0'}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
self.assertEqual(len(domain.masters), 2)
|
self.assertEqual(len(domain.masters), 2)
|
||||||
|
|
||||||
def test_set_masters_none(self):
|
|
||||||
domain = create_test_domain()
|
|
||||||
domain.set_masters(('1.0.0.0', '2.0.0.0'))
|
|
||||||
self.assertEqual(len(domain.attributes), 2)
|
|
||||||
|
|
||||||
def test_get_master_by_ip(self):
|
def test_get_master_by_ip(self):
|
||||||
domain = objects.Domain(
|
domain = objects.Domain(
|
||||||
attributes=objects.DomainAttributeList.from_list([
|
masters=objects.DomainMasterList.from_list([
|
||||||
objects.DomainAttribute(key='master', value='1.0.0.0'),
|
{'host': '1.0.0.0', 'port': 53},
|
||||||
objects.DomainAttribute(key='master', value='2.0.0.0')
|
{'host': '2.0.0.0', 'port': 53}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
m = domain.get_master_by_ip('2.0.0.0').to_data()
|
||||||
|
|
||||||
def mock_split(v):
|
self.assertEqual(m, '2.0.0.0:53')
|
||||||
assert ':' not in v
|
|
||||||
return v, ''
|
|
||||||
|
|
||||||
with mock.patch('designate.objects.domain.utils.split_host_port',
|
|
||||||
side_effect=mock_split):
|
|
||||||
m = domain.get_master_by_ip('2.0.0.0')
|
|
||||||
|
|
||||||
self.assertEqual(m, '2.0.0.0')
|
|
||||||
|
|
||||||
@unittest.expectedFailure # bug: domain.masters is not iterable
|
@unittest.expectedFailure # bug: domain.masters is not iterable
|
||||||
def test_get_master_by_ip_none(self):
|
def test_get_master_by_ip_none(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user