mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Move examples/docker-network/ to testing/static/ and add testing/chaos/ as a new stochastic simulation harness. testing/static/ — Static 5-node test harness: - Fixed mesh, chain, and mesh-public topologies with docker compose - Manual test scripts (ping, iperf, netem) - Build script, config generation, key derivation testing/chaos/ — Stochastic network simulation: - Python orchestrator generating N-node FIPS meshes with dynamic network conditions, driven by reproducible YAML scenarios - Topology generation: random geometric, Erdos-Renyi, or chain graphs with BFS connectivity guarantee - Per-link netem: HTB classful qdiscs with u32 filters for per-peer impairment (delay, loss, jitter), stochastic mutation across configurable policy profiles - Per-link bandwidth pacing: HTB rate limiting with configurable tiers (1/10/100/1000 mbps) randomly assigned per edge - Link flaps: tc netem 100% loss with graph connectivity protection - Node churn: docker stop/start with netem re-application on restart, shared down_nodes tracking across all managers - Traffic generation: random iperf3 sessions between node pairs - Down-node guards: all docker exec callers check container liveness, auto-detect crashed containers via is_container_running() safety net - Log collection and post-run analysis (panics, errors, sessions, MMP metrics, tree reconvergence) - chaos.sh wrapper with --seed, --duration, --verbose, --list options - Four scenarios: smoke-10, chaos-10, churn-10, churn-20
72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
"""Thin wrapper around subprocess for docker exec calls."""
|
|
|
|
import subprocess
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class DockerExecError(Exception):
|
|
def __init__(self, container, cmd, returncode, stderr):
|
|
self.container = container
|
|
self.cmd = cmd
|
|
self.returncode = returncode
|
|
self.stderr = stderr
|
|
super().__init__(
|
|
f"docker exec {container}: {cmd!r} returned {returncode}: {stderr}"
|
|
)
|
|
|
|
|
|
def docker_exec(container: str, cmd: str, timeout: int = 30) -> str:
|
|
"""Execute a command in a running container, return stdout."""
|
|
result = subprocess.run(
|
|
["docker", "exec", container, "/bin/bash", "-c", cmd],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
if result.returncode != 0:
|
|
raise DockerExecError(container, cmd, result.returncode, result.stderr)
|
|
return result.stdout
|
|
|
|
|
|
def docker_exec_quiet(container: str, cmd: str, timeout: int = 30) -> str | None:
|
|
"""Execute a command, return stdout on success or None on failure (logged)."""
|
|
try:
|
|
return docker_exec(container, cmd, timeout)
|
|
except (DockerExecError, subprocess.TimeoutExpired) as e:
|
|
log.warning("docker exec failed on %s: %s", container, e)
|
|
return None
|
|
|
|
|
|
def docker_compose(
|
|
compose_file: str,
|
|
args: list[str],
|
|
timeout: int = 300,
|
|
check: bool = True,
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run a docker compose command with the given compose file."""
|
|
cmd = ["docker", "compose", "-f", compose_file] + args
|
|
log.info("Running: %s", " ".join(cmd))
|
|
return subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
check=check,
|
|
)
|
|
|
|
|
|
def is_container_running(container: str) -> bool:
|
|
"""Check if a container is running."""
|
|
try:
|
|
result = subprocess.run(
|
|
["docker", "inspect", "-f", "{{.State.Running}}", container],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
return result.returncode == 0 and result.stdout.strip() == "true"
|
|
except subprocess.TimeoutExpired:
|
|
return False
|