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

feat(instances): a new VM can have UEFI Secure Boot and a TPM 2.0

Windows 11 does not install without Secure Boot and a TPM 2.0, and the new
VM form offered neither: its firmware list was BIOS and one UEFI loader
guessed from file names, which on RHEL never matched the Secure Boot one.

The firmware choices are now BIOS, UEFI and UEFI Secure Boot, as the
host's libvirt reports them (its firmware features from libvirt 12.1,
before that its loaders), and libvirt picks the firmware itself: UEFI
without Secure Boot, or with Microsoft's keys enrolled. A TPM 2.0 switch,
shown when the host has swtpm, adds an emulated TPM. The create job
refuses a firmware or a TPM the host does not offer, and the API takes
both. The path-guessing helpers are gone.
This commit is contained in:
catborise 2026-11-01 14:05:21 +03:00
parent 40db3dcf3d
commit 005fa59bf0
20 changed files with 399 additions and 228 deletions

View file

@ -72,6 +72,8 @@ class DestroyOptionsSerializer(serializers.Serializer):
class CreateInstanceSerializer(serializers.ModelSerializer):
# BIOS, UEFI or UEFI Secure Boot, as the host offers; left out: the machine's default
firmware = serializers.CharField(required=False)
video = serializers.CharField(initial="vga")
storage = serializers.CharField(initial="default")
cache_mode = serializers.CharField(initial="none")
@ -90,6 +92,8 @@ class CreateInstanceSerializer(serializers.ModelSerializer):
model = CreateInstance
fields = [
"name",
"firmware",
"tpm",
"vcpu",
"vcpu_mode",
"memory",

View file

@ -665,11 +665,11 @@ class CreateInstance(Kind):
def normalize(self, raw):
# what the API leaves out means what create_instance did by default
params = {"template": "", "images": "", "firmware": "", "net_model": "virtio", "add_cdrom": "sata",
"add_input": "default", "meta_prealloc": False, "hdd_size": 0, "cloud_init": False}
"add_input": "default", "meta_prealloc": False, "hdd_size": 0, "cloud_init": False, "tpm": False}
params.update({k: v for k, v in raw.items() if k not in ("submission_key", "create", "csrfmiddlewaretoken")})
params.update(compute=int(raw["compute"]), devices=[list(d) for d in raw.get("devices", [])])
if "UEFI" in params["firmware"] and "secboot" in params["firmware"].partition(":")[2]:
params["machine"] = "q35" # a secure boot loader needs SMM, on q35 only
if params["firmware"] == "UEFI Secure Boot" and "q35" not in params["machine"]:
params["machine"] = "q35" # Secure Boot needs SMM, on q35 only
return params
def validate(self, user, params):
@ -708,8 +708,10 @@ class CreateInstance(Kind):
utils.nic_macs(data["mac"], data["networks"])
except ValueError as err:
raise util.OperationError(str(err))
if "UEFI" in data["firmware"] and ":" not in data["firmware"]:
raise util.OperationError(_("Invalid firmware"))
if data["firmware"] and data["firmware"] not in conn.get_firmwares(data["arch"], data["machine"]):
raise util.OperationError(_("This host offers no firmware %(firmware)s") % {"firmware": data["firmware"]})
if data["tpm"] and not conn.get_tpm_model(data["arch"], data["machine"]):
raise util.OperationError(_("This host offers no TPM 2.0: install swtpm on it"))
def plan(self, user, params):
compute = Compute.objects.get(pk=params["compute"])
@ -727,13 +729,6 @@ class CreateInstance(Kind):
default_disk_owner_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
default_disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID)
firmware = dict()
if "UEFI" in data["firmware"]:
firmware["loader"] = data["firmware"].split(":")[1].strip()
firmware["secure"] = "yes" if "secboot" in firmware["loader"] else "no"
firmware["readonly"] = "yes"
firmware["type"] = "pflash"
def volume(path, device="disk", bus=default_bus):
return {
"path": path,
@ -783,7 +778,7 @@ class CreateInstance(Kind):
uuid=op.intent["uuid"],
arch=data["arch"],
machine=data["machine"],
firmware=firmware,
firmware=data["firmware"],
volumes=volume_list,
networks=data["networks"],
virtio=data["virtio"],
@ -796,6 +791,7 @@ class CreateInstance(Kind):
qemu_ga=data["qemu_ga"],
add_cdrom=data["add_cdrom"],
add_input=data["add_input"],
tpm_model=conn.get_tpm_model(data["arch"], data["machine"]) if data["tpm"] else None,
)
except Exception as err:
# the VM is not defined: its new volumes go

View file

@ -402,6 +402,8 @@ class CreateInstance(models.Model):
error_messages={"required": _("No Virtual Machine name has been entered")},
)
firmware = models.CharField(max_length=64)
# an emulated TPM 2.0, as Windows 11 needs
tpm = models.BooleanField(default=False)
vcpu = models.IntegerField(
error_messages={"required": _("No VCPU has been entered")}, validators=[MinValueValidator(1)]
)

View file

@ -77,6 +77,13 @@
{% endfor %}
</select>
{% field_error vm_form.firmware "vm_firmware" %}
{% if tpm_support %}
<div class="form-check mt-2">
<input id="vm_tpm" class="form-check-input" type="checkbox" name="tpm" value="true"{% if vm_form.tpm.value %} checked{% endif %} aria-describedby="vm_tpm_help">
<label for="vm_tpm" class="form-check-label">{% trans "TPM 2.0" %}</label>
<div id="vm_tpm_help" class="form-text">{% trans "Windows 11 needs it, with UEFI Secure Boot" %}</div>
</div>
{% endif %}
</div>
{% endif %}
{% if dom_caps.cpu_modes %}

View file

@ -32,6 +32,7 @@ class CloudInitCreateTestCase(TransactionTestCase):
self.addCleanup(patcher.stop)
self.conn.get_instances.return_value = []
self.conn.get_cache_modes.return_value = {"none": "Disable cache"}
self.conn.get_firmwares.return_value = ["BIOS", "UEFI", "UEFI Secure Boot"]
self.conn.get_dom_capabilities.return_value = {"cpu_modes": ["host-model"]}
self.conn.get_disk_bus_types.return_value = ["virtio", "sata"]
self.conn.create_volume.return_value = "/srv/default/api-vm.qcow2"

View file

@ -22,7 +22,7 @@ def defined_cpu(vcpu_mode):
patch.object(wvmCreate, "get_dom_capabilities", return_value=DOM_CAPS):
conn.create_instance(name="vm-1", memory=512, vcpu=1, vcpu_mode=vcpu_mode,
uuid="00000000-0000-4000-8000-0000000000c1", arch="x86_64", machine="q35",
firmware={}, volumes=[], networks="", nwfilter="", virtio=True, listener_addr="0.0.0.0",
firmware="BIOS", volumes=[], networks="", nwfilter="", virtio=True, listener_addr="0.0.0.0",
add_cdrom="None", add_input="None")
return etree.fromstring(conn.wvm.defineXML.call_args.args[0]).find("cpu")

View file

@ -182,8 +182,8 @@ class CreateFormValuesTests(TestCase):
self.assertIn("Networks: This field is required.", self.summary(page))
def test_a_firmware_error_without_a_firmware_choice_is_in_the_summary(self):
# an architecture with no firmware label renders no firmware select
with patch("vrtManager.connection.wvmConnect.label_for_firmware_path", return_value=None):
# an architecture with no firmware choice renders no firmware select
with patch("vrtManager.connection.wvmConnect.get_firmwares", return_value=[]):
fields = valid()
del fields["firmware"]
_, response = render_create_page(self, fields)

View file

@ -47,11 +47,24 @@ class CreateInstanceViewTests(TestCase):
@patch("instances.views.wvmCreate", new=wvm_create)
@patch("instances.views.NewVMForm")
def test_secure_boot_loader_on_q35_requests_secure_boot(self, form_class):
_, conn = self.create(form_class, firmware="UEFI x86_64: /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd")
def test_secure_boot_and_a_tpm_reach_the_definition(self, form_class):
_, conn = self.create(form_class, firmware="UEFI Secure Boot", tpm=True)
run(Operation.objects.get())
firmware = conn.create_instance.call_args.kwargs["firmware"]
self.assertEqual(firmware["secure"], "yes")
kwargs = conn.create_instance.call_args.kwargs
self.assertEqual((kwargs["firmware"], kwargs["machine"], kwargs["tpm_model"]),
("UEFI Secure Boot", "q35", "tpm-crb"))
@patch("instances.views.wvmCreate", new=wvm_create)
@patch("instances.views.NewVMForm")
def test_a_firmware_or_a_tpm_the_host_does_not_offer_is_refused(self, form_class):
for data, host in (({"firmware": "UEFI Secure Boot"}, {"get_firmwares": ["BIOS", "UEFI"]}),
({"tpm": True}, {"get_tpm_model": None})):
with self.subTest(data):
Instance.objects.all().delete()
response, conn = self.create(form_class, host=host, **data)
self.assertEqual(response.status_code, 200)
self.assertFalse(Operation.objects.exists())
conn.create_instance.assert_not_called()
@patch("instances.views.wvmCreate", new=wvm_create)
@patch("instances.views.NewVMForm")
@ -71,7 +84,9 @@ class CreateInstanceViewTests(TestCase):
conn.create_volume.assert_not_called()
conn.create_instance.assert_not_called()
def create(self, form_class, **data):
def create(self, form_class, host=None, **data):
"""POST the create form with data over the form's defaults; host: what
the mocked host answers, over its defaults."""
user = get_user_model().objects.filter(username="create-test-admin").first() or (
get_user_model().objects.create_superuser(
username="create-test-admin", password="password", email="create@example.com"
@ -95,8 +110,10 @@ class CreateInstanceViewTests(TestCase):
conn.get_storages.return_value = ["default"]
conn.get_dom_capabilities.return_value = {"loader_enums": [], "loaders": [], "cpu_modes": ["host-model"]}
conn.get_capabilities.return_value = {}
conn.label_for_firmware_path.return_value = None
conn.find_uefi_path_for_arch.return_value = None
conn.get_firmwares.return_value = ["BIOS", "UEFI", "UEFI Secure Boot"]
conn.get_tpm_model.return_value = "tpm-crb"
for method, value in (host or {}).items():
getattr(conn, method).return_value = value
conn.get_volume_path.return_value = "/var/lib/libvirt/images/base.qcow2"
conn.get_volume_format_type.return_value = "qcow2"
@ -110,7 +127,7 @@ class CreateInstanceViewTests(TestCase):
"virtio": True, "listener_addr": "0.0.0.0", "nwfilter": "",
"graphics": "vnc", "video": "vga",
"mac": "52:54:00:00:00:01", "qemu_ga": False,
"add_cdrom": "sata", "add_input": "default",
"add_cdrom": "sata", "add_input": "default", "tpm": False,
**data,
}
@ -138,13 +155,13 @@ class CreateInstanceViewTests(TestCase):
# A host without loader support has no "loaders"/"loader_enums" keys
conn.get_dom_capabilities.return_value = {"loader_support": "no", "cpu_modes": ["host-model"]}
conn.get_capabilities.return_value = {}
conn.label_for_firmware_path.return_value = None
conn.find_uefi_path_for_arch.return_value = None
conn.get_firmwares.return_value = ["BIOS"]
conn.get_tpm_model.return_value = None
response = self.client.get(reverse("instances:create_instance", args=[compute.id, "x86_64", "q35"]))
self.assertEqual(response.status_code, 200)
conn.find_uefi_path_for_arch.assert_called_once_with("x86_64", "q35")
conn.get_firmwares.assert_called_once_with("x86_64", "q35")
class NicMacsTestCase(TestCase):

View file

@ -31,6 +31,7 @@ class CreateJobTestCase(TransactionTestCase):
self.addCleanup(patcher.stop)
self.conn.get_instances.return_value = []
self.conn.get_cache_modes.return_value = {"none": "Disable cache"}
self.conn.get_firmwares.return_value = ["BIOS", "UEFI", "UEFI Secure Boot"]
self.conn.get_disk_bus_types.return_value = ["virtio", "sata"]
self.conn.get_dom_capabilities.return_value = {"cpu_modes": ["host-model", "custom"],
"cpu_custom_models": ["Skylake-Server-noTSX-IBRS"]}
@ -51,6 +52,19 @@ class CreateJobTestCase(TransactionTestCase):
self.assertEqual(self.conn.create_instance.call_args.kwargs["uuid"], op.intent["uuid"])
self.assertTrue(Instance.objects.filter(uuid=op.intent["uuid"], name="api-vm").exists())
def test_the_api_takes_secure_boot_and_a_tpm(self):
url = f"/api/v1/computes/{self.compute.pk}/instances/create/x86_64/pc/"
body = {**BODY, "firmware": "UEFI Secure Boot", "tpm": True}
self.assertEqual(self.client.post(url, body, content_type="application/json").status_code, 202)
params = Operation.objects.get().params
self.assertEqual((params["firmware"], params["tpm"], params["machine"]), ("UEFI Secure Boot", True, "q35"))
def test_secure_boot_keeps_a_versioned_q35_machine(self):
url = f"/api/v1/computes/{self.compute.pk}/instances/create/x86_64/pc-q35-9.2/"
body = {**BODY, "firmware": "UEFI Secure Boot"}
self.assertEqual(self.client.post(url, body, content_type="application/json").status_code, 202)
self.assertEqual(Operation.objects.get().params["machine"], "pc-q35-9.2")
def test_a_refused_create_queues_nothing(self):
url = f"/api/v1/computes/{self.compute.pk}/instances/create/x86_64/q35/"
response = self.client.post(url, {**BODY, "cache_mode": "writeback"}, content_type="application/json")

View file

@ -33,6 +33,7 @@ class CreateRollbackTestCase(TestCase):
with patch("instances.jobs.wvmCreate") as wvm:
conn = wvm.return_value
conn.get_cache_modes.return_value = {"none": "Disable cache"}
conn.get_firmwares.return_value = ["BIOS", "UEFI", "UEFI Secure Boot"]
conn.get_dom_capabilities.return_value = {"cpu_modes": ["host-model"]}
conn.get_instances.return_value = []
conn.create_volume.return_value = "/pool/rb-vm.qcow2"

View file

@ -0,0 +1,126 @@
"""A new VM's firmware and TPM: the choices come from the host's domain
capabilities, UEFI is libvirt's firmware autoselection (Secure Boot with
Microsoft's keys enrolled, as Windows 11 needs) and a TPM is an emulated
TPM 2.0 (swtpm)."""
from unittest.mock import MagicMock, patch
from django.test import SimpleTestCase
from lxml import etree
from vrtManager.connection import wvmConnect
from vrtManager.create import wvmCreate
FULL = """<domainCapabilities><os supported='yes'>
<enum name='firmware'><value>efi</value></enum>
<firmwareFeatures supported='yes'>
<enum name='secureBoot'><value>yes</value><value>no</value></enum>
<enum name='enrolledKeys'><value>yes</value><value>no</value></enum>
</firmwareFeatures>
</os><devices><tpm supported='yes'>
<enum name='model'><value>tpm-tis</value><value>tpm-crb</value></enum>
<enum name='backendModel'><value>emulator</value><value>external</value></enum>
<enum name='backendVersion'><value>2.0</value></enum>
</tpm></devices></domainCapabilities>"""
# Secure Boot firmware without Microsoft's keys, and no swtpm
BARE = """<domainCapabilities><os supported='yes'>
<enum name='firmware'><value>efi</value></enum>
<firmwareFeatures supported='yes'>
<enum name='secureBoot'><value>yes</value><value>no</value></enum>
<enum name='enrolledKeys'><value>no</value></enum>
</firmwareFeatures>
</os><devices><tpm supported='no'/></devices></domainCapabilities>"""
# only Secure Boot firmware, and a TPM of another machine
SECURE_ONLY = """<domainCapabilities><os supported='yes'>
<enum name='firmware'><value>efi</value></enum>
<firmwareFeatures supported='yes'>
<enum name='secureBoot'><value>yes</value></enum>
<enum name='enrolledKeys'><value>yes</value></enum>
</firmwareFeatures>
</os><devices><tpm supported='yes'>
<enum name='model'><value>tpm-spapr</value></enum>
<enum name='backendModel'><value>emulator</value></enum>
<enum name='backendVersion'><value>2.0</value></enum>
</tpm></devices></domainCapabilities>"""
# libvirt before 12.1: no firmwareFeatures, the loaders say whether they do Secure Boot
EARLIER = """<domainCapabilities><os supported='yes'>
<enum name='firmware'><value>efi</value></enum>
<loader supported='yes'><enum name='secure'><value>yes</value><value>no</value></enum></loader>
</os></domainCapabilities>"""
EARLIER_PLAIN = """<domainCapabilities><os supported='yes'>
<enum name='firmware'><value>efi</value></enum>
<loader supported='yes'/>
</os></domainCapabilities>"""
DOM_CAPS = {"domain": "kvm", "os_support": "yes", "cpu_modes": ["host-model"]}
def host(dom_cap_xml):
conn = wvmConnect.__new__(wvmConnect)
conn.get_dom_cap_xml = lambda arch, machine: dom_cap_xml
return conn
def defined(firmware, tpm_model=None):
conn = wvmCreate.__new__(wvmCreate)
conn.wvm = MagicMock()
with patch.object(wvmCreate, "get_capabilities", return_value={"features": ["acpi"], "os_type": "hvm"}), \
patch.object(wvmCreate, "get_dom_capabilities", return_value=DOM_CAPS):
conn.create_instance(name="vm-1", memory=512, vcpu=1, vcpu_mode="host-model",
uuid="00000000-0000-4000-8000-0000000000c2", arch="x86_64", machine="q35",
firmware=firmware, volumes=[], networks="", nwfilter="", virtio=True,
listener_addr="0.0.0.0", add_cdrom="None", add_input="None", tpm_model=tpm_model)
return etree.fromstring(conn.wvm.defineXML.call_args.args[0])
class HostOffersTests(SimpleTestCase):
def test_a_host_with_enrolled_keys_and_swtpm_offers_everything(self):
self.assertEqual(host(FULL).get_firmwares("x86_64", "q35"), ["BIOS", "UEFI", "UEFI Secure Boot"])
self.assertEqual(host(FULL).get_tpm_model("x86_64", "q35"), "tpm-crb")
def test_secure_boot_without_microsofts_keys_and_no_swtpm_are_not_offered(self):
self.assertEqual(host(BARE).get_firmwares("x86_64", "q35"), ["BIOS", "UEFI"])
self.assertIsNone(host(BARE).get_tpm_model("x86_64", "q35"))
def test_bios_is_x86_only_and_secure_boot_x86_64_only(self):
self.assertEqual(host(FULL).get_firmwares("aarch64", "virt"), ["UEFI"])
def test_uefi_is_offered_only_where_secure_boot_can_be_off(self):
self.assertEqual(host(SECURE_ONLY).get_firmwares("x86_64", "q35"), ["BIOS", "UEFI Secure Boot"])
def test_an_earlier_libvirt_offers_what_its_loaders_do(self):
self.assertEqual(host(EARLIER).get_firmwares("x86_64", "q35"), ["BIOS", "UEFI", "UEFI Secure Boot"])
self.assertEqual(host(EARLIER_PLAIN).get_firmwares("x86_64", "q35"), ["BIOS", "UEFI"])
def test_a_machine_without_crb_or_tis_takes_its_own_tpm_model(self):
self.assertEqual(host(SECURE_ONLY).get_tpm_model("ppc64le", "pseries"), "tpm-spapr")
class DefinitionTests(SimpleTestCase):
def features(self, tree):
return {f.get("name"): f.get("enabled") for f in tree.findall("os/firmware/feature")}
def test_bios_leaves_the_firmware_to_the_machine(self):
tree = defined("BIOS")
self.assertIsNone(tree.find("os").get("firmware"))
self.assertEqual((self.features(tree), tree.find("features/smm")), ({}, None))
def test_uefi_is_picked_by_libvirt_without_secure_boot(self):
tree = defined("UEFI")
self.assertEqual(tree.find("os").get("firmware"), "efi")
self.assertEqual(self.features(tree), {"secure-boot": "no"})
self.assertIsNone(tree.find("features/smm"))
def test_secure_boot_has_microsofts_keys_and_smm(self):
tree = defined("UEFI Secure Boot")
self.assertEqual(tree.find("os").get("firmware"), "efi")
self.assertEqual(self.features(tree), {"secure-boot": "yes", "enrolled-keys": "yes"})
self.assertEqual(tree.find("features/smm").get("state"), "on")
def test_a_tpm_is_an_emulated_tpm_2(self):
self.assertEqual(defined("UEFI").findall("devices/tpm"), [])
tpm = defined("UEFI Secure Boot", tpm_model="tpm-crb").find("devices/tpm")
self.assertEqual((tpm.get("model"), tpm.find("backend").get("type"), tpm.find("backend").get("version")),
("tpm-crb", "emulator", "2.0"))

View file

@ -117,8 +117,8 @@ class LiveCreateTestCase(TransactionTestCase):
def test_invalid_cache_mode_allocates_nothing(self):
self.assert_nothing_left("cr-cache", self.create("cr-cache", cache_mode="bogus"))
def test_malformed_uefi_firmware_allocates_nothing(self):
self.assert_nothing_left("cr-uefi", self.create("cr-uefi", firmware="UEFI"))
def test_a_firmware_the_host_does_not_offer_allocates_nothing(self):
self.assert_nothing_left("cr-uefi", self.create("cr-uefi", firmware="Coreboot"))
def test_rejected_definition_removes_the_new_disk(self):
self.assert_nothing_left("cr-define", self.create("cr-define", video="no-such-model"))
@ -129,36 +129,37 @@ class LiveCreateTestCase(TransactionTestCase):
self.assertTrue(self.domain_exists(P + "cr-ok"))
self.assertEqual(len(self.volumes()), 1)
def test_offered_uefi_firmware_boots_on_q35(self):
def offered(self):
page = self.client.get(reverse("instances:create_instance", args=[self.compute.id, "x86_64", "q35"]))
uefi = [f for f in page.context["firmwares"] if f.startswith("UEFI")]
self.assertTrue(uefi, "no UEFI firmware offered for q35")
return page.context["firmwares"], page.context["tpm_support"]
self.create("cr-uefi-ok", firmware=uefi[0])
def test_uefi_boots_without_secure_boot(self):
self.assertIn("UEFI", self.offered()[0], "no UEFI offered for q35")
self.create("cr-uefi-ok", firmware="UEFI")
dom = self.conn.lookupByName(P + "cr-uefi-ok")
loader = etree.fromstring(dom.XMLDesc(0)).findtext("os/loader")
self.assertEqual(loader, uefi[0].split(":", 1)[1].strip())
# Listed by libvirt for q35, so the domain starts with it
caps = etree.fromstring(self.conn.getDomainCapabilities(None, "x86_64", "q35", "kvm"))
self.assertIn(loader, caps.xpath("os/loader/value/text()"))
dom.create()
self.assertTrue(dom.isActive())
tree = etree.fromstring(dom.XMLDesc(0))
self.assertEqual(tree.find("os").get("firmware"), "efi")
self.assertNotEqual(tree.find("os/loader").get("secure"), "yes")
self.assertTrue(tree.findtext("os/nvram"))
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")
def test_secure_boot_with_a_tpm_boots_as_windows_11_needs(self):
firmwares, tpm_support = self.offered()
if "UEFI Secure Boot" not in firmwares or not tpm_support:
self.skipTest("the host offers no Secure Boot with Microsoft's keys, or no swtpm")
self.create("cr-secboot", firmware=f"UEFI x86_64: {secboot[0]}")
self.create("cr-secboot", firmware="UEFI Secure Boot", tpm="true")
dom = self.conn.lookupByName(P + "cr-secboot")
dom.create()
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())
# the variables libvirt chose come with Microsoft's keys enrolled
self.assertEqual(tree.xpath("os/firmware/feature[@name='enrolled-keys']/@enabled"), ["yes"])
self.assertEqual(tree.xpath("devices/tpm/backend/@version"), ["2.0"])
def test_every_offered_cdrom_bus_creates_a_vm_that_starts(self):
page = self.client.get(reverse("instances:create_instance", args=[self.compute.id, "x86_64", "q35"]))

View file

@ -28,7 +28,7 @@ def defined_controllers(machine):
patch.object(wvmCreate, "get_dom_capabilities", return_value=DOM_CAPS):
conn.create_instance(name="vm-1", memory=512, vcpu=1, vcpu_mode="host-model",
uuid="00000000-0000-4000-8000-0000000000c2", arch="x86_64", machine=machine,
firmware={}, volumes=[], networks="default", nwfilter="", virtio=True,
firmware="BIOS", volumes=[], networks="default", nwfilter="", virtio=True,
listener_addr="0.0.0.0", add_cdrom="None", add_input="None")
domain = etree.fromstring(conn.wvm.defineXML.call_args.args[0])
return [c.get("model") for c in domain.findall("devices/controller[@type='pci']")]

View file

@ -2459,7 +2459,6 @@ def create_instance(request, compute_id, arch, machine):
storages = list()
networks = list()
hypervisors = list()
firmwares = list()
meta_prealloc = False
compute = get_object_or_404(Compute, pk=compute_id)
flavors = Flavor.objects.filter().order_by("id")
@ -2512,16 +2511,8 @@ def create_instance(request, compute_id, arch, machine):
caps = conn.get_capabilities(arch)
virtio_support = conn.is_supports_virtio(arch, machine)
# Add BIOS
label = conn.label_for_firmware_path(arch, None)
if label:
firmwares.append(label)
# Add UEFI
loader_path = conn.find_uefi_path_for_arch(arch, machine)
label = conn.label_for_firmware_path(arch, loader_path)
if label:
firmwares.append(label)
firmwares = list(set(firmwares))
firmwares = conn.get_firmwares(arch, machine)
tpm_support = bool(conn.get_tpm_model(arch, machine))
if not storages:
raise util.OperationError(_("You haven't defined any storage pools"))