273 lines
7.7 KiB
Python
273 lines
7.7 KiB
Python
"""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
|