"""CRUD for folders, datacenters, clusters, resource pools, hosts, DVS.""" from __future__ import annotations from typing import Any from app.db.pool import Database from app.vsphere import inventory from app.vsphere.errors import invalid_argument, not_found async def create_folder( database: Database, *, name: str, parent: str, folder_type: str = "VIRTUAL_MACHINE", ) -> str: parent_obj = await inventory.get_object(database, parent) if parent_obj is None: raise not_found(f"Parent {parent} not found") moid = await inventory.next_moid(database, "group") # next_moid with group- may not work well; use folder- style import secrets moid = f"group-{secrets.token_hex(3)}" await inventory.upsert_object( database, moid=moid, type_name="Folder", name=name, parent_moid=parent, props={"folder_type": folder_type}, ) return moid async def create_datacenter(database: Database, *, name: str, folder: str = "group-d1") -> str: import secrets parent_obj = await inventory.get_object(database, folder) if parent_obj is None: raise not_found(f"Parent {folder} not found") moid = f"datacenter-{secrets.randbelow(900) + 100}" host_folder = f"group-h{secrets.randbelow(90) + 10}" vm_folder = f"group-v{secrets.randbelow(90) + 10}" ds_folder = f"group-s{secrets.randbelow(90) + 10}" net_folder = f"group-n{secrets.randbelow(90) + 10}" await inventory.upsert_object( database, moid=moid, type_name="Datacenter", name=name, parent_moid=folder, props={ "datastore_folder": ds_folder, "host_folder": host_folder, "vm_folder": vm_folder, "network_folder": net_folder, }, ) for child_moid, child_name, ftype in ( (host_folder, "host", "HOST"), (vm_folder, "vm", "VIRTUAL_MACHINE"), (ds_folder, "datastore", "DATASTORE"), (net_folder, "network", "NETWORK"), ): await inventory.upsert_object( database, moid=child_moid, type_name="Folder", name=child_name, parent_moid=moid, props={"folder_type": ftype}, ) return moid async def create_cluster( database: Database, *, name: str, folder: str = "group-h23", drs_enabled: bool = True, ha_enabled: bool = True, ) -> str: import secrets parent_obj = await inventory.get_object(database, folder) if parent_obj is None: raise not_found(f"Parent {folder} not found") moid = f"domain-c{secrets.randbelow(900) + 100}" rp = f"resgroup-{secrets.randbelow(900) + 100}" await inventory.upsert_object( database, moid=moid, type_name="ClusterComputeResource", name=name, parent_moid=folder, props={"drs_enabled": drs_enabled, "ha_enabled": ha_enabled, "resource_pool": rp}, ) await inventory.upsert_object( database, moid=rp, type_name="ResourcePool", name="Resources", parent_moid=moid, props={"cpu_limit_mhz": -1, "memory_limit_mib": -1}, ) return moid async def create_resource_pool( database: Database, *, name: str, parent: str, ) -> str: parent_obj = await inventory.get_object(database, parent) if parent_obj is None: raise not_found(f"Parent {parent} not found") import secrets moid = f"resgroup-{secrets.randbelow(900) + 100}" await inventory.upsert_object( database, moid=moid, type_name="ResourcePool", name=name, parent_moid=parent, props={"cpu_limit_mhz": -1, "memory_limit_mib": -1}, ) return moid async def rename_object(database: Database, moid: str, name: str) -> None: obj = await inventory.get_object(database, moid) if obj is None: raise not_found(f"Object {moid} not found") await inventory.upsert_object( database, moid=obj.moid, type_name=obj.type, name=name, parent_moid=obj.parent_moid, props=obj.props, ) async def move_object(database: Database, moid: str, parent: str) -> None: obj = await inventory.get_object(database, moid) if obj is None: raise not_found(f"Object {moid} not found") parent_obj = await inventory.get_object(database, parent) if parent_obj is None: raise not_found(f"Parent {parent} not found") await inventory.upsert_object( database, moid=obj.moid, type_name=obj.type, name=obj.name, parent_moid=parent, props=obj.props, ) async def delete_managed(database: Database, moid: str) -> None: obj = await inventory.get_object(database, moid) if obj is None: raise not_found(f"Object {moid} not found") # Protect the lab seed spine so surface/matrix probes cannot empty the inventory dump. if moid in _SEED_PROTECTED_MOIDS or _is_seed_host_or_named_vm(moid, obj): raise invalid_argument(f"Cannot delete protected seed object {moid}") children = [ child for child in await inventory.list_objects(database) if child.parent_moid == moid ] if children: raise invalid_argument(f"Object {moid} still has children") await inventory.delete_object(database, moid) # Stable MOIDs from profiles._topology / small named VMs (all seed sizes). _SEED_PROTECTED_MOIDS = frozenset( { "group-d1", "datacenter-21", "group-h23", "group-v23", "group-s23", "group-n23", "domain-c21", "resgroup-22", "group-v100", "group-v101", "group-v102", "network-41", "dvs-51", *(f"datastore-{30 + n}" for n in range(1, 9)), *(f"dvportgroup-{40 + n}" for n in range(2, 9)), *(f"group-v{103 + n}" for n in range(0, 8)), "vm-101", "vm-102", "vm-103", "vm-104", "vm-105", } ) def _is_seed_host_or_named_vm(moid: str, obj: Any) -> bool: if obj.type == "HostSystem" and moid.startswith("host-"): # Seed hosts are host-11..host-N; probe hosts use other patterns if any. try: return 11 <= int(moid.split("-", 1)[1]) <= 40 except ValueError: return False return False async def set_host_maintenance(database: Database, host: str, enabled: bool) -> dict[str, Any]: obj = await inventory.get_object(database, host) if obj is None or obj.type != "HostSystem": raise not_found(f"Host {host} not found") props = dict(obj.props) props["connection_state"] = "CONNECTED" props["maintenance_mode"] = enabled await inventory.update_props(database, host, props) return {"host": host, "maintenance_mode": enabled} async def create_dvs( database: Database, *, name: str, folder: str = "group-n23", ) -> str: import secrets moid = f"dvs-{secrets.randbelow(900) + 100}" await inventory.upsert_object( database, moid=moid, type_name="VmwareDistributedVirtualSwitch", name=name, parent_moid=folder, props={"version": "8.0.0", "num_ports": 128}, ) return moid async def create_dvpg( database: Database, *, name: str, dvs: str, vlan_id: int = 0, ) -> str: parent = await inventory.get_object(database, dvs) if parent is None: raise not_found(f"DVS {dvs} not found") import secrets moid = f"dvportgroup-{secrets.randbelow(900) + 100}" await inventory.upsert_object( database, moid=moid, type_name="DistributedVirtualPortgroup", name=name, parent_moid=dvs, props={"type": "DISTRIBUTED_PORTGROUP", "vlan_id": vlan_id, "dvs": dvs}, ) return moid