integration-e2e-shadow
1 #!/usr/bin/env python3 2 """ 3 Run integration-e2e inside the shadow network simulator. This can be helpful 4 vs running it directly for several reasons. 5 6 * shadow simulates time, and can collapse idle time. This speeds up the 7 network bootstrapping step in particular. 8 * shadow tries to be deterministic. There are some gaps, but in general 9 there *should* be less nondeterministic flakiness under shadow 10 than when running natively. 11 """ 12 13 import os 14 import pathlib 15 import shutil 16 import subprocess 17 import yaml 18 19 from typing import Optional, Any 20 21 SHADOW_DATA_DIR = "shadow.chutney.data" 22 SHADOW_CONFIG_FILE = "shadow.chutney.yaml" 23 SHADOW_LOG_FILE = "shadow.log" 24 # must be syncd with `TEST_DOMAIN` in `tests/chutney/test`. 25 TEST_DOMAIN = "example.com" 26 27 28 def gen_shadow_config(chutney_bin: Optional[str]) -> dict[str, Any]: 29 """ 30 Generate a shadow config file, as a string, for the given parameters. 31 """ 32 33 env = { 34 "RUNNING_IN_SHADOW": "yes", 35 # unix sockets aren't supported in shadow 36 "CHUTNEY_ENABLE_CONTROLSOCKET": "no", 37 # ipv6 isn't supported in shadow 38 "CHUTNEY_DISABLE_IPV6": "yes", 39 # re-export PATH. The test scripts assume that 40 # usual shell utilities are on it. 41 "PATH": os.getenv("PATH"), 42 } 43 if chutney_bin is not None: 44 env["CHUTNEY_BIN"] = chutney_bin 45 46 return { 47 "general": { 48 "stop_time": "10m", 49 }, 50 "network": { 51 "graph": {"type": "1_gbit_switch"}, 52 }, 53 "hosts": { 54 "host": { 55 "network_node_id": 0, 56 "processes": [ 57 { 58 "path": "./tests/chutney/integration-e2e", 59 "environment": env, 60 # Give the web server below a little time to start. 61 "start_time": "5s", 62 } 63 ], 64 }, 65 TEST_DOMAIN: { 66 "network_node_id": 0, 67 "processes": [ 68 { 69 "path": "python3", 70 "args": "-m http.server 80", 71 "start_time": "0", 72 "expected_final_state": "running", 73 } 74 ], 75 }, 76 }, 77 } 78 79 80 def main(): 81 toplevel = pathlib.Path( 82 os.fsdecode( 83 subprocess.check_output("git rev-parse --show-toplevel", shell=True) 84 ).strip() 85 ) 86 os.chdir(toplevel) 87 88 # Get CHUTNEY_BIN if it's set. 89 chutney_bin = os.getenv("CHUTNEY_BIN", None) 90 91 # Write out shadow config. We could just pipe it directly to the shadow 92 # process below, but writing it out is useful for debugging. 93 shadow_config = gen_shadow_config(chutney_bin) 94 with open(SHADOW_CONFIG_FILE, "w") as f: 95 f.write(yaml.safe_dump(shadow_config)) 96 97 # Remove shadow's data dir. (It will bail if the directory already exists) 98 if os.path.isdir(SHADOW_DATA_DIR): 99 shutil.rmtree(SHADOW_DATA_DIR) 100 101 shadow_args = [ 102 "shadow", 103 "--data-directory=" + SHADOW_DATA_DIR, 104 "--progress=true", 105 "--model-unblocked-syscall-latency=true", 106 SHADOW_CONFIG_FILE, 107 ] 108 with open(SHADOW_LOG_FILE, "w") as shadow_log_file: 109 subprocess.run(shadow_args, check=True, stdout=shadow_log_file) 110 111 112 if __name__ == "__main__": 113 main()