mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 11:16:47 +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:
parent
40db3dcf3d
commit
005fa59bf0
20 changed files with 399 additions and 228 deletions
|
|
@ -42,6 +42,7 @@ WebVirtCloud and `virt-manager`/`virsh` can manage the same libvirt host **concu
|
|||
* Storage pool and volume management
|
||||
* Network (with DHCP leases), interface, network filter and secret management
|
||||
* 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
|
||||
* Role-based access control: users only see the VMs assigned to them, within their quota
|
||||
* 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
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
**Installer or manual installation**, on the panel host:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
126
instances/tests/test_firmware_tpm.py
Normal file
126
instances/tests/test_firmware_tpm.py
Normal 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"))
|
||||
|
|
@ -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"]))
|
||||
|
|
|
|||
|
|
@ -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']")]
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -11,7 +11,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: catborise <muhammetalisag@gmail.com>, 2020\n"
|
||||
"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_volume.html:108
|
||||
#: 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/edit_instance_volume.html:111
|
||||
#: 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
|
||||
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
|
||||
msgid "Login Lockout Minutes"
|
||||
|
|
@ -1413,7 +1415,9 @@ msgstr "Anlık görüntü boş alan yüzdesi"
|
|||
msgid ""
|
||||
"Free space a storage pool keeps for a snapshot, in percent of the VM's disks "
|
||||
"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
|
||||
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
|
||||
msgid ""
|
||||
"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
|
||||
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
|
||||
msgid ""
|
||||
"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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
msgid ""
|
||||
"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
|
||||
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
|
||||
msgid ""
|
||||
"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
|
||||
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
|
||||
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
|
||||
msgid "The Instances page shows each VM's DRBD state"
|
||||
|
|
@ -1921,7 +1941,7 @@ msgid "Connection"
|
|||
msgstr "Bağlantı"
|
||||
|
||||
#: computes/templates/computes/form.html:31
|
||||
#: instances/templates/create_instance_w2.html:248
|
||||
#: instances/templates/create_instance_w2.html:255
|
||||
msgid "Advanced"
|
||||
msgstr "Gelişmiş"
|
||||
|
||||
|
|
@ -1978,8 +1998,8 @@ msgstr "Depolama"
|
|||
#: computes/templates/computes/host_nav.html:19
|
||||
#: computes/templates/computes/host_nav.html:20
|
||||
#: instances/templates/add_instance_network_block.html:18
|
||||
#: instances/templates/create_instance_w2.html:220
|
||||
#: instances/templates/create_instance_w2.html:222
|
||||
#: instances/templates/create_instance_w2.html:227
|
||||
#: instances/templates/create_instance_w2.html:229
|
||||
#: instances/templates/instances/settings_tab.html:379
|
||||
#: networks/templates/networks.html:5 networks/templates/networks.html:7
|
||||
msgid "Networks"
|
||||
|
|
@ -2376,9 +2396,9 @@ msgstr "Ölçekleme"
|
|||
|
||||
#: console/templates/console-vnc.html:29
|
||||
#: instances/templates/add_instance_network_block.html:41
|
||||
#: instances/templates/create_instance_w2.html:275
|
||||
#: instances/templates/create_instance_w2.html:304
|
||||
#: instances/templates/create_instance_w2.html:314
|
||||
#: instances/templates/create_instance_w2.html:282
|
||||
#: instances/templates/create_instance_w2.html:311
|
||||
#: instances/templates/create_instance_w2.html:321
|
||||
#: instances/templates/instance.html:55
|
||||
#: instances/templates/instances/settings_tab.html:186
|
||||
#: instances/templates/instances/settings_tab.html:342
|
||||
|
|
@ -2451,7 +2471,7 @@ msgstr "Bağlan"
|
|||
#: instances/templates/allinstances.html:32
|
||||
#: instances/templates/allinstances.html:88
|
||||
#: 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/move_disk.html:13
|
||||
#: 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"
|
||||
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"
|
||||
msgstr "Diskler kopyalanıyor"
|
||||
|
||||
|
|
@ -2704,7 +2724,7 @@ msgid "libvirt is still copying the disks"
|
|||
msgstr "libvirt diskleri hâlâ kopyalıyor"
|
||||
|
||||
#: 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"
|
||||
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"
|
||||
|
||||
#: instances/jobs.py:712
|
||||
msgid "Invalid firmware"
|
||||
msgstr "Geçersiz donanım yazılımı"
|
||||
#, python-format
|
||||
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"
|
||||
msgstr "cloud-init tohumu yazılıyor"
|
||||
|
||||
#: instances/jobs.py:777
|
||||
#: instances/jobs.py:772
|
||||
msgid "Defining the VM"
|
||||
msgstr "Sanal makine tanımlanıyor"
|
||||
|
||||
#: instances/jobs.py:833
|
||||
#: instances/jobs.py:829
|
||||
#, python-format
|
||||
msgid "The instance is already on %(compute)s"
|
||||
msgstr "Sanal makine zaten %(compute)s üzerinde"
|
||||
|
||||
#: instances/jobs.py:835
|
||||
#: instances/jobs.py:831
|
||||
#, python-format
|
||||
msgid "%(compute)s is not reachable"
|
||||
msgstr "%(compute)s sunucusuna ulaşılamıyor"
|
||||
|
||||
#: instances/jobs.py:837
|
||||
#: instances/jobs.py:833
|
||||
#, python-format
|
||||
msgid "%(compute)s is the VM's own host under another name"
|
||||
msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı"
|
||||
|
||||
#: instances/jobs.py:855
|
||||
#: instances/jobs.py:851
|
||||
msgid "Migrate VM"
|
||||
msgstr "Sanal makineyi taşı"
|
||||
|
||||
#: instances/jobs.py:899
|
||||
#: instances/jobs.py:895
|
||||
msgid "Migrating the VM"
|
||||
msgstr "Sanal makine taşınıyor"
|
||||
|
||||
#: instances/jobs.py:931
|
||||
#: instances/jobs.py:927
|
||||
msgid "libvirt is still migrating the VM"
|
||||
msgstr "libvirt sanal makineyi hâlâ taşıyor"
|
||||
|
||||
#: instances/jobs.py:972
|
||||
#: instances/jobs.py:968
|
||||
msgid "Move disk"
|
||||
msgstr "Diski taşı"
|
||||
|
||||
#: instances/jobs.py:1003
|
||||
#: instances/jobs.py:999
|
||||
msgid "Copying the disk"
|
||||
msgstr "Disk kopyalanıyor"
|
||||
|
||||
|
|
@ -2832,14 +2857,14 @@ msgstr "oluşturuldu"
|
|||
msgid "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/settings_tab.html:25
|
||||
#: instances/templates/instances/stats_tab.html:49
|
||||
msgid "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
|
||||
msgid "CD-ROM"
|
||||
msgstr "CD-ROM"
|
||||
|
|
@ -2872,27 +2897,27 @@ msgstr "Güvenli olmayan"
|
|||
msgid "No Virtual Machine name has been entered"
|
||||
msgstr "Sanal makine adı girilmedi"
|
||||
|
||||
#: instances/models.py:406
|
||||
#: instances/models.py:408
|
||||
msgid "No VCPU has been entered"
|
||||
msgstr "VCPU belirtilmedi"
|
||||
|
||||
#: instances/models.py:412
|
||||
#: instances/models.py:414
|
||||
msgid "No RAM size has been entered"
|
||||
msgstr "RAM boyutu belirtilmedi"
|
||||
|
||||
#: instances/models.py:416
|
||||
#: instances/models.py:418
|
||||
msgid "No Network pool has been choosen"
|
||||
msgstr "Ağ havuzu seçilmedi"
|
||||
|
||||
#: instances/models.py:424
|
||||
#: instances/models.py:426
|
||||
msgid "Please select HDD cache mode"
|
||||
msgstr "Lütfen HDD ön bellek modunu seçin"
|
||||
|
||||
#: instances/models.py:436
|
||||
#: instances/models.py:438
|
||||
msgid "Please select a video driver"
|
||||
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"
|
||||
msgstr "Parolasız konsol erişimi yapabilir"
|
||||
|
||||
|
|
@ -2986,12 +3011,12 @@ msgid "Add disk form"
|
|||
msgstr "Disk ekleme formu"
|
||||
|
||||
#: instances/templates/add_instance_volume.html:19
|
||||
#: instances/templates/create_instance_w2.html:110
|
||||
#: instances/templates/create_instance_w2.html:117
|
||||
msgid "Disk source"
|
||||
msgstr "Disk kaynağı"
|
||||
|
||||
#: 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"
|
||||
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:70
|
||||
#: instances/templates/create_instance_w2.html:127
|
||||
#: instances/templates/create_instance_w2.html:155
|
||||
#: instances/templates/create_instance_w2.html:134
|
||||
#: instances/templates/create_instance_w2.html:162
|
||||
#: instances/templates/instances/move_disk.html:16
|
||||
#: instances/templates/instances/settings_tab.html:229
|
||||
msgid "Storage pool"
|
||||
|
|
@ -3033,8 +3058,8 @@ msgid "Format"
|
|||
msgstr "Biçem"
|
||||
|
||||
#: instances/templates/add_instance_volume.html:61
|
||||
#: instances/templates/create_instance_w2.html:144
|
||||
#: instances/templates/create_instance_w2.html:210 storages/forms.py:95
|
||||
#: instances/templates/create_instance_w2.html:151
|
||||
#: instances/templates/create_instance_w2.html:217 storages/forms.py:95
|
||||
#: storages/forms.py:115 storages/templates/storage.html:198
|
||||
msgid "Preallocate metadata (qcow2)"
|
||||
msgstr "Meta veriyi önceden ayır (qcow2)"
|
||||
|
|
@ -3052,7 +3077,7 @@ msgid "Choose a pool first"
|
|||
msgstr "Önce bir havuz seçin"
|
||||
|
||||
#: 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:87
|
||||
msgid "Bus"
|
||||
|
|
@ -3335,43 +3360,51 @@ msgstr "Hazır boyutları yönet"
|
|||
msgid "Firmware"
|
||||
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
|
||||
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"
|
||||
msgstr "CPU modu"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:86
|
||||
#: instances/templates/create_instance_w2.html:93
|
||||
#: storages/templates/create_stg_block.html:118
|
||||
msgid "Choose…"
|
||||
msgstr "Seçin…"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:89
|
||||
#: instances/templates/create_instance_w2.html:96
|
||||
msgid "Custom CPU Models"
|
||||
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)"
|
||||
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"
|
||||
msgstr "Mevcut birimler"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:157
|
||||
#: instances/templates/create_instance_w2.html:189
|
||||
#: instances/templates/create_instance_w2.html:164
|
||||
#: instances/templates/create_instance_w2.html:196
|
||||
msgid "Select pool"
|
||||
msgstr "Havuz seçin"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:162
|
||||
#: instances/templates/create_instance_w2.html:163
|
||||
#: instances/templates/create_instance_w2.html:169
|
||||
#: instances/templates/create_instance_w2.html:170
|
||||
#: storages/templates/storage.html:116 storages/templates/storages.html:47
|
||||
msgid "Volumes"
|
||||
msgstr "Birimler"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:167
|
||||
#: instances/templates/create_instance_w2.html:174
|
||||
msgid "Chosen volumes"
|
||||
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
|
||||
#: interfaces/templates/create_iface_block.html:78
|
||||
#: interfaces/templates/interface.html:113 networks/templates/networks.html:39
|
||||
|
|
@ -3380,91 +3413,91 @@ msgstr "Seçilen birimler"
|
|||
msgid "Device"
|
||||
msgstr "Aygıt"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:178
|
||||
#: instances/templates/create_instance_w2.html:236
|
||||
#: instances/templates/create_instance_w2.html:185
|
||||
#: instances/templates/create_instance_w2.html:243
|
||||
#: instances/templates/instances/settings_tab.html:678
|
||||
#: networks/templates/network.html:303
|
||||
msgid "Remove"
|
||||
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"
|
||||
msgstr "Şablondan klonla"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:187
|
||||
#: instances/templates/create_instance_w2.html:194
|
||||
msgid "Template pool"
|
||||
msgstr "Şablon havuzu"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:194
|
||||
#: instances/templates/create_instance_w2.html:201
|
||||
msgid "Template volume"
|
||||
msgstr "Şablon birimi"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:199
|
||||
#: instances/templates/create_instance_w2.html:206
|
||||
msgid "Copy into pool"
|
||||
msgstr "Havuza kopyala"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:221
|
||||
#: instances/templates/create_instance_w2.html:228
|
||||
msgid "Add networks"
|
||||
msgstr "Ağ ekle"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:228
|
||||
#: instances/templates/create_instance_w2.html:235
|
||||
msgid "Network interfaces"
|
||||
msgstr "Ağ arabirimleri"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:235
|
||||
#: instances/templates/create_instance_w2.html:242
|
||||
msgid "MAC address"
|
||||
msgstr "MAC adresi"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:255
|
||||
#: instances/templates/create_instance_w2.html:262
|
||||
msgid "Disk cache mode"
|
||||
msgstr "Disk önbellek modu"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:264
|
||||
#: instances/templates/create_instance_w2.html:271
|
||||
msgid "NIC type"
|
||||
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
|
||||
msgid "Network filter"
|
||||
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"
|
||||
msgstr "Görüntü"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:293
|
||||
#: instances/templates/create_instance_w2.html:300
|
||||
msgid "Console access"
|
||||
msgstr "Konsol erişimi"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:312
|
||||
#: instances/templates/create_instance_w2.html:319
|
||||
msgid "Input device"
|
||||
msgstr "Girdi aygıtı"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:324
|
||||
#: instances/templates/create_instance_w2.html:331
|
||||
msgid "Guest agent"
|
||||
msgstr "Misafir aracı"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:328
|
||||
#: instances/templates/create_instance_w2.html:335
|
||||
msgid "Cloud-init seed"
|
||||
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"
|
||||
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"
|
||||
msgstr "VirtIO aygıtları"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:356
|
||||
#: instances/templates/create_instance_w2.html:363
|
||||
msgid "Flavors"
|
||||
msgstr "Hazır boyutlar"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:375
|
||||
#: instances/templates/create_instance_w2.html:382
|
||||
msgid "Add volume"
|
||||
msgstr "Birim ekle"
|
||||
|
||||
#: instances/templates/create_instance_w2.html:382
|
||||
#: instances/templates/create_instance_w2.html:389
|
||||
msgid "Add network"
|
||||
msgstr "Ağ ekle"
|
||||
|
||||
|
|
@ -5960,35 +5993,35 @@ msgstr "Bir havuzda zaten bulunan birimler"
|
|||
msgid "A copy of a template volume"
|
||||
msgstr "Bir şablon biriminin kopyası"
|
||||
|
||||
#: instances/views.py:2527
|
||||
#: instances/views.py:2518
|
||||
msgid "You haven't defined any storage pools"
|
||||
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"
|
||||
msgstr "Hiç bir ağ havuzu tanımlamadınız"
|
||||
|
||||
#: instances/views.py:2592
|
||||
#: instances/views.py:2583
|
||||
msgid "Flavor Created"
|
||||
msgstr "Flavor Oluşturuldu"
|
||||
|
||||
#: instances/views.py:2600
|
||||
#: instances/views.py:2591
|
||||
msgid "Create Flavor"
|
||||
msgstr "Kullanıcı Oluştur"
|
||||
|
||||
#: instances/views.py:2615
|
||||
#: instances/views.py:2606
|
||||
msgid "Flavor Updated"
|
||||
msgstr "Flavor Güncellendi"
|
||||
|
||||
#: instances/views.py:2621
|
||||
#: instances/views.py:2612
|
||||
msgid "Update Flavor"
|
||||
msgstr "Flavor Güncelle"
|
||||
|
||||
#: instances/views.py:2633
|
||||
#: instances/views.py:2624
|
||||
msgid "Flavor Deleted"
|
||||
msgstr "Flavor Silindi"
|
||||
|
||||
#: instances/views.py:2639
|
||||
#: instances/views.py:2630
|
||||
msgid "Only the size template goes; VMs made from it keep their size."
|
||||
msgstr ""
|
||||
"Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler "
|
||||
|
|
|
|||
|
|
@ -1189,48 +1189,47 @@ class wvmConnect(object):
|
|||
# self.wvm.close()
|
||||
pass
|
||||
|
||||
def find_uefi_path_for_arch(self, arch, machine):
|
||||
"""
|
||||
Search the loader paths for one that matches the passed arch
|
||||
"""
|
||||
if not self.arch_can_uefi(arch):
|
||||
return
|
||||
def get_firmwares(self, arch, machine):
|
||||
"""The firmware a new VM can have: BIOS on x86, UEFI without Secure Boot
|
||||
when libvirt picks the firmware itself, and on x86_64 UEFI Secure Boot
|
||||
with Microsoft's keys enrolled, as Windows 11 needs."""
|
||||
|
||||
loaders = self.get_os_loaders(arch, machine)
|
||||
patterns = util.UEFI_ARCH_PATTERNS.get(arch)
|
||||
for pattern in patterns:
|
||||
for path in loaders:
|
||||
if re.match(pattern, path):
|
||||
return path
|
||||
def firmwares(ctx):
|
||||
os_caps = "/domainCapabilities/os[@supported='yes']"
|
||||
features = f"{os_caps}/firmwareFeatures[@supported='yes']"
|
||||
if ctx.xpath(features): # libvirt 12.1 and later: what its firmware offers
|
||||
secure_boot = ctx.xpath(f"{features}/enum[@name='secureBoot']/value/text()")
|
||||
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 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
|
||||
return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=firmwares)
|
||||
|
||||
for arch, patterns in util.UEFI_ARCH_PATTERNS.items():
|
||||
for pattern in patterns:
|
||||
if re.match(pattern, path):
|
||||
return "UEFI %(arch)s: %(path)s" % {"arch": arch, "path": path}
|
||||
def get_tpm_model(self, arch, machine):
|
||||
"""The model of an emulated TPM 2.0 for a new VM; None when the host has
|
||||
none (no swtpm)."""
|
||||
|
||||
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 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")
|
||||
return util.get_xml_path(self.get_dom_cap_xml(arch, machine), func=tpm_model)
|
||||
|
||||
def is_supports_virtio(self, arch, machine):
|
||||
if not self.is_qemu():
|
||||
|
|
|
|||
|
|
@ -203,10 +203,12 @@ class wvmCreate(wvmConnect):
|
|||
mac=None,
|
||||
qemu_ga=True,
|
||||
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)
|
||||
dom_caps = self.get_dom_capabilities(arch, machine)
|
||||
|
|
@ -221,33 +223,30 @@ class wvmCreate(wvmConnect):
|
|||
<memory unit='KiB'>{memory}</memory>
|
||||
<vcpu>{vcpu}</vcpu>"""
|
||||
|
||||
secure_boot = firmware == "UEFI Secure Boot"
|
||||
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>"""
|
||||
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'/>
|
||||
<boot dev='cdrom'/>
|
||||
<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>"""
|
||||
|
||||
if caps["features"]:
|
||||
xml += """<features>"""
|
||||
for feat in [x for x in ("acpi", "apic", "pae",) if x in caps["features"]]:
|
||||
xml += f"""<{feat}/>"""
|
||||
if firmware.get("secure", "no") == "yes":
|
||||
if secure_boot:
|
||||
xml += """<smm state="on"/>"""
|
||||
xml += """</features>"""
|
||||
|
||||
|
|
@ -382,6 +381,8 @@ class wvmCreate(wvmConnect):
|
|||
xml += f""" <video>
|
||||
<model type='{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
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
# 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):
|
||||
if vol_bus == "ide":
|
||||
return "hd"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue