From f63c1ebe32752adca2244ee39a72d30aed2b3eb2 Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 1 Nov 2026 17:35:32 +0300 Subject: [PATCH] fix(instances): a VM shut down after its Migrate tab opened moves offline The Migrate tab shows a running VM's live migration switch, on by default. A VM shut down after the tab opened, as one is to move it shut off, was then refused: "A shut-off VM cannot be migrated live". A shut-off VM now moves offline whatever the request says about live, as the tab tells for a shut-off VM; the tuning options are for a running VM only, as before. An offline request for a running or paused VM is still refused. --- vrtManager/instance.py | 7 +++---- vrtManager/tests/test_migration.py | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 71752ba3..8d366984 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -116,8 +116,9 @@ class wvmInstances(wvmConnect): The mode follows the VM's state, read once: a running VM migrates live or, without live, paused for the transfer (non-live); a paused - VM stays paused; a shut-off VM moves offline. A request that does not - fit the state is refused. unsafe, autoconverge (live only) and + VM stays paused; a shut-off VM moves offline, also when the request + was made while it ran (live). An offline request for a running or + paused VM is refused. unsafe, autoconverge (live only) and compress apply to a running VM. uri: the native migration URI (tcp://address) on the destination; None lets libvirt use the destination's own hostname. pools: {target: pool here} of the disks @@ -126,8 +127,6 @@ class wvmInstances(wvmConnect): and options used, for the log, and the paths of the copied disks.""" state = conn.get_status() if state == 5: - if live: - raise util.OperationError("A shut-off VM cannot be migrated live") mode = ["offline"] elif state in (1, 3): if offline: diff --git a/vrtManager/tests/test_migration.py b/vrtManager/tests/test_migration.py index ba875edd..e6d22b80 100644 --- a/vrtManager/tests/test_migration.py +++ b/vrtManager/tests/test_migration.py @@ -287,7 +287,7 @@ class MigrationModeTestCase(unittest.TestCase): return mode, source.instance.calls[0][0] if source.instance.calls else None def test_refused_requests(self): - for state, request in ((5, {"live": True}), (1, {"offline": True}), (3, {"offline": True}), + for state, request in ((1, {"offline": True}), (3, {"offline": True}), (1, {"live": True, "offline": True}), (6, {}), (4, {}), (7, {}), (0, {})): with self.subTest(state=state, request=request): source = FakeSource(state) @@ -325,6 +325,8 @@ class MigrationModeTestCase(unittest.TestCase): self.assertEqual(flags & (live | unsafe | converge | compressed), live) self.assertEqual(self.migrate(5), ("offline", None)) self.assertEqual(self.migrate(5, offline=True), ("offline", None)) + # asked while it ran, then shut down: it moves offline, the tuning options unused + self.assertEqual(self.migrate(5, live=True, autoconverge=True, compress=True), ("offline", None)) def with_disks(*disks):