feat: implement QEMU disk resize and move

This commit is contained in:
Sergey Antropoff
2026-07-13 02:20:54 +03:00
parent 41b76b5472
commit 003ee5d634
12 changed files with 239 additions and 7 deletions
+12
View File
@@ -23,6 +23,7 @@ async def client_for(tmp_path: Path) -> AsyncClient:
Parameter(name="node", definition=Schema(type="string")),
Parameter(name="count", definition=Schema(type="integer", minimum=1)),
Parameter(name="force", definition=Schema(type="boolean", optional=True)),
Parameter(name="scsi[n]", definition=Schema(type="string", optional=True)),
),
returns=Schema(type="null"),
checksum="1" * 64,
@@ -41,6 +42,8 @@ async def client_for(tmp_path: Path) -> AsyncClient:
async def handler(_request: Request, inputs: dict[str, Any]) -> None:
assert inputs["values"]["count"] >= 1
if "scsi0" in inputs["values"]:
assert inputs["values"]["scsi0"] == "local:disk,size=8G"
return None
handlers.register("/nodes/{node}/test", "POST", handler)
@@ -96,3 +99,12 @@ async def test_non_object_json_is_rejected_without_fastapi_body(tmp_path: Path)
assert response.status_code == 400
assert response.json()["errors"] == {"body": "expected an object"}
assert "detail" not in response.json()
async def test_indexed_contract_parameter_accepts_concrete_device(tmp_path: Path) -> None:
async with await client_for(tmp_path) as client:
response = await client.post(
"/api2/json/nodes/pve/test", json={"count": 1, "scsi0": "local:disk,size=8G"}
)
assert response.status_code == 200
+16 -2
View File
@@ -66,7 +66,11 @@ class QemuPool:
return {"state": '{"status":"stopped"}', "config": '{"name":"vm"}'}
if "SELECT r.id, r.state" in sql:
status = "running" if self.running else "stopped"
return {"id": self.resource_id, "state": f'{{"status":"{status}"}}'}
return {
"id": self.resource_id,
"state": f'{{"status":"{status}"}}',
"config": '{"scsi0":"local-lvm:vm-150-disk-0,size=8G"}',
}
if "SELECT r.id, r.state, v.config" in sql:
return {"id": self.resource_id, "state": '{"status":"stopped"}', "config": "{}"}
if "SELECT s.* FROM snapshots" in sql:
@@ -292,7 +296,9 @@ async def test_qemu_clone_and_migrate_handlers(monkeypatch: pytest.MonkeyPatch)
clone = registry.get("/nodes/{node}/qemu/{vmid}/clone", "POST")
migrate_get = registry.get("/nodes/{node}/qemu/{vmid}/migrate", "GET")
migrate = registry.get("/nodes/{node}/qemu/{vmid}/migrate", "POST")
assert clone and migrate_get and migrate
resize = registry.get("/nodes/{node}/qemu/{vmid}/resize", "PUT")
move = registry.get("/nodes/{node}/qemu/{vmid}/move_disk", "POST")
assert clone and migrate_get and migrate and resize and move
clone_upid = await clone(
http_request, inputs(node="pve1", vmid=150, newid=151, name="clone", full=True)
@@ -307,6 +313,14 @@ async def test_qemu_clone_and_migrate_handlers(monkeypatch: pytest.MonkeyPatch)
)
assert migrate_upid.startswith("UPID:pve1:")
assert tasks[-1]["task_type"] == "qemu-migrate"
assert (
await resize(http_request, inputs(node="pve1", vmid=150, disk="scsi0", size="+2G")) is None
)
move_upid = await move(
http_request, inputs(node="pve1", vmid=150, disk="scsi0", storage="local")
)
assert move_upid.startswith("UPID:pve1:")
assert tasks[-1]["task_type"] == "qemu-move-disk"
with pytest.raises(ApiError) as same_node:
await migrate(http_request, inputs(node="pve1", vmid=150, target="pve1"))
+21
View File
@@ -84,6 +84,8 @@ class CrudConnection:
return {"state": '{"status":"stopped","name":"old"}', "config": '{"name":"old"}'}
if "SELECT state FROM resources" in sql:
return {"state": '{"status":"stopped","name":"old"}'}
if "SELECT config FROM virtual_machines" in sql:
return {"config": '{"scsi0":"local-lvm:vm-150-disk-0,size=10G"}'}
if "FROM snapshots" in sql:
return {
"state": (
@@ -255,9 +257,28 @@ async def test_qemu_worker_clone_and_migrate_are_persistent() -> None:
1,
)
)
moved = await handler(
Task(
uuid.uuid4(),
"UPID:move",
"qemu-move-disk",
"running",
{
"resource_id": str(resource_id),
"disk": "scsi0",
"target_disk": "scsi0",
"storage": "local",
"delete": True,
},
0,
False,
1,
)
)
assert cloned == {"vmid": 151, "node": "pve1"}
assert migrated == {"node": "pve2", "status": "stopped"}
assert moved == {"disk": "scsi0", "storage": "local"}
commands = repository.connection.commands
assert any("INSERT INTO resources" in command for command in commands)
assert any("node_id=$2" in command for command in commands)