mirror of
https://github.com/dtzp555-max/aci-sim.git
synced 2026-07-19 09:46:33 +00:00
aci-sim is a stateful REST simulator of a 2-site Cisco ACI fabric (per-site APIC + ND/NDO management planes) — for testing ACI automation (Ansible cisco.aci / cisco.mso, aci-py, custom REST clients) and CI gates without real hardware. Highlights: APIC + NDO REST surface served from one in-memory MIT built from a declarative topology.yaml; port mode + sandbox (real per-device IPs on :443) run modes; LLDP/CDP neighbor visibility; NDO->APIC deploy mirror (multi-site templates materialize onto the target sites' APIC stores); real-APIC fvBD default attributes; file-backed state save/restore; an `aci-sim` CLI (validate/show/graph/run/new/init/lldp); and an 888-test suite. History squashed for the public release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
117 lines
4.4 KiB
Bash
Executable File
117 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Sandbox virtual network — give each ACI controller a real IP on :443 via a
|
|
# loopback alias (macOS: lo0 alias; Linux: ip addr add ... dev lo), so autoACI
|
|
# connects to it by IP with NO port, exactly like real gear. autoACI is NOT
|
|
# modified in any way. Requires root (loopback alias + privileged :443).
|
|
#
|
|
# sudo bash scripts/sandbox-up.sh
|
|
#
|
|
# Safe to re-run: it stops any previous sim (verifying the recorded pid is
|
|
# actually a supervisor process before killing it — #24), waits for :443 to
|
|
# actually free up on each sandbox IP (avoids the bind race that silently
|
|
# killed old restarts), then verifies the servers really came up — failing
|
|
# loudly with the log if not.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
PY="./.venv/bin/python"
|
|
|
|
OS="$(uname -s)"
|
|
|
|
# Sandbox IPs come straight from topology.yaml (single source of truth).
|
|
IPS=$(PYTHONPATH="$PWD" "$PY" -c "from aci_sim.topology.loader import load_topology; t=load_topology('topology.yaml'); print(' '.join([t.fabric.ndo_mgmt_ip]+[s.mgmt_ip for s in t.sites if s.mgmt_ip]))")
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Needs root (loopback alias + bind :443). Re-run: sudo bash scripts/sandbox-up.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Return 0 if (ip, 443) is bindable RIGHT NOW — same options uvicorn will use
|
|
# (SO_REUSEADDR), so this gate matches exactly what the supervisor is about to do.
|
|
port_free() {
|
|
"$PY" -c 'import socket,sys
|
|
s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
|
|
try:
|
|
s.bind((sys.argv[1],443)); s.close()
|
|
except OSError:
|
|
sys.exit(1)' "$1"
|
|
}
|
|
|
|
# Verify a recorded pid really is our supervisor process before we kill it —
|
|
# a stale /tmp pid file could otherwise reference an unrelated (possibly
|
|
# security-sensitive) process that has since reused that pid (#24).
|
|
pid_is_supervisor() {
|
|
local pid="$1"
|
|
if [ "$OS" = "Linux" ]; then
|
|
[ -r "/proc/$pid/cmdline" ] || return 1
|
|
tr '\0' ' ' <"/proc/$pid/cmdline" 2>/dev/null | grep -q "aci_sim.runtime.supervisor"
|
|
else
|
|
ps -p "$pid" -o command= 2>/dev/null | grep -q "aci_sim.runtime.supervisor"
|
|
fi
|
|
}
|
|
|
|
echo "[sandbox] loopback aliases ($OS): $IPS"
|
|
for ip in $IPS; do
|
|
if [ "$OS" = "Linux" ]; then
|
|
ip addr add "${ip}/32" dev lo 2>/dev/null || true
|
|
else
|
|
ifconfig lo0 alias "$ip" 255.255.255.255 up 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
[ -f certs/sim.crt ] || bash scripts/gen_certs.sh
|
|
|
|
# Stop any previous sim, then WAIT for it to die and for :443 to actually free up
|
|
# on every sandbox IP before starting — starting while the old listener is still
|
|
# holding the socket is what made uvicorn sys.exit(1) silently.
|
|
if [ -f /tmp/aci-sim-sandbox.pid ]; then
|
|
old_pid="$(cat /tmp/aci-sim-sandbox.pid)"
|
|
if pid_is_supervisor "$old_pid"; then
|
|
kill "$old_pid" 2>/dev/null || true
|
|
else
|
|
echo "[sandbox] WARNING: /tmp/aci-sim-sandbox.pid ($old_pid) is not an" >&2
|
|
echo "[sandbox] aci_sim.runtime.supervisor process — refusing to kill it." >&2
|
|
fi
|
|
fi
|
|
pkill -f "aci_sim.runtime.supervisor" 2>/dev/null || true
|
|
for i in $(seq 1 40); do # up to ~20s
|
|
pgrep -f "aci_sim.runtime.supervisor" >/dev/null 2>&1 && { sleep 0.5; continue; }
|
|
free=1
|
|
for ip in $IPS; do port_free "$ip" || { free=0; break; }; done
|
|
[ "$free" = 1 ] && break
|
|
sleep 0.5
|
|
done
|
|
|
|
LOG=/tmp/aci-sim-sandbox.log
|
|
export SIM_SANDBOX=1 PYTHONPATH="$PWD"
|
|
nohup "$PY" -m aci_sim.runtime.supervisor >"$LOG" 2>&1 &
|
|
PID=$!
|
|
echo "$PID" >/tmp/aci-sim-sandbox.pid
|
|
ndo_ip=${IPS%% *}
|
|
|
|
# Verify the servers actually came up on :443 (loud, not silent). Any HTTP code
|
|
# (200/401/404) means the server answered; 000 means nothing is listening yet.
|
|
up=0
|
|
for i in $(seq 1 24); do # up to ~12s
|
|
kill -0 "$PID" 2>/dev/null || break # process already died → stop, dump log below
|
|
all=1
|
|
for ip in $IPS; do
|
|
code=$(curl -sk -o /dev/null -w '%{http_code}' --max-time 2 "https://${ip}:443/" 2>/dev/null || echo 000)
|
|
[ "$code" = 000 ] && { all=0; break; }
|
|
done
|
|
[ "$all" = 1 ] && { up=1; break; }
|
|
sleep 0.5
|
|
done
|
|
|
|
if [ "$up" -ne 1 ]; then
|
|
echo "[sandbox] ERROR: sim did not come up on :443 for all of: $IPS" >&2
|
|
echo "----- last 25 log lines ($LOG) -----------------------------------" >&2
|
|
tail -n 25 "$LOG" >&2 || true
|
|
echo "------------------------------------------------------------------" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[sandbox] up — aliases: $IPS"
|
|
echo "[sandbox] sim PID $PID serving :443 (log: $LOG)"
|
|
echo "[sandbox] autoACI → log in to NDO at $ndo_ip → Discover → Connect All Sites"
|
|
echo "[sandbox] stop with: sudo bash scripts/sandbox-down.sh"
|