#!/usr/bin/env python3 """ Run integration-e2e inside the shadow network simulator. This can be helpful vs running it directly for several reasons. * shadow simulates time, and can collapse idle time. This speeds up the network bootstrapping step in particular. * shadow tries to be deterministic. There are some gaps, but in general there *should* be less nondeterministic flakiness under shadow than when running natively. """ import os import pathlib import shutil import subprocess import yaml from typing import Optional, Any SHADOW_DATA_DIR = "shadow.chutney.data" SHADOW_CONFIG_FILE = "shadow.chutney.yaml" SHADOW_LOG_FILE = "shadow.log" # must be syncd with `TEST_DOMAIN` in `tests/chutney/test`. TEST_DOMAIN = "example.com" def gen_shadow_config(chutney_bin: Optional[str]) -> dict[str, Any]: """ Generate a shadow config file, as a string, for the given parameters. """ env = { "RUNNING_IN_SHADOW": "yes", # unix sockets aren't supported in shadow "CHUTNEY_ENABLE_CONTROLSOCKET": "no", # ipv6 isn't supported in shadow "CHUTNEY_DISABLE_IPV6": "yes", # re-export PATH. The test scripts assume that # usual shell utilities are on it. "PATH": os.getenv("PATH"), } if chutney_bin is not None: env["CHUTNEY_BIN"] = chutney_bin return { "general": { "stop_time": "10m", }, "network": { "graph": {"type": "1_gbit_switch"}, }, "hosts": { "host": { "network_node_id": 0, "processes": [ { "path": "./tests/chutney/integration-e2e", "environment": env, # Give the web server below a little time to start. "start_time": "5s", } ], }, TEST_DOMAIN: { "network_node_id": 0, "processes": [ { "path": "python3", "args": "-m http.server 80", "start_time": "0", "expected_final_state": "running", } ], }, }, } def main(): toplevel = pathlib.Path( os.fsdecode( subprocess.check_output("git rev-parse --show-toplevel", shell=True) ).strip() ) os.chdir(toplevel) # Get CHUTNEY_BIN if it's set. chutney_bin = os.getenv("CHUTNEY_BIN", None) # Write out shadow config. We could just pipe it directly to the shadow # process below, but writing it out is useful for debugging. shadow_config = gen_shadow_config(chutney_bin) with open(SHADOW_CONFIG_FILE, "w") as f: f.write(yaml.safe_dump(shadow_config)) # Remove shadow's data dir. (It will bail if the directory already exists) if os.path.isdir(SHADOW_DATA_DIR): shutil.rmtree(SHADOW_DATA_DIR) shadow_args = [ "shadow", "--data-directory=" + SHADOW_DATA_DIR, "--progress=true", "--model-unblocked-syscall-latency=true", SHADOW_CONFIG_FILE, ] with open(SHADOW_LOG_FILE, "w") as shadow_log_file: subprocess.run(shadow_args, check=True, stdout=shadow_log_file) if __name__ == "__main__": main()