mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-30 14:07:59 +00:00
chg: test: Rewrite stub system test to pytest
Merge branch 'mnowak/pytest_rewrite_stub' into 'main' See merge request isc-projects/bind9!9190
This commit is contained in:
@@ -9,7 +9,6 @@
|
|||||||
# See the COPYRIGHT file distributed with this work for additional
|
# See the COPYRIGHT file distributed with this work for additional
|
||||||
# information regarding copyright ownership.
|
# information regarding copyright ownership.
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
import filecmp
|
import filecmp
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -18,7 +17,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from typing import Any, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -483,46 +482,6 @@ def templates(system_test_dir: Path):
|
|||||||
return isctest.template.TemplateEngine(system_test_dir)
|
return isctest.template.TemplateEngine(system_test_dir)
|
||||||
|
|
||||||
|
|
||||||
def _run_script(
|
|
||||||
system_test_dir: Path,
|
|
||||||
interpreter: str,
|
|
||||||
script: str,
|
|
||||||
args: Optional[List[str]] = None,
|
|
||||||
):
|
|
||||||
"""Helper function for the shell / perl script invocations (through fixtures below)."""
|
|
||||||
if args is None:
|
|
||||||
args = []
|
|
||||||
path = Path(script)
|
|
||||||
if not path.is_absolute():
|
|
||||||
# make sure relative paths are always relative to system_dir
|
|
||||||
path = system_test_dir.parent / path
|
|
||||||
script = str(path)
|
|
||||||
cwd = os.getcwd()
|
|
||||||
if not path.exists():
|
|
||||||
raise FileNotFoundError(f"script {script} not found in {cwd}")
|
|
||||||
isctest.log.debug("running script: %s %s %s", interpreter, script, " ".join(args))
|
|
||||||
isctest.log.debug(" workdir: %s", cwd)
|
|
||||||
returncode = 1
|
|
||||||
|
|
||||||
cmd = [interpreter, script] + args
|
|
||||||
with subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
bufsize=1,
|
|
||||||
universal_newlines=True,
|
|
||||||
errors="backslashreplace",
|
|
||||||
) as proc:
|
|
||||||
if proc.stdout:
|
|
||||||
for line in proc.stdout:
|
|
||||||
isctest.log.info(" %s", line.rstrip("\n"))
|
|
||||||
proc.communicate()
|
|
||||||
returncode = proc.returncode
|
|
||||||
if returncode:
|
|
||||||
raise subprocess.CalledProcessError(returncode, cmd)
|
|
||||||
isctest.log.debug(" exited with %d", returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_node_path(node) -> Path:
|
def _get_node_path(node) -> Path:
|
||||||
if isinstance(node.parent, pytest.Session):
|
if isinstance(node.parent, pytest.Session):
|
||||||
if _pytest_major_ver >= 8:
|
if _pytest_major_ver >= 8:
|
||||||
@@ -533,23 +492,11 @@ def _get_node_path(node) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def shell(system_test_dir):
|
def run_tests_sh(system_test_dir):
|
||||||
"""Function to call a shell script with arguments."""
|
|
||||||
return partial(_run_script, system_test_dir, os.environ["SHELL"])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def perl(system_test_dir):
|
|
||||||
"""Function to call a perl script with arguments."""
|
|
||||||
return partial(_run_script, system_test_dir, os.environ["PERL"])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def run_tests_sh(system_test_dir, shell):
|
|
||||||
"""Utility function to execute tests.sh as a python test."""
|
"""Utility function to execute tests.sh as a python test."""
|
||||||
|
|
||||||
def run_tests():
|
def run_tests():
|
||||||
shell(f"{system_test_dir}/tests.sh")
|
isctest.run.shell(f"{system_test_dir}/tests.sh")
|
||||||
|
|
||||||
return run_tests
|
return run_tests
|
||||||
|
|
||||||
@@ -559,8 +506,6 @@ def system_test(
|
|||||||
request,
|
request,
|
||||||
system_test_dir,
|
system_test_dir,
|
||||||
templates,
|
templates,
|
||||||
shell,
|
|
||||||
perl,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Driver of the test setup/teardown process. Used automatically for every test module.
|
Driver of the test setup/teardown process. Used automatically for every test module.
|
||||||
@@ -586,14 +531,16 @@ def system_test(
|
|||||||
|
|
||||||
def check_net_interfaces():
|
def check_net_interfaces():
|
||||||
try:
|
try:
|
||||||
perl("testsock.pl", ["-p", os.environ["PORT"]])
|
isctest.run.perl(
|
||||||
|
f"{os.environ['srcdir']}/testsock.pl", ["-p", os.environ["PORT"]]
|
||||||
|
)
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
isctest.log.error("testsock.pl: exited with code %d", exc.returncode)
|
isctest.log.error("testsock.pl: exited with code %d", exc.returncode)
|
||||||
pytest.skip("Network interface aliases not set up.")
|
pytest.skip("Network interface aliases not set up.")
|
||||||
|
|
||||||
def check_prerequisites():
|
def check_prerequisites():
|
||||||
try:
|
try:
|
||||||
shell(f"{system_test_dir}/prereq.sh")
|
isctest.run.shell(f"{system_test_dir}/prereq.sh")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass # prereq.sh is optional
|
pass # prereq.sh is optional
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
@@ -602,7 +549,7 @@ def system_test(
|
|||||||
def setup_test():
|
def setup_test():
|
||||||
templates.render_auto()
|
templates.render_auto()
|
||||||
try:
|
try:
|
||||||
shell(f"{system_test_dir}/setup.sh")
|
isctest.run.shell(f"{system_test_dir}/setup.sh")
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass # setup.sh is optional
|
pass # setup.sh is optional
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
@@ -611,14 +558,17 @@ def system_test(
|
|||||||
|
|
||||||
def start_servers():
|
def start_servers():
|
||||||
try:
|
try:
|
||||||
perl("start.pl", ["--port", os.environ["PORT"], system_test_dir.name])
|
isctest.run.perl(
|
||||||
|
f"{os.environ['srcdir']}/start.pl",
|
||||||
|
["--port", os.environ["PORT"], system_test_dir.name],
|
||||||
|
)
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
isctest.log.error("Failed to start servers")
|
isctest.log.error("Failed to start servers")
|
||||||
pytest.fail(f"start.pl exited with {exc.returncode}")
|
pytest.fail(f"start.pl exited with {exc.returncode}")
|
||||||
|
|
||||||
def stop_servers():
|
def stop_servers():
|
||||||
try:
|
try:
|
||||||
perl("stop.pl", [system_test_dir.name])
|
isctest.run.perl(f"{os.environ['srcdir']}/stop.pl", [system_test_dir.name])
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
isctest.log.error("Failed to stop servers")
|
isctest.log.error("Failed to stop servers")
|
||||||
get_core_dumps()
|
get_core_dumps()
|
||||||
@@ -626,7 +576,9 @@ def system_test(
|
|||||||
|
|
||||||
def get_core_dumps():
|
def get_core_dumps():
|
||||||
try:
|
try:
|
||||||
shell("get_core_dumps.sh", [system_test_dir.name])
|
isctest.run.shell(
|
||||||
|
f"{os.environ['srcdir']}/get_core_dumps.sh", [system_test_dir.name]
|
||||||
|
)
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
isctest.log.error("Found core dumps or sanitizer reports")
|
isctest.log.error("Found core dumps or sanitizer reports")
|
||||||
pytest.fail(f"get_core_dumps.sh exited with {exc.returncode}")
|
pytest.fail(f"get_core_dumps.sh exited with {exc.returncode}")
|
||||||
|
@@ -102,6 +102,10 @@ def is_executable(cmd: str, errmsg: str) -> None:
|
|||||||
assert executable is not None, errmsg
|
assert executable is not None, errmsg
|
||||||
|
|
||||||
|
|
||||||
|
def notauth(message: dns.message.Message) -> None:
|
||||||
|
rcode(message, dns.rcode.NOTAUTH)
|
||||||
|
|
||||||
|
|
||||||
def nxdomain(message: dns.message.Message) -> None:
|
def nxdomain(message: dns.message.Message) -> None:
|
||||||
rcode(message, dns.rcode.NXDOMAIN)
|
rcode(message, dns.rcode.NXDOMAIN)
|
||||||
|
|
||||||
|
@@ -11,13 +11,15 @@
|
|||||||
# See the COPYRIGHT file distributed with this work for additional
|
# See the COPYRIGHT file distributed with this work for additional
|
||||||
# information regarding copyright ownership.
|
# information regarding copyright ownership.
|
||||||
|
|
||||||
from typing import NamedTuple, Optional
|
from typing import List, NamedTuple, Optional
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .rndc import RNDCBinaryExecutor, RNDCException, RNDCExecutor
|
from .rndc import RNDCBinaryExecutor, RNDCException, RNDCExecutor
|
||||||
|
from .run import perl
|
||||||
from .log import info, LogFile, WatchLogFromStart, WatchLogFromHere
|
from .log import info, LogFile, WatchLogFromStart, WatchLogFromHere
|
||||||
|
|
||||||
|
|
||||||
@@ -48,13 +50,17 @@ class NamedInstance:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
identifier: str,
|
identifier: str,
|
||||||
|
num: Optional[int] = None,
|
||||||
ports: Optional[NamedPorts] = None,
|
ports: Optional[NamedPorts] = None,
|
||||||
rndc_logger: Optional[logging.Logger] = None,
|
rndc_logger: Optional[logging.Logger] = None,
|
||||||
rndc_executor: Optional[RNDCExecutor] = None,
|
rndc_executor: Optional[RNDCExecutor] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
`identifier` must be an `ns<X>` string, where `<X>` is an integer
|
`identifier` is the name of the instance's directory
|
||||||
identifier of the `named` instance this object should represent.
|
|
||||||
|
`num` is optional if the identifier is in a form of `ns<X>`, in which
|
||||||
|
case `<X>` is assumed to be numeric identifier; otherwise it must be
|
||||||
|
provided to assign a numeric identification to the server
|
||||||
|
|
||||||
`ports` is the `NamedPorts` instance listing the UDP/TCP ports on which
|
`ports` is the `NamedPorts` instance listing the UDP/TCP ports on which
|
||||||
this `named` instance is listening for various types of traffic (both
|
this `named` instance is listening for various types of traffic (both
|
||||||
@@ -67,7 +73,13 @@ class NamedInstance:
|
|||||||
`rndc_executor` is an object implementing the `RNDCExecutor` interface
|
`rndc_executor` is an object implementing the `RNDCExecutor` interface
|
||||||
that is used for executing RNDC commands on this `named` instance.
|
that is used for executing RNDC commands on this `named` instance.
|
||||||
"""
|
"""
|
||||||
self.ip = self._identifier_to_ip(identifier)
|
self.directory = Path(identifier).absolute()
|
||||||
|
if not self.directory.is_dir():
|
||||||
|
raise ValueError(f"{self.directory} isn't a directory")
|
||||||
|
self.system_test_name = self.directory.parent.name
|
||||||
|
|
||||||
|
self.identifier = identifier
|
||||||
|
self.num = self._identifier_to_num(identifier, num)
|
||||||
if ports is None:
|
if ports is None:
|
||||||
ports = NamedPorts.from_env()
|
ports = NamedPorts.from_env()
|
||||||
self.ports = ports
|
self.ports = ports
|
||||||
@@ -75,12 +87,21 @@ class NamedInstance:
|
|||||||
self._rndc_executor = rndc_executor or RNDCBinaryExecutor()
|
self._rndc_executor = rndc_executor or RNDCBinaryExecutor()
|
||||||
self._rndc_logger = rndc_logger
|
self._rndc_logger = rndc_logger
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip(self) -> str:
|
||||||
|
"""IPv4 address of the instance."""
|
||||||
|
return f"10.53.0.{self.num}"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _identifier_to_ip(identifier: str) -> str:
|
def _identifier_to_num(identifier: str, num: Optional[int] = None) -> int:
|
||||||
regex_match = re.match(r"^ns(?P<index>[0-9]{1,2})$", identifier)
|
regex_match = re.match(r"^ns(?P<index>[0-9]{1,2})$", identifier)
|
||||||
if not regex_match:
|
if not regex_match:
|
||||||
raise ValueError("Invalid named instance identifier" + identifier)
|
if num is None:
|
||||||
return "10.53.0." + regex_match.group("index")
|
raise ValueError(f'Can\'t parse numeric identifier from "{identifier}"')
|
||||||
|
return num
|
||||||
|
parsed_num = int(regex_match.group("index"))
|
||||||
|
assert num is None or num == parsed_num, "mismatched num and identifier"
|
||||||
|
return parsed_num
|
||||||
|
|
||||||
def rndc(self, command: str, ignore_errors: bool = False, log: bool = True) -> str:
|
def rndc(self, command: str, ignore_errors: bool = False, log: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -175,3 +196,19 @@ class NamedInstance:
|
|||||||
info(fmt, args)
|
info(fmt, args)
|
||||||
else:
|
else:
|
||||||
self._rndc_logger.info(fmt, args)
|
self._rndc_logger.info(fmt, args)
|
||||||
|
|
||||||
|
def stop(self, args: Optional[List[str]] = None) -> None:
|
||||||
|
"""Stop the instance."""
|
||||||
|
args = args or []
|
||||||
|
perl(
|
||||||
|
f"{os.environ['srcdir']}/stop.pl",
|
||||||
|
[self.system_test_name, self.identifier] + args,
|
||||||
|
)
|
||||||
|
|
||||||
|
def start(self, args: Optional[List[str]] = None) -> None:
|
||||||
|
"""Start the instance."""
|
||||||
|
args = args or []
|
||||||
|
perl(
|
||||||
|
f"{os.environ['srcdir']}/start.pl",
|
||||||
|
[self.system_test_name, self.identifier] + args,
|
||||||
|
)
|
||||||
|
@@ -10,9 +10,10 @@
|
|||||||
# information regarding copyright ownership.
|
# information regarding copyright ownership.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import isctest.log
|
import isctest.log
|
||||||
from isctest.compat import dns_rcode
|
from isctest.compat import dns_rcode
|
||||||
@@ -65,6 +66,51 @@ def cmd(
|
|||||||
return exc
|
return exc
|
||||||
|
|
||||||
|
|
||||||
|
def _run_script(
|
||||||
|
interpreter: str,
|
||||||
|
script: str,
|
||||||
|
args: Optional[List[str]] = None,
|
||||||
|
):
|
||||||
|
if args is None:
|
||||||
|
args = []
|
||||||
|
path = Path(script)
|
||||||
|
script = str(path)
|
||||||
|
cwd = os.getcwd()
|
||||||
|
if not path.exists():
|
||||||
|
raise FileNotFoundError(f"script {script} not found in {cwd}")
|
||||||
|
isctest.log.debug("running script: %s %s %s", interpreter, script, " ".join(args))
|
||||||
|
isctest.log.debug(" workdir: %s", cwd)
|
||||||
|
returncode = 1
|
||||||
|
|
||||||
|
command = [interpreter, script] + args
|
||||||
|
with subprocess.Popen(
|
||||||
|
command,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
bufsize=1,
|
||||||
|
universal_newlines=True,
|
||||||
|
errors="backslashreplace",
|
||||||
|
) as proc:
|
||||||
|
if proc.stdout:
|
||||||
|
for line in proc.stdout:
|
||||||
|
isctest.log.info(" %s", line.rstrip("\n"))
|
||||||
|
proc.communicate()
|
||||||
|
returncode = proc.returncode
|
||||||
|
if returncode:
|
||||||
|
raise subprocess.CalledProcessError(returncode, command)
|
||||||
|
isctest.log.debug(" exited with %d", returncode)
|
||||||
|
|
||||||
|
|
||||||
|
def shell(script: str, args: Optional[List[str]] = None) -> None:
|
||||||
|
"""Run a given script with system's shell interpreter."""
|
||||||
|
_run_script(os.environ["SHELL"], script, args)
|
||||||
|
|
||||||
|
|
||||||
|
def perl(script: str, args: Optional[List[str]] = None) -> None:
|
||||||
|
"""Run a given script with system's perl interpreter."""
|
||||||
|
_run_script(os.environ["PERL"], script, args)
|
||||||
|
|
||||||
|
|
||||||
def retry_with_timeout(func, timeout, delay=1, msg=None):
|
def retry_with_timeout(func, timeout, delay=1, msg=None):
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
while time.time() < start_time + timeout:
|
while time.time() < start_time + timeout:
|
||||||
@@ -91,18 +137,6 @@ def get_named_cmdline(cfg_dir, cfg_file="named.conf"):
|
|||||||
return named_cmdline
|
return named_cmdline
|
||||||
|
|
||||||
|
|
||||||
def get_custom_named_instance(assumed_ns):
|
|
||||||
# This test launches and monitors a named instance itself rather than using
|
|
||||||
# bin/tests/system/start.pl, so manually defining a NamedInstance here is
|
|
||||||
# necessary for sending RNDC commands to that instance. If this "custom"
|
|
||||||
# instance listens on 10.53.0.3, use "ns3" as the identifier passed to
|
|
||||||
# the NamedInstance constructor.
|
|
||||||
named_ports = isctest.instance.NamedPorts.from_env()
|
|
||||||
instance = isctest.instance.NamedInstance(assumed_ns, named_ports)
|
|
||||||
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
def assert_custom_named_is_alive(named_proc, resolver_ip):
|
def assert_custom_named_is_alive(named_proc, resolver_ip):
|
||||||
assert named_proc.poll() is None, "named isn't running"
|
assert named_proc.poll() is None, "named isn't running"
|
||||||
msg = dns.message.make_query("version.bind", "TXT", "CH")
|
msg = dns.message.make_query("version.bind", "TXT", "CH")
|
||||||
|
@@ -170,7 +170,7 @@ def test_named_shutdown(kill_method):
|
|||||||
cfg_dir = "resolver"
|
cfg_dir = "resolver"
|
||||||
|
|
||||||
named_cmdline = isctest.run.get_named_cmdline(cfg_dir)
|
named_cmdline = isctest.run.get_named_cmdline(cfg_dir)
|
||||||
instance = isctest.run.get_custom_named_instance("ns3")
|
instance = isctest.instance.NamedInstance("resolver", num=3)
|
||||||
|
|
||||||
with open(os.path.join(cfg_dir, "named.run"), "ab") as named_log:
|
with open(os.path.join(cfg_dir, "named.run"), "ab") as named_log:
|
||||||
with subprocess.Popen(
|
with subprocess.Popen(
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
|
|
||||||
; <<>> DiG 8.2 <<>> -p @10.53.0.3 +norec data.child.example txt
|
|
||||||
; (1 server found)
|
|
||||||
;; res options: init defnam dnsrch
|
|
||||||
;; got answer:
|
|
||||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 216
|
|
||||||
;; flags: qr ra ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
|
|
||||||
;; QUERY SECTION:
|
|
||||||
;; data.child.example, type = TXT, class = IN
|
|
||||||
|
|
||||||
;; AUTHORITY SECTION:
|
|
||||||
child.example. 5M IN NS ns2.child.example.
|
|
||||||
|
|
||||||
;; ADDITIONAL SECTION:
|
|
||||||
ns2.child.example. 5M IN A 10.53.0.2
|
|
||||||
|
|
||||||
;; Total query time: 3 msec
|
|
||||||
;; FROM: draco to SERVER: 10.53.0.3
|
|
||||||
;; WHEN: Wed Jun 21 10:58:37 2000
|
|
||||||
;; MSG SIZE sent: 36 rcvd: 70
|
|
||||||
|
|
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
; <<>> DiG 8.2 <<>> -p @10.53.0.3 data.child.example txt
|
|
||||||
; (1 server found)
|
|
||||||
;; res options: init recurs defnam dnsrch
|
|
||||||
;; got answer:
|
|
||||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6
|
|
||||||
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1
|
|
||||||
;; QUERY SECTION:
|
|
||||||
;; data.child.example, type = TXT, class = IN
|
|
||||||
|
|
||||||
;; ANSWER SECTION:
|
|
||||||
data.child.example. 5M IN TXT "some" "test" "data"
|
|
||||||
|
|
||||||
;; Total query time: 8 msec
|
|
||||||
;; FROM: draco to SERVER: 10.53.0.3
|
|
||||||
;; WHEN: Wed Jun 21 10:58:54 2000
|
|
||||||
;; MSG SIZE sent: 36 rcvd: 97
|
|
||||||
|
|
@@ -25,6 +25,15 @@ options {
|
|||||||
minimal-responses no;
|
minimal-responses no;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
key rndc_key {
|
||||||
|
secret "1234abcd8765";
|
||||||
|
algorithm @DEFAULT_HMAC@;
|
||||||
|
};
|
||||||
|
|
||||||
|
controls {
|
||||||
|
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||||
|
};
|
||||||
|
|
||||||
zone "." {
|
zone "." {
|
||||||
type hint;
|
type hint;
|
||||||
file "../../_common/root.hint";
|
file "../../_common/root.hint";
|
||||||
|
@@ -1,100 +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 -p ${PORT}"
|
|
||||||
|
|
||||||
status=0
|
|
||||||
echo_i "check that the stub zone has been saved to disk"
|
|
||||||
for i in 1 2 3 4 5 6 7 8 9 20; do
|
|
||||||
[ -f ns3/child.example.st ] && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
[ -f ns3/child.example.st ] || {
|
|
||||||
status=1
|
|
||||||
echo_i "failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
for pass in 1 2; do
|
|
||||||
|
|
||||||
echo_i "trying an axfr that should be denied (NOTAUTH) (pass=$pass)"
|
|
||||||
ret=0
|
|
||||||
$DIG $DIGOPTS child.example. @10.53.0.3 axfr >dig.out.ns3 || ret=1
|
|
||||||
grep "; Transfer failed." dig.out.ns3 >/dev/null || ret=1
|
|
||||||
[ $ret = 0 ] || {
|
|
||||||
status=1
|
|
||||||
echo_i "failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_i "look for stub zone data without recursion (should not be found) (pass=$pass)"
|
|
||||||
for i in 1 2 3 4 5 6 7 8 9; do
|
|
||||||
ret=0
|
|
||||||
$DIG $DIGOPTS +norec data.child.example. \
|
|
||||||
@10.53.0.3 txt >dig.out.ns3 || ret=1
|
|
||||||
grep "status: NOERROR" dig.out.ns3 >/dev/null || ret=1
|
|
||||||
[ $ret = 0 ] && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
digcomp knowngood.dig.out.norec dig.out.ns3 || ret=1
|
|
||||||
[ $ret = 0 ] || {
|
|
||||||
status=1
|
|
||||||
echo_i "failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo_i "look for stub zone data with recursion (should be found) (pass=$pass)"
|
|
||||||
ret=0
|
|
||||||
$DIG $DIGOPTS +noauth +noadd data.child.example. @10.53.0.3 txt >dig.out.ns3 || ret=1
|
|
||||||
digcomp knowngood.dig.out.rec dig.out.ns3 || ret=1
|
|
||||||
[ $ret = 0 ] || {
|
|
||||||
status=1
|
|
||||||
echo_i "failed"
|
|
||||||
}
|
|
||||||
|
|
||||||
[ $pass = 1 ] && {
|
|
||||||
echo_i "stopping stub server"
|
|
||||||
stop_server ns3
|
|
||||||
|
|
||||||
echo_i "re-starting stub server"
|
|
||||||
start_server --noclean --restart --port ${PORT} ns3
|
|
||||||
}
|
|
||||||
done
|
|
||||||
|
|
||||||
echo_i "check that glue record is correctly transferred from primary when minimal-responses is on"
|
|
||||||
ret=0
|
|
||||||
# First ensure that zone data was transfered.
|
|
||||||
for i in 1 2 3 4 5 6 7; do
|
|
||||||
[ -f ns5/example.db ] && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ -f ns5/example.db ]; then
|
|
||||||
# If NS glue wasn't transferred, this query would fail.
|
|
||||||
$DIG $DIGOPTS +nodnssec @10.53.0.5 target.example. txt >dig.out.ns5 || ret=1
|
|
||||||
grep 'target\.example.*TXT.*"test"' dig.out.ns5 >/dev/null || ret=1
|
|
||||||
# Ensure both ipv4 and ipv6 glue records were transferred.
|
|
||||||
grep -E 'ns4.example.[[:space:]]+300 IN A[[:space:]]+10.53.0.4' ns5/example.db >/dev/null || ret=1
|
|
||||||
grep -E 'ns4.example.[[:space:]]+300 IN AAAA[[:space:]]+fd92:7065:b8e:ffff::4' ns5/example.db >/dev/null || ret=1
|
|
||||||
[ $ret = 0 ] || {
|
|
||||||
status=1
|
|
||||||
echo_i "failed"
|
|
||||||
}
|
|
||||||
else
|
|
||||||
status=1
|
|
||||||
echo_i "failed: stub zone transfer failed ns4(primary) <---> ns5/example.db"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo_i "exit status: $status"
|
|
||||||
[ $status -eq 0 ] || exit 1
|
|
@@ -1,24 +0,0 @@
|
|||||||
# 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 pytest
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.extra_artifacts(
|
|
||||||
[
|
|
||||||
"dig.out.*",
|
|
||||||
"ns3/child.example.st",
|
|
||||||
"ns5/example.db",
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_stub(run_tests_sh):
|
|
||||||
run_tests_sh()
|
|
100
bin/tests/system/stub/tests_stub.py
Normal file
100
bin/tests/system/stub/tests_stub.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# 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 os
|
||||||
|
|
||||||
|
import dns.message
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import isctest
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.extra_artifacts(
|
||||||
|
[
|
||||||
|
"dig.out.*",
|
||||||
|
"ns3/child.example.st",
|
||||||
|
"ns5/example.db",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stub_zones_availability(servers):
|
||||||
|
# check that the stub zone has been saved to disk
|
||||||
|
assert os.path.exists("ns3/child.example.st")
|
||||||
|
|
||||||
|
# try an AXFR that should be denied (NOTAUTH)
|
||||||
|
def axfr_denied():
|
||||||
|
msg = dns.message.make_query("child.example.", "AXFR")
|
||||||
|
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||||
|
isctest.check.notauth(res)
|
||||||
|
|
||||||
|
# look for stub zone data without recursion (should not be found)
|
||||||
|
def stub_zone_lookout_without_recursion():
|
||||||
|
# drop all flags (dns.flags.RD is set by default)
|
||||||
|
msg = dns.message.make_query("data.child.example.", "TXT")
|
||||||
|
msg.flags = 0
|
||||||
|
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||||
|
isctest.check.noerror(res)
|
||||||
|
assert not res.answer
|
||||||
|
assert res.authority[0] == dns.rrset.from_text(
|
||||||
|
"child.example.", "300", "IN", "NS", "ns2.child.example."
|
||||||
|
)
|
||||||
|
assert res.additional[0] == dns.rrset.from_text(
|
||||||
|
"ns2.child.example.", "300", "IN", "A", "10.53.0.2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# look for stub zone data with recursion (should be found)
|
||||||
|
def stub_zone_lookout_with_recursion():
|
||||||
|
# dns.flags.RD is set by default
|
||||||
|
msg = dns.message.make_query("data.child.example.", "TXT")
|
||||||
|
res = isctest.query.tcp(msg, "10.53.0.3")
|
||||||
|
isctest.check.noerror(res)
|
||||||
|
assert res.answer[0] == dns.rrset.from_text(
|
||||||
|
"data.child.example.", "300", "IN", "TXT", '"some" "test" "data"'
|
||||||
|
)
|
||||||
|
|
||||||
|
axfr_denied()
|
||||||
|
stub_zone_lookout_without_recursion()
|
||||||
|
stub_zone_lookout_with_recursion()
|
||||||
|
|
||||||
|
servers["ns3"].stop()
|
||||||
|
servers["ns3"].start(["--noclean", "--restart", "--port", os.environ["PORT"]])
|
||||||
|
|
||||||
|
axfr_denied()
|
||||||
|
stub_zone_lookout_without_recursion()
|
||||||
|
stub_zone_lookout_with_recursion()
|
||||||
|
|
||||||
|
|
||||||
|
# check that glue record is correctly transferred from primary when the "minimal-responses" option is on
|
||||||
|
def test_stub_glue_record_with_minimal_response():
|
||||||
|
# ensure zone data were transfered
|
||||||
|
assert os.path.exists("ns5/example.db")
|
||||||
|
|
||||||
|
# this query would fail if NS glue wasn't transferred
|
||||||
|
msg_txt = dns.message.make_query("target.example.", "TXT", want_dnssec=False)
|
||||||
|
res_txt = isctest.query.tcp(msg_txt, "10.53.0.5")
|
||||||
|
isctest.check.noerror(res_txt)
|
||||||
|
assert res_txt.answer[0] == dns.rrset.from_text(
|
||||||
|
"target.example.", "300", "IN", "TXT", '"test"'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ensure both IPv4 and IPv6 glue records were transferred
|
||||||
|
msg_a = dns.message.make_query("ns4.example.", "A")
|
||||||
|
res_a = isctest.query.tcp(msg_a, "10.53.0.5")
|
||||||
|
assert res_a.answer[0] == dns.rrset.from_text(
|
||||||
|
"ns4.example.", "300", "IN", "A", "10.53.0.4"
|
||||||
|
)
|
||||||
|
|
||||||
|
msg_aaaa = dns.message.make_query("ns4.example.", "AAAA")
|
||||||
|
res_aaaa = isctest.query.tcp(msg_aaaa, "10.53.0.5")
|
||||||
|
assert res_aaaa.answer[0] == dns.rrset.from_text(
|
||||||
|
"ns4.example.", "300", "IN", "AAAA", "fd92:7065:b8e:ffff::4"
|
||||||
|
)
|
Reference in New Issue
Block a user