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 16 17 18def run_rndc(server, rndc_command): 19 """ 20 Send the specified 'rndc_command' to 'server' with a timeout of 10 seconds 21 """ 22 rndc = os.getenv("RNDC") 23 port = os.getenv("CONTROLPORT") 24 25 cmdline = [rndc, "-c", "../_common/rndc.conf", "-p", port, "-s", server] 26 cmdline.extend(rndc_command) 27 28 subprocess.check_output(cmdline, stderr=subprocess.STDOUT, timeout=10) 29 30 31def rndc_loop(test_state, domain): 32 """ 33 Run "rndc addzone", "rndc modzone", and "rndc delzone" in a tight loop 34 until the test is considered finished, ignoring errors 35 """ 36 rndc_commands = [ 37 ["addzone", domain, '{ type primary; file "example.db"; };'], 38 [ 39 "modzone", 40 domain, 41 '{ type primary; file "example.db"; allow-transfer { any; }; };', 42 ], 43 ["delzone", domain], 44 ] 45 46 while not test_state["finished"]: 47 for command in rndc_commands: 48 try: 49 run_rndc("10.53.0.3", command) 50 except subprocess.SubprocessError: 51 pass 52 53 54def check_if_server_is_responsive(): 55 """ 56 Check if server status can be successfully retrieved using "rndc status" 57 """ 58 try: 59 run_rndc("10.53.0.3", ["status"]) 60 return True 61 except subprocess.SubprocessError: 62 return False 63 64 65def test_rndc_deadlock(): 66 """ 67 Test whether running "rndc addzone", "rndc modzone", and "rndc delzone" 68 commands concurrently does not trigger a deadlock 69 """ 70 test_state = {"finished": False} 71 72 # Create 4 worker threads running "rndc" commands in a loop. 73 with concurrent.futures.ThreadPoolExecutor() as executor: 74 for i in range(1, 5): 75 domain = "example%d" % i 76 executor.submit(rndc_loop, test_state, domain) 77 78 # Run "rndc status" 10 times, with 1-second pauses between attempts. 79 # Each "rndc status" invocation has a timeout of 10 seconds. If any of 80 # them fails, the loop will be interrupted. 81 server_is_responsive = True 82 attempts = 10 83 while server_is_responsive and attempts > 0: 84 server_is_responsive = check_if_server_is_responsive() 85 attempts -= 1 86 time.sleep(1) 87 88 # Signal worker threads that the test is finished. 89 test_state["finished"] = True 90 91 # Check whether all "rndc status" commands succeeded. 92 assert server_is_responsive 93