feat(firecracker): persistent infra-VM singleton lifecycle (Stage B, 4/n)
The infra VM must outlive the short-lived `start` launcher and be reused across launches. Add an idempotent singleton: - `ensure_running()` adopts the infra VM when its control plane is already healthy (a prior launcher booted it), else clears any stale VM and boots a fresh one. Returns a handle usable for CA fetch / git-gate provisioning whether we booted it or adopted it. - boot is `detached` (firecracker in its own session via start_new_session) so it survives the launcher exiting; its PID is recorded so a later process can `stop()` it. `_kill_pidfile` SIGTERM/SIGKILLs but only if the PID is still a firecracker process (guards a recycled PID). - a STABLE SSH key (generated once under the infra cache dir, re-injected each boot via the cmdline) so any launcher can SSH in to fetch the CA / provision, not just the one that booted the VM. `InfraVm.vm` is None in the adopted case; teardown then goes through the PID file. Verified on a KVM host: first ensure_running boots; a second adopts it (same PID, no reboot) and can still reach /health and fetch the gateway CA over SSH; stop() tears it down (control plane then unreachable). Next: git-gate provisioning into the VM over SSH (today docker exec/cp), then swap consolidated_launch.py onto the infra VM. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -118,9 +118,15 @@ def boot(
|
||||
vcpus: int = 2,
|
||||
mem_mib: int = 2048,
|
||||
guest_mac: str = "06:00:AC:10:00:02",
|
||||
detached: bool = False,
|
||||
) -> VmHandle:
|
||||
"""Write the config and launch the VMM. Returns once the process is
|
||||
spawned; callers wait for SSH readiness separately."""
|
||||
spawned; callers wait for SSH readiness separately.
|
||||
|
||||
`detached` starts the VMM in its own session (`start_new_session`) so it
|
||||
survives the launcher exiting — used for the persistent per-host infra
|
||||
VM, which must outlive the short-lived `start` process (agent VMs stay
|
||||
attached and are torn down with the launcher)."""
|
||||
run_dir.mkdir(parents=True, exist_ok=True)
|
||||
config_path = run_dir / "config.json"
|
||||
console_log = run_dir / "console.log"
|
||||
@@ -137,6 +143,7 @@ def boot(
|
||||
process = subprocess.Popen(
|
||||
["firecracker", "--no-api", "--config-file", str(config_path)],
|
||||
stdout=log_fh, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL,
|
||||
start_new_session=detached,
|
||||
)
|
||||
return VmHandle(process=process, guest_ip=guest_ip, console_log=console_log)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user