1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46: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

@ -42,6 +42,7 @@ WebVirtCloud and `virt-manager`/`virsh` can manage the same libvirt host **concu
* Storage pool and volume management * Storage pool and volume management
* Network (with DHCP leases), interface, network filter and secret management * Network (with DHCP leases), interface, network filter and secret management
* PCI and USB devices of the host given to VMs * PCI and USB devices of the host given to VMs
* UEFI Secure Boot and TPM 2.0 for new VMs, as Windows 11 needs
* Browser-based VNC console (noVNC) and serial console * Browser-based VNC console (noVNC) and serial console
* Role-based access control: users only see the VMs assigned to them, within their quota * Role-based access control: users only see the VMs assigned to them, within their quota
* 2FA (OTP / TOTP) and failed-login lockout * 2FA (OTP / TOTP) and failed-login lockout
@ -238,6 +239,8 @@ A compute node is a KVM host the panel manages over libvirt.
# or, from a cloned repository: sudo sh ./dev/libvirt-bootstrap.sh # or, from a cloned repository: sudo sh ./dev/libvirt-bootstrap.sh
``` ```
The new VM form offers UEFI, UEFI Secure Boot (with Microsoft's keys, for Windows 11) and a TPM 2.0 only when the host's libvirt finds the UEFI firmware and swtpm: install `ovmf swtpm-tools` (Debian / Ubuntu) or `edk2-ovmf swtpm swtpm-tools` (RHEL family) if they are missing.
2. For SSH connections, give the user the panel runs as an SSH key and copy it to the compute node. **Use only the block for your installation type:** a key set up with the host commands is not seen by the panel in Docker. `ssh-copy-id` asks for the compute node's root password once. `accept-new` accepts a new host's key on first connect and refuses a changed one. 2. For SSH connections, give the user the panel runs as an SSH key and copy it to the compute node. **Use only the block for your installation type:** a key set up with the host commands is not seen by the panel in Docker. `ssh-copy-id` asks for the compute node's root password once. `accept-new` accepts a new host's key on first connect and refuses a changed one.
**Installer or manual installation**, on the panel host: **Installer or manual installation**, on the panel host:

View file

@ -72,6 +72,8 @@ class DestroyOptionsSerializer(serializers.Serializer):
class CreateInstanceSerializer(serializers.ModelSerializer): 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") video = serializers.CharField(initial="vga")
storage = serializers.CharField(initial="default") storage = serializers.CharField(initial="default")
cache_mode = serializers.CharField(initial="none") cache_mode = serializers.CharField(initial="none")
@ -90,6 +92,8 @@ class CreateInstanceSerializer(serializers.ModelSerializer):
model = CreateInstance model = CreateInstance
fields = [ fields = [
"name", "name",
"firmware",
"tpm",
"vcpu", "vcpu",
"vcpu_mode", "vcpu_mode",
"memory", "memory",

View file

@ -665,11 +665,11 @@ class CreateInstance(Kind):
def normalize(self, raw): def normalize(self, raw):
# what the API leaves out means what create_instance did by default # what the API leaves out means what create_instance did by default
params = {"template": "", "images": "", "firmware": "", "net_model": "virtio", "add_cdrom": "sata", 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({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", [])]) 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]: if params["firmware"] == "UEFI Secure Boot" and "q35" not in params["machine"]:
params["machine"] = "q35" # a secure boot loader needs SMM, on q35 only params["machine"] = "q35" # Secure Boot needs SMM, on q35 only
return params return params
def validate(self, user, params): def validate(self, user, params):
@ -708,8 +708,10 @@ class CreateInstance(Kind):
utils.nic_macs(data["mac"], data["networks"]) utils.nic_macs(data["mac"], data["networks"])
except ValueError as err: except ValueError as err:
raise util.OperationError(str(err)) raise util.OperationError(str(err))
if "UEFI" in data["firmware"] and ":" not in data["firmware"]: if data["firmware"] and data["firmware"] not in conn.get_firmwares(data["arch"], data["machine"]):
raise util.OperationError(_("Invalid firmware")) 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): def plan(self, user, params):
compute = Compute.objects.get(pk=params["compute"]) 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_uid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_UID)
default_disk_owner_gid = int(app_settings.INSTANCE_VOLUME_DEFAULT_OWNER_GID) 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): def volume(path, device="disk", bus=default_bus):
return { return {
"path": path, "path": path,
@ -783,7 +778,7 @@ class CreateInstance(Kind):
uuid=op.intent["uuid"], uuid=op.intent["uuid"],
arch=data["arch"], arch=data["arch"],
machine=data["machine"], machine=data["machine"],
firmware=firmware, firmware=data["firmware"],
volumes=volume_list, volumes=volume_list,
networks=data["networks"], networks=data["networks"],
virtio=data["virtio"], virtio=data["virtio"],
@ -796,6 +791,7 @@ class CreateInstance(Kind):
qemu_ga=data["qemu_ga"], qemu_ga=data["qemu_ga"],
add_cdrom=data["add_cdrom"], add_cdrom=data["add_cdrom"],
add_input=data["add_input"], add_input=data["add_input"],
tpm_model=conn.get_tpm_model(data["arch"], data["machine"]) if data["tpm"] else None,
) )
except Exception as err: except Exception as err:
# the VM is not defined: its new volumes go # 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")}, error_messages={"required": _("No Virtual Machine name has been entered")},
) )
firmware = models.CharField(max_length=64) firmware = models.CharField(max_length=64)
# an emulated TPM 2.0, as Windows 11 needs
tpm = models.BooleanField(default=False)
vcpu = models.IntegerField( vcpu = models.IntegerField(
error_messages={"required": _("No VCPU has been entered")}, validators=[MinValueValidator(1)] error_messages={"required": _("No VCPU has been entered")}, validators=[MinValueValidator(1)]
) )

View file

@ -77,6 +77,13 @@
{% endfor %} {% endfor %}
</select> </select>
{% field_error vm_form.firmware "vm_firmware" %} {% 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> </div>
{% endif %} {% endif %}
{% if dom_caps.cpu_modes %} {% if dom_caps.cpu_modes %}

View file

@ -32,6 +32,7 @@ class CloudInitCreateTestCase(TransactionTestCase):
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
self.conn.get_instances.return_value = [] self.conn.get_instances.return_value = []
self.conn.get_cache_modes.return_value = {"none": "Disable cache"} 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_dom_capabilities.return_value = {"cpu_modes": ["host-model"]}
self.conn.get_disk_bus_types.return_value = ["virtio", "sata"] self.conn.get_disk_bus_types.return_value = ["virtio", "sata"]
self.conn.create_volume.return_value = "/srv/default/api-vm.qcow2" 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): patch.object(wvmCreate, "get_dom_capabilities", return_value=DOM_CAPS):
conn.create_instance(name="vm-1", memory=512, vcpu=1, vcpu_mode=vcpu_mode, 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", 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") add_cdrom="None", add_input="None")
return etree.fromstring(conn.wvm.defineXML.call_args.args[0]).find("cpu") 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)) self.assertIn("Networks: This field is required.", self.summary(page))
def test_a_firmware_error_without_a_firmware_choice_is_in_the_summary(self): def test_a_firmware_error_without_a_firmware_choice_is_in_the_summary(self):
# an architecture with no firmware label renders no firmware select # an architecture with no firmware choice renders no firmware select
with patch("vrtManager.connection.wvmConnect.label_for_firmware_path", return_value=None): with patch("vrtManager.connection.wvmConnect.get_firmwares", return_value=[]):
fields = valid() fields = valid()
del fields["firmware"] del fields["firmware"]
_, response = render_create_page(self, fields) _, 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.wvmCreate", new=wvm_create)
@patch("instances.views.NewVMForm") @patch("instances.views.NewVMForm")
def test_secure_boot_loader_on_q35_requests_secure_boot(self, form_class): def test_secure_boot_and_a_tpm_reach_the_definition(self, form_class):
_, conn = self.create(form_class, firmware="UEFI x86_64: /usr/share/edk2/ovmf/OVMF_CODE.secboot.fd") _, conn = self.create(form_class, firmware="UEFI Secure Boot", tpm=True)
run(Operation.objects.get()) run(Operation.objects.get())
firmware = conn.create_instance.call_args.kwargs["firmware"] kwargs = conn.create_instance.call_args.kwargs
self.assertEqual(firmware["secure"], "yes") 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.wvmCreate", new=wvm_create)
@patch("instances.views.NewVMForm") @patch("instances.views.NewVMForm")
@ -71,7 +84,9 @@ class CreateInstanceViewTests(TestCase):
conn.create_volume.assert_not_called() conn.create_volume.assert_not_called()
conn.create_instance.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 ( user = get_user_model().objects.filter(username="create-test-admin").first() or (
get_user_model().objects.create_superuser( get_user_model().objects.create_superuser(
username="create-test-admin", password="password", email="create@example.com" 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_storages.return_value = ["default"]
conn.get_dom_capabilities.return_value = {"loader_enums": [], "loaders": [], "cpu_modes": ["host-model"]} conn.get_dom_capabilities.return_value = {"loader_enums": [], "loaders": [], "cpu_modes": ["host-model"]}
conn.get_capabilities.return_value = {} conn.get_capabilities.return_value = {}
conn.label_for_firmware_path.return_value = None conn.get_firmwares.return_value = ["BIOS", "UEFI", "UEFI Secure Boot"]
conn.find_uefi_path_for_arch.return_value = None 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_path.return_value = "/var/lib/libvirt/images/base.qcow2"
conn.get_volume_format_type.return_value = "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": "", "virtio": True, "listener_addr": "0.0.0.0", "nwfilter": "",
"graphics": "vnc", "video": "vga", "graphics": "vnc", "video": "vga",
"mac": "52:54:00:00:00:01", "qemu_ga": False, "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, **data,
} }
@ -138,13 +155,13 @@ class CreateInstanceViewTests(TestCase):
# A host without loader support has no "loaders"/"loader_enums" keys # 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_dom_capabilities.return_value = {"loader_support": "no", "cpu_modes": ["host-model"]}
conn.get_capabilities.return_value = {} conn.get_capabilities.return_value = {}
conn.label_for_firmware_path.return_value = None conn.get_firmwares.return_value = ["BIOS"]
conn.find_uefi_path_for_arch.return_value = None conn.get_tpm_model.return_value = None
response = self.client.get(reverse("instances:create_instance", args=[compute.id, "x86_64", "q35"])) response = self.client.get(reverse("instances:create_instance", args=[compute.id, "x86_64", "q35"]))
self.assertEqual(response.status_code, 200) 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): class NicMacsTestCase(TestCase):

View file

@ -31,6 +31,7 @@ class CreateJobTestCase(TransactionTestCase):
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
self.conn.get_instances.return_value = [] self.conn.get_instances.return_value = []
self.conn.get_cache_modes.return_value = {"none": "Disable cache"} 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_disk_bus_types.return_value = ["virtio", "sata"]
self.conn.get_dom_capabilities.return_value = {"cpu_modes": ["host-model", "custom"], self.conn.get_dom_capabilities.return_value = {"cpu_modes": ["host-model", "custom"],
"cpu_custom_models": ["Skylake-Server-noTSX-IBRS"]} "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.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()) 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): def test_a_refused_create_queues_nothing(self):
url = f"/api/v1/computes/{self.compute.pk}/instances/create/x86_64/q35/" 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") 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: with patch("instances.jobs.wvmCreate") as wvm:
conn = wvm.return_value conn = wvm.return_value
conn.get_cache_modes.return_value = {"none": "Disable cache"} 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_dom_capabilities.return_value = {"cpu_modes": ["host-model"]}
conn.get_instances.return_value = [] conn.get_instances.return_value = []
conn.create_volume.return_value = "/pool/rb-vm.qcow2" 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): def test_invalid_cache_mode_allocates_nothing(self):
self.assert_nothing_left("cr-cache", self.create("cr-cache", cache_mode="bogus")) self.assert_nothing_left("cr-cache", self.create("cr-cache", cache_mode="bogus"))
def test_malformed_uefi_firmware_allocates_nothing(self): def test_a_firmware_the_host_does_not_offer_allocates_nothing(self):
self.assert_nothing_left("cr-uefi", self.create("cr-uefi", firmware="UEFI")) self.assert_nothing_left("cr-uefi", self.create("cr-uefi", firmware="Coreboot"))
def test_rejected_definition_removes_the_new_disk(self): def test_rejected_definition_removes_the_new_disk(self):
self.assert_nothing_left("cr-define", self.create("cr-define", video="no-such-model")) 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.assertTrue(self.domain_exists(P + "cr-ok"))
self.assertEqual(len(self.volumes()), 1) 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"])) 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")] return page.context["firmwares"], page.context["tpm_support"]
self.assertTrue(uefi, "no UEFI firmware offered for q35")
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") 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() 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): def test_secure_boot_with_a_tpm_boots_as_windows_11_needs(self):
caps = etree.fromstring(self.conn.getDomainCapabilities(None, "x86_64", "q35", "kvm")) firmwares, tpm_support = self.offered()
secboot = [p for p in caps.xpath("os/loader/value/text()") if "secboot" in p and "OVMF_CODE" in p] if "UEFI Secure Boot" not in firmwares or not tpm_support:
if not secboot: self.skipTest("the host offers no Secure Boot with Microsoft's keys, or no swtpm")
self.skipTest("the host has no secure boot loader for q35")
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 = self.conn.lookupByName(P + "cr-secboot")
dom.create()
tree = etree.fromstring(dom.XMLDesc(0)) tree = etree.fromstring(dom.XMLDesc(0))
self.assertEqual(tree.find("os/loader").get("secure"), "yes") self.assertEqual(tree.find("os/loader").get("secure"), "yes")
self.assertEqual(tree.find("features/smm").get("state"), "on") self.assertEqual(tree.find("features/smm").get("state"), "on")
dom.create() # the variables libvirt chose come with Microsoft's keys enrolled
self.assertTrue(dom.isActive()) 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): 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"])) 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): patch.object(wvmCreate, "get_dom_capabilities", return_value=DOM_CAPS):
conn.create_instance(name="vm-1", memory=512, vcpu=1, vcpu_mode="host-model", 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, 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") listener_addr="0.0.0.0", add_cdrom="None", add_input="None")
domain = etree.fromstring(conn.wvm.defineXML.call_args.args[0]) domain = etree.fromstring(conn.wvm.defineXML.call_args.args[0])
return [c.get("model") for c in domain.findall("devices/controller[@type='pci']")] 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() storages = list()
networks = list() networks = list()
hypervisors = list() hypervisors = list()
firmwares = list()
meta_prealloc = False meta_prealloc = False
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
flavors = Flavor.objects.filter().order_by("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) caps = conn.get_capabilities(arch)
virtio_support = conn.is_supports_virtio(arch, machine) virtio_support = conn.is_supports_virtio(arch, machine)
# Add BIOS firmwares = conn.get_firmwares(arch, machine)
label = conn.label_for_firmware_path(arch, None) tpm_support = bool(conn.get_tpm_model(arch, machine))
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))
if not storages: if not storages:
raise util.OperationError(_("You haven't defined any storage pools")) raise util.OperationError(_("You haven't defined any storage pools"))

Binary file not shown.

View file

@ -11,7 +11,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-31 18:13+0000\n" "POT-Creation-Date: 2026-11-01 10:56+0000\n"
"PO-Revision-Date: 2020-11-04 12:00+0000\n" "PO-Revision-Date: 2020-11-04 12:00+0000\n"
"Last-Translator: catborise <muhammetalisag@gmail.com>, 2020\n" "Last-Translator: catborise <muhammetalisag@gmail.com>, 2020\n"
"Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/" "Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/"
@ -275,7 +275,7 @@ msgstr "Parola Değiştir"
#: instances/templates/add_instance_owner_block.html:29 #: instances/templates/add_instance_owner_block.html:29
#: instances/templates/add_instance_volume.html:108 #: instances/templates/add_instance_volume.html:108
#: instances/templates/create_instance_w1.html:39 #: instances/templates/create_instance_w1.html:39
#: instances/templates/create_instance_w2.html:345 #: instances/templates/create_instance_w2.html:352
#: instances/templates/instances/destroy_instance_form.html:48 #: instances/templates/instances/destroy_instance_form.html:48
#: instances/templates/instances/edit_instance_volume.html:111 #: instances/templates/instances/edit_instance_volume.html:111
#: instances/templates/instances/move_disk.html:27 #: instances/templates/instances/move_disk.html:27
@ -1395,7 +1395,9 @@ msgstr "Oturum açma hata sınırı"
#: appsettings/migrations/0014_login_lockout_settings.py:8 #: appsettings/migrations/0014_login_lockout_settings.py:8
msgid "Failed logins of a user from one address before it is locked" msgid "Failed logins of a user from one address before it is locked"
msgstr "Bir kullanıcının bir adresten kilitlenmeden önceki başarısız oturum açma sayısı" msgstr ""
"Bir kullanıcının bir adresten kilitlenmeden önceki başarısız oturum açma "
"sayısı"
#: appsettings/migrations/0014_login_lockout_settings.py:9 #: appsettings/migrations/0014_login_lockout_settings.py:9
msgid "Login Lockout Minutes" msgid "Login Lockout Minutes"
@ -1413,7 +1415,9 @@ msgstr "Anlık görüntü boş alan yüzdesi"
msgid "" msgid ""
"Free space a storage pool keeps for a snapshot, in percent of the VM's disks " "Free space a storage pool keeps for a snapshot, in percent of the VM's disks "
"in it (0: none)" "in it (0: none)"
msgstr "Bir depolama havuzunun anlık görüntü için ayırdığı boş alan; sanal makinenin bu havuzdaki disklerinin yüzdesi olarak (0: yok)" msgstr ""
"Bir depolama havuzunun anlık görüntü için ayırdığı boş alan; sanal makinenin "
"bu havuzdaki disklerinin yüzdesi olarak (0: yok)"
#: appsettings/migrations/0017_snapshot_free_space_settings.py:9 #: appsettings/migrations/0017_snapshot_free_space_settings.py:9
msgid "Snapshot Free Min GiB" msgid "Snapshot Free Min GiB"
@ -1422,7 +1426,9 @@ msgstr "Anlık görüntü en az boş alan (GiB)"
#: appsettings/migrations/0017_snapshot_free_space_settings.py:10 #: appsettings/migrations/0017_snapshot_free_space_settings.py:10
msgid "" msgid ""
"Free space a storage pool keeps for a snapshot at least, in GiB (0: none)" "Free space a storage pool keeps for a snapshot at least, in GiB (0: none)"
msgstr "Bir depolama havuzunun anlık görüntü için en az ayırdığı boş alan, GiB olarak (0: yok)" msgstr ""
"Bir depolama havuzunun anlık görüntü için en az ayırdığı boş alan, GiB "
"olarak (0: yok)"
#: appsettings/migrations/0018_worker_settings.py:7 #: appsettings/migrations/0018_worker_settings.py:7
msgid "Worker Concurrency" msgid "Worker Concurrency"
@ -1459,7 +1465,9 @@ msgstr "Yeni bir sanal makine için önce seçilen CPU modu"
#: appsettings/migrations/0021_setting_descriptions.py:10 #: appsettings/migrations/0021_setting_descriptions.py:10
msgid "" msgid ""
"Network card model chosen first for a new VM (default: libvirt's choice)" "Network card model chosen first for a new VM (default: libvirt's choice)"
msgstr "Yeni bir sanal makine için önce seçilen ağ kartı modeli (default: libvirt'in seçimi)" msgstr ""
"Yeni bir sanal makine için önce seçilen ağ kartı modeli (default: libvirt'in "
"seçimi)"
#: appsettings/migrations/0021_setting_descriptions.py:11 #: appsettings/migrations/0021_setting_descriptions.py:11
msgid "Video device chosen first for a new VM" msgid "Video device chosen first for a new VM"
@ -1467,7 +1475,8 @@ msgstr "Yeni bir sanal makine için önce seçilen video aygıtı"
#: appsettings/migrations/0021_setting_descriptions.py:12 #: appsettings/migrations/0021_setting_descriptions.py:12
msgid "Input device chosen first for a new VM (None: no input device)" msgid "Input device chosen first for a new VM (None: no input device)"
msgstr "Yeni bir sanal makine için önce seçilen girdi aygıtı (None: girdi aygıtı yok)" msgstr ""
"Yeni bir sanal makine için önce seçilen girdi aygıtı (None: girdi aygıtı yok)"
#: appsettings/migrations/0021_setting_descriptions.py:13 #: appsettings/migrations/0021_setting_descriptions.py:13
msgid "Bus of the CD-ROM a new VM gets (None: no CD-ROM)" msgid "Bus of the CD-ROM a new VM gets (None: no CD-ROM)"
@ -1487,16 +1496,21 @@ msgstr "Yeni disklerin önbellek modu"
#: appsettings/migrations/0021_setting_descriptions.py:17 #: appsettings/migrations/0021_setting_descriptions.py:17
msgid "I/O mode of new disks (native needs cache none or directsync)" msgid "I/O mode of new disks (native needs cache none or directsync)"
msgstr "Yeni disklerin G/Ç modu (native, önbellek modu none veya directsync gerektirir)" msgstr ""
"Yeni disklerin G/Ç modu (native, önbellek modu none veya directsync "
"gerektirir)"
#: appsettings/migrations/0021_setting_descriptions.py:18 #: appsettings/migrations/0021_setting_descriptions.py:18
msgid "Discard of new disks (unmap: the guest's TRIM frees space in the image)" msgid "Discard of new disks (unmap: the guest's TRIM frees space in the image)"
msgstr "Yeni disklerin discard ayarı (unmap: konuğun TRIM komutu imajda yer açar)" msgstr ""
"Yeni disklerin discard ayarı (unmap: konuğun TRIM komutu imajda yer açar)"
#: appsettings/migrations/0021_setting_descriptions.py:19 #: appsettings/migrations/0021_setting_descriptions.py:19
msgid "" msgid ""
"Detect zeroes of new disks: how blocks of zeros the guest writes are stored" "Detect zeroes of new disks: how blocks of zeros the guest writes are stored"
msgstr "Yeni disklerin sıfır algılama ayarı: konuğun yazdığı sıfır bloklarının nasıl saklanacağı" msgstr ""
"Yeni disklerin sıfır algılama ayarı: konuğun yazdığı sıfır bloklarının nasıl "
"saklanacağı"
#: appsettings/migrations/0021_setting_descriptions.py:20 #: appsettings/migrations/0021_setting_descriptions.py:20
msgid "Controller model for disks on the SCSI bus" msgid "Controller model for disks on the SCSI bus"
@ -1549,15 +1563,21 @@ msgstr "Bir sanal makine birden fazla kullanıcıya verilebilir"
#: appsettings/migrations/0021_setting_descriptions.py:32 #: appsettings/migrations/0021_setting_descriptions.py:32
msgid "" msgid ""
"A quota refusal shows the numbers: what would be used and what is allowed" "A quota refusal shows the numbers: what would be used and what is allowed"
msgstr "Kota reddi sayıları gösterir: ne kadar kullanılacağını ve ne kadarına izin verildiğini" msgstr ""
"Kota reddi sayıları gösterir: ne kadar kullanılacağını ve ne kadarına izin "
"verildiğini"
#: appsettings/migrations/0021_setting_descriptions.py:33 #: appsettings/migrations/0021_setting_descriptions.py:33
msgid "Instances page for administrators: VMs under their host, or one list" msgid "Instances page for administrators: VMs under their host, or one list"
msgstr "Yöneticiler için Sanal Makineler sayfası: sanal makineler sunucularının altında ya da tek bir listede" msgstr ""
"Yöneticiler için Sanal Makineler sayfası: sanal makineler sunucularının "
"altında ya da tek bir listede"
#: appsettings/migrations/0021_setting_descriptions.py:34 #: appsettings/migrations/0021_setting_descriptions.py:34
msgid "A VM's page shows a bar with its host's links at the bottom" msgid "A VM's page shows a bar with its host's links at the bottom"
msgstr "Sanal makinenin sayfası, altta sunucusunun bağlantılarını içeren bir çubuk gösterir" msgstr ""
"Sanal makinenin sayfası, altta sunucusunun bağlantılarını içeren bir çubuk "
"gösterir"
#: appsettings/migrations/0021_setting_descriptions.py:35 #: appsettings/migrations/0021_setting_descriptions.py:35
msgid "The Instances page shows each VM's DRBD state" msgid "The Instances page shows each VM's DRBD state"
@ -1921,7 +1941,7 @@ msgid "Connection"
msgstr "Bağlantı" msgstr "Bağlantı"
#: computes/templates/computes/form.html:31 #: computes/templates/computes/form.html:31
#: instances/templates/create_instance_w2.html:248 #: instances/templates/create_instance_w2.html:255
msgid "Advanced" msgid "Advanced"
msgstr "Gelişmiş" msgstr "Gelişmiş"
@ -1978,8 +1998,8 @@ msgstr "Depolama"
#: computes/templates/computes/host_nav.html:19 #: computes/templates/computes/host_nav.html:19
#: computes/templates/computes/host_nav.html:20 #: computes/templates/computes/host_nav.html:20
#: instances/templates/add_instance_network_block.html:18 #: instances/templates/add_instance_network_block.html:18
#: instances/templates/create_instance_w2.html:220 #: instances/templates/create_instance_w2.html:227
#: instances/templates/create_instance_w2.html:222 #: instances/templates/create_instance_w2.html:229
#: instances/templates/instances/settings_tab.html:379 #: instances/templates/instances/settings_tab.html:379
#: networks/templates/networks.html:5 networks/templates/networks.html:7 #: networks/templates/networks.html:5 networks/templates/networks.html:7
msgid "Networks" msgid "Networks"
@ -2376,9 +2396,9 @@ msgstr "Ölçekleme"
#: console/templates/console-vnc.html:29 #: console/templates/console-vnc.html:29
#: instances/templates/add_instance_network_block.html:41 #: instances/templates/add_instance_network_block.html:41
#: instances/templates/create_instance_w2.html:275 #: instances/templates/create_instance_w2.html:282
#: instances/templates/create_instance_w2.html:304 #: instances/templates/create_instance_w2.html:311
#: instances/templates/create_instance_w2.html:314 #: instances/templates/create_instance_w2.html:321
#: instances/templates/instance.html:55 #: instances/templates/instance.html:55
#: instances/templates/instances/settings_tab.html:186 #: instances/templates/instances/settings_tab.html:186
#: instances/templates/instances/settings_tab.html:342 #: instances/templates/instances/settings_tab.html:342
@ -2451,7 +2471,7 @@ msgstr "Bağlan"
#: instances/templates/allinstances.html:32 #: instances/templates/allinstances.html:32
#: instances/templates/allinstances.html:88 #: instances/templates/allinstances.html:88
#: instances/templates/create_inst_block.html:12 #: instances/templates/create_inst_block.html:12
#: instances/templates/create_instance_w2.html:357 #: instances/templates/create_instance_w2.html:364
#: instances/templates/instances/edit_instance_volume.html:13 #: instances/templates/instances/edit_instance_volume.html:13
#: instances/templates/instances/move_disk.html:13 #: instances/templates/instances/move_disk.html:13
#: instances/templates/instances/settings_tab.html:366 #: instances/templates/instances/settings_tab.html:366
@ -2695,7 +2715,7 @@ msgstr "libvirt sanal makineyi hâlâ geri döndürüyor"
msgid "Clone VM" msgid "Clone VM"
msgstr "Sanal makineyi klonla" msgstr "Sanal makineyi klonla"
#: instances/jobs.py:495 instances/jobs.py:503 instances/jobs.py:751 #: instances/jobs.py:495 instances/jobs.py:503 instances/jobs.py:746
msgid "Copying disks" msgid "Copying disks"
msgstr "Diskler kopyalanıyor" msgstr "Diskler kopyalanıyor"
@ -2704,7 +2724,7 @@ msgid "libvirt is still copying the disks"
msgstr "libvirt diskleri hâlâ kopyalıyor" msgstr "libvirt diskleri hâlâ kopyalıyor"
#: instances/jobs.py:662 instances/templates/create_instance_w1.html:58 #: instances/jobs.py:662 instances/templates/create_instance_w1.html:58
#: instances/templates/create_instance_w2.html:346 #: instances/templates/create_instance_w2.html:353
msgid "Create VM" msgid "Create VM"
msgstr "Sanal makine oluştur" msgstr "Sanal makine oluştur"
@ -2754,49 +2774,54 @@ msgid "The cloud-init seed needs a directory storage pool for the disk"
msgstr "cloud-init tohumu, disk için bir dizin depolama havuzu gerektirir" msgstr "cloud-init tohumu, disk için bir dizin depolama havuzu gerektirir"
#: instances/jobs.py:712 #: instances/jobs.py:712
msgid "Invalid firmware" #, python-format
msgstr "Geçersiz donanım yazılımı" msgid "This host offers no firmware %(firmware)s"
msgstr "Bu sunucu %(firmware)s firmware'ini sunmuyor"
#: instances/jobs.py:770 #: instances/jobs.py:714
msgid "This host offers no TPM 2.0: install swtpm on it"
msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun"
#: instances/jobs.py:765
msgid "Writing the cloud-init seed" msgid "Writing the cloud-init seed"
msgstr "cloud-init tohumu yazılıyor" msgstr "cloud-init tohumu yazılıyor"
#: instances/jobs.py:777 #: instances/jobs.py:772
msgid "Defining the VM" msgid "Defining the VM"
msgstr "Sanal makine tanımlanıyor" msgstr "Sanal makine tanımlanıyor"
#: instances/jobs.py:833 #: instances/jobs.py:829
#, python-format #, python-format
msgid "The instance is already on %(compute)s" msgid "The instance is already on %(compute)s"
msgstr "Sanal makine zaten %(compute)s üzerinde" msgstr "Sanal makine zaten %(compute)s üzerinde"
#: instances/jobs.py:835 #: instances/jobs.py:831
#, python-format #, python-format
msgid "%(compute)s is not reachable" msgid "%(compute)s is not reachable"
msgstr "%(compute)s sunucusuna ulaşılamıyor" msgstr "%(compute)s sunucusuna ulaşılamıyor"
#: instances/jobs.py:837 #: instances/jobs.py:833
#, python-format #, python-format
msgid "%(compute)s is the VM's own host under another name" msgid "%(compute)s is the VM's own host under another name"
msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı" msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı"
#: instances/jobs.py:855 #: instances/jobs.py:851
msgid "Migrate VM" msgid "Migrate VM"
msgstr "Sanal makineyi taşı" msgstr "Sanal makineyi taşı"
#: instances/jobs.py:899 #: instances/jobs.py:895
msgid "Migrating the VM" msgid "Migrating the VM"
msgstr "Sanal makine taşınıyor" msgstr "Sanal makine taşınıyor"
#: instances/jobs.py:931 #: instances/jobs.py:927
msgid "libvirt is still migrating the VM" msgid "libvirt is still migrating the VM"
msgstr "libvirt sanal makineyi hâlâ taşıyor" msgstr "libvirt sanal makineyi hâlâ taşıyor"
#: instances/jobs.py:972 #: instances/jobs.py:968
msgid "Move disk" msgid "Move disk"
msgstr "Diski taşı" msgstr "Diski taşı"
#: instances/jobs.py:1003 #: instances/jobs.py:999
msgid "Copying the disk" msgid "Copying the disk"
msgstr "Disk kopyalanıyor" msgstr "Disk kopyalanıyor"
@ -2832,14 +2857,14 @@ msgstr "oluşturuldu"
msgid "drbd" msgid "drbd"
msgstr "drbd" msgstr "drbd"
#: instances/models.py:221 instances/templates/create_instance_w2.html:108 #: instances/models.py:221 instances/templates/create_instance_w2.html:115
#: instances/templates/instances/resize_tab.html:20 #: instances/templates/instances/resize_tab.html:20
#: instances/templates/instances/settings_tab.html:25 #: instances/templates/instances/settings_tab.html:25
#: instances/templates/instances/stats_tab.html:49 #: instances/templates/instances/stats_tab.html:49
msgid "Disk" msgid "Disk"
msgstr "Disk" msgstr "Disk"
#: instances/models.py:223 instances/templates/create_instance_w2.html:302 #: instances/models.py:223 instances/templates/create_instance_w2.html:309
#: instances/templates/instances/settings_tab.html:167 #: instances/templates/instances/settings_tab.html:167
msgid "CD-ROM" msgid "CD-ROM"
msgstr "CD-ROM" msgstr "CD-ROM"
@ -2872,27 +2897,27 @@ msgstr "Güvenli olmayan"
msgid "No Virtual Machine name has been entered" msgid "No Virtual Machine name has been entered"
msgstr "Sanal makine adı girilmedi" msgstr "Sanal makine adı girilmedi"
#: instances/models.py:406 #: instances/models.py:408
msgid "No VCPU has been entered" msgid "No VCPU has been entered"
msgstr "VCPU belirtilmedi" msgstr "VCPU belirtilmedi"
#: instances/models.py:412 #: instances/models.py:414
msgid "No RAM size has been entered" msgid "No RAM size has been entered"
msgstr "RAM boyutu belirtilmedi" msgstr "RAM boyutu belirtilmedi"
#: instances/models.py:416 #: instances/models.py:418
msgid "No Network pool has been choosen" msgid "No Network pool has been choosen"
msgstr "Ağ havuzu seçilmedi" msgstr "Ağ havuzu seçilmedi"
#: instances/models.py:424 #: instances/models.py:426
msgid "Please select HDD cache mode" msgid "Please select HDD cache mode"
msgstr "Lütfen HDD ön bellek modunu seçin" msgstr "Lütfen HDD ön bellek modunu seçin"
#: instances/models.py:436 #: instances/models.py:438
msgid "Please select a video driver" msgid "Please select a video driver"
msgstr "Lütfen bir video sürücüsü seçin" msgstr "Lütfen bir video sürücüsü seçin"
#: instances/models.py:473 #: instances/models.py:475
msgid "Can access console without password" msgid "Can access console without password"
msgstr "Parolasız konsol erişimi yapabilir" msgstr "Parolasız konsol erişimi yapabilir"
@ -2986,12 +3011,12 @@ msgid "Add disk form"
msgstr "Disk ekleme formu" msgstr "Disk ekleme formu"
#: instances/templates/add_instance_volume.html:19 #: instances/templates/add_instance_volume.html:19
#: instances/templates/create_instance_w2.html:110 #: instances/templates/create_instance_w2.html:117
msgid "Disk source" msgid "Disk source"
msgstr "Disk kaynağı" msgstr "Disk kaynağı"
#: instances/templates/add_instance_volume.html:21 #: instances/templates/add_instance_volume.html:21
#: instances/templates/create_instance_w2.html:124 instances/views.py:2427 #: instances/templates/create_instance_w2.html:131 instances/views.py:2427
msgid "New disk" msgid "New disk"
msgstr "Yeni disk" msgstr "Yeni disk"
@ -3009,8 +3034,8 @@ msgstr "Sunucunun bir havuzunda zaten bulunan bir birim."
#: instances/templates/add_instance_volume.html:31 #: instances/templates/add_instance_volume.html:31
#: instances/templates/add_instance_volume.html:70 #: instances/templates/add_instance_volume.html:70
#: instances/templates/create_instance_w2.html:127 #: instances/templates/create_instance_w2.html:134
#: instances/templates/create_instance_w2.html:155 #: instances/templates/create_instance_w2.html:162
#: instances/templates/instances/move_disk.html:16 #: instances/templates/instances/move_disk.html:16
#: instances/templates/instances/settings_tab.html:229 #: instances/templates/instances/settings_tab.html:229
msgid "Storage pool" msgid "Storage pool"
@ -3033,8 +3058,8 @@ msgid "Format"
msgstr "Biçem" msgstr "Biçem"
#: instances/templates/add_instance_volume.html:61 #: instances/templates/add_instance_volume.html:61
#: instances/templates/create_instance_w2.html:144 #: instances/templates/create_instance_w2.html:151
#: instances/templates/create_instance_w2.html:210 storages/forms.py:95 #: instances/templates/create_instance_w2.html:217 storages/forms.py:95
#: storages/forms.py:115 storages/templates/storage.html:198 #: storages/forms.py:115 storages/templates/storage.html:198
msgid "Preallocate metadata (qcow2)" msgid "Preallocate metadata (qcow2)"
msgstr "Meta veriyi önceden ayır (qcow2)" msgstr "Meta veriyi önceden ayır (qcow2)"
@ -3052,7 +3077,7 @@ msgid "Choose a pool first"
msgstr "Önce bir havuz seçin" msgstr "Önce bir havuz seçin"
#: instances/templates/add_instance_volume.html:90 #: instances/templates/add_instance_volume.html:90
#: instances/templates/create_instance_w2.html:174 #: instances/templates/create_instance_w2.html:181
#: instances/templates/instances/edit_instance_volume.html:82 #: instances/templates/instances/edit_instance_volume.html:82
#: instances/templates/instances/edit_instance_volume.html:87 #: instances/templates/instances/edit_instance_volume.html:87
msgid "Bus" msgid "Bus"
@ -3335,43 +3360,51 @@ msgstr "Hazır boyutları yönet"
msgid "Firmware" msgid "Firmware"
msgstr "Üretici yazılımı" msgstr "Üretici yazılımı"
#: instances/templates/create_instance_w2.html:83
msgid "TPM 2.0"
msgstr "TPM 2.0"
#: instances/templates/create_instance_w2.html:84 #: instances/templates/create_instance_w2.html:84
msgid "Windows 11 needs it, with UEFI Secure Boot"
msgstr "Windows 11, UEFI Secure Boot ile birlikte bunu ister"
#: instances/templates/create_instance_w2.html:91
msgid "CPU mode" msgid "CPU mode"
msgstr "CPU modu" msgstr "CPU modu"
#: instances/templates/create_instance_w2.html:86 #: instances/templates/create_instance_w2.html:93
#: storages/templates/create_stg_block.html:118 #: storages/templates/create_stg_block.html:118
msgid "Choose…" msgid "Choose…"
msgstr "Seçin…" msgstr "Seçin…"
#: instances/templates/create_instance_w2.html:89 #: instances/templates/create_instance_w2.html:96
msgid "Custom CPU Models" msgid "Custom CPU Models"
msgstr "Özel CPU modelleri" msgstr "Özel CPU modelleri"
#: instances/templates/create_instance_w2.html:136 storages/forms.py:94 #: instances/templates/create_instance_w2.html:143 storages/forms.py:94
msgid "Size (GiB)" msgid "Size (GiB)"
msgstr "Boyut (GiB)" msgstr "Boyut (GiB)"
#: instances/templates/create_instance_w2.html:151 instances/views.py:2428 #: instances/templates/create_instance_w2.html:158 instances/views.py:2428
msgid "Existing volumes" msgid "Existing volumes"
msgstr "Mevcut birimler" msgstr "Mevcut birimler"
#: instances/templates/create_instance_w2.html:157 #: instances/templates/create_instance_w2.html:164
#: instances/templates/create_instance_w2.html:189 #: instances/templates/create_instance_w2.html:196
msgid "Select pool" msgid "Select pool"
msgstr "Havuz seçin" msgstr "Havuz seçin"
#: instances/templates/create_instance_w2.html:162 #: instances/templates/create_instance_w2.html:169
#: instances/templates/create_instance_w2.html:163 #: instances/templates/create_instance_w2.html:170
#: storages/templates/storage.html:116 storages/templates/storages.html:47 #: storages/templates/storage.html:116 storages/templates/storages.html:47
msgid "Volumes" msgid "Volumes"
msgstr "Birimler" msgstr "Birimler"
#: instances/templates/create_instance_w2.html:167 #: instances/templates/create_instance_w2.html:174
msgid "Chosen volumes" msgid "Chosen volumes"
msgstr "Seçilen birimler" msgstr "Seçilen birimler"
#: instances/templates/create_instance_w2.html:171 #: instances/templates/create_instance_w2.html:178
#: instances/templates/instances/edit_instance_volume.html:81 #: instances/templates/instances/edit_instance_volume.html:81
#: interfaces/templates/create_iface_block.html:78 #: interfaces/templates/create_iface_block.html:78
#: interfaces/templates/interface.html:113 networks/templates/networks.html:39 #: interfaces/templates/interface.html:113 networks/templates/networks.html:39
@ -3380,91 +3413,91 @@ msgstr "Seçilen birimler"
msgid "Device" msgid "Device"
msgstr "Aygıt" msgstr "Aygıt"
#: instances/templates/create_instance_w2.html:178 #: instances/templates/create_instance_w2.html:185
#: instances/templates/create_instance_w2.html:236 #: instances/templates/create_instance_w2.html:243
#: instances/templates/instances/settings_tab.html:678 #: instances/templates/instances/settings_tab.html:678
#: networks/templates/network.html:303 #: networks/templates/network.html:303
msgid "Remove" msgid "Remove"
msgstr "Kaldır" msgstr "Kaldır"
#: instances/templates/create_instance_w2.html:184 instances/views.py:2429 #: instances/templates/create_instance_w2.html:191 instances/views.py:2429
msgid "Clone from template" msgid "Clone from template"
msgstr "Şablondan klonla" msgstr "Şablondan klonla"
#: instances/templates/create_instance_w2.html:187 #: instances/templates/create_instance_w2.html:194
msgid "Template pool" msgid "Template pool"
msgstr "Şablon havuzu" msgstr "Şablon havuzu"
#: instances/templates/create_instance_w2.html:194 #: instances/templates/create_instance_w2.html:201
msgid "Template volume" msgid "Template volume"
msgstr "Şablon birimi" msgstr "Şablon birimi"
#: instances/templates/create_instance_w2.html:199 #: instances/templates/create_instance_w2.html:206
msgid "Copy into pool" msgid "Copy into pool"
msgstr "Havuza kopyala" msgstr "Havuza kopyala"
#: instances/templates/create_instance_w2.html:221 #: instances/templates/create_instance_w2.html:228
msgid "Add networks" msgid "Add networks"
msgstr "Ağ ekle" msgstr "Ağ ekle"
#: instances/templates/create_instance_w2.html:228 #: instances/templates/create_instance_w2.html:235
msgid "Network interfaces" msgid "Network interfaces"
msgstr "Ağ arabirimleri" msgstr "Ağ arabirimleri"
#: instances/templates/create_instance_w2.html:235 #: instances/templates/create_instance_w2.html:242
msgid "MAC address" msgid "MAC address"
msgstr "MAC adresi" msgstr "MAC adresi"
#: instances/templates/create_instance_w2.html:255 #: instances/templates/create_instance_w2.html:262
msgid "Disk cache mode" msgid "Disk cache mode"
msgstr "Disk önbellek modu" msgstr "Disk önbellek modu"
#: instances/templates/create_instance_w2.html:264 #: instances/templates/create_instance_w2.html:271
msgid "NIC type" msgid "NIC type"
msgstr "NIC tipi" msgstr "NIC tipi"
#: instances/templates/create_instance_w2.html:273 #: instances/templates/create_instance_w2.html:280
#: nwfilters/templates/nwfilter.html:6 nwfilters/templates/nwfilter.html:8 #: nwfilters/templates/nwfilter.html:6 nwfilters/templates/nwfilter.html:8
msgid "Network filter" msgid "Network filter"
msgstr "Ağ filtresi" msgstr "Ağ filtresi"
#: instances/templates/create_instance_w2.html:283 vrtManager/nodedevs.py:29 #: instances/templates/create_instance_w2.html:290 vrtManager/nodedevs.py:29
msgid "Video" msgid "Video"
msgstr "Görüntü" msgstr "Görüntü"
#: instances/templates/create_instance_w2.html:293 #: instances/templates/create_instance_w2.html:300
msgid "Console access" msgid "Console access"
msgstr "Konsol erişimi" msgstr "Konsol erişimi"
#: instances/templates/create_instance_w2.html:312 #: instances/templates/create_instance_w2.html:319
msgid "Input device" msgid "Input device"
msgstr "Girdi aygıtı" msgstr "Girdi aygıtı"
#: instances/templates/create_instance_w2.html:324 #: instances/templates/create_instance_w2.html:331
msgid "Guest agent" msgid "Guest agent"
msgstr "Misafir aracı" msgstr "Misafir aracı"
#: instances/templates/create_instance_w2.html:328 #: instances/templates/create_instance_w2.html:335
msgid "Cloud-init seed" msgid "Cloud-init seed"
msgstr "Cloud-init tohumu" msgstr "Cloud-init tohumu"
#: instances/templates/create_instance_w2.html:329 #: instances/templates/create_instance_w2.html:336
msgid "The hostname and your SSH keys on the CD-ROM, for cloud images" msgid "The hostname and your SSH keys on the CD-ROM, for cloud images"
msgstr "Bulut imajları için CD-ROM'da sunucuadı ve SSH anahtarlarınız" msgstr "Bulut imajları için CD-ROM'da sunucuadı ve SSH anahtarlarınız"
#: instances/templates/create_instance_w2.html:334 #: instances/templates/create_instance_w2.html:341
msgid "VirtIO devices" msgid "VirtIO devices"
msgstr "VirtIO aygıtları" msgstr "VirtIO aygıtları"
#: instances/templates/create_instance_w2.html:356 #: instances/templates/create_instance_w2.html:363
msgid "Flavors" msgid "Flavors"
msgstr "Hazır boyutlar" msgstr "Hazır boyutlar"
#: instances/templates/create_instance_w2.html:375 #: instances/templates/create_instance_w2.html:382
msgid "Add volume" msgid "Add volume"
msgstr "Birim ekle" msgstr "Birim ekle"
#: instances/templates/create_instance_w2.html:382 #: instances/templates/create_instance_w2.html:389
msgid "Add network" msgid "Add network"
msgstr "Ağ ekle" msgstr "Ağ ekle"
@ -5960,35 +5993,35 @@ msgstr "Bir havuzda zaten bulunan birimler"
msgid "A copy of a template volume" msgid "A copy of a template volume"
msgstr "Bir şablon biriminin kopyası" msgstr "Bir şablon biriminin kopyası"
#: instances/views.py:2527 #: instances/views.py:2518
msgid "You haven't defined any storage pools" msgid "You haven't defined any storage pools"
msgstr "Hiç bir depolama havuzu tanımlamadınız" msgstr "Hiç bir depolama havuzu tanımlamadınız"
#: instances/views.py:2529 #: instances/views.py:2520
msgid "You haven't defined any network pools" msgid "You haven't defined any network pools"
msgstr "Hiç bir ağ havuzu tanımlamadınız" msgstr "Hiç bir ağ havuzu tanımlamadınız"
#: instances/views.py:2592 #: instances/views.py:2583
msgid "Flavor Created" msgid "Flavor Created"
msgstr "Flavor Oluşturuldu" msgstr "Flavor Oluşturuldu"
#: instances/views.py:2600 #: instances/views.py:2591
msgid "Create Flavor" msgid "Create Flavor"
msgstr "Kullanıcı Oluştur" msgstr "Kullanıcı Oluştur"
#: instances/views.py:2615 #: instances/views.py:2606
msgid "Flavor Updated" msgid "Flavor Updated"
msgstr "Flavor Güncellendi" msgstr "Flavor Güncellendi"
#: instances/views.py:2621 #: instances/views.py:2612
msgid "Update Flavor" msgid "Update Flavor"
msgstr "Flavor Güncelle" msgstr "Flavor Güncelle"
#: instances/views.py:2633 #: instances/views.py:2624
msgid "Flavor Deleted" msgid "Flavor Deleted"
msgstr "Flavor Silindi" msgstr "Flavor Silindi"
#: instances/views.py:2639 #: instances/views.py:2630
msgid "Only the size template goes; VMs made from it keep their size." msgid "Only the size template goes; VMs made from it keep their size."
msgstr "" msgstr ""
"Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler " "Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler "

View file

@ -1189,48 +1189,47 @@ class wvmConnect(object):
# self.wvm.close() # self.wvm.close()
pass pass
def find_uefi_path_for_arch(self, arch, machine): def get_firmwares(self, arch, machine):
""" """The firmware a new VM can have: BIOS on x86, UEFI without Secure Boot
Search the loader paths for one that matches the passed arch when libvirt picks the firmware itself, and on x86_64 UEFI Secure Boot
""" with Microsoft's keys enrolled, as Windows 11 needs."""
if not self.arch_can_uefi(arch):
return
loaders = self.get_os_loaders(arch, machine) def firmwares(ctx):
patterns = util.UEFI_ARCH_PATTERNS.get(arch) os_caps = "/domainCapabilities/os[@supported='yes']"
for pattern in patterns: features = f"{os_caps}/firmwareFeatures[@supported='yes']"
for path in loaders: if ctx.xpath(features): # libvirt 12.1 and later: what its firmware offers
if re.match(pattern, path): secure_boot = ctx.xpath(f"{features}/enum[@name='secureBoot']/value/text()")
return path enrolled_keys = ctx.xpath(f"{features}/enum[@name='enrolledKeys']/value/text()")
else: # earlier: the loaders; libvirt refuses the VM if no firmware has the keys
secure_boot = ctx.xpath(f"{os_caps}/loader[@supported='yes']/enum[@name='secure']/value/text()") or ["no"]
enrolled_keys = secure_boot
choices = ["BIOS"] if arch in ("i686", "x86_64") else []
if "efi" in ctx.xpath(f"{os_caps}/enum[@name='firmware']/value/text()"):
if "no" in secure_boot:
choices.append("UEFI")
if arch == "x86_64" and "yes" in secure_boot and "yes" in enrolled_keys:
choices.append("UEFI Secure Boot")
return choices
def label_for_firmware_path(self, arch, path): return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=firmwares)
"""
Return a pretty label for passed path, based on if we know
about it or not
"""
if not path:
if arch in ["i686", "x86_64"]:
return "BIOS"
return
for arch, patterns in util.UEFI_ARCH_PATTERNS.items(): def get_tpm_model(self, arch, machine):
for pattern in patterns: """The model of an emulated TPM 2.0 for a new VM; None when the host has
if re.match(pattern, path): none (no swtpm)."""
return "UEFI %(arch)s: %(path)s" % {"arch": arch, "path": path}
return "Custom: %(path)s" % {"path": path} def tpm_model(ctx):
tpm = "/domainCapabilities/devices/tpm[@supported='yes']"
if ("emulator" not in ctx.xpath(f"{tpm}/enum[@name='backendModel']/value/text()")
or "2.0" not in ctx.xpath(f"{tpm}/enum[@name='backendVersion']/value/text()")):
return None
models = ctx.xpath(f"{tpm}/enum[@name='model']/value/text()")
# x86: CRB, which Windows takes, or TIS; another machine: its own
for model in ("tpm-crb", "tpm-tis", *models):
if model in models:
return model
return None
def arch_can_uefi(self, arch): return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=tpm_model)
"""
Return True if we know how to setup UEFI for the passed arch
"""
return arch in list(util.UEFI_ARCH_PATTERNS.keys())
def supports_uefi_xml(self, loader_enums):
"""
Return True if libvirt advertises support for proper UEFI setup
"""
return "readonly" in loader_enums and "yes" in loader_enums.get("readonly")
def is_supports_virtio(self, arch, machine): def is_supports_virtio(self, arch, machine):
if not self.is_qemu(): if not self.is_qemu():

View file

@ -203,10 +203,12 @@ class wvmCreate(wvmConnect):
mac=None, mac=None,
qemu_ga=True, qemu_ga=True,
add_cdrom="sata", add_cdrom="sata",
add_input="default" add_input="default",
tpm_model=None,
): ):
""" """
Create VM function Create VM function. firmware: BIOS, UEFI or UEFI Secure Boot, of
get_firmwares; tpm_model: an emulated TPM 2.0 of get_tpm_model, or none.
""" """
caps = self.get_capabilities(arch) caps = self.get_capabilities(arch)
dom_caps = self.get_dom_capabilities(arch, machine) dom_caps = self.get_dom_capabilities(arch, machine)
@ -221,33 +223,30 @@ class wvmCreate(wvmConnect):
<memory unit='KiB'>{memory}</memory> <memory unit='KiB'>{memory}</memory>
<vcpu>{vcpu}</vcpu>""" <vcpu>{vcpu}</vcpu>"""
secure_boot = firmware == "UEFI Secure Boot"
if dom_caps["os_support"] == "yes": if dom_caps["os_support"] == "yes":
xml += f"""<os> # UEFI: libvirt picks the firmware and its variables, for Secure
# Boot one with Microsoft's keys enrolled
efi = " firmware='efi'" if firmware in ("UEFI", "UEFI Secure Boot") else ""
xml += f"""<os{efi}>
<type arch='{arch}' machine='{machine}'>{caps["os_type"]}</type>""" <type arch='{arch}' machine='{machine}'>{caps["os_type"]}</type>"""
if secure_boot:
xml += """<firmware>
<feature enabled='yes' name='secure-boot'/>
<feature enabled='yes' name='enrolled-keys'/>
</firmware>"""
elif efi:
xml += """<firmware><feature enabled='no' name='secure-boot'/></firmware>"""
xml += """ <boot dev='hd'/> xml += """ <boot dev='hd'/>
<boot dev='cdrom'/> <boot dev='cdrom'/>
<bootmenu enable='yes'/>""" <bootmenu enable='yes'/>"""
if firmware:
if firmware["secure"] == "yes":
xml += """<loader readonly='%s' type='%s' secure='%s'>%s</loader>""" % (
firmware["readonly"],
firmware["type"],
firmware["secure"],
firmware["loader"],
)
if firmware["secure"] == "no":
xml += """<loader readonly='%s' type='%s'>%s</loader>""" % (
firmware["readonly"],
firmware["type"],
firmware["loader"],
)
xml += """</os>""" xml += """</os>"""
if caps["features"]: if caps["features"]:
xml += """<features>""" xml += """<features>"""
for feat in [x for x in ("acpi", "apic", "pae",) if x in caps["features"]]: for feat in [x for x in ("acpi", "apic", "pae",) if x in caps["features"]]:
xml += f"""<{feat}/>""" xml += f"""<{feat}/>"""
if firmware.get("secure", "no") == "yes": if secure_boot:
xml += """<smm state="on"/>""" xml += """<smm state="on"/>"""
xml += """</features>""" xml += """</features>"""
@ -382,6 +381,8 @@ class wvmCreate(wvmConnect):
xml += f""" <video> xml += f""" <video>
<model type='{video}'/> <model type='{video}'/>
</video>""" </video>"""
if tpm_model:
xml += f"""<tpm model='{tpm_model}'><backend type='emulator' version='2.0'/></tpm>"""
# q35 plugs each PCIe device (a virtio disk, a NIC) into a root port of # q35 plugs each PCIe device (a virtio disk, a NIC) into a root port of
# its own and libvirt makes only as many as the VM is defined with: as # its own and libvirt makes only as many as the VM is defined with: as
# virt-install, 14 ports, so a running VM can take more devices # virt-install, 14 ports, so a running VM can take more devices

View file

@ -195,31 +195,6 @@ def validate_macaddr(val):
raise ValueError("MAC address must be of the format AA:BB:CC:DD:EE:FF, was {}".format(val)) raise ValueError("MAC address must be of the format AA:BB:CC:DD:EE:FF, was {}".format(val))
# Mapping of UEFI binary names to their associated architectures. We
# only use this info to do things automagically for the user, it shouldn't
# validate anything the user explicitly enters.
UEFI_ARCH_PATTERNS = {
"i686": [
r".*ovmf-ia32.*", # fedora, gerd's firmware repo
],
"x86_64": [
r".*OVMF_CODE\.fd", # RHEL
r".*ovmf-x64/OVMF.*\.fd", # gerd's firmware repo
r".*ovmf-x86_64-.*", # SUSE
r".*ovmf.*",
".*OVMF.*", # generic attempt at a catchall
],
"aarch64": [
r".*AAVMF_CODE\.fd", # RHEL
r".*aarch64/QEMU_EFI.*", # gerd's firmware repo
r".*aarch64.*", # generic attempt at a catchall
],
"armv7l": [
r".*arm/QEMU_EFI.*", # fedora, gerd's firmware repo
],
}
def vol_dev_type(vol_bus): def vol_dev_type(vol_bus):
if vol_bus == "ide": if vol_bus == "ide":
return "hd" return "hd"