/ tests / chutney / integration-e2e-shadow
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()