Initial commit: VMware vSphere API simulator scaffold.
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
"""Ceph pool/OSD mutation persistence tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, cast
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
|
||||
from app.api.registry import HandlerRegistry
|
||||
from app.db.pool import AsyncpgDatabase
|
||||
from app.handlers.ceph import register_ceph_handlers
|
||||
from app.simulation.seed import CLUSTER_ID
|
||||
|
||||
|
||||
class CephPool:
|
||||
def __init__(self) -> None:
|
||||
self.cluster_metadata: dict[str, Any] = {}
|
||||
self.nodes = {"pve1": {"id": uuid4(), "metadata": {}}}
|
||||
self.resources: dict[Any, dict[str, Any]] = {}
|
||||
|
||||
async def fetch(self, query: str, *arguments: object) -> list[dict[str, Any]]:
|
||||
if "r.kind='ceph-osd'" in query and "ORDER BY" in query:
|
||||
node = str(arguments[0])
|
||||
node_id = self.nodes[node]["id"]
|
||||
return [
|
||||
{"external_id": item["external_id"], "state": item["state"]}
|
||||
for item in self.resources.values()
|
||||
if item["node_id"] == node_id
|
||||
]
|
||||
raise AssertionError(query)
|
||||
|
||||
async def fetchrow(self, query: str, *arguments: object) -> dict[str, Any] | None:
|
||||
if "FROM clusters WHERE id" in query:
|
||||
return {"metadata": json.dumps(self.cluster_metadata)}
|
||||
if "SELECT metadata FROM nodes WHERE name" in query:
|
||||
node = self.nodes.get(str(arguments[0]))
|
||||
return None if node is None else {"metadata": json.dumps(node["metadata"])}
|
||||
if "storage_type='ceph'" in query:
|
||||
return {"capacity_bytes": 1000, "used_bytes": 100}
|
||||
if "r.kind='ceph-osd'" in query:
|
||||
node_name = str(arguments[0])
|
||||
osdid = str(arguments[1])
|
||||
node_id = self.nodes[node_name]["id"]
|
||||
for item in self.resources.values():
|
||||
if item["node_id"] == node_id and item["external_id"] in {
|
||||
osdid,
|
||||
f"osd.{osdid}",
|
||||
arguments[2] if len(arguments) > 2 else "",
|
||||
}:
|
||||
return item
|
||||
return None
|
||||
raise AssertionError(query)
|
||||
|
||||
async def fetchval(self, query: str, *arguments: object) -> Any:
|
||||
if "EXISTS(SELECT 1 FROM nodes" in query:
|
||||
return str(arguments[0]) in self.nodes
|
||||
if "SELECT id FROM nodes WHERE name" in query:
|
||||
node = self.nodes.get(str(arguments[0]))
|
||||
return None if node is None else node["id"]
|
||||
if "count(*)::int FROM resources WHERE kind='ceph-osd'" in query:
|
||||
return len(self.resources)
|
||||
if "COALESCE" in query and "ceph-osd" in query:
|
||||
return len(self.resources)
|
||||
raise AssertionError(query)
|
||||
|
||||
async def execute(self, query: str, *arguments: object) -> str:
|
||||
if "jsonb_set" in query and "'{ceph}'" in query:
|
||||
self.cluster_metadata["ceph"] = json.loads(str(arguments[1]))
|
||||
return "UPDATE 1"
|
||||
if "UPDATE nodes SET metadata" in query:
|
||||
self.nodes[str(arguments[0])]["metadata"] = json.loads(str(arguments[1]))
|
||||
return "UPDATE 1"
|
||||
if "INSERT INTO resources" in query:
|
||||
resource_id = uuid4()
|
||||
self.resources[resource_id] = {
|
||||
"id": resource_id,
|
||||
"node_id": arguments[0],
|
||||
"external_id": arguments[1],
|
||||
"state": arguments[2],
|
||||
}
|
||||
return "INSERT 0 1"
|
||||
if "UPDATE resources SET state" in query:
|
||||
existing_id = arguments[0]
|
||||
self.resources[existing_id]["state"] = arguments[1]
|
||||
return "UPDATE 1"
|
||||
if "DELETE FROM resources WHERE id" in query:
|
||||
self.resources.pop(arguments[0], None)
|
||||
return "DELETE 1"
|
||||
raise AssertionError(query)
|
||||
|
||||
|
||||
def request(pool: CephPool) -> Request:
|
||||
app = FastAPI()
|
||||
app.state.database = cast(AsyncpgDatabase, type("DB", (), {"pool": pool})())
|
||||
result = Request(
|
||||
{
|
||||
"type": "http",
|
||||
"app": app,
|
||||
"method": "POST",
|
||||
"path": "/",
|
||||
"headers": [],
|
||||
"query_string": b"",
|
||||
"server": ("test", 80),
|
||||
"client": ("test", 123),
|
||||
"scheme": "http",
|
||||
}
|
||||
)
|
||||
result.state.principal = "root@pam"
|
||||
return result
|
||||
|
||||
|
||||
async def test_ceph_pool_and_osd_mutations_persist() -> None:
|
||||
registry = HandlerRegistry()
|
||||
register_ceph_handlers(registry)
|
||||
pool = CephPool()
|
||||
http = request(pool)
|
||||
|
||||
create_pool = registry.get("/nodes/{node}/ceph/pool", "POST")
|
||||
list_pool = registry.get("/nodes/{node}/ceph/pool", "GET")
|
||||
create_osd = registry.get("/nodes/{node}/ceph/osd", "POST")
|
||||
osd_out = registry.get("/nodes/{node}/ceph/osd/{osdid}/out", "POST")
|
||||
assert create_pool and list_pool and create_osd and osd_out
|
||||
|
||||
await create_pool(http, {"values": {"node": "pve1", "name": "vms"}, "provided": frozenset()})
|
||||
pools = await list_pool(http, {"values": {"node": "pve1"}, "provided": frozenset()})
|
||||
assert any(item["pool"] == "vms" for item in pools)
|
||||
assert "vms" in pool.cluster_metadata["ceph"]["pools"]
|
||||
|
||||
await create_osd(http, {"values": {"node": "pve1", "dev": "/dev/sdb"}, "provided": frozenset()})
|
||||
assert len(pool.resources) == 1
|
||||
resource_id = next(iter(pool.resources))
|
||||
osdid = "0"
|
||||
await osd_out(
|
||||
http,
|
||||
{"values": {"node": "pve1", "osdid": osdid}, "provided": frozenset()},
|
||||
)
|
||||
assert json.loads(pool.resources[resource_id]["state"])["in"] is False
|
||||
assert CLUSTER_ID
|
||||
Reference in New Issue
Block a user