feat(ip-pool): pre-assign static IPs to build VMs via VMX guestinfo
Eliminates VMware-Tools IP-detect polling (observed: 39–177 s variance on
Linux host B7 benchmark). When [ip_pool] is configured the orchestrator:
1. Allocates a slot IP from a JSON-backed pool (fcntl-locked on Linux)
2. Injects guestinfo.ip-assignment / netmask / gateway into the cloned VMX
before vmrun start
3. Skips _wait_for_ip entirely — uses the pre-assigned IP immediately
4. Releases the slot in _destroy_clone (all paths: success/failure/interrupt)
New modules / changes:
src/ci_orchestrator/ip_pool.py IpSlotPool + _file_lock context manager
src/ci_orchestrator/config.py IpPoolConfig dataclass; [ip_pool] TOML
src/ci_orchestrator/commands/job.py _inject_guestinfo_ip helper; pool wiring
tests/python/test_ip_pool.py 12 unit tests (acquire/release/persist)
tests/python/test_config.py ip_pool config parsing tests
tests/python/test_commands_job.py pool path + _inject_guestinfo_ip tests
config.example.toml [ip_pool] section (commented example)
Coverage 90.02%; mypy --strict clean.
This commit is contained in:
@@ -22,6 +22,9 @@ from typing import Any
|
||||
|
||||
_ENV_KEYS = ("CI_ROOT", "CI_TEMPLATES", "CI_BUILD_VMS", "CI_ARTIFACTS", "CI_KEYS")
|
||||
|
||||
_IP_POOL_LEASE_DEFAULT_LINUX = "/var/lib/ci/ip-pool.json"
|
||||
_IP_POOL_LEASE_DEFAULT_WIN = r"F:\CI\ip-pool.json"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Paths:
|
||||
@@ -34,6 +37,26 @@ class Paths:
|
||||
keys: Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IpPoolConfig:
|
||||
"""Pre-assigned IP pool for zero-polling VM ready detection.
|
||||
|
||||
When present, the orchestrator injects ``guestinfo.ip-assignment`` into
|
||||
the cloned VMX before starting the VM so the guest can apply a static IP
|
||||
at boot (see ``template/guest-setup/``).
|
||||
"""
|
||||
|
||||
addresses: list[str]
|
||||
netmask: str = "255.255.255.0"
|
||||
gateway: str = ""
|
||||
lease_file: Path = field(
|
||||
default_factory=lambda: Path(
|
||||
_IP_POOL_LEASE_DEFAULT_WIN if os.name == "nt"
|
||||
else _IP_POOL_LEASE_DEFAULT_LINUX
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BackendConfig:
|
||||
"""Backend selector. ``type`` is currently always 'workstation'."""
|
||||
@@ -51,6 +74,7 @@ class Config:
|
||||
vmrun_path: str | None = None
|
||||
ssh_key_path: Path | None = None
|
||||
guest_cred_target: str = "BuildVMGuest"
|
||||
ip_pool: IpPoolConfig | None = None
|
||||
|
||||
|
||||
def _platform_defaults() -> Paths:
|
||||
@@ -133,6 +157,7 @@ def load_config(
|
||||
vmrun_path: str | None = env.get("CI_VMRUN_PATH")
|
||||
ssh_key_path = Path(env["CI_SSH_KEY_PATH"]) if env.get("CI_SSH_KEY_PATH") else None
|
||||
guest_cred_target = env.get("CI_GUEST_CRED_TARGET", "BuildVMGuest")
|
||||
ip_pool: IpPoolConfig | None = None
|
||||
|
||||
if toml_path and toml_path.is_file():
|
||||
data = _load_toml(toml_path)
|
||||
@@ -148,6 +173,20 @@ def load_config(
|
||||
if ssh_key_path is None and data.get("ssh_key_path"):
|
||||
ssh_key_path = Path(data["ssh_key_path"])
|
||||
guest_cred_target = data.get("guest_cred_target", guest_cred_target)
|
||||
pool_section = data.get("ip_pool", {})
|
||||
if pool_section and pool_section.get("addresses"):
|
||||
addrs = pool_section["addresses"]
|
||||
if isinstance(addrs, list) and addrs:
|
||||
_lease_default = (
|
||||
_IP_POOL_LEASE_DEFAULT_WIN if os.name == "nt"
|
||||
else _IP_POOL_LEASE_DEFAULT_LINUX
|
||||
)
|
||||
ip_pool = IpPoolConfig(
|
||||
addresses=[str(a) for a in addrs],
|
||||
netmask=str(pool_section.get("netmask", "255.255.255.0")),
|
||||
gateway=str(pool_section.get("gateway", "")),
|
||||
lease_file=Path(pool_section.get("lease_file") or _lease_default),
|
||||
)
|
||||
|
||||
paths = _apply_env(paths, env)
|
||||
|
||||
@@ -157,7 +196,8 @@ def load_config(
|
||||
vmrun_path=vmrun_path,
|
||||
ssh_key_path=ssh_key_path,
|
||||
guest_cred_target=guest_cred_target,
|
||||
ip_pool=ip_pool,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["BackendConfig", "Config", "Paths", "load_config"]
|
||||
__all__ = ["BackendConfig", "Config", "IpPoolConfig", "Paths", "load_config"]
|
||||
|
||||
Reference in New Issue
Block a user