2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-31 14:35:26 +00:00

Merge branch 'mnowak/pytest_rewrite_xferquota' into 'main'

Rewrite xferquota system test to pytest

See merge request isc-projects/bind9!8676
This commit is contained in:
Michal Nowak
2024-02-23 10:48:38 +00:00
9 changed files with 149 additions and 116 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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())
)
)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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"
)

View File

@@ -17,7 +17,7 @@
. ../conf.sh
$PERL setup.pl
$PYTHON setup.py
cp -f ns1/changing1.db ns1/changing.db

View File

@@ -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

View File

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