1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-10 12:16:49 +00:00

fix(vrtManager): a shut-off VM with NVRAM or TPM state is not migrated

libvirt keeps a VM's UEFI variables (NVRAM) and emulated TPM state in
files of its host. A live migration carries both: on the test hosts
(libvirt 11.10, QEMU 10.1) the destination got the same NVRAM content
once the VM ran and the same TPM state, and the source TPM state was
removed. Moving a shut-off VM moves only its definition, so neither
went along: on its first start on the destination libvirt made a new
NVRAM from the template and a new TPM. Boot entries and enrolled keys
are lost, and disks sealed to the TPM no longer unlock.

A shut-off VM whose definition has an NVRAM file or an emulated TPM is
now refused before anything changes: "Start the VM and migrate it
live." A passthrough or external TPM keeps no state on the host and
still moves. The check sits in moveto, not in the define/undefine
workaround, as libvirt's own offline migration does not carry the files
either. The keep-NVRAM and keep-TPM undefine flags of that workaround
are gone, as no VM that needs them reaches it.

Checked on the test hosts: a shut-off UEFI VM with a TPM is refused and
stays on the source with nothing defined on the destination; the same
VM migrates live. The new tests fail on the previous code.
This commit is contained in:
catborise 2026-10-06 21:02:58 +03:00
parent 98bb6ebdb5
commit c7a7c843fb
2 changed files with 49 additions and 19 deletions

View file

@ -211,15 +211,29 @@ class ShutOffMigrationTestCase(unittest.TestCase):
destination(host).moveto(source, "vm", live=False, unsafe=False, offline=True)
self.assertEqual(host.defined, ["<domain><name>vm</name></domain>"])
self.assertEqual(source.instance.calls, [])
self.assertEqual(source.instance.undefined, libvirt.VIR_DOMAIN_UNDEFINE_KEEP_NVRAM)
self.assertEqual(source.instance.undefined, 0)
def test_tpm_state_stays_on_the_source(self):
source = FakeSource(5, xml="<domain><devices><tpm model='tpm-crb'/></devices></domain>")
destination().moveto(source, "vm", live=False, unsafe=False, offline=True)
self.assertEqual(
source.instance.undefined,
libvirt.VIR_DOMAIN_UNDEFINE_KEEP_NVRAM | libvirt.VIR_DOMAIN_UNDEFINE_KEEP_TPM,
)
def test_a_vm_with_nvram_or_tpm_state_is_refused(self):
"""That state stays on the source host: the VM would start on the
destination with new UEFI variables and a new TPM."""
cases = {
"nvram": "<os><nvram>/var/lib/libvirt/qemu/nvram/vm_VARS.fd</nvram></os>",
"tpm": "<devices><tpm model='tpm-crb'><backend type='emulator' version='2.0'/></tpm></devices>",
}
for case, xml in cases.items():
with self.subTest(case=case):
host = FakeHost()
source = FakeSource(5, xml=f"<domain>{xml}</domain>")
with self.assertRaisesRegex(util.OperationError, "migrate it live"):
destination(host).moveto(source, "vm", live=False, unsafe=False, offline=True)
self.assertEqual(host.defined, [])
self.assertIsNone(source.instance.undefined)
def test_a_tpm_without_local_state_moves(self):
xml = "<domain><devices><tpm model='tpm-tis'><backend type='passthrough'/></tpm></devices></domain>"
host = FakeHost()
destination(host).moveto(FakeSource(5, xml=xml), "vm", live=False, unsafe=False, offline=True)
self.assertEqual(host.defined, [xml])
def test_refused_before_anything_changes(self):
cases = {