Add a client for querying nameservers

Change-Id: I2d2eedcd162e7aeac4f3c9c92342bff448b4a5f5
This commit is contained in:
Paul Glass 2016-05-13 19:34:37 +00:00
parent 42f2ce09a2
commit cf98c2691d
7 changed files with 173 additions and 1 deletions

View File

@ -30,6 +30,8 @@ from designate_tempest_plugin.services.dns.v2.json.pool_client import \
PoolClient
from designate_tempest_plugin.services.dns.v2.json.tld_client import \
TldClient
from designate_tempest_plugin.services.dns.query.query_client import \
QueryClient
CONF = config.CONF
@ -60,3 +62,9 @@ class Manager(clients.Manager):
**params)
self.tld_client = TldClient(self.auth_provider,
**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,
)

View File

@ -147,4 +147,49 @@ def wait_for_recordset_status(client, recordset_id, status):
if caller:
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)

View File

@ -34,5 +34,11 @@ DnsGroup = [
cfg.IntOpt('min_ttl',
default=1,
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"),
]

View 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)

View File

@ -27,6 +27,7 @@ class ZonesTest(base.BaseDnsTest):
super(ZonesTest, cls).setup_clients()
cls.client = cls.os.zones_client
cls.query_client = cls.os.query_client
@test.attr(type='slow')
@test.idempotent_id('d0648f53-4114-45bd-8792-462a82f69d32')
@ -80,3 +81,31 @@ class ZonesTest(base.BaseDnsTest):
self.assertEqual('PENDING', zone['status'])
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)

View File

@ -2,4 +2,6 @@
# of appearance. Changing the order has an impact on the overall integration
# 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