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):