"""Resource pool semantic handlers.""" from __future__ import annotations import json import uuid from typing import Any from fastapi import Request from app.api.errors import ApiError from app.api.registry import HandlerRegistry from app.handlers.common import database, require_value, state, values from app.simulation.seed import CLUSTER_ID, stable_id async def _pool_members(request: Request, pool_id: uuid.UUID) -> list[str]: rows = await database(request).pool.fetch( """SELECT r.external_id FROM pool_members pm JOIN resources r ON r.id = pm.resource_id WHERE pm.pool_id=$1 ORDER BY r.external_id::integer""", pool_id, ) return [str(row["external_id"]) for row in rows] def register_pool_handlers(registry: HandlerRegistry) -> None: async def pool_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]: payload = values(inputs) filter_poolid = payload.get("poolid") rows = await database(request).pool.fetch( """SELECT id, pool_id, comment, metadata FROM pools WHERE ($1::text IS NULL OR pool_id=$1) ORDER BY pool_id""", str(filter_poolid) if filter_poolid is not None else None, ) result: list[dict[str, Any]] = [] for row in rows: metadata = state(row["metadata"]) members = await _pool_members(request, row["id"]) if not members and isinstance(metadata.get("members"), list): members = [str(item) for item in metadata["members"]] item: dict[str, Any] = { "poolid": str(row["pool_id"]), "members": members, } if row["comment"] is not None: item["comment"] = str(row["comment"]) elif metadata.get("comment"): item["comment"] = str(metadata["comment"]) result.append(item) return result async def pool_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]: items = await pool_list(request, inputs) if not items: raise ApiError(404, "pool does not exist") return items[0] async def pool_create(request: Request, inputs: dict[str, Any]) -> None: payload = values(inputs) poolid = str(payload["poolid"]) exists = await database(request).pool.fetchval( "SELECT EXISTS(SELECT 1 FROM pools WHERE pool_id=$1)", poolid, ) if exists: raise ApiError(409, "pool already exists") await database(request).pool.execute( """INSERT INTO pools(id, cluster_id, pool_id, comment, metadata) VALUES($1, $2, $3, $4, $5::jsonb)""", stable_id(f"pool:{poolid}"), CLUSTER_ID, poolid, payload.get("comment"), json.dumps({"members": []}, sort_keys=True), ) async def pool_update(request: Request, inputs: dict[str, Any]) -> None: payload = values(inputs) poolid = str(payload["poolid"]) pool_row = await database(request).pool.fetchrow( "SELECT id FROM pools WHERE pool_id=$1", poolid, ) if pool_row is None: raise ApiError(404, "pool does not exist") if payload.get("comment") is not None: await database(request).pool.execute( "UPDATE pools SET comment=$2 WHERE pool_id=$1", poolid, payload.get("comment"), ) if "vms" in payload: vmids = [item.strip() for item in str(payload["vms"]).split(",") if item.strip()] for vmid in vmids: resource = await database(request).pool.fetchrow( """SELECT id FROM resources WHERE kind IN ('qemu', 'lxc') AND external_id=$1""", vmid, ) if resource is None: continue await database(request).pool.execute( """INSERT INTO pool_members(pool_id, resource_id) VALUES($1, $2) ON CONFLICT DO NOTHING""", pool_row["id"], resource["id"], ) if "delete" in payload: vmids = [item.strip() for item in str(payload["delete"]).split(",") if item.strip()] await database(request).pool.execute( """DELETE FROM pool_members pm USING resources r WHERE pm.pool_id=$1 AND pm.resource_id=r.id AND r.external_id = ANY($2::text[])""", pool_row["id"], vmids, ) async def pool_delete(request: Request, inputs: dict[str, Any]) -> None: poolid = str(require_value(values(inputs), "poolid")) status = await database(request).pool.execute( "DELETE FROM pools WHERE pool_id=$1", poolid, ) if status != "DELETE 1": raise ApiError(404, "pool does not exist") registry.register("/pools", "GET", pool_list) registry.register("/pools", "POST", pool_create) registry.register("/pools", "PUT", pool_update) registry.register("/pools", "DELETE", pool_delete) registry.register("/pools/{poolid}", "GET", pool_get) registry.register("/pools/{poolid}", "PUT", pool_update) registry.register("/pools/{poolid}", "DELETE", pool_delete)