diff --git a/instances/test_destroy.py b/instances/test_destroy.py
index 4ba4c429..61a02521 100644
--- a/instances/test_destroy.py
+++ b/instances/test_destroy.py
@@ -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")
diff --git a/instances/test_live_dataloss.py b/instances/test_live_dataloss.py
index f0ecf211..d45d014c 100644
--- a/instances/test_live_dataloss.py
+++ b/instances/test_live_dataloss.py
@@ -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"{P}nvram/var/lib/libvirt/qemu/nvram", 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"{P}r14-nvram1281"
+ "hvm"
+ ""
+ )
+ 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"{P}nvram/var/lib/libvirt/qemu/nvram", 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"{P}r14-chain.qcow264"
+ f"{base}"
+ "",
+ 0,
+ ).path()
+ dom, inst = self.vm("r14-chain", [overlay])
+ dom.snapshotCreateXML("snap", 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):