Initial commit: stateful OpenStack API laboratory simulator.
Ship Keystone auth, multi-service handlers (Yoga→Dalmatian), Compose/Helm packaging, API contract packs, and pytest/Pulumi coverage labs.
This commit is contained in:
@@ -0,0 +1,576 @@
|
||||
"""Storage semantic handlers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
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_node, state, storage_payload, subdirs, values
|
||||
from app.simulation.seed import CLUSTER_ID, stable_id
|
||||
|
||||
|
||||
def register_storage_handlers(registry: HandlerRegistry) -> None:
|
||||
async def _storage_row(request: Request, node: str | None, storage_id: str) -> Any:
|
||||
row = await database(request).pool.fetchrow(
|
||||
"""SELECT s.storage_id, s.storage_type, s.shared, s.capacity_bytes, s.used_bytes,
|
||||
s.config, n.name AS node_name
|
||||
FROM storages s
|
||||
JOIN resources r ON r.id = s.resource_id
|
||||
JOIN nodes n ON n.id = r.node_id
|
||||
WHERE s.storage_id=$1 AND ($2::text IS NULL OR n.name=$2)""",
|
||||
storage_id,
|
||||
node,
|
||||
)
|
||||
if row is None:
|
||||
raise ApiError(404, "storage does not exist")
|
||||
return row
|
||||
|
||||
async def storage_ids(_request: Request, _inputs: dict[str, Any]) -> list[dict[str, str]]:
|
||||
rows = await database(_request).pool.fetch(
|
||||
"SELECT DISTINCT storage_id FROM storages ORDER BY storage_id"
|
||||
)
|
||||
return [{"storage": str(row["storage_id"])} for row in rows]
|
||||
|
||||
async def storage_create(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = values(inputs)
|
||||
storage_id = str(payload["storage"])
|
||||
storage_type = str(payload.get("type") or "dir")
|
||||
exists = await database(request).pool.fetchval(
|
||||
"SELECT EXISTS(SELECT 1 FROM storages WHERE storage_id=$1)",
|
||||
storage_id,
|
||||
)
|
||||
if exists:
|
||||
raise ApiError(409, "storage ID already exists")
|
||||
node = await database(request).pool.fetchrow(
|
||||
"SELECT id, name FROM nodes ORDER BY name LIMIT 1"
|
||||
)
|
||||
if node is None:
|
||||
raise ApiError(503, "no nodes available")
|
||||
resource_id = stable_id(f"storage:{storage_id}")
|
||||
config = {
|
||||
key: value
|
||||
for key, value in payload.items()
|
||||
if key not in {"storage", "type", "nodes", "delete"}
|
||||
}
|
||||
if "content" in payload:
|
||||
config["content"] = [
|
||||
item.strip() for item in str(payload["content"]).split(",") if item.strip()
|
||||
]
|
||||
async with database(request).pool.acquire() as connection:
|
||||
async with connection.transaction():
|
||||
await connection.execute(
|
||||
"""INSERT INTO resources(id, node_id, kind, external_id, state, cluster_id)
|
||||
VALUES($1, $2, 'storage', $3, $4::jsonb, $5)""",
|
||||
resource_id,
|
||||
node["id"],
|
||||
storage_id,
|
||||
json.dumps({**config, "status": "available"}, sort_keys=True),
|
||||
CLUSTER_ID,
|
||||
)
|
||||
await connection.execute(
|
||||
"""INSERT INTO storages(
|
||||
resource_id, cluster_id, storage_id, storage_type, shared, config
|
||||
) VALUES($1, $2, $3, $4, $5, $6::jsonb)""",
|
||||
resource_id,
|
||||
CLUSTER_ID,
|
||||
storage_id,
|
||||
storage_type,
|
||||
bool(payload.get("shared", False)),
|
||||
json.dumps(config, sort_keys=True),
|
||||
)
|
||||
return {"storage": storage_id, "type": storage_type, "config": config}
|
||||
|
||||
async def storage_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
row = await _storage_row(request, None, storage_id)
|
||||
config = state(row["config"])
|
||||
return {
|
||||
"storage": storage_id,
|
||||
"type": str(row["storage_type"]),
|
||||
"shared": int(bool(row["shared"])),
|
||||
**config,
|
||||
}
|
||||
|
||||
async def storage_update(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
row = await database(request).pool.fetchrow(
|
||||
"""SELECT s.resource_id, s.config FROM storages s WHERE s.storage_id=$1""",
|
||||
storage_id,
|
||||
)
|
||||
if row is None:
|
||||
raise ApiError(404, "storage does not exist")
|
||||
current = state(row["config"])
|
||||
provided = values(inputs)
|
||||
updated = {
|
||||
**current,
|
||||
**{
|
||||
key: value
|
||||
for key, value in provided.items()
|
||||
if key not in {"storage", "delete", "digest"}
|
||||
},
|
||||
}
|
||||
await database(request).pool.execute(
|
||||
"UPDATE storages SET config=$2::jsonb WHERE storage_id=$1",
|
||||
storage_id,
|
||||
json.dumps(updated, sort_keys=True),
|
||||
)
|
||||
return updated
|
||||
|
||||
async def storage_delete(request: Request, inputs: dict[str, Any]) -> None:
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
status = await database(request).pool.execute(
|
||||
"""DELETE FROM resources r USING storages s
|
||||
WHERE s.resource_id=r.id AND s.storage_id=$1""",
|
||||
storage_id,
|
||||
)
|
||||
if status != "DELETE 1":
|
||||
raise ApiError(404, "storage does not exist")
|
||||
|
||||
async def node_storage_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
node = str(values(inputs)["node"])
|
||||
await require_node(request, node)
|
||||
rows = await database(request).pool.fetch(
|
||||
"""SELECT s.storage_id, s.storage_type, s.shared,
|
||||
s.capacity_bytes, s.used_bytes, s.config
|
||||
FROM storages s
|
||||
JOIN resources r ON r.id = s.resource_id
|
||||
JOIN nodes n ON n.id = r.node_id
|
||||
WHERE n.name=$1 OR s.shared = true
|
||||
ORDER BY s.storage_id""",
|
||||
node,
|
||||
)
|
||||
return [storage_payload(row) for row in rows]
|
||||
|
||||
async def node_storage_index(request: Request, inputs: dict[str, Any]) -> list[dict[str, str]]:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
return subdirs("content", "status", "upload")
|
||||
|
||||
async def node_storage_status(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
await require_node(request, node)
|
||||
row = await _storage_row(request, None, storage_id)
|
||||
return storage_payload(row)
|
||||
|
||||
async def node_storage_content(
|
||||
request: Request, inputs: dict[str, Any]
|
||||
) -> list[dict[str, Any]]:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
contents = await database(request).pool.fetch(
|
||||
"""SELECT volume_id, content_type, size_bytes, metadata, created_at
|
||||
FROM storage_contents WHERE storage_resource_id=$1 ORDER BY created_at DESC""",
|
||||
resource_id,
|
||||
)
|
||||
backups = await database(request).pool.fetch(
|
||||
"""SELECT b.volume_id, b.size_bytes, b.metadata, b.created_at, r.external_id AS vmid
|
||||
FROM backups b
|
||||
LEFT JOIN resources r ON r.id = b.resource_id
|
||||
WHERE b.storage_resource_id=$1
|
||||
ORDER BY b.created_at DESC""",
|
||||
resource_id,
|
||||
)
|
||||
result: list[dict[str, Any]] = []
|
||||
for item in contents:
|
||||
metadata = state(item["metadata"])
|
||||
result.append(
|
||||
{
|
||||
"volid": str(item["volume_id"]),
|
||||
"content": str(item["content_type"]),
|
||||
"size": int(item["size_bytes"]),
|
||||
"format": metadata.get("format", "raw"),
|
||||
"ctime": int(item["created_at"].timestamp()),
|
||||
}
|
||||
)
|
||||
for item in backups:
|
||||
metadata = state(item["metadata"])
|
||||
result.append(
|
||||
{
|
||||
"volid": str(item["volume_id"]),
|
||||
"content": "backup",
|
||||
"size": int(item["size_bytes"]),
|
||||
"format": "vma.zst",
|
||||
"vmid": int(item["vmid"]) if item["vmid"] is not None else None,
|
||||
"notes": metadata.get("notes-template"),
|
||||
"ctime": int(item["created_at"].timestamp()),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
async def _content_item(
|
||||
request: Request, storage_resource_id: object, volume_id: str
|
||||
) -> dict[str, Any]:
|
||||
row = await database(request).pool.fetchrow(
|
||||
"""SELECT volume_id, content_type, size_bytes, metadata, created_at
|
||||
FROM storage_contents WHERE storage_resource_id=$1 AND volume_id=$2""",
|
||||
storage_resource_id,
|
||||
volume_id,
|
||||
)
|
||||
if row is not None:
|
||||
metadata = state(row["metadata"])
|
||||
return {
|
||||
"volid": str(row["volume_id"]),
|
||||
"content": str(row["content_type"]),
|
||||
"size": int(row["size_bytes"]),
|
||||
"format": metadata.get("format", "raw"),
|
||||
"ctime": int(row["created_at"].timestamp()),
|
||||
}
|
||||
backup = await database(request).pool.fetchrow(
|
||||
"""SELECT b.volume_id, b.size_bytes, b.metadata, b.created_at, r.external_id AS vmid
|
||||
FROM backups b
|
||||
LEFT JOIN resources r ON r.id = b.resource_id
|
||||
WHERE b.storage_resource_id=$1 AND b.volume_id=$2""",
|
||||
storage_resource_id,
|
||||
volume_id,
|
||||
)
|
||||
if backup is None:
|
||||
raise ApiError(404, "volume does not exist")
|
||||
metadata = state(backup["metadata"])
|
||||
return {
|
||||
"volid": str(backup["volume_id"]),
|
||||
"content": "backup",
|
||||
"size": int(backup["size_bytes"]),
|
||||
"format": "vma.zst",
|
||||
"vmid": int(backup["vmid"]) if backup["vmid"] is not None else None,
|
||||
"notes": metadata.get("notes-template"),
|
||||
"ctime": int(backup["created_at"].timestamp()),
|
||||
}
|
||||
|
||||
async def node_storage_content_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
volume_id = str(values(inputs)["volume"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
return await _content_item(request, resource_id, volume_id)
|
||||
|
||||
async def node_storage_content_delete(request: Request, inputs: dict[str, Any]) -> None:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
volume_id = str(values(inputs)["volume"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
status = await database(request).pool.execute(
|
||||
"DELETE FROM storage_contents WHERE storage_resource_id=$1 AND volume_id=$2",
|
||||
resource_id,
|
||||
volume_id,
|
||||
)
|
||||
if status == "DELETE 1":
|
||||
return
|
||||
status = await database(request).pool.execute(
|
||||
"DELETE FROM backups WHERE storage_resource_id=$1 AND volume_id=$2",
|
||||
resource_id,
|
||||
volume_id,
|
||||
)
|
||||
if status != "DELETE 1":
|
||||
raise ApiError(404, "volume does not exist")
|
||||
|
||||
async def node_storage_upload(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
payload = values(inputs)
|
||||
filename = str(payload.get("filename") or "upload.bin")
|
||||
content_type = str(payload.get("content") or "iso")
|
||||
raw_size = payload.get("size") or 0
|
||||
try:
|
||||
size = int(raw_size)
|
||||
except (TypeError, ValueError) as error:
|
||||
raise ApiError(400, "invalid size") from error
|
||||
volume_id = f"{storage_id}:{content_type}/{filename}"
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
await database(request).pool.execute(
|
||||
"""INSERT INTO storage_contents(
|
||||
id, storage_resource_id, volume_id, content_type, size_bytes, metadata
|
||||
) VALUES(gen_random_uuid(), $1, $2, $3, $4, $5::jsonb)
|
||||
ON CONFLICT (storage_resource_id, volume_id) DO UPDATE
|
||||
SET size_bytes=EXCLUDED.size_bytes,
|
||||
content_type=EXCLUDED.content_type,
|
||||
metadata=EXCLUDED.metadata""",
|
||||
resource_id,
|
||||
volume_id,
|
||||
content_type,
|
||||
size,
|
||||
json.dumps({"filename": filename, "source": "upload"}, sort_keys=True),
|
||||
)
|
||||
return {"uploadid": volume_id, "filename": filename, "size": size, "volid": volume_id}
|
||||
|
||||
async def node_storage_prunebackups(
|
||||
request: Request, inputs: dict[str, Any]
|
||||
) -> list[dict[str, Any]] | None:
|
||||
node = str(values(inputs)["node"])
|
||||
storage_id = str(values(inputs)["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
if request.method == "DELETE":
|
||||
keep = int(values(inputs).get("keep-last") or values(inputs).get("keep_last") or 1)
|
||||
await database(request).pool.execute(
|
||||
"""DELETE FROM backups
|
||||
WHERE storage_resource_id=$1 AND id IN (
|
||||
SELECT id FROM backups
|
||||
WHERE storage_resource_id=$1
|
||||
ORDER BY created_at DESC
|
||||
OFFSET $2
|
||||
)""",
|
||||
resource_id,
|
||||
keep,
|
||||
)
|
||||
return None
|
||||
rows = await database(request).pool.fetch(
|
||||
"""SELECT volume_id, size_bytes, created_at FROM backups
|
||||
WHERE storage_resource_id=$1 ORDER BY created_at DESC""",
|
||||
resource_id,
|
||||
)
|
||||
return [
|
||||
{
|
||||
"volid": str(row["volume_id"]),
|
||||
"size": int(row["size_bytes"]),
|
||||
"ctime": int(row["created_at"].timestamp()),
|
||||
}
|
||||
for row in rows
|
||||
]
|
||||
|
||||
async def content_copy(request: Request, inputs: dict[str, Any]) -> str:
|
||||
payload = values(inputs)
|
||||
node = str(payload["node"])
|
||||
storage_id = str(payload["storage"])
|
||||
volume = str(payload["volume"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
row = await database(request).pool.fetchrow(
|
||||
"""SELECT volume_id, content_type, size_bytes, metadata
|
||||
FROM storage_contents WHERE storage_resource_id=$1 AND volume_id=$2""",
|
||||
resource_id,
|
||||
volume,
|
||||
)
|
||||
if row is None:
|
||||
raise ApiError(404, "volume does not exist")
|
||||
target = str(payload.get("target") or f"{volume}-copy")
|
||||
await database(request).pool.execute(
|
||||
"""INSERT INTO storage_contents(
|
||||
id, storage_resource_id, volume_id, content_type, size_bytes, metadata
|
||||
) VALUES(gen_random_uuid(), $1, $2, $3, $4, $5::jsonb)
|
||||
ON CONFLICT (storage_resource_id, volume_id) DO UPDATE
|
||||
SET size_bytes=EXCLUDED.size_bytes, metadata=EXCLUDED.metadata""",
|
||||
resource_id,
|
||||
target,
|
||||
row["content_type"],
|
||||
row["size_bytes"],
|
||||
json.dumps({**state(row["metadata"]), "copied_from": volume}, sort_keys=True),
|
||||
)
|
||||
return f"UPID:{node}:copy:{target}"
|
||||
|
||||
async def content_update(request: Request, inputs: dict[str, Any]) -> None:
|
||||
payload = values(inputs)
|
||||
node = str(payload["node"])
|
||||
storage_id = str(payload["storage"])
|
||||
volume = str(payload["volume"])
|
||||
await require_node(request, node)
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
row = await database(request).pool.fetchrow(
|
||||
"""SELECT metadata FROM storage_contents
|
||||
WHERE storage_resource_id=$1 AND volume_id=$2""",
|
||||
resource_id,
|
||||
volume,
|
||||
)
|
||||
if row is None:
|
||||
raise ApiError(404, "volume does not exist")
|
||||
meta = state(row["metadata"])
|
||||
if "notes" in payload:
|
||||
meta["notes"] = payload["notes"]
|
||||
if "protected" in payload:
|
||||
meta["protected"] = int(bool(payload["protected"]))
|
||||
await database(request).pool.execute(
|
||||
"""UPDATE storage_contents SET metadata=$3::jsonb
|
||||
WHERE storage_resource_id=$1 AND volume_id=$2""",
|
||||
resource_id,
|
||||
volume,
|
||||
json.dumps(meta, sort_keys=True),
|
||||
)
|
||||
|
||||
async def download_url(request: Request, inputs: dict[str, Any]) -> str:
|
||||
payload = values(inputs)
|
||||
node = str(payload["node"])
|
||||
storage_id = str(payload["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
filename = str(payload.get("filename") or "download.bin")
|
||||
content_type = str(payload.get("content") or "iso")
|
||||
volume_id = f"{storage_id}:{content_type}/{filename}"
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
await database(request).pool.execute(
|
||||
"""INSERT INTO storage_contents(
|
||||
id, storage_resource_id, volume_id, content_type, size_bytes, metadata
|
||||
) VALUES(gen_random_uuid(), $1, $2, $3, 0, $4::jsonb)
|
||||
ON CONFLICT (storage_resource_id, volume_id) DO UPDATE
|
||||
SET metadata=EXCLUDED.metadata""",
|
||||
resource_id,
|
||||
volume_id,
|
||||
content_type,
|
||||
json.dumps(
|
||||
{
|
||||
"filename": filename,
|
||||
"url": payload.get("url"),
|
||||
"source": "download-url",
|
||||
},
|
||||
sort_keys=True,
|
||||
),
|
||||
)
|
||||
return f"UPID:{node}:download:{filename}"
|
||||
|
||||
async def oci_pull(request: Request, inputs: dict[str, Any]) -> str:
|
||||
payload = values(inputs)
|
||||
node = str(payload["node"])
|
||||
storage_id = str(payload["storage"])
|
||||
await require_node(request, node)
|
||||
await _storage_row(request, None, storage_id)
|
||||
reference = str(payload.get("reference") or "image:latest")
|
||||
filename = str(payload.get("filename") or reference.replace("/", "_"))
|
||||
volume_id = f"{storage_id}:import/{filename}"
|
||||
resource_id = await database(request).pool.fetchval(
|
||||
"SELECT resource_id FROM storages WHERE storage_id=$1",
|
||||
storage_id,
|
||||
)
|
||||
await database(request).pool.execute(
|
||||
"""INSERT INTO storage_contents(
|
||||
id, storage_resource_id, volume_id, content_type, size_bytes, metadata
|
||||
) VALUES(gen_random_uuid(), $1, $2, 'import', 0, $3::jsonb)
|
||||
ON CONFLICT (storage_resource_id, volume_id) DO UPDATE
|
||||
SET metadata=EXCLUDED.metadata""",
|
||||
resource_id,
|
||||
volume_id,
|
||||
json.dumps({"reference": reference, "source": "oci"}, sort_keys=True),
|
||||
)
|
||||
return f"UPID:{node}:oci-pull:{filename}"
|
||||
|
||||
async def file_restore_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
await _storage_row(request, None, str(payload["storage"]))
|
||||
filepath = str(payload.get("filepath") or "/")
|
||||
return [{"filepath": filepath.rstrip("/") + "/etc", "type": "d", "text": "etc"}]
|
||||
|
||||
async def file_restore_download(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
await _storage_row(request, None, str(payload["storage"]))
|
||||
return {
|
||||
"download-url": f"/api2/json/nodes/{payload['node']}/storage/"
|
||||
f"{payload['storage']}/file-restore/download",
|
||||
"filepath": payload.get("filepath") or "/",
|
||||
"volume": payload.get("volume"),
|
||||
}
|
||||
|
||||
async def storage_identity(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
row = await _storage_row(request, None, str(payload["storage"]))
|
||||
return {
|
||||
"storage": str(row["storage_id"]),
|
||||
"type": str(row["storage_type"]),
|
||||
"fingerprint": f"sim-{row['storage_id']}",
|
||||
}
|
||||
|
||||
async def import_metadata(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
storage_id = str(payload["storage"])
|
||||
volume = str(payload["volume"])
|
||||
await _storage_row(request, None, storage_id)
|
||||
return {
|
||||
"type": "qemu",
|
||||
"source": volume,
|
||||
"disks": {"scsi0": f"{storage_id}:0/vm-import.raw"},
|
||||
"net0": "virtio,bridge=vmbr0",
|
||||
}
|
||||
|
||||
async def storage_rrd(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
storage_id = str(payload["storage"])
|
||||
await _storage_row(request, None, storage_id)
|
||||
return {"filename": f"pve-storage-{storage_id}.rrd"}
|
||||
|
||||
async def storage_rrddata(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
payload = values(inputs)
|
||||
await require_node(request, str(payload["node"]))
|
||||
await _storage_row(request, None, str(payload["storage"]))
|
||||
return [
|
||||
{"time": 1_700_000_000, "used": 10, "total": 100},
|
||||
{"time": 1_700_000_060, "used": 12, "total": 100},
|
||||
]
|
||||
|
||||
registry.register("/storage", "GET", storage_ids)
|
||||
registry.register("/storage", "POST", storage_create)
|
||||
registry.register("/storage/{storage}", "GET", storage_get)
|
||||
registry.register("/storage/{storage}", "PUT", storage_update)
|
||||
registry.register("/storage/{storage}", "DELETE", storage_delete)
|
||||
registry.register("/nodes/{node}/storage", "GET", node_storage_list)
|
||||
registry.register("/nodes/{node}/storage/{storage}", "GET", node_storage_index)
|
||||
registry.register("/nodes/{node}/storage/{storage}/status", "GET", node_storage_status)
|
||||
registry.register("/nodes/{node}/storage/{storage}/content", "GET", node_storage_content)
|
||||
registry.register("/nodes/{node}/storage/{storage}/content", "POST", node_storage_upload)
|
||||
registry.register(
|
||||
"/nodes/{node}/storage/{storage}/content/{volume}", "GET", node_storage_content_get
|
||||
)
|
||||
registry.register(
|
||||
"/nodes/{node}/storage/{storage}/content/{volume}", "DELETE", node_storage_content_delete
|
||||
)
|
||||
registry.register("/nodes/{node}/storage/{storage}/upload", "POST", node_storage_upload)
|
||||
registry.register(
|
||||
"/nodes/{node}/storage/{storage}/prunebackups", "GET", node_storage_prunebackups
|
||||
)
|
||||
registry.register(
|
||||
"/nodes/{node}/storage/{storage}/prunebackups", "DELETE", node_storage_prunebackups
|
||||
)
|
||||
registry.register("/nodes/{node}/storage/{storage}/content/{volume}", "POST", content_copy)
|
||||
registry.register("/nodes/{node}/storage/{storage}/content/{volume}", "PUT", content_update)
|
||||
registry.register("/nodes/{node}/storage/{storage}/download-url", "POST", download_url)
|
||||
registry.register("/nodes/{node}/storage/{storage}/oci-registry-pull", "POST", oci_pull)
|
||||
registry.register("/nodes/{node}/storage/{storage}/file-restore/list", "GET", file_restore_list)
|
||||
registry.register(
|
||||
"/nodes/{node}/storage/{storage}/file-restore/download", "GET", file_restore_download
|
||||
)
|
||||
registry.register("/nodes/{node}/storage/{storage}/identity", "GET", storage_identity)
|
||||
registry.register("/nodes/{node}/storage/{storage}/import-metadata", "GET", import_metadata)
|
||||
registry.register("/nodes/{node}/storage/{storage}/rrd", "GET", storage_rrd)
|
||||
registry.register("/nodes/{node}/storage/{storage}/rrddata", "GET", storage_rrddata)
|
||||
Reference in New Issue
Block a user