fix(ip-pool): reconcile stale leases against live clones (§2.8, W1)

A job killed mid-flight never released its IP lease; with only 4 pool addresses a single leak exhausted the pool ("IP pool exhausted after 60s"). IpSlotPool.reconcile() releases leases whose owner clone-dir is gone; vm cleanup runs it only against the real build_vms base. Closes TODO §2.8.
This commit is contained in:
2026-07-23 17:15:23 +02:00
parent b0415f36e4
commit f0d856b644
8 changed files with 263 additions and 3 deletions
+19
View File
@@ -27,6 +27,7 @@ from ci_orchestrator.backends import load_backend
from ci_orchestrator.backends.errors import BackendError, BackendNotAvailable
from ci_orchestrator.backends.protocol import VmHandle
from ci_orchestrator.config import load_config
from ci_orchestrator.ip_pool import IpSlotPool
from ci_orchestrator.operations import vms as ops_vms
if TYPE_CHECKING: # pragma: no cover
@@ -307,6 +308,24 @@ def vm_cleanup(
f"[vm cleanup] removed stale lock file (age: {lock_age_min} min): {lock_path}"
)
# Reconcile the IP pool against surviving clones (TODO §2.8). Only when
# cleaning the *real* clone base — the lease owners are clone names created
# under config.paths.build_vms, so reconciling a custom --clone-base-dir
# would wrongly release every live lease.
if config.ip_pool is not None and base_path.resolve() == config.paths.build_vms.resolve():
pool = IpSlotPool(
addresses=config.ip_pool.addresses,
netmask=config.ip_pool.netmask,
gateway=config.ip_pool.gateway,
lease_file=config.ip_pool.lease_file,
)
released = ops_vms.reconcile_ip_leases(base_path, pool, dry_run=what_if)
if released:
verb = "would release" if what_if else "released"
click.echo(
f"[vm cleanup] {verb} {len(released)} stale IP lease(s): {', '.join(released)}"
)
click.echo("[vm cleanup] done.")