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

fix(instances): internal snapshots no longer rewrite UEFI loaders

Taking or reverting an internal snapshot redefined a pflash VM with a
rom loader around the operation. libvirt 10.10 rejects that definition,
so the request ended in a 500; where it was accepted, the snapshot
recorded the fake loader, and a failure between rewrite and restore
left the VM unable to boot.

The definition is no longer touched: libvirt decides, and its error is
shown (for a running VM with raw NVRAM: 'internal snapshots of a VM
with pflash based firmware require QCOW2 nvram format'). A stopped VM's
disk-only internal snapshot is taken, as libvirt allows it. Revert
errors are shown too. The external snapshot path keeps its rewrite for
now.

Live tests cover running and stopped UEFI VMs; both failed with a 500
on the previous code.
This commit is contained in:
catborise 2026-10-03 15:10:53 +03:00
parent 3e427a41b8
commit 21f0777e76
4 changed files with 71 additions and 31 deletions

View file

@ -1367,15 +1367,12 @@ class wvmInstance(wvmConnect):
state,
time.time(),
)
changed = self.change_snapshot_xml()
try:
xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE)
xml += """<active>0</active>
</domainsnapshot>"""
self._snapshotCreateXML(xml, 0)
finally:
if changed:
self.recover_snapshot_xml()
# libvirt decides whether this firmware/NVRAM supports internal
# snapshots; the domain definition is never rewritten for it (R-13).
xml += self._XMLDesc(VIR_DOMAIN_XML_SECURE)
xml += """<active>0</active>
</domainsnapshot>"""
self._snapshotCreateXML(xml, 0)
def change_snapshot_xml(self):
try:
@ -1508,13 +1505,8 @@ class wvmInstance(wvmConnect):
snap.delete(0)
def snapshot_revert(self, snapshot):
changed = self.change_snapshot_xml()
try:
snap = self.instance.snapshotLookupByName(snapshot, 0)
self.instance.revertToSnapshot(snap, 0)
finally:
if changed:
self.recover_snapshot_xml()
snap = self.instance.snapshotLookupByName(snapshot, 0)
self.instance.revertToSnapshot(snap, 0)
def get_managed_save_image(self):
return self.instance.hasManagedSaveImage(0)

View file

@ -68,8 +68,8 @@ class TestXmlSafety(unittest.TestCase):
with self.assertRaises(RuntimeError):
inst.create_snapshot(bad_name, bad_desc)
inst.change_snapshot_xml.assert_called_once()
inst.recover_snapshot_xml.assert_called_once()
inst.change_snapshot_xml.assert_not_called()
inst.recover_snapshot_xml.assert_not_called()
def test_snapshot_xml_payload_is_escaped(self):
inst = wvmInstance.__new__(wvmInstance)
@ -138,12 +138,8 @@ class TestXmlSafety(unittest.TestCase):
self.assertFalse(inst.change_snapshot_xml())
inst._defineXML.assert_not_called()
# Create snapshot should not invoke recover_snapshot_xml if not changed
inst.change_snapshot_xml = MagicMock(return_value=False)
inst.recover_snapshot_xml = MagicMock()
inst.create_snapshot("snap_rom", "testing rom preservation")
inst.change_snapshot_xml.assert_called_once()
inst.recover_snapshot_xml.assert_not_called()
inst._defineXML.assert_not_called()
def test_malformed_xml_raises_in_change(self):
inst = wvmInstance.__new__(wvmInstance)
@ -165,15 +161,28 @@ class TestXmlSafety(unittest.TestCase):
mock_snap = MagicMock()
inst.instance.snapshotLookupByName = MagicMock(return_value=mock_snap)
inst.instance.revertToSnapshot = MagicMock()
inst.change_snapshot_xml = MagicMock(return_value=False)
inst.recover_snapshot_xml = MagicMock()
inst._defineXML = MagicMock()
inst.snapshot_revert("snap_rom")
inst.change_snapshot_xml.assert_called_once()
inst.instance.snapshotLookupByName.assert_called_once_with("snap_rom", 0)
inst.instance.revertToSnapshot.assert_called_once_with(mock_snap, 0)
inst.recover_snapshot_xml.assert_not_called()
inst._defineXML.assert_not_called()
def test_internal_snapshot_never_rewrites_a_pflash_loader(self):
# R-13: libvirt decides; the definition is not switched to rom.
inst = wvmInstance.__new__(wvmInstance)
inst.get_status = MagicMock(return_value=5)
inst._XMLDesc = MagicMock(
return_value="<domain><os><loader readonly='yes' type='pflash'>/usr/share/OVMF/OVMF.fd</loader></os></domain>"
)
inst._defineXML = MagicMock()
inst._snapshotCreateXML = MagicMock()
inst.create_snapshot("snap", "uefi")
inst._defineXML.assert_not_called()
self.assertIn("type='pflash'", inst._snapshotCreateXML.call_args[0][0])
if __name__ == "__main__":