]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Port TCP request statistics checks to Python
authorŠtěpán Balážik <stepan@isc.org>
Sat, 25 Apr 2026 14:00:38 +0000 (16:00 +0200)
committerŠtěpán Balážik <stepan@isc.org>
Thu, 16 Jul 2026 18:32:49 +0000 (20:32 +0200)
Add a helper that runs `rndc stats` and reads the TCP request counter
from named.stats, then use it to port the resolver and forwarder checks
from the shell script to tests_tcp.py. Record named.stats as an extra
artifact so the generated statistics remain available after test runs.

bin/tests/system/tcp/tests.sh
bin/tests/system/tcp/tests_sh_tcp.py
bin/tests/system/tcp/tests_tcp.py

index 18276c8441b53e9a261e797e1a48231caa41ff54..3e0f203c5e388306b9c7aaf62dd57f339c3de013 100644 (file)
@@ -27,50 +27,6 @@ rndccmd() {
 status=0
 n=0
 
-n=$((n + 1))
-echo_i "initializing TCP statistics ($n)"
-ret=0
-rndccmd 10.53.0.1 stats || ret=1
-rndccmd 10.53.0.2 stats || ret=1
-mv ns1/named.stats ns1/named.stats.test$n
-mv ns2/named.stats ns2/named.stats.test$n
-ntcp10="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
-ntcp20="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "checking TCP request statistics (resolver) ($n)"
-ret=0
-dig_with_opts @10.53.0.3 txt.example. >dig.out.test$n
-sleep 1
-rndccmd 10.53.0.1 stats || ret=1
-rndccmd 10.53.0.2 stats || ret=1
-mv ns1/named.stats ns1/named.stats.test$n
-mv ns2/named.stats ns2/named.stats.test$n
-ntcp11="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
-ntcp21="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
-if [ "$ntcp10" -ge "$ntcp11" ]; then ret=1; fi
-if [ "$ntcp20" -ne "$ntcp21" ]; then ret=1; fi
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status + ret))
-
-n=$((n + 1))
-echo_i "checking TCP request statistics (forwarder) ($n)"
-ret=0
-dig_with_opts @10.53.0.4 txt.example. >dig.out.test$n
-sleep 1
-rndccmd 10.53.0.1 stats || ret=1
-rndccmd 10.53.0.2 stats || ret=1
-mv ns1/named.stats ns1/named.stats.test$n
-mv ns2/named.stats ns2/named.stats.test$n
-ntcp12="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
-ntcp22="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
-if [ "$ntcp11" -ne "$ntcp12" ]; then ret=1; fi
-if [ "$ntcp21" -ge "$ntcp22" ]; then ret=1; fi
-if [ $ret != 0 ]; then echo_i "failed"; fi
-status=$((status + ret))
-
 # -------- TCP high-water tests ----------
 refresh_tcp_stats() {
   rndccmd 10.53.0.5 status >rndc.out.$n || ret=1
index 0e0c9fc0e5044fd10e19909d42be80bae61ddf07..f2430db9eeec55326aa03ce383bc4cee3c5bfb72 100644 (file)
@@ -17,7 +17,6 @@ pytestmark = pytest.mark.extra_artifacts(
         "rndc.out.*",
         "ans*/ans.run",
         "ans*/ans.run.prev",
-        "ns*/named.stats.*",
     ]
 )
 
index 940b89f7c74c4c2a4f45e1b58cee67c8feaa1fc1..be2605884ff05ad9d603ce21fb20622cd34cf0cb 100644 (file)
@@ -18,6 +18,7 @@ import time
 import dns.message
 import dns.name
 import dns.query
+import dns.rcode
 import dns.rrset
 import pytest
 
@@ -25,7 +26,7 @@ from isctest.instance import NamedInstance
 
 import isctest
 
-pytestmark = pytest.mark.extra_artifacts(["ans*/ans.run"])
+pytestmark = pytest.mark.extra_artifacts(["ns*/named.stats"])
 
 TIMEOUT: int = 10
 
@@ -36,6 +37,15 @@ def create_socket(host: str, port: int) -> socket.socket:
     return sock
 
 
+def tcp_requests_received(ns: NamedInstance) -> int:
+    ns.rndc("stats")
+    stats = isctest.text.TextFile(str(ns.directory / "named.stats"))
+    matches = stats.grep("TCP requests received")
+    assert matches, f"'TCP requests received' not found in {stats}"
+    # `rndc stats` appends to the file; only the last occurrence is current
+    return int(matches[-1].string.split()[0])
+
+
 def tcp_round_trip(
     sock: socket.socket, msg: dns.message.Message
 ) -> dns.message.Message:
@@ -133,3 +143,47 @@ def test_tcp_big(ns7: NamedInstance, named_port: int) -> None:
             "a.example.", "A", dnssec=False, use_edns=-1, ad=False
         )
         tcp_round_trip(sock, msg)
+
+
+def wait_for_stable_tcp_requests(ns: NamedInstance, timeout: int = 10) -> int:
+    """Read the TCP request counter until it stops changing.
+
+    The counter is incremented on request receipt, so a client response
+    implies its upstream queries are already counted; this only needs to
+    absorb unsynchronized traffic such as the resolver's root priming query.
+    """
+    last = -1
+
+    def stable() -> bool:
+        nonlocal last
+        previous, last = last, tcp_requests_received(ns)
+        return previous == last
+
+    isctest.run.retry_with_timeout(stable, timeout=timeout)
+    return last
+
+
+def test_tcp_request_statistics(
+    ns1: NamedInstance, ns2: NamedInstance, ns3: NamedInstance, ns4: NamedInstance
+) -> None:
+    isctest.log.info("initializing TCP statistics")
+    ns1_tcp = tcp_requests_received(ns1)
+    ns2_tcp = tcp_requests_received(ns2)
+
+    isctest.log.info("checking TCP request statistics (resolver)")
+    msg = isctest.query.create("txt.example.", "A")
+    isctest.query.udp(msg, ns3.ip, expected_rcode=dns.rcode.NXDOMAIN)
+
+    ns1_tcp_after_resolver = wait_for_stable_tcp_requests(ns1)
+    ns2_tcp_after_resolver = wait_for_stable_tcp_requests(ns2)
+    assert ns1_tcp < ns1_tcp_after_resolver
+    assert ns2_tcp == ns2_tcp_after_resolver
+
+    isctest.log.info("checking TCP request statistics (forwarder)")
+    msg = isctest.query.create("txt.example.", "A")
+    isctest.query.udp(msg, ns4.ip, expected_rcode=dns.rcode.NXDOMAIN)
+
+    ns1_tcp_after_forwarder = wait_for_stable_tcp_requests(ns1)
+    ns2_tcp_after_forwarder = wait_for_stable_tcp_requests(ns2)
+    assert ns1_tcp_after_resolver == ns1_tcp_after_forwarder
+    assert ns2_tcp_after_resolver < ns2_tcp_after_forwarder