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