diff --git a/bin/tests/system/isctest/__init__.py b/bin/tests/system/isctest/__init__.py index 047ca7762a..e0014adbba 100644 --- a/bin/tests/system/isctest/__init__.py +++ b/bin/tests/system/isctest/__init__.py @@ -13,6 +13,7 @@ from . import check from . import instance from . import query from . import rndc +from . import run from . import log # isctest.mark module is intentionally NOT imported, because it relies on diff --git a/bin/tests/system/isctest/check.py b/bin/tests/system/isctest/check.py index 1fabad7477..054f0f8b76 100644 --- a/bin/tests/system/isctest/check.py +++ b/bin/tests/system/isctest/check.py @@ -32,3 +32,10 @@ def rcode(message: dns.message.Message, expected_rcode) -> None: def noerror(message: dns.message.Message) -> None: rcode(message, dns_rcode.NOERROR) + + +def rrsets_equal(first_rrset: dns.rrset.RRset, second_rrset: dns.rrset.RRset) -> None: + for rr in first_rrset: + assert rr in second_rrset + for rr in second_rrset: + assert rr in first_rrset diff --git a/bin/tests/system/isctest/log/watchlog.py b/bin/tests/system/isctest/log/watchlog.py index 1931f13aff..b17cd0597f 100644 --- a/bin/tests/system/isctest/log/watchlog.py +++ b/bin/tests/system/isctest/log/watchlog.py @@ -9,7 +9,7 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. -from typing import Iterator, Optional, TextIO, Dict, Any +from typing import Iterator, Optional, TextIO, Dict, Any, Union, Pattern import abc import os @@ -100,8 +100,9 @@ class WatchLog(abc.ABC): Block execution until a line containing the provided `string` appears in the log file. Return `None` once the line is found or raise a `TimeoutError` after `timeout` seconds (default: 10) if `string` does - not appear in the log file. (Catching this exception is discouraged as - it indicates that the test code did not behave as expected.) + not appear in the log file (strings and regular expressions are + supported). (Catching this exception is discouraged as it indicates + that the test code did not behave as expected.) Recommended use: @@ -156,7 +157,9 @@ class WatchLog(abc.ABC): """ return self._wait_for({string: None}, timeout) - def wait_for_lines(self, strings: Dict[str, Any], timeout: int = 10) -> None: + def wait_for_lines( + self, strings: Dict[Union[str, Pattern], Any], timeout: int = 10 + ) -> None: """ Block execution until a line of interest appears in the log file. This function is a "multi-match" variant of `wait_for_line()` which is @@ -165,10 +168,11 @@ class WatchLog(abc.ABC): `strings` is a `dict` associating each string to look for with the value this function should return when that string is found in the log - file. If none of the `strings` being looked for appear in the log file - after `timeout` seconds, a `TimeoutError` is raised. - (Catching this exception is discouraged as it indicates that the test - code did not behave as expected.) + file (strings and regular expressions are supported). If none of the + `strings` being looked for appear in the log file after `timeout` + seconds, a `TimeoutError` is raised. (Catching this exception is + discouraged as it indicates that the test code did not behave as + expected.) Since `strings` is a `dict` and preserves key order (in CPython 3.6 as implementation detail, since 3.7 by language design), each line is @@ -212,7 +216,7 @@ class WatchLog(abc.ABC): """ return self._wait_for(strings, timeout) - def _wait_for(self, strings: Dict[str, Any], timeout: int) -> Any: + def _wait_for(self, patterns: Dict[Union[str, Pattern], Any], timeout: int) -> Any: """ Block execution until one of the `strings` being looked for appears in the log file. Raise a `TimeoutError` if none of the `strings` being @@ -234,13 +238,15 @@ class WatchLog(abc.ABC): else: line = leftover + line leftover = "" - for string, retval in strings.items(): - if string in line: + for string, retval in patterns.items(): + if isinstance(string, Pattern) and string.search(line): + return retval + if isinstance(string, str) and string in line: return retval time.sleep(0.1) raise TimeoutError( "Timeout reached watching {} for {}".format( - self._path, list(strings.keys()) + self._path, list(patterns.keys()) ) ) diff --git a/bin/tests/system/xferquota/tests_sh_xferquota.py b/bin/tests/system/isctest/run.py similarity index 55% rename from bin/tests/system/xferquota/tests_sh_xferquota.py rename to bin/tests/system/isctest/run.py index 762e182585..2efb39d137 100644 --- a/bin/tests/system/xferquota/tests_sh_xferquota.py +++ b/bin/tests/system/isctest/run.py @@ -9,6 +9,15 @@ # See the COPYRIGHT file distributed with this work for additional # information regarding copyright ownership. +import time -def test_xferquota(run_tests_sh): - run_tests_sh() + +def retry_with_timeout(func, timeout, delay=1, msg=None): + start_time = time.time() + while time.time() < start_time + timeout: + if func(): + return + time.sleep(delay) + if msg is None: + msg = f"{func.__module__}.{func.__qualname__} timed out after {timeout} s" + assert False, msg diff --git a/bin/tests/system/xferquota/setup.pl b/bin/tests/system/xferquota/setup.pl deleted file mode 100644 index ab5450cba8..0000000000 --- a/bin/tests/system/xferquota/setup.pl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/perl - -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -# -# Set up test data for zone transfer quota tests. -# -use FileHandle; - -my $priconf = new FileHandle("ns1/zones.conf", "w") or die; -my $secconf = new FileHandle("ns2/zones.conf", "w") or die; - -for ($z = 0; $z < 300; $z++) { - my $zn = sprintf("zone%06d.example", $z); - print $priconf "zone \"$zn\" { type primary; file \"$zn.db\"; };\n"; - print $secconf "zone \"$zn\" { type secondary; file \"$zn.bk\"; masterfile-format text; primaries { 10.53.0.1; }; };\n"; - my $fn = "ns1/$zn.db"; - my $f = new FileHandle($fn, "w") or die "open: $fn: $!"; - print $f "\$TTL 300 -\@ IN SOA ns1 . 1 300 120 3600 86400 - NS ns1 - NS ns2 -ns1 A 10.53.0.1 -ns2 A 10.53.0.2 - MX 10 mail1.isp.example. - MX 20 mail2.isp.example. -www A 10.0.0.1 -xyzzy A 10.0.0.2 -"; - $f->close; -} diff --git a/bin/tests/system/xferquota/setup.py b/bin/tests/system/xferquota/setup.py new file mode 100644 index 0000000000..cbbb0529da --- /dev/null +++ b/bin/tests/system/xferquota/setup.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 + +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +# +# Set up test data for zone transfer quota tests. +# + +zones = 300 + +for z in range(zones): + zn = f"zone{z:06d}.example" + with open(f"ns1/{zn}.db", "w", encoding="utf-8") as f: + f.write( + """$TTL 300 +@ IN SOA ns1 . 1 300 120 3600 86400 + NS ns1 + NS ns2 +ns1 A 10.53.0.1 +ns2 A 10.53.0.2 + MX 10 mail1.isp.example. + MX 20 mail2.isp.example. +www A 10.0.0.1 +xyzzy A 10.0.0.2 +""" + ) + +with open("ns1/zones.conf", "w", encoding="utf-8") as priconf, open( + "ns2/zones.conf", "w", encoding="utf-8" +) as secconf: + for z in range(zones): + zn = f"zone{z:06d}.example" + priconf.write(f'zone "{zn}" {{ type primary; file "{zn}.db"; }};\n') + secconf.write( + f'zone "{zn}" {{ type secondary; file "{zn}.bk"; ' + f"masterfile-format text; primaries {{ 10.53.0.1; }}; }};\n" + ) diff --git a/bin/tests/system/xferquota/setup.sh b/bin/tests/system/xferquota/setup.sh index 55e34b94f2..b352403b8b 100644 --- a/bin/tests/system/xferquota/setup.sh +++ b/bin/tests/system/xferquota/setup.sh @@ -17,7 +17,7 @@ . ../conf.sh -$PERL setup.pl +$PYTHON setup.py cp -f ns1/changing1.db ns1/changing.db diff --git a/bin/tests/system/xferquota/tests.sh b/bin/tests/system/xferquota/tests.sh deleted file mode 100755 index 4f4eed1195..0000000000 --- a/bin/tests/system/xferquota/tests.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -set -e - -. ../conf.sh - -DIGOPTS="+tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p ${PORT}" -RNDCCMD="$RNDC -c ../_common/rndc.conf -p ${CONTROLPORT} -s" - -# -# Perform tests -# - -count=0 -ticks=0 -while [ $count != 300 ]; do - if [ $ticks = 1 ]; then - echo_i "Changing test zone..." - cp -f ns1/changing2.db ns1/changing.db - kill -HUP $(cat ns1/named.pid) - fi - sleep 1 - ticks=$((ticks + 1)) - seconds=$((ticks * 1)) - if [ $ticks = 360 ]; then - echo_i "Took too long to load zones" - exit 1 - fi - count=$(cat ns2/zone*.bk | grep xyzzy | wc -l) - echo_i "Have $count zones up in $seconds seconds" -done - -status=0 - -$DIG $DIGOPTS zone000099.example. @10.53.0.1 axfr >dig.out.ns1 || status=1 - -$DIG $DIGOPTS zone000099.example. @10.53.0.2 axfr >dig.out.ns2 || status=1 - -digcomp dig.out.ns1 dig.out.ns2 || status=1 - -sleep 15 - -$DIG $DIGOPTS a.changing. @10.53.0.1 a >dig.out.ns1 || status=1 - -$DIG $DIGOPTS a.changing. @10.53.0.2 a >dig.out.ns2 || status=1 - -digcomp dig.out.ns1 dig.out.ns2 || status=1 - -echo_i "exit status: $status" -[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/xferquota/tests_xferquota.py b/bin/tests/system/xferquota/tests_xferquota.py new file mode 100644 index 0000000000..dbc29b937d --- /dev/null +++ b/bin/tests/system/xferquota/tests_xferquota.py @@ -0,0 +1,65 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import glob +import os +import re +import shutil +import signal + +import isctest + +import dns.message + + +def test_xferquota(named_port, servers): + # Changing test zone + shutil.copyfile("ns1/changing2.db", "ns1/changing.db") + with open("ns1/named.pid", "r", encoding="utf-8") as pidfile: + pid = int(pidfile.read()) + os.kill(pid, signal.SIGHUP) + with servers["ns1"].watch_log_from_start() as watcher: + watcher.wait_for_line("received SIGHUP signal to reload zones") + + def check_line_count(): + matching_line_count = 0 + # Iterate through zone files and count matching lines + for file_path in glob.glob("ns2/zone000*.example.bk"): + with open(file_path, "r", encoding="utf-8") as zonefile: + # Count the number of lines containing the search string + for line in zonefile: + if "xyzzy A 10.0.0.2" in line: + matching_line_count += 1 + return matching_line_count == 300 + + isctest.run.retry_with_timeout(check_line_count, timeout=360) + + axfr_msg = dns.message.make_query("zone000099.example.", "AXFR") + a_msg = dns.message.make_query("a.changing.", "A") + + def query_and_compare(msg): + ns1response = isctest.query.tcp(msg, "10.53.0.1") + ns2response = isctest.query.tcp(msg, "10.53.0.2") + isctest.check.noerror(ns1response) + isctest.check.noerror(ns2response) + isctest.check.rrsets_equal(ns1response.answer, ns2response.answer) + + query_and_compare(axfr_msg) + pattern = re.compile( + f"transfer of 'changing/IN' from 10.53.0.1#{named_port}: " + f"Transfer completed: .*\\(serial 2\\)" + ) + with servers["ns2"].watch_log_from_start() as watcher: + watcher.wait_for_line( + pattern, + timeout=30, + ) + query_and_compare(a_msg)