1# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
2#
3# SPDX-License-Identifier: MPL-2.0
4#
5# This Source Code Form is subject to the terms of the Mozilla Public
6# License, v. 2.0.  If a copy of the MPL was not distributed with this
7# file, you can obtain one at https://mozilla.org/MPL/2.0/.
8#
9# See the COPYRIGHT file distributed with this work for additional
10# information regarding copyright ownership.
11
12import concurrent.futures
13import os
14import subprocess
15import time
16import dns.query
17import dns.update
18
19
20def rndc_loop(test_state, server):
21    rndc = os.getenv("RNDC")
22    port = os.getenv("CONTROLPORT")
23
24    cmdline = [
25        rndc,
26        "-c",
27        "../_common/rndc.conf",
28        "-p",
29        port,
30        "-s",
31        server,
32        "reload",
33    ]
34
35    while not test_state["finished"]:
36        subprocess.run(cmdline, check=False)
37        time.sleep(1)
38
39
40def update_zone(test_state, zone, named_port, logger):
41    server = "10.53.0.2"
42    for i in range(1000):
43        if test_state["finished"]:
44            return
45        update = dns.update.UpdateMessage(zone)
46        update.add(f"dynamic-{i}.{zone}", 300, "TXT", f"txt-{i}")
47        try:
48            response = dns.query.udp(update, server, 10, named_port)
49            assert response.rcode() == dns.rcode.NOERROR
50        except dns.exception.Timeout:
51            logger.info(f"error: query timeout for {zone}")
52
53    logger.info(f"Update of {server} zone {zone} successful")
54
55
56# If the test has run to completion without named crashing, it has succeeded.
57def test_update_stress(named_port, logger):
58    test_state = {"finished": False}
59
60    with concurrent.futures.ThreadPoolExecutor() as executor:
61        executor.submit(rndc_loop, test_state, "10.53.0.3")
62
63        updaters = []
64        for i in range(5):
65            zone = f"zone00000{i}.example."
66            updaters.append(
67                executor.submit(update_zone, test_state, zone, named_port, logger)
68            )
69
70        # All the update_zone() tasks are expected to complete within 5
71        # minutes.  If they do not, we cannot assert immediately as that will
72        # cause the ThreadPoolExecutor context manager to wait indefinitely;
73        # instead, we first signal all tasks that it is time to exit and only
74        # check whether any task failed to finish within 5 minutes outside of
75        # the ThreadPoolExecutor context manager.
76        unfinished_tasks = concurrent.futures.wait(updaters, timeout=300).not_done
77        test_state["finished"] = True
78
79    assert not unfinished_tasks
80