Files
proxmox-api-simulator/app/handlers/pools.py
T
Sergey Antropoff 48df10b17e Prepare 0.1.0 for lab release: durable handlers, HTTP Compose, CI, and pulumi-tests.
- Harden DB-backed handlers and seed profiles; align client wire shapes for
  cluster resources, QEMU config, and node SSL fields
- Serve plain HTTP on Compose :8006; keep TLS optional (--profile tls) and
  terminate HTTPS at Kubernetes Ingress
- Add pulumi-tests (full contract surface majors 6–9 + BPG lifecycle) and
  make pulumi-tests
- Ship bilingual docs, CHANGELOG, SECURITY, CONTRIBUTING, and GitHub Actions
  (make ci + Compose/Helm validation)
2026-07-18 04:18:05 +03:00

135 lines
5.3 KiB
Python

"""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)