Align sized cluster seeds and GET dumps with PVE wire shapes; restyle DATA panel.

- 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
This commit is contained in:
Sergey Antropoff
2026-07-18 08:46:11 +03:00
parent 48df10b17e
commit 0773f721ea
77 changed files with 4870 additions and 855 deletions
+106 -16
View File
@@ -242,11 +242,45 @@ def register_ceph_handlers(registry: HandlerRegistry) -> None:
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephrestart")
def _wire_ceph_pool(name: str, item: dict[str, Any], *, index: int) -> dict[str, Any]:
pool_id = item.get("pool_id", item.get("pool"))
try:
pool_num = int(pool_id)
except (TypeError, ValueError):
pool_num = index
crush_raw = item.get("crush_rule", 0)
try:
crush_id = int(crush_raw)
crush_name = str(item.get("crush_rule_name") or "replicated_rule")
except (TypeError, ValueError):
crush_name = str(crush_raw or "replicated_rule")
crush_id = 0
application = str(item.get("application") or "rbd")
return {
"pool": pool_num,
"pool_name": str(item.get("pool_name") or name),
"size": int(item.get("size") or 3),
"min_size": int(item.get("min_size") or 2),
"pg_num": int(item.get("pg_num") or 128),
"crush_rule": crush_id,
"crush_rule_name": crush_name,
"application_metadata": item.get("application_metadata")
if isinstance(item.get("application_metadata"), dict)
else {application: {}},
"bytes_used": int(item.get("bytes_used") or 0),
"percent_used": float(item.get("percent_used") or 0.0),
"healthy": bool(item.get("healthy", True)),
}
async def pool_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
await require_node(request, str(values(inputs)["node"]))
ceph = await _load_cluster_ceph(request)
pools = ceph.get("pools") or {}
return [dict(item) for _, item in sorted(pools.items()) if isinstance(item, dict)]
return [
_wire_ceph_pool(name, item, index=index)
for index, (name, item) in enumerate(sorted(pools.items()))
if isinstance(item, dict)
]
async def pool_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
@@ -278,7 +312,10 @@ def register_ceph_handlers(registry: HandlerRegistry) -> None:
pool = (ceph.get("pools") or {}).get(name)
if not isinstance(pool, dict):
raise ApiError(404, "pool does not exist")
return [dict(pool)]
# Reuse list wire shape for detail GETs.
names = sorted((ceph.get("pools") or {}).keys())
index = names.index(name) if name in names else 0
return [_wire_ceph_pool(name, pool, index=index)]
async def pool_update(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
@@ -482,30 +519,63 @@ def register_ceph_handlers(registry: HandlerRegistry) -> None:
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephdestroymon")
async def osd_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
async def osd_list(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
node = str(values(inputs)["node"])
await require_node(request, node)
rows = await database(request).pool.fetch(
"""SELECT r.external_id, r.state
"""SELECT r.external_id, r.state, n.name AS node
FROM resources r JOIN nodes n ON n.id=r.node_id
WHERE n.name=$1 AND r.kind='ceph-osd'
ORDER BY r.external_id""",
node,
WHERE r.kind='ceph-osd'
ORDER BY n.name, r.external_id"""
)
result: list[dict[str, Any]] = []
hosts: dict[str, dict[str, Any]] = {}
host_id = -2
for row in rows:
host_name = str(row["node"])
host = hosts.get(host_name)
if host is None:
host = {
"id": host_id,
"name": host_name,
"type": "host",
"type_id": 1,
"children": [],
}
hosts[host_name] = host
host_id -= 1
payload = state(row["state"])
osd_id = payload.get("osd_id", row["external_id"])
result.append(
osd_raw = payload.get("osd_id", row["external_id"])
try:
osd_id = int(osd_raw)
except (TypeError, ValueError):
digits = "".join(ch for ch in str(osd_raw) if ch.isdigit())
osd_id = int(digits) if digits else 0
host["children"].append(
{
"osd": int(osd_id) if str(osd_id).isdigit() else osd_id,
"id": osd_id,
"name": f"osd.{osd_id}",
"type": "osd",
"type_id": 0,
"status": payload.get("status", "up"),
"in": 1 if payload.get("in", True) else 0,
"weight": payload.get("weight", 1.0),
"weight": float(payload.get("weight", 1.0)),
"device_class": payload.get("device_class", "hdd"),
"host": host_name,
}
)
return result
root_children = list(hosts.values())
# Prefer the requested node first in the tree for local dumps.
root_children.sort(key=lambda item: (0 if item["name"] == node else 1, item["name"]))
return {
"flags": "",
"root": {
"id": -1,
"name": "default",
"type": "root",
"type_id": 10,
"children": root_children,
},
}
async def osd_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
@@ -677,16 +747,36 @@ def register_ceph_handlers(registry: HandlerRegistry) -> None:
health = ceph.get("health")
if not isinstance(health, dict):
health = {"status": "HEALTH_OK" if ceph.get("running") else "HEALTH_WARN"}
node_count = int(await database(_request).pool.fetchval("SELECT COUNT(*) FROM nodes") or 0)
return {
"version": version_str,
"version": {
"version": version_str,
"release": "reef",
"epoch": 0,
},
"health": health,
"osdmap": {
"epoch": 1,
"num_osds": num_osds,
"num_up_osds": num_up,
"num_in_osds": num_in,
"num_remapped_pgs": 0,
},
"pgmap": {"bytes_used": used, "bytes_total": total},
"fsmap": {"filesystems": list((ceph.get("fs") or {}).keys())},
"pgmap": {
"bytes_used": used,
"bytes_total": total,
"bytes_avail": max(total - used, 0),
"data_bytes": used,
"num_pgs": max(num_osds * 32, 32),
},
"fsmap": {
"epoch": 1,
"by_rank": [],
"filesystems": list((ceph.get("fs") or {}).keys()),
},
"monmap": {"num_mons": max(node_count, 1)},
"mgrmap": {"available": True, "num_standbys": 0},
"servicemap": {"epoch": 1, "services": {}},
}
base = "/nodes/{node}/ceph"