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

test(destroy): NVRAM deletion and the base of an overlay

Destroy deletes the NVRAM file only when asked (VIR_DOMAIN_UNDEFINE_NVRAM,
otherwise KEEP_NVRAM): a unit test checks the flags, live tests check the
file on the host both ways. A VM whose disk is an overlay with a snapshot
goes with its own image and the snapshot metadata, and the base image
stays, since other images may grow from it.
This commit is contained in:
catborise 2026-10-09 05:59:36 +03:00
parent 975b7f0baf
commit f094982737
2 changed files with 83 additions and 3 deletions

View file

@ -6,7 +6,7 @@ from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from instances.models import Instance
from libvirt import libvirtError
from libvirt import VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM, libvirtError
class DestroyOrderTestCase(TestCase):
@ -19,15 +19,29 @@ class DestroyOrderTestCase(TestCase):
compute=compute, name="destroy-vm", uuid="11111111-2222-3333-4444-555555555555"
)
def destroy(self, undefine_error=None):
def destroy(self, undefine_error=None, data=None):
with patch("instances.models.wvmInstance") as wvm:
proxy = wvm.return_value
proxy.instance.isActive.return_value = False
proxy.split_disk_paths_by_use.return_value = (["/pool/own.qcow2"], ["/pool/shared.qcow2"])
proxy.delete.side_effect = undefine_error
self.client.post(reverse("instances:destroy", args=[self.instance.id]), {"delete_disk": "1"})
self.client.post(reverse("instances:destroy", args=[self.instance.id]), data or {"delete_disk": "1"})
return proxy
def test_the_nvram_goes_only_when_asked(self):
for data, wanted, unwanted in (
({"delete_nvram": "1"}, VIR_DOMAIN_UNDEFINE_NVRAM, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM),
({}, VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, VIR_DOMAIN_UNDEFINE_NVRAM),
):
with self.subTest(data=data):
Instance.objects.get_or_create(
compute=self.instance.compute, name="destroy-vm", uuid="11111111-2222-3333-4444-555555555555"
)
self.instance = Instance.objects.get(uuid="11111111-2222-3333-4444-555555555555")
flags = self.destroy(data=data).delete.call_args[0][0]
self.assertTrue(flags & wanted)
self.assertFalse(flags & unwanted)
def test_disks_are_deleted_after_the_undefine(self):
proxy = self.destroy()
proxy.get_volume_by_path.assert_called_once_with("/pool/own.qcow2")

View file

@ -210,6 +210,72 @@ class LiveDataLossTestCase(TestCase):
self.assertFalse(self.domain_exists(dom.UUIDString()), "paused VM is still running")
def nvram_files(self):
"""The test VMs' files in libvirt's NVRAM directory, read through a
transient pool (libvirt lists files only as volumes)."""
pool = self.conn.storagePoolCreateXML(
f"<pool type='dir'><name>{P}nvram</name><target><path>/var/lib/libvirt/qemu/nvram</path></target></pool>", 0
)
try:
return {name for name in pool.listVolumes() if name.startswith(P)}
finally:
pool.destroy()
def _destroy_uefi_vm(self, data):
dom = self.conn.defineXML(
f"<domain type='kvm'><name>{P}r14-nvram</name><memory unit='MiB'>128</memory><vcpu>1</vcpu>"
"<os firmware='efi'><type arch='x86_64' machine='q35'>hvm</type></os>"
"<features><acpi/></features><devices/></domain>"
)
refr(self.compute)
inst = Instance.objects.get(compute=self.compute, uuid=dom.UUIDString())
dom.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) # creates the NVRAM file
dom.destroy()
self.assertIn(f"{P}r14-nvram_VARS.fd", self.nvram_files())
self.post("destroy", inst, data)
self.assertFalse(self.domain_exists(dom.UUIDString()), "VM is still defined")
return f"{P}r14-nvram_VARS.fd" in self.nvram_files()
def test_destroy_deletes_the_nvram_when_asked(self):
self.assertFalse(self._destroy_uefi_vm({"delete_nvram": "1"}), "the NVRAM file is still there")
def test_destroy_keeps_the_nvram_otherwise(self):
try:
self.assertTrue(self._destroy_uefi_vm({}), "the NVRAM file was deleted")
finally:
pool = self.conn.storagePoolCreateXML(
f"<pool type='dir'><name>{P}nvram</name><target><path>/var/lib/libvirt/qemu/nvram</path></target></pool>", 0
)
try:
for name in pool.listVolumes():
if name.startswith(P):
pool.storageVolLookupByName(name).delete(0)
finally:
pool.destroy()
def test_destroy_of_an_overlay_keeps_its_base(self):
# The base may be a template other images grow from; only the VM's
# own top image goes, with the snapshot metadata.
base = livetest.create_volume(self.conn, P + "r14-chain-base")
pool = self.conn.storagePoolLookupByName(livetest.POOL)
overlay = pool.createXML(
f"<volume><name>{P}r14-chain.qcow2</name><capacity unit='MiB'>64</capacity>"
f"<target><format type='qcow2'/></target><backingStore><path>{base}</path>"
"<format type='qcow2'/></backingStore></volume>",
0,
).path()
dom, inst = self.vm("r14-chain", [overlay])
dom.snapshotCreateXML("<domainsnapshot><name>snap</name></domainsnapshot>", 0)
response = self.post("destroy", inst, {"delete_disk": "1"})
self.assertEqual(response.status_code, 302)
self.assertFalse(self.domain_exists(dom.UUIDString()), "VM is still defined")
self.assertFalse(livetest.volume_exists(self.conn, overlay), "the VM's own image is still there")
self.assertTrue(livetest.volume_exists(self.conn, base), "the base image was deleted")
# Delete a volume only after it is detached
def _delete_attached_volume(self, pause):