From e35342bff1dc064c73cffdb79f3b8d34ec2f9763 Mon Sep 17 00:00:00 2001 From: catborise Date: Sun, 1 Nov 2026 16:38:14 +0300 Subject: [PATCH] feat(instances): a VM's TPM 2.0 is switched on and off on its Options tab A VM made without a TPM, a Windows 10 to be upgraded to Windows 11, had no way to get one but the XML editor. The Options tab has a TPM 2.0 switch, as the RNG one: it adds an emulated TPM of the host's model to the definition for the next start, or removes it. Its state stays on the host until the VM is undefined, so a TPM added again has it back. A TPM the switch did not make (passthrough, 1.2, encrypted, with its own state path) is left to the XML editor, and a host without swtpm says so. --- instances/models.py | 4 + .../templates/instances/settings_tab.html | 20 ++ instances/tests/test_live_xml_effects.py | 5 + instances/tests/test_pending_edits.py | 19 +- instances/urls.py | 1 + instances/views.py | 15 + locale/tr/LC_MESSAGES/django.mo | Bin 156422 -> 157093 bytes locale/tr/LC_MESSAGES/django.po | 284 ++++++++++-------- vrtManager/instance.py | 31 ++ vrtManager/tests/test_device_edits.py | 39 +++ 10 files changed, 288 insertions(+), 130 deletions(-) diff --git a/instances/models.py b/instances/models.py index 64fd6c61..aaae924e 100644 --- a/instances/models.py +++ b/instances/models.py @@ -289,6 +289,10 @@ class Instance(models.Model): def config_rng(self) -> str | None: return self.proxy.get_rng() + @cached_property + def config_tpm(self) -> str | None: + return self.proxy.get_tpm() + @cached_property def config_on_crash(self) -> str: return self.proxy.get_on_crash() diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index 0a5bb40b..2e8cf4af 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -936,6 +936,26 @@ +
{% csrf_token %} +
+
+ +

{% trans "An emulated TPM 2.0, as Windows 11 needs with UEFI Secure Boot. Its state stays on the host until the VM is destroyed: a TPM added again has it back. From the next start." %}

+
+
+
+ + +
+ {% if instance.config_tpm == "custom" %} +

{% trans "The VM has its own TPM: change it in the XML editor." %}

+ {% elif not instance.config_tpm and not tpm_support %} +

{% trans "This host offers no TPM 2.0: install swtpm on it." %}

+ {% endif %} +
+
+
{% csrf_token %}
diff --git a/instances/tests/test_live_xml_effects.py b/instances/tests/test_live_xml_effects.py index 8d382ff4..a777d023 100644 --- a/instances/tests/test_live_xml_effects.py +++ b/instances/tests/test_live_xml_effects.py @@ -130,6 +130,11 @@ EDITS = { lambda t: not t.xpath("devices/rng"), ["devices/rng"], True, False, ), + "set_tpm": ( + lambda vm: vm.set_tpm(True), + lambda t: first(t, "devices/tpm/backend/@version") == "2.0", + ["devices/tpm"], True, False, + ), "set_on_crash": ( lambda vm: vm.set_on_crash("restart"), lambda t: t.findtext("on_crash") == "restart", diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py index 7d27cbfa..5e19f30e 100644 --- a/instances/tests/test_pending_edits.py +++ b/instances/tests/test_pending_edits.py @@ -29,9 +29,9 @@ class PendingEditsTestCase(TestCase): compute = Compute.objects.create(name="pe", hostname="127.0.0.1:1", login="root", password="", type=1) self.instance = Instance.objects.create(compute=compute, name="pe-vm", uuid="aaaaaaaa-1111-2222-3333-444444444444") - def page(self, live=(), config=(), nics=((), ()), status=1, **getters): + def page(self, live=(), config=(), nics=((), ()), status=1, tpm_model="tpm-crb", **getters): """live and config: disks; nics and getters (get_vcpu=(2, 4)): values of - the live and the persistent definition.""" + the live and the persistent definition; tpm_model: the host's TPM.""" with patch("instances.models.wvmInstance") as wvm, patch.object( Compute, "status", new_callable=PropertyMock, return_value=True ): @@ -54,6 +54,7 @@ class PendingEditsTestCase(TestCase): proxy.get_console_type.return_value = "vnc" proxy.get_video_models.return_value = ["vga", "virtio"] proxy.get_cache_modes.return_value = {"default": "Default", "none": "Disabled"} + proxy.get_tpm_model.return_value = tpm_model response = self.client.get(reverse("instances:instance", args=[self.instance.id])) self.assertEqual(response.status_code, 200) return html.fromstring(response.content) @@ -220,6 +221,20 @@ class PendingEditsTestCase(TestCase): self.assertEqual(doc.xpath("//input[@name='rng']/@checked"), ["checked"]) self.assertEqual(doc.xpath("//select[@name='on_crash']/option[@selected]/@value"), ["restart"]) + def test_the_tpm_switch_is_off_where_it_cannot_change(self): + tpm = "//input[@name='tpm']" + doc = self.page(get_tpm=("emulator", "emulator")) + self.assertEqual((doc.xpath(f"{tpm}/@checked"), doc.xpath(f"{tpm}/@disabled")), (["checked"], [])) + doc = self.page(get_tpm=("custom", "custom")) + self.assertTrue(doc.xpath(f"{tpm}/@disabled")) + self.assertIn("change it in the XML editor", doc.xpath("string(//p[@id='tpm-state'])")) + # no swtpm: none can be added, one the VM has can still go + doc = self.page(get_tpm=(None, None), tpm_model=None) + self.assertTrue(doc.xpath(f"{tpm}/@disabled")) + self.assertIn("install swtpm", doc.xpath("string(//p[@id='tpm-state'])")) + doc = self.page(get_tpm=("emulator", "emulator"), tpm_model=None) + self.assertEqual(doc.xpath(f"{tpm}/@disabled"), []) + def test_the_users_tab_shows_each_owner_and_offers_only_new_owners(self): from accounts.models import UserInstance diff --git a/instances/urls.py b/instances/urls.py index 092c44d8..c12ba49d 100755 --- a/instances/urls.py +++ b/instances/urls.py @@ -56,6 +56,7 @@ urlpatterns = [ path("/set_guest_agent/", views.set_guest_agent, name="set_guest_agent"), path("/set_video_model/", views.set_video_model, name="set_video_model"), path("/set_rng/", views.set_rng, name="set_rng"), + path("/set_tpm/", views.set_tpm, name="set_tpm"), path("/set_on_crash/", views.set_on_crash, name="set_on_crash"), path("/change_network/", views.change_network, name="change_network"), path("/add_network/", views.add_network, name="add_network"), diff --git a/instances/views.py b/instances/views.py index 4926a993..50fbaceb 100755 --- a/instances/views.py +++ b/instances/views.py @@ -308,6 +308,7 @@ def _instance_page(request, pk, instance, compute): memory_host = instance.proxy.get_max_memory() bus_host = instance.proxy.get_disk_bus_types(instance.arch, instance.machine) cdrom_buses = cdrom_bus_choices(instance) + tpm_support = bool(instance.proxy.get_tpm_model(instance.arch, instance.machine)) networks_host = sorted(instance.proxy.get_networks()) interfaces_host = sorted(instance.proxy.get_ifaces()) nwfilters_host = instance.proxy.get_nwfilters() @@ -1883,6 +1884,20 @@ def set_rng(request, pk): return _back(request, pk, "options") +@require_POST +@superuser_only +@serialize_instance_mutation +def set_tpm(request, pk): + instance = get_instance(request.user, pk) + enabled = bool(request.POST.get("tpm")) + try: + with audit_vm(request, instance, "add TPM" if enabled else "remove TPM"): + instance.proxy.set_tpm(enabled) + except util.OperationError as err: + messages.error(request, err) + return _back(request, pk, "options") + + @require_POST @superuser_only @serialize_instance_mutation diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index 36f2a5209cab6b2682eee535e5b3e5ac9c1e1067..a4fbf3209966559018aad6e4a5ff349572deed76 100644 GIT binary patch delta 37777 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 delta 37349 zcmZYI1$Y(Lqp#sfa0?KEO9BZY5S&18OK^90cc=JZ#ofKd-Q69EyOyHGt+>PeetRv> z<(@mwGv{4yv)1gLkp9o&6S0OJiRIq(jXKrgIvU+^QepKxj*~i;IZje6jB2kYs(d$0hf7iAj$;bPah*2=3X|Y7*^}T@z$C<5 zqbj(V2^U}*JY{``!NlWCah%MU6Ek8XRQZvp`kPP_xMK5tnQmO-1u+TzJ7oyOBcU-S z$Bw9xkHGl2*ye9WRXm7l@GLgQIMd7uv`4iw7B#Thm=F)3+PP@s{~|MY-eZ0GcM?u_ zoF>>2li*n_j}L8n*bK+XKzteo;T}wc_fRwbfJHFvOw(}_%tm|!s@^tKz1ygji8jk| z5@2?8HR56fl4B**%-djU9A;gFiHRRTwuy5U1MwDa#3-}P0Jfq!{D8U9e~ww98d#6` z7A%0ia~&r=R-DWF>zQ^ZK^;%Vq__^1{uh?Phu98t%yXPboQwVOE~doBe>hHF?1#B< zGgiUJxEBljY3hB!?8J-Ccbu%)Wxi`F{z(F><6J{u?6$y+bQqQ*egI2j?1iR$HPpby zV1B%e4Kd*&6Yq>{4`(H+T(QND(+o$WR_G;G!ldpJW<{VChT=^8f`?HJ?_X+`@+D5cO8arYN9D^BgDF)&>)QsO@T8zDh{ezh?02`sJV=|C{Dx60R z;618?_-oB0NP%&Q2V)k@g+bT^qv0^rz{X%yT!8U#sdY1I00&VMxsQ4?zFf=tE5U1> zS-LnFpLil{g@LFBN24~=1dM}=Q03O4R$w0{z?-NS%uCe3Lf4yibD-k+Q0)~#^&7dK z_4hnG5&}r5gE_E|jjzH?#7|*G{D`VpYJ>T3se;<=-BAPUiyG)~)Lxj4>Ug2eUygdz zn^7ye-6f!rAHf`W1ruVdji%w0sHIGYs+b*pFbp;D2-NYch}v9jQJ;F

*C9>XEKO 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 diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index 56e6793c..75afe0a4 100644 --- a/locale/tr/LC_MESSAGES/django.po +++ b/locale/tr/LC_MESSAGES/django.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-11-01 13:18+0000\n" +"POT-Creation-Date: 2026-11-01 13:33+0000\n" "PO-Revision-Date: 2020-11-04 12:00+0000\n" "Last-Translator: catborise , 2020\n" "Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/" @@ -712,8 +712,8 @@ msgstr "Üyeler" #: instances/templates/instances/settings_tab.html:647 #: instances/templates/instances/settings_tab.html:873 #: instances/templates/instances/settings_tab.html:915 -#: instances/templates/instances/settings_tab.html:955 -#: instances/templates/instances/settings_tab.html:985 +#: instances/templates/instances/settings_tab.html:975 +#: instances/templates/instances/settings_tab.html:1005 #: networks/templates/add_network_qos.html:36 #: networks/templates/network.html:335 networks/templates/network.html:375 #: networks/templates/network.html:402 networks/templates/network.html:441 @@ -2132,14 +2132,14 @@ msgstr "vCPU" #: console/templates/console-vnc.html:13 #: console/templates/console-xterm.html:16 #: instances/templates/allinstances_index_grouped.html:36 -#: instances/templates/instances/settings_tab.html:972 +#: instances/templates/instances/settings_tab.html:992 #: networks/templates/network.html:260 msgid "Connected" msgstr "Bağlı" #: computes/templates/computes/list.html:73 computes/templates/overview.html:21 #: instances/templates/allinstances_index_grouped.html:38 -#: instances/templates/instances/settings_tab.html:973 +#: instances/templates/instances/settings_tab.html:993 msgid "Not connected" msgstr "Bağlı değil" @@ -2731,7 +2731,7 @@ msgstr "libvirt diskleri hâlâ kopyalıyor" msgid "Create VM" msgstr "Sanal makine oluştur" -#: instances/jobs.py:685 instances/views.py:2427 +#: instances/jobs.py:685 instances/views.py:2442 msgid "A virtual machine with this name already exists" msgstr "Bu isimde bir sanal makine mevcut" @@ -2876,51 +2876,51 @@ msgstr "CD-ROM" msgid "NIC" msgstr "NIC" -#: instances/models.py:386 +#: instances/models.py:390 msgid "Live" msgstr "Canlı" -#: instances/models.py:387 +#: instances/models.py:391 msgid "Offline" msgstr "Çevrimdışı" -#: instances/models.py:388 +#: instances/models.py:392 msgid "Auto Converge" msgstr "Otomatik yavaşlat" -#: instances/models.py:389 +#: instances/models.py:393 msgid "Compress" msgstr "Sıkıştır" -#: instances/models.py:390 +#: instances/models.py:394 msgid "Unsafe" msgstr "Güvenli olmayan" -#: instances/models.py:402 +#: instances/models.py:406 msgid "No Virtual Machine name has been entered" msgstr "Sanal makine adı girilmedi" -#: instances/models.py:408 +#: instances/models.py:412 msgid "No VCPU has been entered" msgstr "VCPU belirtilmedi" -#: instances/models.py:414 +#: instances/models.py:418 msgid "No RAM size has been entered" msgstr "RAM boyutu belirtilmedi" -#: instances/models.py:418 +#: instances/models.py:422 msgid "No Network pool has been choosen" msgstr "Ağ havuzu seçilmedi" -#: instances/models.py:426 +#: instances/models.py:430 msgid "Please select HDD cache mode" msgstr "Lütfen HDD ön bellek modunu seçin" -#: instances/models.py:438 +#: instances/models.py:442 msgid "Please select a video driver" msgstr "Lütfen bir video sürücüsü seçin" -#: instances/models.py:475 +#: instances/models.py:479 msgid "Can access console without password" msgstr "Parolasız konsol erişimi yapabilir" @@ -2980,13 +2980,18 @@ msgstr "Ortam yok" msgid "" "Windows sees USB, SATA and IDE drives without a driver; SCSI ones need the " "virtio driver." -msgstr "Windows USB, SATA ve IDE sürücüleri sürücü yazılımı olmadan görür; SCSI olanlar için virtio sürücüsü gerekir." +msgstr "" +"Windows USB, SATA ve IDE sürücüleri sürücü yazılımı olmadan görür; SCSI " +"olanlar için virtio sürücüsü gerekir." #: instances/templates/add_instance_cdrom.html:33 msgid "" "A running VM takes USB and SCSI drives. A USB drive needs an ISO image, is " "detached instead of ejected, and needs a QEMU with usb-bot (not RHEL 9's)." -msgstr "Çalışan bir sanal makineye USB ve SCSI sürücüler eklenebilir. USB sürücü bir ISO imajı ister, çıkarılmaz, ayrılır ve usb-bot destekli bir QEMU gerektirir (RHEL 9'unki desteklemez)." +msgstr "" +"Çalışan bir sanal makineye USB ve SCSI sürücüler eklenebilir. USB sürücü bir " +"ISO imajı ister, çıkarılmaz, ayrılır ve usb-bot destekli bir QEMU gerektirir " +"(RHEL 9'unki desteklemez)." #: instances/templates/add_instance_network_block.html:4 #: instances/templates/add_instance_network_block.html:10 @@ -3058,7 +3063,7 @@ msgid "Disk source" msgstr "Disk kaynağı" #: instances/templates/add_instance_volume.html:21 -#: instances/templates/create_instance_w2.html:131 instances/views.py:2446 +#: instances/templates/create_instance_w2.html:131 instances/views.py:2461 msgid "New disk" msgstr "Yeni disk" @@ -3396,6 +3401,7 @@ msgid "Firmware" msgstr "Üretici yazılımı" #: instances/templates/create_instance_w2.html:83 +#: instances/templates/instances/settings_tab.html:949 msgid "TPM 2.0" msgstr "TPM 2.0" @@ -3420,7 +3426,7 @@ msgstr "Özel CPU modelleri" msgid "Size (GiB)" msgstr "Boyut (GiB)" -#: instances/templates/create_instance_w2.html:158 instances/views.py:2447 +#: instances/templates/create_instance_w2.html:158 instances/views.py:2462 msgid "Existing volumes" msgstr "Mevcut birimler" @@ -3455,7 +3461,7 @@ msgstr "Aygıt" msgid "Remove" msgstr "Kaldır" -#: instances/templates/create_instance_w2.html:191 instances/views.py:2448 +#: instances/templates/create_instance_w2.html:191 instances/views.py:2463 msgid "Clone from template" msgstr "Şablondan klonla" @@ -3790,7 +3796,7 @@ msgstr "" msgid "Start the VM to open its console." msgstr "Konsolunu açmak için sanal makineyi başlatın." -#: instances/templates/instances/access_tab.html:41 instances/views.py:1076 +#: instances/templates/instances/access_tab.html:41 instances/views.py:1077 msgid "Reset root password" msgstr "Root parolasını sıfırla" @@ -4402,12 +4408,12 @@ msgid "Shut the VM down to change this." msgstr "Bunu değiştirmek için sanal makineyi kapatın." #: instances/templates/instances/resize_tab.html:109 -#: instances/templates/instances/settings_tab.html:980 +#: instances/templates/instances/settings_tab.html:1000 msgid "Enabled" msgstr "Etkin" #: instances/templates/instances/resize_tab.html:110 -#: instances/templates/instances/settings_tab.html:981 +#: instances/templates/instances/settings_tab.html:1001 msgid "Disabled" msgstr "Devredışı" @@ -4575,7 +4581,10 @@ msgstr "Sırayı uygula" msgid "" "The VM is running: CD-ROM drives added now use the USB or SCSI bus. Shut it " "down to detach a SATA or IDE CD-ROM drive, or to detach or delete disks." -msgstr "Sanal makine çalışıyor: şimdi eklenen CD-ROM sürücüleri USB veya SCSI veri yolunu kullanır. SATA veya IDE bir CD-ROM sürücüsünü ayırmak ya da diskleri ayırmak veya silmek için sanal makineyi kapatın." +msgstr "" +"Sanal makine çalışıyor: şimdi eklenen CD-ROM sürücüleri USB veya SCSI veri " +"yolunu kullanır. SATA veya IDE bir CD-ROM sürücüsünü ayırmak ya da diskleri " +"ayırmak veya silmek için sanal makineyi kapatın." #: instances/templates/instances/settings_tab.html:154 msgid "Removable media" @@ -5204,10 +5213,29 @@ msgid "The VM has its own RNG device: change it in the XML editor." msgstr "Sanal makinenin kendi RNG aygıtı var: XML düzenleyicide değiştirin." #: instances/templates/instances/settings_tab.html:942 +msgid "TPM" +msgstr "TPM" + +#: instances/templates/instances/settings_tab.html:943 +msgid "" +"An emulated TPM 2.0, as Windows 11 needs with UEFI Secure Boot. Its state " +"stays on the host until the VM is destroyed: a TPM added again has it back. " +"From the next start." +msgstr "Windows 11'in UEFI Secure Boot ile birlikte istediği emüle edilmiş bir TPM 2.0. Durumu sanal makine yok edilene kadar sunucuda kalır: yeniden eklenen TPM onu geri alır. Bir sonraki başlatmadan itibaren." + +#: instances/templates/instances/settings_tab.html:952 +msgid "The VM has its own TPM: change it in the XML editor." +msgstr "Sanal makinenin kendi TPM'i var: XML düzenleyicide değiştirin." + +#: instances/templates/instances/settings_tab.html:954 +msgid "This host offers no TPM 2.0: install swtpm on it." +msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun." + +#: instances/templates/instances/settings_tab.html:962 msgid "When the guest crashes" msgstr "Konuk sistem çöktüğünde" -#: instances/templates/instances/settings_tab.html:943 +#: instances/templates/instances/settings_tab.html:963 msgid "" "For a crash the guest reports, not a hang. Stop keeps a VM that crashes " "again and again down for a look. From the next start." @@ -5216,39 +5244,39 @@ msgstr "" "tekrar tekrar çöken bir sanal makineyi incelemek için kapalı tutar. Sonraki " "başlatmadan itibaren." -#: instances/templates/instances/settings_tab.html:950 +#: instances/templates/instances/settings_tab.html:970 msgid "Restart" msgstr "Yeniden başlat" -#: instances/templates/instances/settings_tab.html:951 +#: instances/templates/instances/settings_tab.html:971 #: interfaces/templates/interface.html:16 networks/templates/network.html:16 #: storages/templates/storage.html:18 msgid "Stop" msgstr "Durdur" -#: instances/templates/instances/settings_tab.html:959 +#: instances/templates/instances/settings_tab.html:979 #, python-format msgid "%(action)s: change it in the XML editor." msgstr "%(action)s: XML düzenleyicide değiştirin." -#: instances/templates/instances/settings_tab.html:967 +#: instances/templates/instances/settings_tab.html:987 msgid "QEMU guest agent" msgstr "QEMU konuk ajanı" -#: instances/templates/instances/settings_tab.html:968 +#: instances/templates/instances/settings_tab.html:988 msgid "The channel to the agent in the guest: a running VM gets it at once." msgstr "" "Konuk sistemdeki ajana giden kanal: çalışan bir sanal makine onu hemen alır." -#: instances/templates/instances/settings_tab.html:969 +#: instances/templates/instances/settings_tab.html:989 msgid "Agent" msgstr "Ajan" -#: instances/templates/instances/settings_tab.html:970 +#: instances/templates/instances/settings_tab.html:990 msgid "VM not running" msgstr "Sanal makine çalışmıyor" -#: instances/templates/instances/settings_tab.html:971 +#: instances/templates/instances/settings_tab.html:991 msgid "No channel" msgstr "Kanal yok" @@ -5558,16 +5586,16 @@ msgstr "'%(name)s' sanal makinesinin sunucusuna şu anda erişilemiyor." msgid " Jobs on it can be followed under Jobs in the menu." msgstr " Üzerindeki işler menüdeki İşler bölümünden izlenebilir." -#: instances/views.py:334 +#: instances/views.py:335 msgid "There is no other host to migrate to." msgstr "Taşınacak başka bir sunucu yok." -#: instances/views.py:336 +#: instances/views.py:337 msgid "A VM migrates when it is running, paused or shut off." msgstr "" "Sanal makine çalışırken, duraklatılmışken ya da kapalıyken taşınabilir." -#: instances/views.py:338 +#: instances/views.py:339 msgid "" "A VM with snapshots cannot be migrated: its snapshots stay on this host. " "Delete them first." @@ -5575,7 +5603,7 @@ msgstr "" "Anlık görüntüsü olan bir sanal makine taşınamaz: anlık görüntüleri bu " "sunucuda kalır. Önce onları silin." -#: instances/views.py:340 +#: instances/views.py:341 msgid "" "A VM with host devices cannot be migrated: the destination may not have " "them. Remove them first." @@ -5583,11 +5611,11 @@ msgstr "" "Sunucu aygıtları olan bir sanal makine taşınamaz: hedefte bu aygıtlar " "olmayabilir. Önce onları kaldırın." -#: instances/views.py:606 +#: instances/views.py:607 msgid "The VM was moved meanwhile; try again." msgstr "Sanal makine bu arada taşındı; yeniden deneyin." -#: instances/views.py:637 +#: instances/views.py:638 msgid "" "The quota cannot be checked because a host of your virtual machines cannot " "be reached. Nothing was changed." @@ -5595,7 +5623,7 @@ msgstr "" "Sanal makinelerinizin bir sunucusuna erişilemediği için kota denetlenemiyor. " "Hiçbir şey değiştirilmedi." -#: instances/views.py:718 +#: instances/views.py:719 msgid "" "The running VM has no free PCI slot for another disk on this bus: choose the " "scsi bus if the VM has a SCSI controller, or shut the VM down and add the " @@ -5608,7 +5636,7 @@ msgstr "" "yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni bir " "birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:722 +#: instances/views.py:723 msgid "" "The running VM has no free PCI slot for the SCSI controller the disk needs: " "shut the VM down and add the disk then; Add PCIe slots on the Disks tab " @@ -5620,82 +5648,82 @@ msgstr "" "PCIe yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni " "bir birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:740 +#: instances/views.py:741 #, python-format msgid "Invalid disk options: %(options)s" msgstr "Geçersiz disk seçenekleri: %(options)s" -#: instances/views.py:750 instances/views.py:765 +#: instances/views.py:751 instances/views.py:766 msgid "Templates cannot be started." msgstr "Şablon makineler başlatılamaz." -#: instances/views.py:861 +#: instances/views.py:862 #, python-format msgid "Disk %(path)s was kept: a disk file that stays needs it" msgstr "%(path)s diski korundu: kalan bir disk dosyası ona ihtiyaç duyuyor" -#: instances/views.py:867 +#: instances/views.py:868 #, python-format msgid "The VM is removed, but disk %(path)s was not deleted: %(err)s" msgstr "Sanal makine kaldırıldı, ancak %(path)s diski silinmedi: %(err)s" -#: instances/views.py:876 +#: instances/views.py:877 #, python-format msgid "Disk %(path)s is used by another VM and was kept" msgstr "" "%(path)s diski başka bir sanal makine tarafından kullanılıyor ve korundu" -#: instances/views.py:998 +#: instances/views.py:999 #, python-format msgid "Set the gstfsd key of %(compute)s first (Computes, edit the host)" msgstr "" "Önce %(compute)s sunucusunun gstfsd anahtarını ayarlayın (Sunucular, " "sunucuyu düzenle)" -#: instances/views.py:1004 instances/views.py:1018 instances/views.py:1027 +#: instances/views.py:1005 instances/views.py:1019 instances/views.py:1028 #, python-format msgid "gstfsd error: %(err)s" msgstr "gstfsd hatası: %(err)s" -#: instances/views.py:1028 +#: instances/views.py:1029 msgid "the reply is not signed with the key" msgstr "yanıt anahtarla imzalanmamış" -#: instances/views.py:1036 +#: instances/views.py:1037 msgid "Shut the VM down, or start its QEMU guest agent, and try again" msgstr "" "Sanal makineyi kapatın ya da QEMU konuk ajanını başlatın ve yeniden deneyin" -#: instances/views.py:1049 +#: instances/views.py:1050 msgid "The change may still be made when the agent answers again." msgstr "Ajan yeniden yanıt verdiğinde değişiklik yine de yapılabilir." -#: instances/views.py:1080 +#: instances/views.py:1081 msgid "Enter a password" msgstr "Bir parola girin" -#: instances/views.py:1094 +#: instances/views.py:1095 msgid "Select an SSH public key" msgstr "Bir SSH açık anahtarı seçin" -#: instances/views.py:1116 +#: instances/views.py:1117 #, python-format msgid "Installed new SSH public key %(keyname)s" msgstr "Yeni %(keyname)s SSH açık anahtarı yüklendi" -#: instances/views.py:1136 +#: instances/views.py:1137 msgid "Enter a whole number of vCPUs" msgstr "vCPU sayısı için bir tam sayı girin" -#: instances/views.py:1140 +#: instances/views.py:1141 msgid "Shut off the VM to change its vCPUs" msgstr "vCPU'larını değiştirmek için sanal makineyi kapatın" -#: instances/views.py:1143 +#: instances/views.py:1144 msgid "The current vCPUs cannot be more than the maximum" msgstr "Geçerli vCPU sayısı en fazla değerden büyük olamaz" -#: instances/views.py:1151 +#: instances/views.py:1152 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!" @@ -5703,20 +5731,20 @@ msgstr "" "%(quota_msg)s kullanıcısı kotası doldu, '%(instance_name)s' in CPU su " "yeniden boyutlandırılamıyor!" -#: instances/views.py:1162 +#: instances/views.py:1163 #, python-format msgid "vCPUs set to %(cur)s (maximum %(max)s)" msgstr "vCPU sayısı %(cur)s olarak ayarlandı (en fazla %(max)s)" -#: instances/views.py:1181 +#: instances/views.py:1182 msgid "Enter a whole number for memory" msgstr "Bellek için bir tam sayı girin" -#: instances/views.py:1184 +#: instances/views.py:1185 msgid "The current memory cannot be more than the maximum" msgstr "Geçerli bellek en fazla değerden büyük olamaz" -#: instances/views.py:1189 +#: instances/views.py:1190 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize memory of " @@ -5725,25 +5753,25 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in belleği yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1198 +#: instances/views.py:1199 #, python-format msgid "Memory set to %(cur)s MiB (maximum %(max)s MiB)" msgstr "Bellek %(cur)s MiB olarak ayarlandı (en fazla %(max)s MiB)" -#: instances/views.py:1218 +#: instances/views.py:1219 msgid "Enter disk sizes as whole GiB" msgstr "Disk boyutlarını tam GiB olarak girin" -#: instances/views.py:1222 +#: instances/views.py:1223 #, python-format msgid "%(dev)s is %(size)s: a disk cannot shrink" msgstr "%(dev)s boyutu %(size)s: disk küçültülemez" -#: instances/views.py:1230 +#: instances/views.py:1231 msgid "No disks were selected or valid for resizing." msgstr "Boyutlandırma için seçilmiş ya da geçerli disk yok." -#: instances/views.py:1240 +#: instances/views.py:1241 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!" @@ -5751,61 +5779,61 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in diski yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1255 +#: instances/views.py:1256 #, python-format msgid "The storage pool of %(dev)s cannot resize its volumes" msgstr "%(dev)s diskinin depolama havuzu birimlerini boyutlandıramaz" -#: instances/views.py:1257 +#: instances/views.py:1258 #, python-format msgid "Disk is resized: %(dev)s" msgstr "Disk boyutu değiştirildi: %(dev)s" -#: instances/views.py:1351 +#: instances/views.py:1352 msgid "The VM's machine has no PCIe slots to add" msgstr "Sanal makinenin makine tipinde eklenecek PCIe yuvası yok" -#: instances/views.py:1356 +#: instances/views.py:1357 #, python-format msgid "%(count)s PCIe slots added" msgstr "%(count)s PCIe yuvası eklendi" -#: instances/views.py:1358 +#: instances/views.py:1359 #, python-format msgid "%(count)s PCIe slots added: the VM gets them at its next start" msgstr "" "%(count)s PCIe yuvası eklendi: sanal makine bunları sonraki başlatmasında " "alır" -#: instances/views.py:1489 +#: instances/views.py:1490 msgid "Volume changes are applied. But it will be activated after shutdown" msgstr "" "Birim değişiklikleri uygulandı. Ama yeniden başlatıldıktan sonra uygulanacak" -#: instances/views.py:1494 +#: instances/views.py:1495 msgid "Volume is changed successfully." msgstr "Birim başarıyla değiştirildi." -#: instances/views.py:1512 +#: instances/views.py:1513 #, python-format msgid "Disk %(dev)s is not a storage volume of this instance" msgstr "%(dev)s diski bu sanal makinenin bir depolama birimi değil" -#: instances/views.py:1519 +#: instances/views.py:1520 #, python-format msgid "Disk %(dev)s has a pending change; delete it once the VM is shut down" msgstr "" "%(dev)s diskinin bekleyen bir değişikliği var; sanal makine kapatıldığında " "silin" -#: instances/views.py:1534 +#: instances/views.py:1535 #, python-format msgid "Volume %(vol)s is used by another VM; detach it instead" msgstr "" "%(vol)s birimi başka bir sanal makine tarafından kullanılıyor; onun yerine " "ayırın" -#: instances/views.py:1546 +#: instances/views.py:1547 #, python-format msgid "" "The guest has not released disk %(dev)s yet; volume %(vol)s was kept. Delete " @@ -5814,52 +5842,52 @@ msgstr "" "Konuk sistem %(dev)s diskini henüz bırakmadı; %(vol)s birimi korundu. Sanal " "makine kapatıldığında depolama havuzundan silin." -#: instances/views.py:1557 +#: instances/views.py:1558 #, python-format msgid "Disk %(dev)s was detached, but its volume was kept: %(err)s" msgstr "%(dev)s diski ayrıldı, ancak birimi korundu: %(err)s" -#: instances/views.py:1583 +#: instances/views.py:1584 #, python-format msgid "This VM cannot take a CD-ROM on the %(bus)s bus now" msgstr "Bu sanal makineye şu anda %(bus)s veri yolunda CD-ROM eklenemez" -#: instances/views.py:1586 +#: instances/views.py:1587 msgid "A USB CD-ROM needs an ISO image: libvirt has no empty USB drive" msgstr "USB CD-ROM bir ISO imajı ister: libvirt boş USB sürücü desteklemiyor" -#: instances/views.py:1679 +#: instances/views.py:1680 #, python-format msgid "Snapshot was not deleted: %(err)s" msgstr "Anlık görüntü silinmedi: %(err)s" -#: instances/views.py:1794 +#: instances/views.py:1795 msgid "Check at least one device to boot from." msgstr "Önyükleme için en az bir aygıt işaretleyin." -#: instances/views.py:1808 +#: instances/views.py:1809 msgid "Boot order changed. It applies at the next start." msgstr "Ön yükleme sırası değiştirildi. Sonraki başlatmada uygulanır." -#: instances/views.py:1810 +#: instances/views.py:1811 msgid "Boot order changed successfully." msgstr "Ön yükleme sırası başarıyla değiştirildi." -#: instances/views.py:1830 +#: instances/views.py:1831 msgid "The XML is empty" msgstr "XML boş" -#: instances/views.py:1834 instances/views.py:2437 +#: instances/views.py:1835 instances/views.py:2452 #, python-format msgid "The XML is not well-formed: %(error)s" msgstr "XML düzgün biçimlendirilmemiş: %(error)s" -#: instances/views.py:1836 +#: instances/views.py:1837 #, python-format msgid "The XML must keep the VM's UUID %(uuid)s." msgstr "XML, sanal makinenin %(uuid)s UUID'sini korumalıdır." -#: instances/views.py:1840 +#: instances/views.py:1841 msgid "" "The definition has changed since the editor was opened. Reload the page to " "edit the current one." @@ -5867,35 +5895,35 @@ msgstr "" "Düzenleyici açıldığından beri tanım değişti. Geçerli tanımı düzenlemek için " "sayfayı yeniden yükleyin." -#: instances/views.py:1852 +#: instances/views.py:1853 msgid "The XML definition was saved." msgstr "XML tanımı kaydedildi." -#: instances/views.py:1854 +#: instances/views.py:1855 msgid "The XML definition was saved; the VM uses it from its next start." msgstr "" "XML tanımı kaydedildi; sanal makine onu sonraki başlatmasından itibaren " "kullanır." -#: instances/views.py:1921 +#: instances/views.py:1936 msgid "Select one network interface to change" msgstr "Değiştirilecek bir ağ arabirimi seçin" -#: instances/views.py:1959 +#: instances/views.py:1974 msgid "Network Device Config is changed. Please shutdown instance to activate." msgstr "" "Ağ aygıtı yapılandırması değiştirildi. Aktifleştirmek için lütfen sanal " "makineyi kapatın." -#: instances/views.py:1961 +#: instances/views.py:1976 msgid "Network device changed" msgstr "Ağ aygıtı değiştirildi" -#: instances/views.py:1973 +#: instances/views.py:1988 msgid "No network selected" msgstr "Ağ seçilmedi" -#: instances/views.py:1993 +#: instances/views.py:2008 msgid "" "The running VM has no free PCI slot for another network device: shut it down " "and add the device then; Add PCIe slots on the Disks tab gives it free ones " @@ -5905,21 +5933,21 @@ msgstr "" "aygıtı sonra ekleyin; Diskler sekmesindeki PCIe yuvaları ekle, sonraki " "başlatmadan itibaren ona boş yuvalar verir." -#: instances/views.py:2030 instances/views.py:2036 +#: instances/views.py:2045 instances/views.py:2051 msgid "The VM has it" msgstr "Sanal makinede var" -#: instances/views.py:2067 +#: instances/views.py:2082 #, python-format msgid "The host has no USB device %(device)s" msgstr "Sunucuda %(device)s USB aygıtı yok" -#: instances/views.py:2088 +#: instances/views.py:2103 #, python-format msgid "The host has no PCI device %(device)s" msgstr "Sunucuda %(device)s PCI aygıtı yok" -#: instances/views.py:2106 +#: instances/views.py:2121 #, python-format msgid "" "Removal of PCI device %(address)s requested: the guest has not let it go " @@ -5929,20 +5957,20 @@ msgstr "" "bırakmadı; bırakana ya da sanal makine durana kadar çalışan sanal makinede " "kalır." -#: instances/views.py:2151 instances/views.py:2181 +#: instances/views.py:2166 instances/views.py:2196 msgid "Select a network interface and a QoS direction" msgstr "Bir ağ arabirimi ve bir QoS yönü seçin" -#: instances/views.py:2154 +#: instances/views.py:2169 msgid "QoS rates must be whole numbers" msgstr "QoS hızları tam sayı olmalıdır" -#: instances/views.py:2161 networks/views.py:229 +#: instances/views.py:2176 networks/views.py:229 #, python-format msgid "%(qos_dir)s QoS is set" msgstr "%(qos_dir)s QoS ayarlandı" -#: instances/views.py:2166 +#: instances/views.py:2181 #, python-format msgid "" "%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on." @@ -5950,12 +5978,12 @@ msgstr "" "%(qos_dir)s QoS ayarlandı. Çalışan bir sanal makine, kapatılıp açıldıktan " "sonra bunu alır." -#: instances/views.py:2188 networks/views.py:255 +#: instances/views.py:2203 networks/views.py:255 #, python-format msgid "%(qos_dir)s QoS is deleted" msgstr "%(qos_dir)s QoS silindi" -#: instances/views.py:2193 +#: instances/views.py:2208 #, python-format msgid "" "%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is " @@ -5964,21 +5992,21 @@ msgstr "" "%(qos_dir)s QoS silindi. Çalışan bir sanal makine, kapatılıp açılana kadar " "eski sınırı korur." -#: instances/views.py:2221 +#: instances/views.py:2236 msgid "Only one owner is allowed and the one already added" msgstr "Sadece bir sahibe izin verilmiştir ve bir sahip zaten eklenmiş" -#: instances/views.py:2223 +#: instances/views.py:2238 #, python-format msgid "%(user)s is already an owner" msgstr "%(user)s zaten bir sahip" -#: instances/views.py:2267 +#: instances/views.py:2282 #, python-format msgid "Console settings were not saved: %(errors)s" msgstr "Konsol ayarları kaydedilmedi: %(errors)s" -#: instances/views.py:2289 +#: instances/views.py:2304 msgid "" "Error setting console password. You should check that your instance have an " "graphic device." @@ -5986,67 +6014,67 @@ msgstr "" "Konsol parolası ayarlanırken hata oluştu. Sanal makinenin bir grafik " "aygıtına sahip olduğundan emin olunuz." -#: instances/views.py:2336 +#: instances/views.py:2351 msgid "Shut the VM down to change whether it is a template." msgstr "Şablon olup olmadığını değiştirmek için sanal makineyi kapatın." -#: instances/views.py:2343 +#: instances/views.py:2358 #, python-format msgid "User %(quota_msg)s quota reached, '%(instance_name)s' stays a template!" msgstr "" "Kullanıcının %(quota_msg)s kotası doldu, '%(instance_name)s' şablon olarak " "kalıyor!" -#: instances/views.py:2425 +#: instances/views.py:2440 msgid "The XML has no name" msgstr "XML'de ad yok" -#: instances/views.py:2439 webvirtcloud/middleware.py:75 +#: instances/views.py:2454 webvirtcloud/middleware.py:75 #, python-format msgid "libvirt Error - %(exception)s" msgstr "libvirt Hatası - %(exception)s" -#: instances/views.py:2446 +#: instances/views.py:2461 msgid "An empty disk of the size you set" msgstr "Belirlediğiniz boyutta boş bir disk" -#: instances/views.py:2447 +#: instances/views.py:2462 msgid "Volumes already in a pool" msgstr "Bir havuzda zaten bulunan birimler" -#: instances/views.py:2448 +#: instances/views.py:2463 msgid "A copy of a template volume" msgstr "Bir şablon biriminin kopyası" -#: instances/views.py:2537 +#: instances/views.py:2552 msgid "You haven't defined any storage pools" msgstr "Hiç bir depolama havuzu tanımlamadınız" -#: instances/views.py:2539 +#: instances/views.py:2554 msgid "You haven't defined any network pools" msgstr "Hiç bir ağ havuzu tanımlamadınız" -#: instances/views.py:2602 +#: instances/views.py:2617 msgid "Flavor Created" msgstr "Flavor Oluşturuldu" -#: instances/views.py:2610 +#: instances/views.py:2625 msgid "Create Flavor" msgstr "Kullanıcı Oluştur" -#: instances/views.py:2625 +#: instances/views.py:2640 msgid "Flavor Updated" msgstr "Flavor Güncellendi" -#: instances/views.py:2631 +#: instances/views.py:2646 msgid "Update Flavor" msgstr "Flavor Güncelle" -#: instances/views.py:2643 +#: instances/views.py:2658 msgid "Flavor Deleted" msgstr "Flavor Silindi" -#: instances/views.py:2649 +#: instances/views.py:2664 msgid "Only the size template goes; VMs made from it keep their size." msgstr "" "Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler " diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 2983fa9f..d85bee2e 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -2968,6 +2968,37 @@ class wvmInstance(wvmConnect): tree.find("devices").append(etree.fromstring(RNG_DEVICE)) self._defineXML(etree.tostring(tree).decode()) + def get_tpm(self): + """The persistent definition's TPM: "emulator" for a plain emulated + TPM 2.0, the panel's own, "custom" for another one, None without one.""" + tpms = etree.fromstring(self._XMLDesc(PERSISTENT_XML)).xpath("devices/tpm") + if not tpms: + return None + # libvirt adds only an alias; encryption, a state path or another + # backend setting is kept by the XML editor, not dropped by the switch + own = ("backend[@type='emulator' and (not(@version) or @version='2.0') and not(*)" + " and not(@*[name()!='type' and name()!='version'])] and not(*[not(self::backend or self::alias)])") + if len(tpms) == 1 and tpms[0].xpath(own): + return "emulator" + return "custom" + + def set_tpm(self, enabled): + """Adds an emulated TPM 2.0 to the persistent definition, or removes it; + another TPM is left to the XML editor. Its state stays on the host until + the VM is undefined, so a TPM added again has it back.""" + if self.get_tpm() == "custom": + raise util.OperationError("The VM has its own TPM: change it in the XML editor") + tree = etree.fromstring(self._XMLDesc(PERSISTENT_XML)) + for tpm in tree.xpath("devices/tpm"): + tpm.getparent().remove(tpm) + if enabled: + model = self.get_tpm_model(self.get_arch(), self.get_machine_type()) + if not model: + raise util.OperationError("This host offers no TPM 2.0: install swtpm on it") + tree.find("devices").append( + etree.fromstring(f"")) + self._defineXML(etree.tostring(tree).decode()) + def get_on_crash(self): """What the next start does when the guest crashes; libvirt's default is destroy.""" return etree.fromstring(self._XMLDesc(PERSISTENT_XML)).findtext("on_crash") or "destroy" diff --git a/vrtManager/tests/test_device_edits.py b/vrtManager/tests/test_device_edits.py index b4c3bb74..225ae1cd 100644 --- a/vrtManager/tests/test_device_edits.py +++ b/vrtManager/tests/test_device_edits.py @@ -176,6 +176,45 @@ class DeviceEditsTestCase(unittest.TestCase): p.set_rng(False) p._defineXML.assert_not_called() + def test_an_emulated_tpm_2_is_added_with_the_host_s_model_or_left_to_the_xml_editor(self): + p = proxy() + p.get_tpm_model = lambda arch, machine: "tpm-crb" + p.get_arch, p.get_machine_type = lambda: "x86_64", lambda: "q35" + self.assertIsNone(p.get_tpm()) + p.set_tpm(True) + tpm = defined(p).find("devices/tpm") + self.assertEqual((tpm.get("model"), dict(tpm.find("backend").attrib)), ("tpm-crb", {"type": "emulator", "version": "2.0"})) + added = etree.tostring(defined(p)).decode() + p._XMLDesc = lambda flags: added + self.assertEqual(p.get_tpm(), "emulator") + p.set_tpm(False) + self.assertIsNone(defined(p).find("devices/tpm")) + # a host without swtpm adds none + p = proxy() + p.get_tpm_model = lambda arch, machine: None + p.get_arch, p.get_machine_type = lambda: "x86_64", lambda: "q35" + with self.assertRaises(util.OperationError): + p.set_tpm(True) + p._defineXML.assert_not_called() + # libvirt's alias leaves it the panel's + p = proxy() + p._XMLDesc = lambda flags: PERSISTENT.replace( + "