Files
proxmox-api-simulator/app/handlers/ceph.py
T
Sergey Antropoff baa1f58ad0 Fix CI/pulumi blockers and document test suites with latest PASS results.
Stabilize node status, cluster-wide Ceph OSD ids, and QEMU cpu utilization so
status/list no longer 500 on model strings; align pulumi defaults to pve1;
add bilingual testing docs with the 2026-07-18 ci-all + pulumi-tests pass.
2026-07-18 09:38:28 +03:00

831 lines
32 KiB
Python

"""Ceph semantic handlers with durable cluster/node state."""
from __future__ import annotations
import json
import secrets
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,
node_metadata,
require_node,
save_node_metadata,
state,
subdirs,
values,
)
from app.simulation.seed import CLUSTER_ID
async def _load_cluster_ceph(request: Request) -> dict[str, Any]:
row = await database(request).pool.fetchrow(
"SELECT metadata FROM clusters WHERE id=$1",
CLUSTER_ID,
)
metadata = state(row["metadata"]) if row is not None else {}
ceph = metadata.get("ceph")
if not isinstance(ceph, dict):
return {}
return dict(ceph)
async def _save_cluster_ceph(request: Request, ceph: dict[str, Any]) -> None:
await database(request).pool.execute(
"""UPDATE clusters SET metadata = jsonb_set(
COALESCE(metadata, '{}'::jsonb), '{ceph}', $2::jsonb, true
), updated_at=now() WHERE id=$1""",
CLUSTER_ID,
json.dumps(ceph, sort_keys=True),
)
async def _load_node_ceph(request: Request, node: str) -> dict[str, Any]:
metadata = await node_metadata(request, node)
ops = metadata.get("ops")
if not isinstance(ops, dict):
return {"mds": {}, "mgr": {}, "mon": {}, "log": []}
ceph = ops.get("ceph")
if not isinstance(ceph, dict):
return {"mds": {}, "mgr": {}, "mon": {}, "log": []}
return {
"mds": ceph.get("mds") if isinstance(ceph.get("mds"), dict) else {},
"mgr": ceph.get("mgr") if isinstance(ceph.get("mgr"), dict) else {},
"mon": ceph.get("mon") if isinstance(ceph.get("mon"), dict) else {},
"log": list(ceph.get("log") or []) if isinstance(ceph.get("log"), list) else [],
**{key: value for key, value in ceph.items() if key not in {"mds", "mgr", "mon", "log"}},
}
async def _save_node_ceph(request: Request, node: str, ceph: dict[str, Any]) -> None:
metadata = await node_metadata(request, node)
ops = metadata.get("ops")
if not isinstance(ops, dict):
ops = {}
metadata["ops"] = ops
ops["ceph"] = ceph
await save_node_metadata(request, node, metadata)
def _upid(node: str, kind: str) -> str:
return f"UPID:{node}:{secrets.token_hex(4)}:{kind}:root@pam:"
async def _osd_row(request: Request, node: str, osdid: str) -> Any:
row = await database(request).pool.fetchrow(
"""SELECT r.id, r.external_id, r.state
FROM resources r JOIN nodes n ON n.id=r.node_id
WHERE n.name=$1 AND r.kind='ceph-osd'
AND (r.external_id=$2 OR r.external_id=$3)""",
node,
osdid,
f"osd.{osdid}",
)
if row is None:
raise ApiError(404, "OSD does not exist")
return row
def register_ceph_handlers(registry: HandlerRegistry) -> None:
async def ceph_index(request: Request, inputs: dict[str, Any]) -> list[dict[str, str]]:
node = str(values(inputs)["node"])
await require_node(request, node)
return subdirs(
"cfg",
"cmd-safety",
"crush",
"fs",
"init",
"log",
"mds",
"mgr",
"mon",
"osd",
"pool",
"restart",
"rules",
"start",
"status",
"stop",
)
async def cfg_index(request: Request, inputs: dict[str, Any]) -> list[dict[str, str]]:
await require_node(request, str(values(inputs)["node"]))
return subdirs("db", "raw", "value")
async def cfg_db(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
await require_node(request, str(values(inputs)["node"]))
ceph = await _load_cluster_ceph(request)
return list(ceph.get("cfg_db") or [])
async def cfg_raw(request: Request, inputs: dict[str, Any]) -> str:
await require_node(request, str(values(inputs)["node"]))
ceph = await _load_cluster_ceph(request)
return str(ceph.get("cfg_raw") or "")
async def cfg_value(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
payload = values(inputs)
await require_node(request, str(payload["node"]))
ceph = await _load_cluster_ceph(request)
keys = [item.strip() for item in str(payload.get("config-keys") or "").split(",") if item]
stored = ceph.get("cfg_values")
if not isinstance(stored, dict):
stored = {}
if keys:
return {key: stored.get(key, "") for key in keys}
return dict(stored)
async def crush(request: Request, inputs: dict[str, Any]) -> str:
await require_node(request, str(values(inputs)["node"]))
ceph = await _load_cluster_ceph(request)
return str(ceph.get("crush") or "")
async def rules(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
await require_node(request, str(values(inputs)["node"]))
ceph = await _load_cluster_ceph(request)
return list(ceph.get("rules") or [])
async def log(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
node = str(values(inputs)["node"])
await require_node(request, node)
ceph = await _load_node_ceph(request, node)
entries = list(ceph.get("log") or [])
start = int(values(inputs).get("start") or 0)
limit = int(values(inputs).get("limit") or 50)
return entries[start : start + limit]
async def cmd_safety(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
payload = values(inputs)
await require_node(request, str(payload["node"]))
ceph = await _load_cluster_ceph(request)
safety = ceph.get("cmd_safety")
result = dict(safety) if isinstance(safety, dict) else {}
for key in ("action", "service", "id"):
if key in payload:
result[key] = payload[key]
return result
async def init(request: Request, inputs: dict[str, Any]) -> None:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
ceph = await _load_cluster_ceph(request)
config = ceph.setdefault("config", {})
for key in (
"network",
"cluster-network",
"size",
"min_size",
"pg_bits",
"disable_cephx",
):
if key in payload:
config[key] = payload[key]
ceph["initialized"] = True
await _save_cluster_ceph(request, ceph)
node_ceph = await _load_node_ceph(request, node)
node_ceph.setdefault("mon", {})[node] = {
"name": node,
"addr": f"{node}.local:6789",
"rank": 0,
}
await _save_node_ceph(request, node, node_ceph)
async def status(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
node = str(values(inputs)["node"])
await require_node(request, node)
return await cluster_ceph_status(request, inputs)
async def start(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
ceph = await _load_cluster_ceph(request)
ceph["running"] = True
ceph["last_service_action"] = {
"action": "start",
"service": payload.get("service"),
"node": node,
}
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephstart")
async def stop(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
ceph = await _load_cluster_ceph(request)
ceph["running"] = False
ceph["last_service_action"] = {
"action": "stop",
"service": payload.get("service"),
"node": node,
}
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephstop")
async def restart(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
ceph = await _load_cluster_ceph(request)
ceph["running"] = True
ceph["last_service_action"] = {
"action": "restart",
"service": payload.get("service"),
"node": node,
}
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) if pool_id is not None else index
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 [
_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)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
pools = ceph.setdefault("pools", {})
if name in pools:
raise ApiError(400, f"pool '{name}' already exists")
pools[name] = {
"pool": name,
"size": int(payload.get("size") or 3),
"min_size": int(payload.get("min_size") or 2),
"pg_num": int(payload.get("pg_num") or 128),
"application": str(payload.get("application") or "rbd"),
"crush_rule": str(payload.get("crush_rule") or "replicated_rule"),
"pg_autoscale_mode": payload.get("pg_autoscale_mode", "warn"),
"target_size": payload.get("target_size"),
}
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephcreatepool")
async def pool_get(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
payload = values(inputs)
await require_node(request, str(payload["node"]))
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
pool = (ceph.get("pools") or {}).get(name)
if not isinstance(pool, dict):
raise ApiError(404, "pool does not exist")
# 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)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
pools = ceph.setdefault("pools", {})
if name not in pools or not isinstance(pools[name], dict):
raise ApiError(404, "pool does not exist")
current = dict(pools[name])
for key in (
"application",
"crush_rule",
"min_size",
"pg_autoscale_mode",
"pg_num",
"pg_num_min",
"size",
"target_size",
"target_size_ratio",
):
if key in payload:
current[key] = payload[key]
pools[name] = current
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephsetpool")
async def pool_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
pools = ceph.setdefault("pools", {})
if name not in pools:
raise ApiError(404, "pool does not exist")
del pools[name]
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephdestroypool")
async def pool_status(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
payload = values(inputs)
await require_node(request, str(payload["node"]))
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
pool = (ceph.get("pools") or {}).get(name)
if not isinstance(pool, dict):
raise ApiError(404, "pool does not exist")
return dict(pool)
async def fs_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)
return [dict(item) for _, item in sorted((ceph.get("fs") or {}).items())]
async def fs_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
filesystems = ceph.setdefault("fs", {})
if name in filesystems:
raise ApiError(400, f"fs '{name}' already exists")
filesystems[name] = {
"name": name,
"metadata": f"{name}_meta",
"data": f"{name}_data",
"pg_num": int(payload.get("pg_num") or 32),
}
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephcreatefs")
async def fs_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_cluster_ceph(request)
filesystems = ceph.setdefault("fs", {})
if name not in filesystems:
raise ApiError(404, "fs does not exist")
del filesystems[name]
await _save_cluster_ceph(request, ceph)
return _upid(node, "cephdestroyfs")
async def mds_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
node = str(values(inputs)["node"])
await require_node(request, node)
ceph = await _load_node_ceph(request, node)
return [
{"name": name, **data}
for name, data in sorted((ceph.get("mds") or {}).items())
if isinstance(data, dict)
]
async def mds_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_node_ceph(request, node)
mds = ceph.setdefault("mds", {})
if name in mds:
raise ApiError(400, f"mds '{name}' already exists")
mds[name] = {
"name": name,
"state": "up:active",
"hotstandby": int(bool(payload.get("hotstandby"))),
}
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephcreatemds")
async def mds_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
name = str(payload["name"])
ceph = await _load_node_ceph(request, node)
mds = ceph.setdefault("mds", {})
if name not in mds:
raise ApiError(404, "mds does not exist")
del mds[name]
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephdestroymds")
async def mgr_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
node = str(values(inputs)["node"])
await require_node(request, node)
ceph = await _load_node_ceph(request, node)
return [
{"name": name, **data}
for name, data in sorted((ceph.get("mgr") or {}).items())
if isinstance(data, dict)
]
async def mgr_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
mgr_id = str(payload["id"])
ceph = await _load_node_ceph(request, node)
mgr = ceph.setdefault("mgr", {})
if mgr_id in mgr:
raise ApiError(400, f"mgr '{mgr_id}' already exists")
mgr[mgr_id] = {"name": mgr_id, "state": "active"}
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephcreatemgr")
async def mgr_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
mgr_id = str(payload["id"])
ceph = await _load_node_ceph(request, node)
mgr = ceph.setdefault("mgr", {})
if mgr_id not in mgr:
raise ApiError(404, "mgr does not exist")
del mgr[mgr_id]
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephdestroymgr")
async def mon_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
node = str(values(inputs)["node"])
await require_node(request, node)
ceph = await _load_node_ceph(request, node)
return [
{"name": name, **data}
for name, data in sorted((ceph.get("mon") or {}).items())
if isinstance(data, dict)
]
async def mon_create(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
monid = str(payload["monid"])
ceph = await _load_node_ceph(request, node)
mons = ceph.setdefault("mon", {})
if monid in mons:
raise ApiError(400, f"mon '{monid}' already exists")
mons[monid] = {
"name": monid,
"addr": str(payload.get("mon-address") or f"{node}.local:6789"),
"rank": len(mons),
}
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephcreatemon")
async def mon_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
await require_node(request, node)
monid = str(payload["monid"])
ceph = await _load_node_ceph(request, node)
mons = ceph.setdefault("mon", {})
if monid not in mons:
raise ApiError(404, "mon does not exist")
del mons[monid]
await _save_node_ceph(request, node, ceph)
return _upid(node, "cephdestroymon")
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, n.name AS node
FROM resources r JOIN nodes n ON n.id=r.node_id
WHERE r.kind='ceph-osd'
ORDER BY n.name, r.external_id"""
)
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_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(
{
"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": float(payload.get("weight", 1.0)),
"device_class": payload.get("device_class", "hdd"),
"host": host_name,
}
)
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)
node = str(payload["node"])
await require_node(request, node)
next_id = await database(request).pool.fetchval(
"""SELECT COALESCE(
MAX(NULLIF(regexp_replace(external_id, '\\D', '', 'g'), '')::int),
-1
) + 1
FROM resources
WHERE kind='ceph-osd'""",
)
osd_id = int(next_id or 0)
external_id = f"osd.{osd_id}"
node_id = await database(request).pool.fetchval(
"SELECT id FROM nodes WHERE name=$1",
node,
)
osd_state = {
"osd_id": osd_id,
"status": "up",
"in": True,
"weight": 1.0,
"device_class": payload.get("crush-device-class") or "hdd",
"dev": payload.get("dev"),
"size_bytes": 0,
"used_bytes": 0,
}
await database(request).pool.execute(
"""INSERT INTO resources(id, node_id, cluster_id, kind, external_id, state)
VALUES(
gen_random_uuid(),
$1,
(SELECT cluster_id FROM nodes WHERE id=$1),
'ceph-osd',
$2,
$3::jsonb
)""",
node_id,
external_id,
json.dumps(osd_state, sort_keys=True),
)
return _upid(node, "cephcreateosd")
async def osd_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
node = str(values(inputs)["node"])
osdid = str(values(inputs)["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
payload = state(row["state"])
return {
"osd": int(osdid) if osdid.isdigit() else osdid,
"status": payload.get("status", "up"),
"in": 1 if payload.get("in", True) else 0,
"weight": payload.get("weight", 1.0),
"size": payload.get("size_bytes", 0),
"used": payload.get("used_bytes", 0),
"device_class": payload.get("device_class", "hdd"),
}
async def osd_delete(request: Request, inputs: dict[str, Any]) -> str:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
await database(request).pool.execute("DELETE FROM resources WHERE id=$1", row["id"])
return _upid(node, "cephdestroyosd")
async def osd_in(request: Request, inputs: dict[str, Any]) -> None:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
current = state(row["state"])
current["in"] = True
await database(request).pool.execute(
"UPDATE resources SET state=$2::jsonb, version=version+1 WHERE id=$1",
row["id"],
json.dumps(current, sort_keys=True),
)
async def osd_out(request: Request, inputs: dict[str, Any]) -> None:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
current = state(row["state"])
current["in"] = False
await database(request).pool.execute(
"UPDATE resources SET state=$2::jsonb, version=version+1 WHERE id=$1",
row["id"],
json.dumps(current, sort_keys=True),
)
async def osd_scrub(request: Request, inputs: dict[str, Any]) -> None:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
current = state(row["state"])
current["last_scrub"] = {
"deep": int(bool(payload.get("deep"))),
"token": secrets.token_hex(4),
}
await database(request).pool.execute(
"UPDATE resources SET state=$2::jsonb, version=version+1 WHERE id=$1",
row["id"],
json.dumps(current, sort_keys=True),
)
async def osd_lv_info(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
current = state(row["state"])
return {
"lv_name": f"osd-block-{osdid}",
"lv_path": f"/dev/ceph/{osdid}",
"lv_size": current.get("size_bytes", 0),
"type": payload.get("type") or "block",
}
async def osd_metadata(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
payload = values(inputs)
node = str(payload["node"])
osdid = str(payload["osdid"])
await require_node(request, node)
row = await _osd_row(request, node, osdid)
current = state(row["state"])
metadata = current.get("metadata")
if isinstance(metadata, dict):
return dict(metadata)
return {
"osd": {
"id": int(osdid) if osdid.isdigit() else osdid,
"uuid": current.get("uuid", ""),
"device_class": current.get("device_class", ""),
},
"devices": [{"dev": current.get("dev", "")}] if current.get("dev") else [],
}
async def cluster_ceph_status(_request: Request, _inputs: dict[str, Any]) -> dict[str, Any]:
row = await database(_request).pool.fetchrow(
"""SELECT capacity_bytes, used_bytes FROM storages
WHERE storage_type='ceph' ORDER BY storage_id LIMIT 1"""
)
total = int(row["capacity_bytes"] or 0) if row is not None else 0
used = int(row["used_bytes"] or 0) if row is not None else 0
osd_rows = await database(_request).pool.fetch(
"SELECT state FROM resources WHERE kind='ceph-osd'"
)
num_osds = len(osd_rows)
num_up = 0
num_in = 0
for osd_row in osd_rows:
osd_state = state(osd_row["state"])
if str(osd_state.get("status") or "") == "up":
num_up += 1
if osd_state.get("in") in (True, 1, "1"):
num_in += 1
ceph = await _load_cluster_ceph(_request)
version = ceph.get("version")
version_str = (
str(version.get("str"))
if isinstance(version, dict) and version.get("str") is not None
else str(version or "")
)
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": 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,
"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"
registry.register(base, "GET", ceph_index)
registry.register(f"{base}/cfg", "GET", cfg_index)
registry.register(f"{base}/cfg/db", "GET", cfg_db)
registry.register(f"{base}/cfg/raw", "GET", cfg_raw)
registry.register(f"{base}/cfg/value", "GET", cfg_value)
registry.register(f"{base}/cmd-safety", "GET", cmd_safety)
registry.register(f"{base}/crush", "GET", crush)
registry.register(f"{base}/fs", "GET", fs_list)
registry.register(f"{base}/fs/{{name}}", "POST", fs_create)
registry.register(f"{base}/fs/{{name}}", "DELETE", fs_delete)
registry.register(f"{base}/init", "POST", init)
registry.register(f"{base}/log", "GET", log)
registry.register(f"{base}/mds", "GET", mds_list)
registry.register(f"{base}/mds/{{name}}", "POST", mds_create)
registry.register(f"{base}/mds/{{name}}", "DELETE", mds_delete)
registry.register(f"{base}/mgr", "GET", mgr_list)
registry.register(f"{base}/mgr/{{id}}", "POST", mgr_create)
registry.register(f"{base}/mgr/{{id}}", "DELETE", mgr_delete)
registry.register(f"{base}/mon", "GET", mon_list)
registry.register(f"{base}/mon/{{monid}}", "POST", mon_create)
registry.register(f"{base}/mon/{{monid}}", "DELETE", mon_delete)
registry.register(f"{base}/osd", "GET", osd_list)
registry.register(f"{base}/osd", "POST", osd_create)
registry.register(f"{base}/osd/{{osdid}}", "GET", osd_get)
registry.register(f"{base}/osd/{{osdid}}", "DELETE", osd_delete)
registry.register(f"{base}/osd/{{osdid}}/in", "POST", osd_in)
registry.register(f"{base}/osd/{{osdid}}/out", "POST", osd_out)
registry.register(f"{base}/osd/{{osdid}}/scrub", "POST", osd_scrub)
registry.register(f"{base}/osd/{{osdid}}/lv-info", "GET", osd_lv_info)
registry.register(f"{base}/osd/{{osdid}}/metadata", "GET", osd_metadata)
registry.register(f"{base}/pool", "GET", pool_list)
registry.register(f"{base}/pool", "POST", pool_create)
registry.register(f"{base}/pool/{{name}}", "GET", pool_get)
registry.register(f"{base}/pool/{{name}}", "PUT", pool_update)
registry.register(f"{base}/pool/{{name}}", "DELETE", pool_delete)
registry.register(f"{base}/pool/{{name}}/status", "GET", pool_status)
registry.register(f"{base}/rules", "GET", rules)
registry.register(f"{base}/status", "GET", status)
registry.register(f"{base}/start", "POST", start)
registry.register(f"{base}/stop", "POST", stop)
registry.register(f"{base}/restart", "POST", restart)
registry.register("/cluster/ceph/status", "GET", cluster_ceph_status)