diff --git a/contrib/devstack/extras.d/70-designate.sh b/contrib/devstack/extras.d/85-designate.sh similarity index 93% rename from contrib/devstack/extras.d/70-designate.sh rename to contrib/devstack/extras.d/85-designate.sh index a8aaf781..b567b81e 100644 --- a/contrib/devstack/extras.d/70-designate.sh +++ b/contrib/devstack/extras.d/85-designate.sh @@ -31,6 +31,9 @@ if is_service_enabled designate; then echo_summary "Initializing Designate" init_designate + echo "Configuring Tempest options for Designate" + configure_designate_tempest + echo_summary "Starting Designate" start_designate diff --git a/contrib/devstack/lib/designate b/contrib/devstack/lib/designate index 10e06e66..76d5a832 100644 --- a/contrib/devstack/lib/designate +++ b/contrib/devstack/lib/designate @@ -206,6 +206,30 @@ function configure_designate { configure_designate_backend } +# Configure the needed tempest options +function configure_designate_tempest() { + if is_service_enabled tempest; then + nameservers=$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_DNS + case $DESIGNATE_BACKEND_DRIVER in + bind9|powerdns) + nameservers="$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_DNS" + ;; + akamai) + nameservers="$DESIGNATE_AKAMAI_NAMESERVERS" + ;; + dynect) + nameservers="$DESIGNATE_DYNECT_NAMESERVERS" + ;; + esac + + if [ ! -z "$DESIGNATE_NAMESERVERS" ]; then + nameservers=$DESIGNATE_NAMESERVERS + fi + + iniset $TEMPEST_CONFIG designate nameservers $nameservers + fi +} + # create_designate_accounts - Set up common required designate accounts # Tenant User Roles diff --git a/functionaltests/api/v2/test_recordset.py b/functionaltests/api/v2/test_recordset.py index d335d739..e39abd64 100644 --- a/functionaltests/api/v2/test_recordset.py +++ b/functionaltests/api/v2/test_recordset.py @@ -13,10 +13,11 @@ 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.rdatatype from tempest_lib import exceptions from functionaltests.common import datagen +from functionaltests.common import dnsclient from functionaltests.common import utils from functionaltests.api.v2.base import DesignateV2Test from functionaltests.api.v2.clients.recordset_client import RecordsetClient @@ -38,6 +39,29 @@ class RecordsetTest(DesignateV2Test): .list_recordsets(self.zone.id) self.assertEqual(resp.status, 200) + def assert_dns(self, model): + results = dnsclient.query_servers(model.name, model.type) + + model_data = model.to_dict() + if model.type == 'AAAA': + model_data['records'] = utils.shorten_ipv6_addrs( + model_data['records']) + + for answer in results: + data = { + "type": dns.rdatatype.to_text(answer.rdtype), + "name": str(answer.canonical_name), + # DNSPython wraps TXT values in "" so '+all v=foo' becomes + # '"+all" "+v=foo"' + "records": [i.to_text().replace('"', '') + for i in answer.rrset.items] + } + + if answer.rrset.ttl != 0: + data['ttl'] = answer.rrset.ttl + + self.assertEqual(model_data, data) + @utils.parameterized({ 'A': dict( make_recordset=lambda z: datagen.random_a_recordset(z.name)), @@ -72,6 +96,8 @@ class RecordsetTest(DesignateV2Test): RecordsetClient.as_user('default').wait_for_recordset( self.zone.id, recordset_id) + self.assert_dns(post_model) + put_model = make_recordset(self.zone) del put_model.name # don't try to update the name resp, put_resp_model = RecordsetClient.as_user('default') \ @@ -85,6 +111,9 @@ class RecordsetTest(DesignateV2Test): RecordsetClient.as_user('default').wait_for_recordset( self.zone.id, recordset_id) + put_model.name = post_model.name + self.assert_dns(put_model) + resp, delete_resp_model = RecordsetClient.as_user('default') \ .delete_recordset(self.zone.id, recordset_id) self.assertEqual(resp.status, 202, "on delete response") diff --git a/functionaltests/common/config.py b/functionaltests/common/config.py index dac9ca3d..e76ac6d5 100644 --- a/functionaltests/common/config.py +++ b/functionaltests/common/config.py @@ -60,6 +60,11 @@ cfg.CONF.register_opts([ ], group='noauth') +cfg.CONF.register_opts([ + cfg.ListOpt('nameservers', default=["127.0.0.1:53"]) +], group="designate") + + def find_config_file(): return os.environ.get( 'TEMPEST_CONFIG', '/opt/stack/tempest/etc/tempest.conf') diff --git a/functionaltests/common/dnsclient.py b/functionaltests/common/dnsclient.py new file mode 100644 index 00000000..9b283955 --- /dev/null +++ b/functionaltests/common/dnsclient.py @@ -0,0 +1,48 @@ +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# Author: Endre Karlson +# +# 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 os + +import dns.resolver +from oslo_config import cfg + +from functionaltests.common import utils + + +def query(name, type_, server="127.0.0.1", port=53, timeout=3): + resolver = dns.resolver.Resolver() + resolver.nameservers = [server] + resolver.port = int(port) + resolver.timeout = timeout + + try: + return resolver.query(name, type_) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): + return False + + +def query_servers(name, type_, servers=None, timeout=3): + servers = servers or os.environ.get("DESIGNATE_SERVERS", + cfg.CONF.designate.nameservers) + + results = [] + for srv in servers: + server, port = srv.split(":") + port = port or 53 + result = utils.wait_for_condition( + lambda: query(name, type_, server, port)) + results.append(result) + + return results diff --git a/functionaltests/common/utils.py b/functionaltests/common/utils.py index 8fbb8fca..c02c7202 100644 --- a/functionaltests/common/utils.py +++ b/functionaltests/common/utils.py @@ -18,6 +18,8 @@ import functools import time import types +import netaddr + def def_method(f, *args, **kwargs): @functools.wraps(f) @@ -88,8 +90,9 @@ def parameterized(data): def wait_for_condition(condition, interval=1, timeout=40): end_time = time.time() + timeout while time.time() < end_time: - if condition(): - return + result = condition() + if result: + return result time.sleep(interval) raise Exception("Timed out after {0} seconds".format(timeout)) @@ -110,3 +113,12 @@ def memoized(func): cache[args] = value return value return wrapper + + +def shorten_ipv6_addrs(addrs): + """Shorten ipv6 addresses""" + new_addrs = [] + for a in addrs: + an = netaddr.IPAddress(a, version=6) + new_addrs.append(an.format(netaddr.ipv6_compact)) + return new_addrs