0773f721ea
- Scale small/large/big seeds (3×50 / 10×1000 / 20×2000) with proportional backups, snapshots, HA, replication, Ceph capacity, and OSD totals (10 / 100 / 500) plus matching node disks and crush/pg metadata - Enrich handler responses for apt, certificates, qemu/lxc status, storage, SDN, metrics export, and related cluster/node dumps - Flatten nested body_example fields into PARAMS and sync the request body via dotted paths (oVirt-style) - Restyle DATA controls as size cards with full-width Reset to minimal / Refresh stats; unload reloads the minimal cluster
173 lines
6.1 KiB
Python
173 lines
6.1 KiB
Python
"""Persistence tests for remaining gap handlers (nodes_extra / cluster_extra)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any, cast
|
|
|
|
import pytest
|
|
from fastapi import FastAPI, Request
|
|
|
|
from app.api.registry import HandlerRegistry
|
|
from app.db.pool import AsyncpgDatabase
|
|
from app.handlers.cluster_extra import register_cluster_extra_handlers
|
|
from app.handlers.nodes_extra import register_nodes_extra_handlers
|
|
|
|
|
|
class GapRemainingPool:
|
|
def __init__(self) -> None:
|
|
self.cluster_metadata: dict[str, Any] = {}
|
|
self.node_metadata: dict[str, Any] = {}
|
|
|
|
async def fetchrow(self, query: str, *arguments: object) -> dict[str, Any] | None:
|
|
if "SELECT metadata FROM clusters" in query:
|
|
return {"metadata": json.dumps(self.cluster_metadata)}
|
|
if "SELECT metadata FROM nodes" in query:
|
|
name = str(arguments[0])
|
|
return {"metadata": json.dumps(self.node_metadata.get(name, {}))}
|
|
raise AssertionError(query)
|
|
|
|
async def fetchval(self, query: str, *arguments: object) -> Any:
|
|
del arguments
|
|
if "EXISTS(SELECT 1 FROM nodes" in query:
|
|
return True
|
|
if "SELECT name FROM nodes ORDER BY name LIMIT 1" in query:
|
|
return "pve01"
|
|
raise AssertionError(query)
|
|
|
|
async def execute(self, query: str, *arguments: object) -> str:
|
|
if "UPDATE clusters SET metadata" in query:
|
|
self.cluster_metadata = json.loads(str(arguments[1]))
|
|
return "UPDATE 1"
|
|
if "UPDATE nodes SET metadata" in query:
|
|
self.node_metadata[str(arguments[0])] = json.loads(str(arguments[1]))
|
|
return "UPDATE 1"
|
|
raise AssertionError(query)
|
|
|
|
|
|
class FakeDatabase:
|
|
def __init__(self, pool: GapRemainingPool) -> None:
|
|
self.pool = pool
|
|
|
|
|
|
def _request(pool: GapRemainingPool, *, method: str = "GET") -> Request:
|
|
app = FastAPI()
|
|
app.state.database = cast(AsyncpgDatabase, FakeDatabase(pool))
|
|
result = Request(
|
|
{
|
|
"type": "http",
|
|
"app": app,
|
|
"method": method,
|
|
"path": "/",
|
|
"headers": [],
|
|
"query_string": b"",
|
|
"server": ("test", 80),
|
|
"client": ("test", 123),
|
|
"scheme": "http",
|
|
}
|
|
)
|
|
result.state.principal = "root@pam"
|
|
return result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_disks_directory_create_persists(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
registry = HandlerRegistry()
|
|
register_nodes_extra_handlers(registry)
|
|
pool = GapRemainingPool()
|
|
|
|
class FakeTaskRepository:
|
|
async def create(self, **kwargs: Any) -> Any:
|
|
return type("Task", (), {"upid": kwargs["upid"]})()
|
|
|
|
monkeypatch.setattr("app.handlers.nodes.TaskRepository", lambda _pool: FakeTaskRepository())
|
|
create = registry.get("/nodes/{node}/disks/directory", "POST")
|
|
assert create is not None
|
|
created = await create(
|
|
_request(pool, method="POST"),
|
|
{
|
|
"values": {"node": "pve01", "name": "tank", "device": "/dev/sdb"},
|
|
"provided": frozenset(),
|
|
},
|
|
)
|
|
assert isinstance(created, str) and created.startswith("UPID:")
|
|
ops = pool.node_metadata["pve01"]["ops"]
|
|
assert any(item["name"] == "tank" for item in ops["disks"]["directory"])
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_certificates_custom_create_does_not_echo_key() -> None:
|
|
registry = HandlerRegistry()
|
|
register_nodes_extra_handlers(registry)
|
|
pool = GapRemainingPool()
|
|
create = registry.get("/nodes/{node}/certificates/custom", "POST")
|
|
info = registry.get("/nodes/{node}/certificates/info", "GET")
|
|
assert create is not None and info is not None
|
|
await create(
|
|
_request(pool, method="POST"),
|
|
{
|
|
"values": {
|
|
"node": "pve01",
|
|
"certificates": "-----BEGIN CERTIFICATE-----\nSIM\n-----END CERTIFICATE-----",
|
|
"key": "-----BEGIN PRIVATE KEY-----\nSECRET\n-----END PRIVATE KEY-----",
|
|
},
|
|
"provided": frozenset(),
|
|
},
|
|
)
|
|
stored = pool.node_metadata["pve01"]["ops"]["certificates"]["custom"]
|
|
assert stored["key"].startswith("-----BEGIN PRIVATE KEY-----")
|
|
listing = await info(
|
|
_request(pool),
|
|
{"values": {"node": "pve01"}, "provided": frozenset()},
|
|
)
|
|
blob = json.dumps(listing)
|
|
assert "PRIVATE KEY" not in blob
|
|
assert "SECRET" not in blob
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_realm_sync_job_create_persists() -> None:
|
|
registry = HandlerRegistry()
|
|
register_cluster_extra_handlers(registry)
|
|
pool = GapRemainingPool()
|
|
create = registry.get("/cluster/jobs/realm-sync/{id}", "POST")
|
|
listing = registry.get("/cluster/jobs/realm-sync", "GET")
|
|
assert create is not None and listing is not None
|
|
created = await create(
|
|
_request(pool, method="POST"),
|
|
{
|
|
"values": {"id": "pam-nightly", "realm": "pam", "schedule": "0 2 * * *"},
|
|
"provided": frozenset(),
|
|
},
|
|
)
|
|
assert created["id"] == "pam-nightly"
|
|
assert pool.cluster_metadata["jobs"]["realm_sync"]["pam-nightly"]["realm"] == "pam"
|
|
items = await listing(_request(pool), {"values": {}, "provided": frozenset()})
|
|
assert items[0]["id"] == "pam-nightly"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metrics_server_create_persists() -> None:
|
|
registry = HandlerRegistry()
|
|
register_cluster_extra_handlers(registry)
|
|
pool = GapRemainingPool()
|
|
create = registry.get("/cluster/metrics/server/{id}", "POST")
|
|
listing = registry.get("/cluster/metrics/server", "GET")
|
|
assert create is not None and listing is not None
|
|
created = await create(
|
|
_request(pool, method="POST"),
|
|
{
|
|
"values": {
|
|
"id": "influx1",
|
|
"type": "influxdb",
|
|
"server": "10.0.0.20",
|
|
"port": 8089,
|
|
},
|
|
"provided": frozenset(),
|
|
},
|
|
)
|
|
assert created["id"] == "influx1"
|
|
assert pool.cluster_metadata["metrics"]["servers"]["influx1"]["server"] == "10.0.0.20"
|
|
items = await listing(_request(pool), {"values": {}, "provided": frozenset()})
|
|
assert items[0]["id"] == "influx1"
|