Expand lab seed tiers, OpenAPI PARAMS, and console DATA/Params UX.
This commit is contained in:
+152
-42
@@ -40,6 +40,7 @@ class VsphereSeedProfile:
|
||||
permissions: tuple[PermissionSpec, ...]
|
||||
host_count: int
|
||||
vm_count: int
|
||||
extras_scale: int = 1
|
||||
|
||||
|
||||
POWER_CYCLE = ("POWERED_ON", "POWERED_ON", "POWERED_ON", "POWERED_OFF", "SUSPENDED")
|
||||
@@ -63,6 +64,7 @@ def _topology(
|
||||
host_count: int,
|
||||
datastore_count: int = 4,
|
||||
network_count: int = 3,
|
||||
extra_vm_folders: int = 0,
|
||||
) -> list[ObjectSpec]:
|
||||
specs: list[ObjectSpec] = [
|
||||
ObjectSpec("group-d1", "Folder", "Datacenters", None, {"folder_type": "DATACENTER"}),
|
||||
@@ -111,6 +113,16 @@ def _topology(
|
||||
"group-v102", "Folder", "templates", "group-v23", {"folder_type": "VIRTUAL_MACHINE"}
|
||||
),
|
||||
]
|
||||
for index in range(extra_vm_folders):
|
||||
specs.append(
|
||||
ObjectSpec(
|
||||
f"group-v{103 + index}",
|
||||
"Folder",
|
||||
f"team-{index + 1:02d}",
|
||||
"group-v23",
|
||||
{"folder_type": "VIRTUAL_MACHINE"},
|
||||
)
|
||||
)
|
||||
for index in range(1, host_count + 1):
|
||||
moid = f"host-{10 + index}"
|
||||
specs.append(
|
||||
@@ -291,7 +303,13 @@ def _vm_device_props(*, name: str, power: str, index: int, nic_mac: str) -> dict
|
||||
}
|
||||
|
||||
|
||||
def _vm_spec(index: int, *, host_count: int) -> ObjectSpec:
|
||||
def _vm_spec(
|
||||
index: int,
|
||||
*,
|
||||
host_count: int,
|
||||
datastore_count: int = 4,
|
||||
folder_choices: tuple[str, ...] | None = None,
|
||||
) -> ObjectSpec:
|
||||
moid = f"vm-{100 + index}"
|
||||
role = ROLE_PREFIX[index % len(ROLE_PREFIX)]
|
||||
name = f"{role}-{index:04d}"
|
||||
@@ -299,9 +317,10 @@ def _vm_spec(index: int, *, host_count: int) -> ObjectSpec:
|
||||
host = f"host-{10 + (index % host_count) + 1}"
|
||||
cpus = 1 + (index % 8)
|
||||
memory = 1024 * (1 + (index % 16))
|
||||
folder = ("group-v100", "group-v101", "group-v23")[index % 3]
|
||||
folders = folder_choices or ("group-v100", "group-v101", "group-v23")
|
||||
folder = folders[index % len(folders)]
|
||||
guest = GUEST_OS[index % len(GUEST_OS)]
|
||||
ds_index = 1 + (index % 4)
|
||||
ds_index = 1 + (index % max(1, datastore_count))
|
||||
nic_tail = f"{(index % 250):02x}"
|
||||
devices = _vm_device_props(
|
||||
name=name,
|
||||
@@ -351,10 +370,35 @@ def lab_permissions() -> tuple[PermissionSpec, ...]:
|
||||
)
|
||||
|
||||
|
||||
def small_vsphere_profile() -> VsphereSeedProfile:
|
||||
"""Compact seed used by unit/integration tests (named VMs)."""
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ProfileSize:
|
||||
"""Canonical lab sizes: hosts / VMs / datastores / networks / platform extras scale."""
|
||||
|
||||
name: str
|
||||
host_count: int
|
||||
vm_count: int
|
||||
datastore_count: int
|
||||
network_count: int
|
||||
extras_scale: int
|
||||
|
||||
|
||||
# Proportional inventory tiers shown in the Web UI DATA panel.
|
||||
PROFILE_SIZES: dict[str, ProfileSize] = {
|
||||
# Reset / unload target — cookbook-only inventory.
|
||||
"minimal": ProfileSize(
|
||||
"minimal", host_count=3, vm_count=5, datastore_count=1, network_count=1, extras_scale=1
|
||||
),
|
||||
"small": ProfileSize("small", host_count=3, vm_count=50, datastore_count=2, network_count=2, extras_scale=1),
|
||||
"large": ProfileSize(
|
||||
"large", host_count=10, vm_count=1000, datastore_count=4, network_count=4, extras_scale=2
|
||||
),
|
||||
"big": ProfileSize("big", host_count=20, vm_count=2000, datastore_count=8, network_count=8, extras_scale=4),
|
||||
}
|
||||
|
||||
|
||||
def _named_lab_vms() -> list[ObjectSpec]:
|
||||
"""Stable cookbook VMs (vm-101..vm-105) present in every profile."""
|
||||
|
||||
objects = _topology(host_count=3, datastore_count=2, network_count=2)
|
||||
named = (
|
||||
("web-01", "POWERED_ON", "host-11", 2, 4096),
|
||||
("web-02", "POWERED_ON", "host-12", 2, 4096),
|
||||
@@ -396,52 +440,97 @@ def small_vsphere_profile() -> VsphereSeedProfile:
|
||||
},
|
||||
)
|
||||
)
|
||||
return vms
|
||||
|
||||
|
||||
def _build_sized_profile(size: ProfileSize) -> VsphereSeedProfile:
|
||||
if size.host_count < 1 or size.vm_count < 1:
|
||||
raise ValueError("host_count and vm_count must be positive")
|
||||
# Folders scale with extras: minimal/small=0, large=2, big=6.
|
||||
extra_vm_folders = max(0, (size.extras_scale - 1) * 2)
|
||||
objects = _topology(
|
||||
host_count=size.host_count,
|
||||
datastore_count=size.datastore_count,
|
||||
network_count=size.network_count,
|
||||
extra_vm_folders=extra_vm_folders,
|
||||
)
|
||||
named = _named_lab_vms()
|
||||
# Minimal / single-datastore profiles still reference datastore-31.
|
||||
if size.datastore_count < 1:
|
||||
raise ValueError("datastore_count must be positive")
|
||||
folder_choices = (
|
||||
"group-v100",
|
||||
"group-v101",
|
||||
"group-v23",
|
||||
*(f"group-v{103 + i}" for i in range(extra_vm_folders)),
|
||||
)
|
||||
vms: list[ObjectSpec] = list(named)
|
||||
if size.vm_count > len(named):
|
||||
vms.extend(
|
||||
_vm_spec(
|
||||
index,
|
||||
host_count=size.host_count,
|
||||
datastore_count=size.datastore_count,
|
||||
folder_choices=folder_choices,
|
||||
)
|
||||
for index in range(len(named) + 1, size.vm_count + 1)
|
||||
)
|
||||
elif size.vm_count < len(named):
|
||||
vms = vms[: size.vm_count]
|
||||
return VsphereSeedProfile(
|
||||
name="small",
|
||||
name=size.name,
|
||||
objects=tuple(objects + vms),
|
||||
credentials=lab_credentials(),
|
||||
permissions=lab_permissions(),
|
||||
host_count=3,
|
||||
vm_count=5,
|
||||
host_count=size.host_count,
|
||||
vm_count=len(vms),
|
||||
extras_scale=size.extras_scale,
|
||||
)
|
||||
|
||||
|
||||
def minimal_vsphere_profile() -> VsphereSeedProfile:
|
||||
"""Reset target: 3 hosts · 5 cookbook VMs · 1 datastore · 1 network."""
|
||||
|
||||
return _build_sized_profile(PROFILE_SIZES["minimal"])
|
||||
|
||||
|
||||
def small_vsphere_profile() -> VsphereSeedProfile:
|
||||
"""Lab tier: 3 hosts · 50 VMs · 2 datastores · 2 networks."""
|
||||
|
||||
return _build_sized_profile(PROFILE_SIZES["small"])
|
||||
|
||||
|
||||
def large_vsphere_profile(*, host_count: int = 10, vm_count: int = 1000) -> VsphereSeedProfile:
|
||||
if host_count < 1 or vm_count < 1:
|
||||
raise ValueError("host_count and vm_count must be positive")
|
||||
objects = _topology(host_count=host_count, datastore_count=4, network_count=4)
|
||||
# Keep first five named VMs for cookbook / smoke compatibility.
|
||||
base = small_vsphere_profile()
|
||||
named_vms = [obj for obj in base.objects if obj.type == "VirtualMachine"]
|
||||
generated = [_vm_spec(index, host_count=host_count) for index in range(6, vm_count + 1)]
|
||||
# Ensure first 5 from small keep stable ids/names; replace generated slots 1-5.
|
||||
vms = list(named_vms)
|
||||
if vm_count > 5:
|
||||
vms.extend(generated)
|
||||
elif vm_count < 5:
|
||||
vms = vms[:vm_count]
|
||||
return VsphereSeedProfile(
|
||||
name="large",
|
||||
objects=tuple(objects + vms),
|
||||
credentials=lab_credentials(),
|
||||
permissions=lab_permissions(),
|
||||
host_count=host_count,
|
||||
vm_count=len(vms),
|
||||
"""Lab tier: 10 hosts · 1000 VMs (defaults); kwargs keep Makefile overrides."""
|
||||
|
||||
size = PROFILE_SIZES["large"]
|
||||
if host_count == size.host_count and vm_count == size.vm_count:
|
||||
return _build_sized_profile(size)
|
||||
# Custom scale: keep datastore/network proportion to hosts (≈0.4× hosts, min 2).
|
||||
datastore_count = max(2, round(host_count * 0.4))
|
||||
network_count = max(2, round(host_count * 0.4))
|
||||
return _build_sized_profile(
|
||||
ProfileSize(
|
||||
name="large",
|
||||
host_count=host_count,
|
||||
vm_count=vm_count,
|
||||
datastore_count=datastore_count,
|
||||
network_count=network_count,
|
||||
extras_scale=max(1, datastore_count // 2),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def big_vsphere_profile() -> VsphereSeedProfile:
|
||||
"""Lab tier: 20 hosts · 2000 VMs · 8 datastores · 8 networks."""
|
||||
|
||||
return _build_sized_profile(PROFILE_SIZES["big"])
|
||||
|
||||
|
||||
def demo_cluster_vsphere_profile() -> VsphereSeedProfile:
|
||||
"""Enterprise-shaped cluster: 20 hosts, 1000 VMs (aligned with Proxmox demo-cluster)."""
|
||||
"""Backward-compatible alias for ``big`` (UI / older docs used demo-cluster)."""
|
||||
|
||||
profile = large_vsphere_profile(host_count=20, vm_count=1000)
|
||||
return VsphereSeedProfile(
|
||||
name="demo-cluster",
|
||||
objects=profile.objects,
|
||||
credentials=profile.credentials,
|
||||
permissions=profile.permissions,
|
||||
host_count=profile.host_count,
|
||||
vm_count=profile.vm_count,
|
||||
)
|
||||
return big_vsphere_profile()
|
||||
|
||||
|
||||
def build_vsphere_profile(
|
||||
@@ -455,14 +544,35 @@ def build_vsphere_profile(
|
||||
large_hosts if large_hosts is not None else int(os.getenv("SEED_VSPHERE_LARGE_HOSTS", "10"))
|
||||
)
|
||||
vms = large_vms if large_vms is not None else int(os.getenv("SEED_VSPHERE_LARGE_VMS", "1000"))
|
||||
if profile_name.lower() in {"small", "minimal"}:
|
||||
if profile_name in {"minimal", "mini", "reset"}:
|
||||
return minimal_vsphere_profile()
|
||||
if profile_name in {"small"}:
|
||||
return small_vsphere_profile()
|
||||
if profile_name in {"demo-cluster", "demo", "enterprise"}:
|
||||
return demo_cluster_vsphere_profile()
|
||||
if profile_name in {"big", "demo-cluster", "demo", "enterprise"}:
|
||||
return big_vsphere_profile()
|
||||
if profile_name == "large":
|
||||
return large_vsphere_profile(host_count=hosts, vm_count=vms)
|
||||
raise ValueError(f"unknown vSphere seed profile: {profile_name}")
|
||||
|
||||
|
||||
def infer_profile_hint(*, hosts: int, vms: int, datastores: int = 0) -> str:
|
||||
"""Map live inventory counts back to a DATA-panel profile name."""
|
||||
|
||||
for size in PROFILE_SIZES.values():
|
||||
if hosts == size.host_count and vms == size.vm_count:
|
||||
if datastores and datastores != size.datastore_count:
|
||||
continue
|
||||
return size.name
|
||||
if hosts >= 15 and vms >= 1500:
|
||||
return "big"
|
||||
if hosts >= 8 and vms >= 500:
|
||||
return "large"
|
||||
if hosts <= 4 and vms <= 10:
|
||||
return "minimal"
|
||||
if hosts <= 4:
|
||||
return "small"
|
||||
return "custom"
|
||||
|
||||
|
||||
def props_json(props: dict[str, Any]) -> str:
|
||||
return json.dumps(props)
|
||||
|
||||
Reference in New Issue
Block a user