Files
fips/testing/chaos/sim/compose.py
T
Johnathan Corgan 5be7c6d0cb Give each chaos scenario its own container names and config directory
Chaos scenarios run four at a time under local CI, but every one of them claimed the container names fips-node-nNN and wrote its generated configs and compose file to the same generated-configs/sim directory. Container names are global in Docker and are not scoped by the compose project, so concurrent scenarios collided on both, and a scenario could start containers from a compose file another had overwritten.

Thread the existing FIPS_CI_NAME_SUFFIX into the simulation. run_chaos narrows the run-wide suffix to the scenario, and the sim reads it once when the topology is built, applying it to the container names and to the config directory basename. The compose template renders the name through the topology accessor instead of duplicating the literal, so one expression produces every chaos container name.

The suffix is empty when the variable is unset, so a bare chaos.sh run and the hosted CI jobs render byte-identical names and paths. Verified by rendering every scenario's compose file before and after with the variable unset and diffing.
2026-07-22 07:35:11 +00:00

93 lines
2.3 KiB
Python

"""Generate docker-compose.yml for a simulation topology."""
from __future__ import annotations
import os
from jinja2 import Template
from .scenario import Scenario
from .topology import SimTopology
# Image name for the pre-built FIPS test image.
# The runner builds this once before starting containers.
FIPS_SIM_IMAGE = "fips-test:latest"
# Jinja2 template for the compose file.
# Uses a pre-built image instead of per-service build to support large topologies.
_COMPOSE_TEMPLATE = Template(
"""\
networks:
fips-net:
driver: bridge
labels:
- "com.corganlabs.fips-ci=1"
ipam:
config:
- subnet: {{ subnet }}
x-fips-common: &fips-common
image: {{ image }}
cap_add:
- NET_ADMIN
- NET_RAW
devices:
- /dev/net/tun:/dev/net/tun
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
restart: "no"
env_file:
- ./npubs.env
environment:
- RUST_LOG={{ rust_log }}
- RUST_BACKTRACE=1
- FIPS_TEST_MODE=chaos
services:
{% for node in nodes %}
{{ node.node_id }}:
<<: *fips-common
container_name: {{ topology.container_name(node.node_id) }}
hostname: {{ node.node_id }}
volumes:
- ./{{ node.node_id }}.yaml:/etc/fips/fips.yaml:ro
- {{ resolv_conf }}:/etc/resolv.conf:ro
networks:
fips-net:
ipv4_address: {{ node.docker_ip }}
{% endfor %}
"""
)
def generate_compose(
topology: SimTopology,
scenario: Scenario,
output_dir: str,
) -> str:
"""Render docker-compose.yml and write to output_dir. Returns the file path."""
os.makedirs(output_dir, exist_ok=True)
nodes = [topology.nodes[nid] for nid in sorted(topology.nodes)]
# Absolute path to the shared resolv.conf (bind-mounted into containers
# so Docker's runtime resolv.conf generation doesn't overwrite it).
resolv_conf = os.path.normpath(
os.path.join(os.path.dirname(__file__), "..", "..", "docker", "resolv.conf")
)
content = _COMPOSE_TEMPLATE.render(
subnet=scenario.topology.subnet,
rust_log=scenario.logging.rust_log,
image=FIPS_SIM_IMAGE,
nodes=nodes,
resolv_conf=resolv_conf,
topology=topology,
)
path = os.path.join(output_dir, "docker-compose.yml")
with open(path, "w") as f:
f.write(content)
return path