feat: implement QEMU clone and local migration

This commit is contained in:
Sergey Antropoff
2026-07-13 02:14:00 +03:00
parent 48f7cc39ff
commit 41b76b5472
10 changed files with 279 additions and 4 deletions
+49
View File
@@ -262,3 +262,52 @@ async def test_qemu_snapshot_handlers(monkeypatch: pytest.MonkeyPatch) -> None:
assert (await rollback(http_request, common)).startswith("UPID:pve1:")
assert (await delete(http_request, common)).startswith("UPID:pve1:")
assert tasks == ["qemu-snapshot-create", "qemu-snapshot-rollback", "qemu-snapshot-delete"]
async def test_qemu_clone_and_migrate_handlers(monkeypatch: pytest.MonkeyPatch) -> None:
tasks: list[dict[str, Any]] = []
class FakeTaskRepository:
def __init__(self, pool: object) -> None:
del pool
async def create(self, **kwargs: Any) -> Task:
tasks.append(kwargs)
return Task(
uuid.uuid4(),
str(kwargs["upid"]),
str(kwargs["task_type"]),
"queued",
{},
0,
False,
0,
)
monkeypatch.setattr("app.handlers.qemu.TaskRepository", FakeTaskRepository)
registry = HandlerRegistry()
register_qemu_handlers(registry)
pool = QemuPool()
http_request = request(pool)
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
clone_upid = await clone(
http_request, inputs(node="pve1", vmid=150, newid=151, name="clone", full=True)
)
assert clone_upid.startswith("UPID:pve1:")
assert tasks[-1]["task_type"] == "qemu-clone"
assert (await migrate_get(http_request, inputs(node="pve1", vmid=150, target="pve2")))[
"local_disks"
] == []
migrate_upid = await migrate(
http_request, inputs(node="pve1", vmid=150, target="pve2", online=False)
)
assert migrate_upid.startswith("UPID:pve1:")
assert tasks[-1]["task_type"] == "qemu-migrate"
with pytest.raises(ApiError) as same_node:
await migrate(http_request, inputs(node="pve1", vmid=150, target="pve1"))
assert same_node.value.status_code == 400
+44
View File
@@ -82,6 +82,8 @@ class CrudConnection:
return {"id": uuid.uuid4(), "cluster_id": uuid.uuid4()}
if "JOIN virtual_machines" in sql:
return {"state": '{"status":"stopped","name":"old"}', "config": '{"name":"old"}'}
if "SELECT state FROM resources" in sql:
return {"state": '{"status":"stopped","name":"old"}'}
if "FROM snapshots" in sql:
return {
"state": (
@@ -217,3 +219,45 @@ async def test_qemu_worker_snapshot_create_rollback_and_delete_are_persistent()
assert any("INSERT INTO snapshots" in command for command in commands)
assert any("UPDATE virtual_machines" in command for command in commands)
assert any("DELETE FROM snapshots" in command for command in commands)
async def test_qemu_worker_clone_and_migrate_are_persistent() -> None:
repository = CrudRepository()
handler = qemu_handler(cast(TaskRepository, repository), cast(Clock, ImmediateClock()))
resource_id = uuid.uuid4()
cloned = await handler(
Task(
uuid.uuid4(),
"UPID:clone",
"qemu-clone",
"running",
{
"source_resource_id": str(resource_id),
"node": "pve1",
"vmid": 151,
"name": "clone",
},
0,
False,
1,
)
)
migrated = await handler(
Task(
uuid.uuid4(),
"UPID:migrate",
"qemu-migrate",
"running",
{"resource_id": str(resource_id), "target": "pve2"},
0,
False,
1,
)
)
assert cloned == {"vmid": 151, "node": "pve1"}
assert migrated == {"node": "pve2", "status": "stopped"}
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)