Files
vmware-api-simulator/app/vsphere/seed.py
T

206 lines
7.4 KiB
Python

"""Deterministic vSphere inventory seed for labs."""
from __future__ import annotations
import os
from typing import Any
from app.db.pool import Database
from app.security.auth import hash_secret
from app.vsphere import inventory
from app.vsphere.domain.api_state import seed_api_surface
from app.vsphere.domain.appliance import seed_appliance_state
from app.vsphere.domain.content import seed_platform_extras
from app.vsphere.domain.platform_surface import seed_platform_surface
from app.vsphere.profiles import VsphereSeedProfile, build_vsphere_profile, infer_profile_hint, props_json
from app.vsphere.security.session import ensure_default_credentials
async def _seed_platform(database: Database, *, extras_scale: int = 1) -> dict[str, Any]:
"""Libraries/tags/files + full Automation API surface state (all profiles)."""
extras: dict[str, Any] = {}
try:
await seed_platform_extras(database, scale=extras_scale)
extras["platform_extras"] = True
extras["extras_scale"] = extras_scale
except Exception as error:
extras["platform_extras_error"] = str(error)
try:
extras["api_surface"] = await seed_api_surface(database)
except Exception as error:
extras["api_surface_error"] = str(error)
try:
await seed_appliance_state(database)
extras["appliance"] = True
except Exception as error:
extras["appliance_error"] = str(error)
try:
extras["platform_surface"] = await seed_platform_surface(database)
except Exception as error:
extras["platform_surface_error"] = str(error)
return extras
async def seed_vsphere_inventory(
database: Database,
*,
force: bool = False,
profile: str | None = None,
large_hosts: int | None = None,
large_vms: int | None = None,
) -> dict[str, Any]:
"""Populate vCenter-like inventory from a named profile (default: large / 1000 VMs)."""
resolved = build_vsphere_profile(profile, large_hosts=large_hosts, large_vms=large_vms)
existing = await inventory.count_objects(database)
if existing and not force:
await ensure_default_credentials(database)
platform = await _seed_platform(database, extras_scale=resolved.extras_scale)
by_type = await inventory.count_by_type(database)
return {
"seeded": False,
"profile": resolved.name,
"objects": existing,
"by_type": by_type,
"vms": by_type.get("VirtualMachine", 0),
"hosts": by_type.get("HostSystem", 0),
**platform,
}
await _wipe(database)
await _apply_profile(database, resolved)
await ensure_default_credentials(database)
platform = await _seed_platform(database, extras_scale=resolved.extras_scale)
by_type = await inventory.count_by_type(database)
return {
"seeded": True,
"profile": resolved.name,
"objects": await inventory.count_objects(database),
"by_type": by_type,
"vms": by_type.get("VirtualMachine", 0),
"hosts": by_type.get("HostSystem", 0),
**platform,
}
async def _wipe(database: Database) -> None:
from app.vsphere.domain.content import clear_transfer_sessions
from app.vsphere.soap.property_collector import clear_pc_state
await clear_pc_state(database)
await clear_transfer_sessions(database)
pool = database.pool # type: ignore[attr-defined]
async with pool.acquire() as conn:
await conn.execute(
"""
DO $$ BEGIN
IF to_regclass('public.vsphere_nfc_leases') IS NOT NULL THEN
DELETE FROM vsphere_nfc_leases;
END IF;
IF to_regclass('public.vsphere_transfer_sessions') IS NOT NULL THEN
DELETE FROM vsphere_transfer_sessions;
END IF;
IF to_regclass('public.vsphere_console_tickets') IS NOT NULL THEN
DELETE FROM vsphere_console_tickets;
END IF;
IF to_regclass('public.vsphere_pc_state') IS NOT NULL THEN
DELETE FROM vsphere_pc_state;
END IF;
END $$;
"""
)
await conn.execute("DELETE FROM vsphere_permissions")
await conn.execute("DELETE FROM vsphere_datastore_files")
await conn.execute("DELETE FROM vsphere_snapshots")
await conn.execute("DELETE FROM vsphere_tag_associations")
await conn.execute("DELETE FROM vsphere_tags")
await conn.execute("DELETE FROM vsphere_tag_categories")
await conn.execute("DELETE FROM vsphere_library_items")
await conn.execute("DELETE FROM vsphere_libraries")
await conn.execute("DELETE FROM vsphere_tasks")
await conn.execute(
"""
DO $$ BEGIN
IF to_regclass('public.vsphere_api_state') IS NOT NULL THEN
DELETE FROM vsphere_api_state;
END IF;
END $$;
"""
)
await conn.execute("UPDATE vsphere_objects SET parent_moid = NULL")
await conn.execute("DELETE FROM vsphere_objects")
async def _apply_profile(database: Database, profile: VsphereSeedProfile) -> None:
rows = [
{
"moid": obj.moid,
"type": obj.type,
"name": obj.name,
"parent_moid": obj.parent_moid,
"props": obj.props,
}
for obj in profile.objects
]
# Parents first: folders → dc → cluster → hosts/vms. Spec order already topological.
await inventory.upsert_objects_batch(database, rows)
pool = database.pool # type: ignore[attr-defined]
async with pool.acquire() as conn:
for cred in profile.credentials:
await conn.execute(
"""
INSERT INTO vsphere_credentials (username, password_hash, roles)
VALUES ($1, $2, $3)
ON CONFLICT (username) DO UPDATE SET
password_hash = EXCLUDED.password_hash,
roles = EXCLUDED.roles
""",
cred.username,
hash_secret(cred.password),
list(cred.roles),
)
for perm in profile.permissions:
await conn.execute(
"""
INSERT INTO vsphere_permissions (principal, role, entity_moid, propagate)
VALUES ($1, $2, $3, $4)
""",
perm.principal,
perm.role,
perm.entity_moid,
perm.propagate,
)
def default_profile_name() -> str:
return os.getenv("SEED_VSPHERE_PROFILE", "large")
async def vsphere_state_summary(database: Database) -> dict[str, Any]:
by_type = await inventory.count_by_type(database)
hosts = by_type.get("HostSystem", 0)
vms = by_type.get("VirtualMachine", 0)
datastores = by_type.get("Datastore", 0)
networks = by_type.get("Network", 0) + by_type.get("DistributedVirtualPortgroup", 0)
return {
"hosts": hosts,
"vms": vms,
"datastores": datastores,
"networks": networks,
"datacenters": by_type.get("Datacenter", 0),
"clusters": by_type.get("ClusterComputeResource", 0),
"objects": sum(by_type.values()),
"by_type": by_type,
"profile_hint": infer_profile_hint(hosts=hosts, vms=vms, datastores=datastores),
}
__all__ = [
"default_profile_name",
"props_json",
"seed_vsphere_inventory",
"vsphere_state_summary",
]