mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-02 15:45:25 +00:00
Merge branch 'mnowak/pytest_rewrite_spf' into 'main'
Rewrite spf system test to pytest See merge request isc-projects/bind9!8572
This commit is contained in:
@@ -489,20 +489,16 @@ def test_checkds(servers, params):
|
|||||||
# Wait until the provided zone is signed and then verify its DNSSEC data.
|
# Wait until the provided zone is signed and then verify its DNSSEC data.
|
||||||
zone_check(servers["ns9"], params.zone)
|
zone_check(servers["ns9"], params.zone)
|
||||||
|
|
||||||
# Wait until all the expected log lines are found in the log file for the
|
# Wait up to 10 seconds until all the expected log lines are found in the
|
||||||
# provided server.
|
# log file for the provided server. Rekey every second if necessary.
|
||||||
|
time_remaining = 10
|
||||||
for log_string in params.logs_to_wait_for:
|
for log_string in params.logs_to_wait_for:
|
||||||
for _ in range(10):
|
|
||||||
with servers["ns9"].watch_log_from_start() as watcher:
|
|
||||||
line = f"zone {params.zone}/IN (signed): checkds: {log_string}"
|
line = f"zone {params.zone}/IN (signed): checkds: {log_string}"
|
||||||
try:
|
while line not in servers["ns9"].log:
|
||||||
watcher.wait_for_line(line, timeout=1)
|
|
||||||
except TimeoutError:
|
|
||||||
rekey(params.zone)
|
rekey(params.zone)
|
||||||
else:
|
time_remaining -= 1
|
||||||
break
|
assert time_remaining, f'Timed out waiting for "{log_string}" to be logged'
|
||||||
else:
|
time.sleep(1)
|
||||||
raise TimeoutError
|
|
||||||
|
|
||||||
# Check whether key states on the parent server provided match
|
# Check whether key states on the parent server provided match
|
||||||
# expectations.
|
# expectations.
|
||||||
|
@@ -356,6 +356,27 @@ def mlogger(system_test_name):
|
|||||||
return logging.getLogger(system_test_name)
|
return logging.getLogger(system_test_name)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_marker(node, marker):
|
||||||
|
try:
|
||||||
|
# pytest >= 4.x
|
||||||
|
return node.get_closest_marker(marker)
|
||||||
|
except AttributeError:
|
||||||
|
# pytest < 4.x
|
||||||
|
return node.get_marker(marker)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def wait_for_zones_loaded(request, servers):
|
||||||
|
"""Wait for all zones to be loaded by specified named instances."""
|
||||||
|
instances = _get_marker(request.node, "requires_zones_loaded")
|
||||||
|
if not instances:
|
||||||
|
return
|
||||||
|
|
||||||
|
for instance in instances.args:
|
||||||
|
with servers[instance].watch_log_from_start() as watcher:
|
||||||
|
watcher.wait_for_line("all zones loaded")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def logger(request, system_test_name):
|
def logger(request, system_test_name):
|
||||||
"""Logging facility specific to a particular test."""
|
"""Logging facility specific to a particular test."""
|
||||||
|
@@ -13,4 +13,4 @@ from . import check
|
|||||||
from . import instance
|
from . import instance
|
||||||
from . import query
|
from . import query
|
||||||
from . import rndc
|
from . import rndc
|
||||||
from . import watchlog
|
from . import log
|
||||||
|
@@ -18,7 +18,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .rndc import RNDCBinaryExecutor, RNDCException, RNDCExecutor
|
from .rndc import RNDCBinaryExecutor, RNDCException, RNDCExecutor
|
||||||
from .watchlog import WatchLogFromStart, WatchLogFromHere
|
from .log import LogFile, WatchLogFromStart, WatchLogFromHere
|
||||||
|
|
||||||
|
|
||||||
class NamedPorts(NamedTuple):
|
class NamedPorts(NamedTuple):
|
||||||
@@ -63,7 +63,7 @@ class NamedInstance:
|
|||||||
"""
|
"""
|
||||||
self.ip = self._identifier_to_ip(identifier)
|
self.ip = self._identifier_to_ip(identifier)
|
||||||
self.ports = ports
|
self.ports = ports
|
||||||
self._log_file = os.path.join(identifier, "named.run")
|
self.log = LogFile(os.path.join(identifier, "named.run"))
|
||||||
self._rndc_executor = rndc_executor or RNDCBinaryExecutor()
|
self._rndc_executor = rndc_executor or RNDCBinaryExecutor()
|
||||||
self._rndc_logger = rndc_logger or logging.getLogger()
|
self._rndc_logger = rndc_logger or logging.getLogger()
|
||||||
|
|
||||||
@@ -133,14 +133,14 @@ class NamedInstance:
|
|||||||
Return an instance of the `WatchLogFromStart` context manager for this
|
Return an instance of the `WatchLogFromStart` context manager for this
|
||||||
`named` instance's log file.
|
`named` instance's log file.
|
||||||
"""
|
"""
|
||||||
return WatchLogFromStart(self._log_file)
|
return WatchLogFromStart(self.log.path)
|
||||||
|
|
||||||
def watch_log_from_here(self) -> WatchLogFromHere:
|
def watch_log_from_here(self) -> WatchLogFromHere:
|
||||||
"""
|
"""
|
||||||
Return an instance of the `WatchLogFromHere` context manager for this
|
Return an instance of the `WatchLogFromHere` context manager for this
|
||||||
`named` instance's log file.
|
`named` instance's log file.
|
||||||
"""
|
"""
|
||||||
return WatchLogFromHere(self._log_file)
|
return WatchLogFromHere(self.log.path)
|
||||||
|
|
||||||
def reconfigure(self) -> None:
|
def reconfigure(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
# 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 Optional, TextIO, Dict, Any, overload, List, Union
|
from typing import Iterator, Optional, TextIO, Dict, Any, overload, List, Union
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
import os
|
import os
|
||||||
@@ -22,6 +22,40 @@ class WatchLogException(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LogFile:
|
||||||
|
"""
|
||||||
|
Log file wrapper with a path and means to find a string in its contents.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: str):
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _lines(self) -> Iterator[str]:
|
||||||
|
with open(self.path, encoding="utf-8") as f:
|
||||||
|
yield from f
|
||||||
|
|
||||||
|
def __contains__(self, substring: str) -> bool:
|
||||||
|
"""
|
||||||
|
Return whether any of the lines in the log contains a given string.
|
||||||
|
"""
|
||||||
|
for line in self._lines:
|
||||||
|
if substring in line:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def expect(self, msg: str):
|
||||||
|
"""Check the string is present anywhere in the log file."""
|
||||||
|
if msg in self:
|
||||||
|
return
|
||||||
|
assert False, f"log message not found in log {self.path}: {msg}"
|
||||||
|
|
||||||
|
def prohibit(self, msg: str):
|
||||||
|
"""Check the string is not present in the entire log file."""
|
||||||
|
if msg in self:
|
||||||
|
assert False, f"forbidden message appeared in log {self.path}: {msg}"
|
||||||
|
|
||||||
|
|
||||||
class WatchLog(abc.ABC):
|
class WatchLog(abc.ABC):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -193,6 +227,7 @@ class WatchLog(abc.ABC):
|
|||||||
if not self._fd:
|
if not self._fd:
|
||||||
raise WatchLogException("No file to watch")
|
raise WatchLogException("No file to watch")
|
||||||
leftover = ""
|
leftover = ""
|
||||||
|
assert timeout, "Do not use this class unless you want to WAIT for something."
|
||||||
deadline = time.time() + timeout
|
deadline = time.time() + timeout
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
for line in self._fd.readlines():
|
for line in self._fd.readlines():
|
@@ -18,3 +18,5 @@ log_level = INFO
|
|||||||
python_files = tests_*.py
|
python_files = tests_*.py
|
||||||
junit_logging = log
|
junit_logging = log
|
||||||
junit_log_passing_tests = 0
|
junit_log_passing_tests = 0
|
||||||
|
markers =
|
||||||
|
requires_zones_loaded: ensures the test does not start until the specified named instances load all configured zones
|
||||||
|
@@ -1,46 +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
|
|
||||||
|
|
||||||
n=1
|
|
||||||
status=0
|
|
||||||
|
|
||||||
# Wait until all zones are loaded before checking SPF related logs
|
|
||||||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
|
||||||
grep "all zones loaded" ns1/named.run >/dev/null && break
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo_i "checking that SPF warnings have been correctly generated ($n)"
|
|
||||||
ret=0
|
|
||||||
|
|
||||||
grep "zone spf/IN: loaded serial 0" ns1/named.run >/dev/null || ret=1
|
|
||||||
grep "'y.spf' found type SPF" ns1/named.run >/dev/null || ret=1
|
|
||||||
grep "'spf' found type SPF" ns1/named.run >/dev/null && ret=1
|
|
||||||
|
|
||||||
grep "zone warn/IN: loaded serial 0" ns1/named.run >/dev/null || ret=1
|
|
||||||
grep "'y.warn' found type SPF" ns1/named.run >/dev/null || ret=1
|
|
||||||
grep "'warn' found type SPF" ns1/named.run >/dev/null && ret=1
|
|
||||||
|
|
||||||
grep "zone nowarn/IN: loaded serial 0" ns1/named.run >/dev/null || ret=1
|
|
||||||
grep "'y.nowarn' found type SPF" ns1/named.run >/dev/null && ret=1
|
|
||||||
grep "'nowarn' found type SPF" ns1/named.run >/dev/null && ret=1
|
|
||||||
n=$((n + 1))
|
|
||||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
|
||||||
status=$((status + ret))
|
|
||||||
|
|
||||||
echo_i "exit status: $status"
|
|
||||||
[ $status -eq 0 ] || exit 1
|
|
@@ -1,14 +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.
|
|
||||||
|
|
||||||
|
|
||||||
def test_spf(run_tests_sh):
|
|
||||||
run_tests_sh()
|
|
32
bin/tests/system/spf/tests_spf_zones.py
Normal file
32
bin/tests/system/spf/tests_spf_zones.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_zones_loaded("ns1")
|
||||||
|
def test_spf_log(servers):
|
||||||
|
for msg in (
|
||||||
|
"zone spf/IN: 'y.spf' found type SPF record but no SPF TXT record found",
|
||||||
|
"zone warn/IN: 'y.warn' found type SPF record but no SPF TXT record found",
|
||||||
|
"zone spf/IN: loaded serial 0",
|
||||||
|
"zone warn/IN: loaded serial 0",
|
||||||
|
"zone nowarn/IN: loaded serial 0",
|
||||||
|
):
|
||||||
|
servers["ns1"].log.expect(msg)
|
||||||
|
|
||||||
|
for msg in (
|
||||||
|
"zone nowarn/IN: 'y.nowarn' found type SPF record but no SPF TXT record found",
|
||||||
|
"zone spf/IN: 'spf' found type SPF record but no SPF TXT record found",
|
||||||
|
"zone warn/IN: 'warn' found type SPF record but no SPF TXT record found",
|
||||||
|
"zone nowarn/IN: 'nowarn' found type SPF record but no SPF TXT record found",
|
||||||
|
):
|
||||||
|
servers["ns1"].log.prohibit(msg)
|
Reference in New Issue
Block a user