Add a client for querying nameservers
Change-Id: I2d2eedcd162e7aeac4f3c9c92342bff448b4a5f5
This commit is contained in:
parent
42f2ce09a2
commit
cf98c2691d
@ -30,6 +30,8 @@ from designate_tempest_plugin.services.dns.v2.json.pool_client import \
|
|||||||
PoolClient
|
PoolClient
|
||||||
from designate_tempest_plugin.services.dns.v2.json.tld_client import \
|
from designate_tempest_plugin.services.dns.v2.json.tld_client import \
|
||||||
TldClient
|
TldClient
|
||||||
|
from designate_tempest_plugin.services.dns.query.query_client import \
|
||||||
|
QueryClient
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
|
||||||
@ -60,3 +62,9 @@ class Manager(clients.Manager):
|
|||||||
**params)
|
**params)
|
||||||
self.tld_client = TldClient(self.auth_provider,
|
self.tld_client = TldClient(self.auth_provider,
|
||||||
**params)
|
**params)
|
||||||
|
self.query_client = QueryClient(
|
||||||
|
nameservers=CONF.dns.nameservers,
|
||||||
|
query_timeout=CONF.dns.query_timeout,
|
||||||
|
build_interval=CONF.dns.build_interval,
|
||||||
|
build_timeout=CONF.dns.build_timeout,
|
||||||
|
)
|
||||||
|
@ -147,4 +147,49 @@ def wait_for_recordset_status(client, recordset_id, status):
|
|||||||
if caller:
|
if caller:
|
||||||
message = '(%s) %s' % (caller, message)
|
message = '(%s) %s' % (caller, message)
|
||||||
|
|
||||||
raise lib_exc.TimeoutException(message)
|
raise lib_exc.TimeoutException(message)
|
||||||
|
|
||||||
|
|
||||||
|
def wait_for_query(client, name, rdatatype, found=True):
|
||||||
|
"""Query nameservers until the record of the given name and type is found.
|
||||||
|
|
||||||
|
:param client: A QueryClient
|
||||||
|
:param name: The record name for which to query
|
||||||
|
:param rdatatype: The record type for which to query
|
||||||
|
:param found: If True, wait until the record is found. Else, wait until the
|
||||||
|
record disappears.
|
||||||
|
"""
|
||||||
|
state = "found" if found else "removed"
|
||||||
|
LOG.info("Waiting for record %s of type %s to be %s on nameservers %s",
|
||||||
|
name, rdatatype, state, client.nameservers)
|
||||||
|
start = int(time.time())
|
||||||
|
|
||||||
|
while True:
|
||||||
|
time.sleep(client.build_interval)
|
||||||
|
|
||||||
|
responses = client.query(name, rdatatype)
|
||||||
|
if found:
|
||||||
|
all_answers_good = all(r.answer for r in responses)
|
||||||
|
else:
|
||||||
|
all_answers_good = all(not r.answer for r in responses)
|
||||||
|
|
||||||
|
if not client.nameservers or all_answers_good:
|
||||||
|
LOG.info("Record %s of type %s was successfully %s on nameservers "
|
||||||
|
"%s", name, rdatatype, state, client.nameservers)
|
||||||
|
return
|
||||||
|
|
||||||
|
if int(time.time()) - start >= client.build_timeout:
|
||||||
|
message = ('Record %(name)s of type %(rdatatype)s not %(state)s '
|
||||||
|
'on nameservers %(nameservers)s within the required '
|
||||||
|
'time (%(timeout)s s)' %
|
||||||
|
{'name': name,
|
||||||
|
'rdatatype': rdatatype,
|
||||||
|
'state': state,
|
||||||
|
'nameservers': client.nameservers,
|
||||||
|
'timeout': client.build_timeout})
|
||||||
|
|
||||||
|
caller = misc_utils.find_test_caller()
|
||||||
|
if caller:
|
||||||
|
message = "(%s) %s" % (caller, message)
|
||||||
|
|
||||||
|
raise lib_exc.TimeoutException(message)
|
||||||
|
@ -34,5 +34,11 @@ DnsGroup = [
|
|||||||
cfg.IntOpt('min_ttl',
|
cfg.IntOpt('min_ttl',
|
||||||
default=1,
|
default=1,
|
||||||
help="The minimum value to respect when generating ttls"),
|
help="The minimum value to respect when generating ttls"),
|
||||||
|
cfg.ListOpt('nameservers',
|
||||||
|
default=[],
|
||||||
|
help="The nameservers to check for change going live"),
|
||||||
|
cfg.IntOpt('query_timeout',
|
||||||
|
default=1,
|
||||||
|
help="The timeout on a single dns query to a nameserver"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
82
designate_tempest_plugin/services/dns/query/query_client.py
Normal file
82
designate_tempest_plugin/services/dns/query/query_client.py
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Copyright 2016 Rackspace
|
||||||
|
#
|
||||||
|
# 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 dns
|
||||||
|
import dns.exception
|
||||||
|
import dns.query
|
||||||
|
from tempest import config
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class QueryClient(object):
|
||||||
|
"""A client which queries multiple nameservers"""
|
||||||
|
|
||||||
|
def __init__(self, nameservers=None, query_timeout=None,
|
||||||
|
build_interval=None, build_timeout=None):
|
||||||
|
self.nameservers = nameservers or []
|
||||||
|
self.query_timeout = query_timeout or CONF.dns.query_timeout
|
||||||
|
self.build_interval = build_interval or CONF.dns.build_interval
|
||||||
|
self.build_timeout = build_timeout or CONF.dns.build_timeout
|
||||||
|
|
||||||
|
self.clients = [SingleQueryClient(ns, query_timeout=query_timeout)
|
||||||
|
for ns in nameservers]
|
||||||
|
|
||||||
|
def query(self, zone_name, rdatatype):
|
||||||
|
return [c.query(zone_name, rdatatype) for c in self.clients]
|
||||||
|
|
||||||
|
|
||||||
|
class SingleQueryClient(object):
|
||||||
|
"""A client which queries a single nameserver"""
|
||||||
|
|
||||||
|
def __init__(self, nameserver, query_timeout):
|
||||||
|
self.nameserver = Nameserver(nameserver)
|
||||||
|
self.query_timeout = query_timeout
|
||||||
|
|
||||||
|
def query(self, name, rdatatype):
|
||||||
|
return self._dig(name, rdatatype, self.nameserver.ip,
|
||||||
|
self.nameserver.port, timeout=self.query_timeout)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _prepare_query(cls, zone_name, rdatatype):
|
||||||
|
# support plain strings: "SOA", "A"
|
||||||
|
if isinstance(rdatatype, basestring):
|
||||||
|
rdatatype = dns.rdatatype.from_text(rdatatype)
|
||||||
|
dns_message = dns.message.make_query(zone_name, rdatatype)
|
||||||
|
dns_message.set_opcode(dns.opcode.QUERY)
|
||||||
|
return dns_message
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _dig(cls, name, rdatatype, ip, port, timeout):
|
||||||
|
query = cls._prepare_query(name, rdatatype)
|
||||||
|
return dns.query.udp(query, ip, port=port, timeout=timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class Nameserver(object):
|
||||||
|
|
||||||
|
def __init__(self, ip, port=53):
|
||||||
|
self.ip = ip
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s:%s" % (self.ip, self.port)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_str(self, nameserver):
|
||||||
|
if ':' in nameserver:
|
||||||
|
ip, port = nameserver.split(':')
|
||||||
|
return Nameserver(ip, int(port))
|
||||||
|
return Nameserver(nameserver)
|
@ -27,6 +27,7 @@ class ZonesTest(base.BaseDnsTest):
|
|||||||
super(ZonesTest, cls).setup_clients()
|
super(ZonesTest, cls).setup_clients()
|
||||||
|
|
||||||
cls.client = cls.os.zones_client
|
cls.client = cls.os.zones_client
|
||||||
|
cls.query_client = cls.os.query_client
|
||||||
|
|
||||||
@test.attr(type='slow')
|
@test.attr(type='slow')
|
||||||
@test.idempotent_id('d0648f53-4114-45bd-8792-462a82f69d32')
|
@test.idempotent_id('d0648f53-4114-45bd-8792-462a82f69d32')
|
||||||
@ -80,3 +81,31 @@ class ZonesTest(base.BaseDnsTest):
|
|||||||
self.assertEqual('PENDING', zone['status'])
|
self.assertEqual('PENDING', zone['status'])
|
||||||
|
|
||||||
waiters.wait_for_zone_404(self.client, zone['id'])
|
waiters.wait_for_zone_404(self.client, zone['id'])
|
||||||
|
|
||||||
|
@test.attr(type='slow')
|
||||||
|
@test.idempotent_id('ad8d1f5b-da66-46a0-bbee-14dc84a5d791')
|
||||||
|
def test_zone_create_propagates_to_nameservers(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.client.create_zone()
|
||||||
|
self.addCleanup(self.client.delete_zone, zone['id'])
|
||||||
|
|
||||||
|
waiters.wait_for_zone_status(self.client, zone['id'], "ACTIVE")
|
||||||
|
waiters.wait_for_query(self.query_client, zone['name'], "SOA")
|
||||||
|
|
||||||
|
@test.attr(type='slow')
|
||||||
|
@test.idempotent_id('d13d3095-c78f-4aae-8fe3-a74ccc335c84')
|
||||||
|
def test_zone_delete_propagates_to_nameservers(self):
|
||||||
|
LOG.info('Create a zone')
|
||||||
|
_, zone = self.client.create_zone()
|
||||||
|
self.addCleanup(self.client.delete_zone, zone['id'],
|
||||||
|
ignore_errors=lib_exc.NotFound)
|
||||||
|
|
||||||
|
waiters.wait_for_zone_status(self.client, zone['id'], "ACTIVE")
|
||||||
|
waiters.wait_for_query(self.query_client, zone['name'], "SOA")
|
||||||
|
|
||||||
|
LOG.info('Delete the zone')
|
||||||
|
self.client.delete_zone(zone['id'])
|
||||||
|
|
||||||
|
waiters.wait_for_zone_404(self.client, zone['id'])
|
||||||
|
waiters.wait_for_query(self.query_client, zone['name'], "SOA",
|
||||||
|
found=False)
|
||||||
|
@ -2,4 +2,6 @@
|
|||||||
# of appearance. Changing the order has an impact on the overall integration
|
# of appearance. Changing the order has an impact on the overall integration
|
||||||
# process, which may cause wedges in the gate later.
|
# process, which may cause wedges in the gate later.
|
||||||
|
|
||||||
|
dnspython>=1.12.0,!=1.13.0;python_version<'3.0' # http://www.dnspython.org/LICENSE
|
||||||
|
dnspython3>=1.12.0;python_version>='3.0' # http://www.dnspython.org/LICENSE
|
||||||
tempest>=11.0.0 # Apache-2.0
|
tempest>=11.0.0 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user