From 78ab85f2110cbda38e0476b760658b8d08b3cc6f Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 4 Oct 2026 17:26:25 +0300 Subject: [PATCH] fix(instances): a secure boot loader on q35 gets secure boot create_instance set secure='yes' (and with it SMM) only while switching a non-q35 machine to q35. With q35 already chosen, a secure boot loader was defined with secure='no' and without SMM. Secure boot is now requested whenever the loader is a secure boot build; the switch to q35 is unchanged. libvirt 11 on RHEL fills in secure='yes' and SMM from the firmware descriptor of a known loader, so there the domain was already correct; without such a descriptor (older libvirt, custom loader) it was not. A unit test checks what WebVirtCloud requests (fails on the previous code); a live test creates a q35 VM with the host's secure boot loader and starts it. --- instances/test_create_instance.py | 12 +++++++++++- instances/test_live_create.py | 15 +++++++++++++++ instances/views.py | 16 +++++++++------- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/instances/test_create_instance.py b/instances/test_create_instance.py index 6f9bd273..241fa609 100644 --- a/instances/test_create_instance.py +++ b/instances/test_create_instance.py @@ -13,6 +13,16 @@ class CreateInstanceViewTests(TestCase): @patch("instances.views.wvmCreate") @patch("instances.views.NewVMForm") def test_successful_create_redirects_after_saving_instance(self, form_class, connection_class): + self.create(form_class, connection_class, "BIOS") + + @patch("instances.views.wvmCreate") + @patch("instances.views.NewVMForm") + def test_secure_boot_loader_on_q35_requests_secure_boot(self, form_class, connection_class): + self.create(form_class, connection_class, "UEFI x86_64: /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd") + firmware = connection_class.return_value.create_instance.call_args.kwargs["firmware"] + self.assertEqual(firmware["secure"], "yes") + + def create(self, form_class, connection_class, firmware): user = get_user_model().objects.create_superuser( username="create-test-admin", password="password", email="create@example.com" ) @@ -43,7 +53,7 @@ class CreateInstanceViewTests(TestCase): form.cleaned_data = { "name": "new-vm", "meta_prealloc": False, "hdd_size": 0, "template": "", "images": "base.qcow2", "cache_mode": "default", - "firmware": "BIOS", "net_model": "virtio", "memory": 1024, + "firmware": firmware, "net_model": "virtio", "memory": 1024, "vcpu": 1, "vcpu_mode": "host-model", "networks": "default", "virtio": True, "listener_addr": "0.0.0.0", "nwfilter": "", "graphics": "vnc", "video": "vga", "console_pass": "", diff --git a/instances/test_live_create.py b/instances/test_live_create.py index 96a91233..9bdafaf0 100644 --- a/instances/test_live_create.py +++ b/instances/test_live_create.py @@ -128,6 +128,21 @@ class LiveCreateTestCase(TestCase): dom.create() self.assertTrue(dom.isActive()) + def test_secure_boot_loader_on_q35_gets_secure_boot(self): + caps = etree.fromstring(self.conn.getDomainCapabilities(None, "x86_64", "q35", "kvm")) + secboot = [p for p in caps.xpath("os/loader/value/text()") if "secboot" in p and "OVMF_CODE" in p] + if not secboot: + self.skipTest("the host has no secure boot loader for q35") + + self.create("cr-secboot", firmware=f"UEFI x86_64: {secboot[0]}") + + dom = self.conn.lookupByName(P + "cr-secboot") + tree = etree.fromstring(dom.XMLDesc(0)) + self.assertEqual(tree.find("os/loader").get("secure"), "yes") + self.assertEqual(tree.find("features/smm").get("state"), "on") + dom.create() + self.assertTrue(dom.isActive()) + def test_api_rejected_definition_removes_the_new_disk(self): response = self.client.post( f"/api/v1/computes/{self.compute.id}/instances/create/x86_64/q35/", diff --git a/instances/views.py b/instances/views.py index 759a3cf7..52119cb2 100755 --- a/instances/views.py +++ b/instances/views.py @@ -2141,13 +2141,15 @@ def create_instance(request, compute_id, arch, machine): firmware["secure"] = "no" firmware["readonly"] = "yes" firmware["type"] = "pflash" - if "secboot" in firmware["loader"] and machine != "q35": - messages.warning( - request, - "Changing machine type from '%s' to 'q35' " - "which is required for UEFI secure boot." % machine, - ) - machine = "q35" + if "secboot" in firmware["loader"]: + # A secure boot loader needs secure='yes' and SMM, on q35 only + if machine != "q35": + messages.warning( + request, + "Changing machine type from '%s' to 'q35' " + "which is required for UEFI secure boot." % machine, + ) + machine = "q35" firmware["secure"] = "yes" if data["net_model"] == "default":