From 5388743053d7784590f4837370c3c47e64819f85 Mon Sep 17 00:00:00 2001 From: catborise Date: Tue, 13 Oct 2026 07:43:59 +0300 Subject: [PATCH] fix(vrtManager): refuse to migrate a VM with snapshots in every state libvirt keeps snapshot metadata on the source host and a migration does not carry it, even when both hosts share the disks. Only the shut-off path refused such a VM itself; a running or paused one relied on libvirt's own refusal. moveto now refuses it for every state, before any check against the destination, with the same message. --- vrtManager/instance.py | 6 ++++-- vrtManager/tests/test_migration.py | 13 ++++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index f5e11259..39803ae5 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -117,6 +117,10 @@ class wvmInstances(wvmConnect): mode.append(label) dom = conn.instance + # libvirt keeps snapshot metadata on the source host: shared disks do + # not carry it to the destination. + if dom.snapshotNum(0): + raise util.OperationError("A VM with snapshots cannot be migrated") if state == 5: self.refuse_local_state(dom.XMLDesc(0)) definitions = {dom.XMLDesc(0)} @@ -283,8 +287,6 @@ class wvmInstances(wvmConnect): first (RHEL-145770) and tracks the fix as RHEL-156800; RHEL/Rocky 9.8 with libvirt 11.10.0-12.el9_8 is affected. When supported hosts all have the fix, offline migration can go back to dom.migrate.""" - if dom.snapshotNum(0): - raise util.OperationError("A VM with snapshots cannot be migrated") if dom.hasManagedSaveImage(0): raise util.OperationError("A VM with a saved state cannot be migrated") for lookup, key in ((self.wvm.lookupByUUIDString, dom.UUIDString()), (self.wvm.lookupByName, dom.name())): diff --git a/vrtManager/tests/test_migration.py b/vrtManager/tests/test_migration.py index 729b11ee..8e46fcb2 100644 --- a/vrtManager/tests/test_migration.py +++ b/vrtManager/tests/test_migration.py @@ -237,7 +237,6 @@ class ShutOffMigrationTestCase(unittest.TestCase): def test_refused_before_anything_changes(self): cases = { - "snapshots": (FakeHost(), {"snapshots": 2}), "saved state": (FakeHost(), {"saved": True}), "same uuid on the destination": (FakeHost(existing={"u-1"}), {}), "same name on the destination": (FakeHost(existing={"vm"}), {}), @@ -300,6 +299,18 @@ class MigrationModeTestCase(unittest.TestCase): destination().moveto(source, "vm", unsafe=False, **{"live": False, "offline": False, **request}) self.assertEqual(source.instance.calls, []) + def test_a_vm_with_snapshots_is_refused_in_every_state(self): + """libvirt keeps snapshot metadata on the source host; a migration + does not carry it, even with shared disks.""" + for state, request in ((1, {"live": True}), (1, {}), (3, {}), (5, {})): + with self.subTest(state=state, request=request): + host = FakeHost() + source = FakeSource(state, snapshots=1) + with self.assertRaisesRegex(util.OperationError, "A VM with snapshots cannot be migrated"): + destination(host).moveto(source, "vm", unsafe=False, **{"live": False, "offline": False, **request}) + self.assertEqual(source.instance.calls, []) + self.assertEqual(host.defined, []) + def test_modes(self): live, unsafe = libvirt.VIR_MIGRATE_LIVE, libvirt.VIR_MIGRATE_UNSAFE converge, compressed = libvirt.VIR_MIGRATE_AUTO_CONVERGE, libvirt.VIR_MIGRATE_COMPRESSED