Add DomainMaster Object to designate objects
Change-Id: I60dbe23be0d6f2ea6b6f3f9d7bd26ce2e2f7686f
This commit is contained in:
parent
d602a6421c
commit
ea4b054ac6
designate
@ -40,6 +40,18 @@ class RelationNotLoaded(Base):
|
||||
error_code = 500
|
||||
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):
|
||||
error_code = 500
|
||||
@ -318,6 +330,10 @@ class DomainNotFound(NotFound):
|
||||
error_type = 'domain_not_found'
|
||||
|
||||
|
||||
class DomainMasterNotFound(NotFound):
|
||||
error_type = 'domain_master_not_found'
|
||||
|
||||
|
||||
class DomainAttributeNotFound(NotFound):
|
||||
error_type = 'domain_attribute_not_found'
|
||||
|
||||
|
@ -31,7 +31,7 @@ class XFRMixin(object):
|
||||
"""
|
||||
def domain_sync(self, context, domain, servers=None):
|
||||
servers = servers or domain.masters
|
||||
servers = dnsutils.expand_servers(servers)
|
||||
servers = servers.to_list()
|
||||
|
||||
timeout = cfg.CONF["service:mdns"].xfr_timeout
|
||||
try:
|
||||
|
@ -21,6 +21,7 @@ from designate.objects.base import PagedListObjectMixin # noqa
|
||||
from designate.objects.blacklist import Blacklist, BlacklistList # noqa
|
||||
from designate.objects.domain import Domain, DomainList # 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.pool_manager_status import PoolManagerStatus, PoolManagerStatusList # noqa
|
||||
from designate.objects.pool import Pool, PoolList # noqa
|
||||
|
@ -16,6 +16,7 @@ from designate.objects.adapters.base import DesignateAdapter # noqa
|
||||
# API v2
|
||||
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_master import DomainMasterAPIv2Adapter, DomainMasterListAPIv2Adapter # 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.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 import objects
|
||||
from designate import exceptions
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -64,26 +63,16 @@ class DomainAPIv2Adapter(base.APIv2Adapter):
|
||||
|
||||
@classmethod
|
||||
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 isinstance(values['masters'], list):
|
||||
object.set_masters(values.get('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())
|
||||
|
||||
object.masters = objects.adapters.DesignateAdapter.parse(
|
||||
cls.ADAPTER_FORMAT,
|
||||
values['masters'],
|
||||
objects.DomainMasterList(),
|
||||
*args, **kwargs)
|
||||
|
||||
del values['masters']
|
||||
|
||||
return super(DomainAPIv2Adapter, cls)._parse_object(
|
||||
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):
|
||||
if name in self.FIELDS and self.FIELDS[name].get('relation', False):
|
||||
if not self.obj_attr_is_set(name):
|
||||
raise exceptions.RelationNotLoaded
|
||||
raise exceptions.RelationNotLoaded(object=self, relation=name)
|
||||
|
||||
@classmethod
|
||||
def obj_cls_from_name(cls, name):
|
||||
@ -310,9 +310,21 @@ class DesignateObject(object):
|
||||
ValidationErrorList = self.obj_cls_from_name('ValidationErrorList')
|
||||
ValidationError = self.obj_cls_from_name('ValidationError')
|
||||
|
||||
values = self.to_dict()
|
||||
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", {
|
||||
'name': self.obj_name(),
|
||||
'values': values,
|
||||
|
@ -17,8 +17,6 @@ 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.domain_attribute import DomainAttribute
|
||||
from designate.objects.domain_attribute import DomainAttributeList
|
||||
|
||||
|
||||
class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
||||
@ -145,6 +143,10 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
||||
'relation': True,
|
||||
'relation_cls': 'DomainAttributeList'
|
||||
},
|
||||
'masters': {
|
||||
'relation': True,
|
||||
'relation_cls': 'DomainMasterList'
|
||||
},
|
||||
'type': {
|
||||
'schema': {
|
||||
'type': 'string',
|
||||
@ -165,47 +167,43 @@ class Domain(base.DictObjectMixin, base.SoftDeleteObjectMixin,
|
||||
'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):
|
||||
"""
|
||||
Utility to get the master by it's ip for this domain.
|
||||
"""
|
||||
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:
|
||||
return srv
|
||||
return False
|
||||
|
||||
def validate(self):
|
||||
if self.type == 'SECONDARY' and self.masters is None:
|
||||
try:
|
||||
if self.type == 'SECONDARY' and self.masters is None:
|
||||
errors = ValidationErrorList()
|
||||
e = ValidationError()
|
||||
e.path = ['type']
|
||||
e.validator = 'required'
|
||||
e.validator_value = ['masters']
|
||||
e.message = "'masters' is a required property"
|
||||
errors.append(e)
|
||||
raise exceptions.InvalidObject(
|
||||
"Provided object does not match "
|
||||
"schema", errors=errors, object=self)
|
||||
|
||||
super(Domain, self).validate()
|
||||
except exceptions.RelationNotLoaded as ex:
|
||||
errors = ValidationErrorList()
|
||||
e = ValidationError()
|
||||
e.path = ['type']
|
||||
e.validator = 'required'
|
||||
e.validator_value = ['masters']
|
||||
e.message = "'masters' is a required property"
|
||||
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)
|
||||
|
||||
super(Domain, self).validate()
|
||||
|
||||
|
||||
class DomainList(base.ListObjectMixin, base.DesignateObject,
|
||||
base.PagedListObjectMixin):
|
||||
|
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
|
||||
|
||||
|
||||
@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")
|
||||
@draft4_format_checker.checks("domainname")
|
||||
def is_domainname(instance):
|
||||
|
@ -232,9 +232,17 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||
|
||||
def _load_relations(domain):
|
||||
if domain.type == 'SECONDARY':
|
||||
domain.attributes = self._find_domain_attributes(
|
||||
domain.masters = self._find_domain_masters(
|
||||
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:
|
||||
_load_relations(domains)
|
||||
@ -252,7 +260,7 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||
# Don't handle recordsets for now
|
||||
domain = self._create(
|
||||
tables.domains, domain, exceptions.DuplicateDomain,
|
||||
['attributes', 'recordsets'],
|
||||
['attributes', 'recordsets', 'masters'],
|
||||
extra_values=extra_values)
|
||||
|
||||
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)
|
||||
else:
|
||||
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
|
||||
|
||||
@ -288,7 +301,7 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||
updated_domain = self._update(
|
||||
context, tables.domains, domain, exceptions.DuplicateDomain,
|
||||
exceptions.DomainNotFound,
|
||||
['attributes', 'recordsets'])
|
||||
['attributes', 'recordsets', 'masters'])
|
||||
|
||||
if domain.obj_attr_is_set('attributes'):
|
||||
# Gather the Attribute ID's we have
|
||||
@ -330,6 +343,46 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||
attr.domain_id = domain.id
|
||||
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'):
|
||||
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
|
||||
|
||||
# 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
|
||||
def _find_recordsets(self, context, criterion, one=False, marker=None,
|
||||
limit=None, sort_key=None, sort_dir=None):
|
||||
|
@ -518,7 +518,6 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
||||
# Create a zone
|
||||
fixture = self.get_domain_fixture('SECONDARY', 0)
|
||||
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
||||
fixture['attributes'] = [{"key": "master", "value": "10.0.0.10"}]
|
||||
|
||||
# Create a zone
|
||||
zone = self.create_domain(**fixture)
|
||||
|
@ -121,13 +121,16 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
|
||||
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 []
|
||||
masters = masters or [{"host": "10.0.0.1", "port": 53}]
|
||||
fixture = self.get_domain_fixture("SECONDARY", values=values)
|
||||
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
|
||||
|
||||
domain = objects.Domain(**fixture)
|
||||
domain.attributes = objects.DomainAttributeList()
|
||||
domain.attributes = objects.DomainAttributeList().from_list(attributes)
|
||||
domain.masters = objects.DomainMasterList().from_list(masters)
|
||||
return domain
|
||||
|
||||
def _get_soa_answer(self, serial):
|
||||
@ -145,8 +148,6 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
|
||||
master = "10.0.0.1"
|
||||
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
|
||||
# id 50048
|
||||
@ -176,7 +177,8 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
response = next(self.handler(request)).to_wire()
|
||||
|
||||
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))
|
||||
|
||||
@mock.patch.object(dns.resolver.Resolver, 'query')
|
||||
@ -186,8 +188,6 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
|
||||
master = "10.0.0.1"
|
||||
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
|
||||
# id 50048
|
||||
@ -226,10 +226,8 @@ class MdnsRequestHandlerTest(MdnsTestCase):
|
||||
# Have a domain with different master then the one where the notify
|
||||
# comes from causing it to be "ignored" as in not transferred and
|
||||
# logged
|
||||
master = "10.0.0.1"
|
||||
|
||||
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
|
||||
# id 50048
|
||||
|
@ -18,7 +18,6 @@ import unittest
|
||||
|
||||
from oslo_log import log as logging
|
||||
from testtools import ExpectedException as raises # with raises(...): ...
|
||||
import mock
|
||||
import oslotest.base
|
||||
|
||||
from designate import exceptions
|
||||
@ -42,47 +41,37 @@ class DomainTest(oslotest.base.BaseTestCase):
|
||||
|
||||
def test_masters_none(self):
|
||||
domain = objects.Domain()
|
||||
self.assertEqual(domain.masters, None)
|
||||
with raises(exceptions.RelationNotLoaded):
|
||||
self.assertEqual(domain.masters, None)
|
||||
|
||||
def test_masters(self):
|
||||
domain = objects.Domain(
|
||||
attributes=objects.DomainAttributeList.from_list([
|
||||
objects.DomainAttribute(key='master', value='1.0.0.0')
|
||||
masters=objects.DomainMasterList.from_list([
|
||||
{'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):
|
||||
domain = objects.Domain(
|
||||
attributes=objects.DomainAttributeList.from_list([
|
||||
objects.DomainAttribute(key='master', value='1.0.0.0'),
|
||||
objects.DomainAttribute(key='master', value='2.0.0.0')
|
||||
masters=objects.DomainMasterList.from_list([
|
||||
{'host': '1.0.0.0'},
|
||||
{'host': '2.0.0.0'}
|
||||
])
|
||||
)
|
||||
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):
|
||||
domain = objects.Domain(
|
||||
attributes=objects.DomainAttributeList.from_list([
|
||||
objects.DomainAttribute(key='master', value='1.0.0.0'),
|
||||
objects.DomainAttribute(key='master', value='2.0.0.0')
|
||||
masters=objects.DomainMasterList.from_list([
|
||||
{'host': '1.0.0.0', 'port': 53},
|
||||
{'host': '2.0.0.0', 'port': 53}
|
||||
])
|
||||
)
|
||||
m = domain.get_master_by_ip('2.0.0.0').to_data()
|
||||
|
||||
def mock_split(v):
|
||||
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')
|
||||
self.assertEqual(m, '2.0.0.0:53')
|
||||
|
||||
@unittest.expectedFailure # bug: domain.masters is not iterable
|
||||
def test_get_master_by_ip_none(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user