diff --git a/instances/templates/add_instance_cdrom.html b/instances/templates/add_instance_cdrom.html new file mode 100644 index 00000000..7ae858a5 --- /dev/null +++ b/instances/templates/add_instance_cdrom.html @@ -0,0 +1,43 @@ +{% load i18n %} +{% load bootstrap_icons %} + + + diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index 62daa717..0a5bb40b 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -147,14 +147,12 @@
{# its CD-ROM drives, then its disks as list rows; the forms are those of the actions #} {% if instance.status != 5 %} -

{% bs_icon 'lock' %} {% trans "The VM is running: CD-ROM drives added now use the SCSI bus. Shut it down to detach other CD-ROM drives, or to detach or delete disks." %}

+

{% bs_icon 'lock' %} {% trans "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." %}

{% endif %}

{% trans "Removable media" %}

-
{% csrf_token %} - -
+ {% include 'add_instance_cdrom.html' %}
{% for cd in instance.media %}
@@ -168,11 +166,13 @@
{{ cd.dev }} · {{ cd.bus }} · {% if cd.image %}{{ cd.image }}{% else %}{% trans "No media" %}{% endif %}
- {% if not cd.image %} - + {% if not cd.image or cd.bus == "usb" %} + {% endif %} - {% if cd.image %} + {% if cd.bus == "usb" %} + {# libvirt has no empty USB drive: it is detached, not ejected #} + {% elif cd.image %} {% else %} diff --git a/instances/tests/test_live_dataloss.py b/instances/tests/test_live_dataloss.py index 38f23329..604080d7 100644 --- a/instances/tests/test_live_dataloss.py +++ b/instances/tests/test_live_dataloss.py @@ -601,6 +601,33 @@ class LiveDataLossTestCase(TestCase): tree = etree.fromstring(dom.XMLDesc(flags)) self.assertEqual(tree.xpath("devices/disk[@device='cdrom']/target/@dev"), ["sda"]) + def test_a_usb_cdrom_with_its_iso_is_plugged_into_a_running_vm_and_detached(self): + from lxml import etree + + pool = self.conn.storagePoolLookupByName(livetest.POOL) + iso = pool.createXML(f"{P}usb.iso1" + "", 0) + dom, inst = self.vm("cdrom-usb", [], extra_devices="") + dom.create() + + def cdroms(flags): + tree = etree.fromstring(dom.XMLDesc(flags)) + return [(d.find("target").get("bus"), d.get("model"), d.xpath("string(source/@file)")) + for d in tree.xpath("devices/disk[@device='cdrom']")] + + response = self.client.post(reverse("instances:add_cdrom", args=[inst.id]), + {"bus": "usb", "media": f"{P}usb.iso"}, follow=True) + if not cdroms(0): # a QEMU without usb-bot (RHEL 9): refused, nothing added + self.assertIn("usb-bot", " ".join(str(m) for m in response.context["messages"])) + self.assertEqual(cdroms(libvirt.VIR_DOMAIN_XML_INACTIVE), []) + return + for flags in (0, libvirt.VIR_DOMAIN_XML_INACTIVE): + self.assertEqual(cdroms(flags), [("usb", "usb-bot", iso.path())]) + self.client.post(reverse("instances:detach_cdrom", args=[inst.id, "sda"]), + HTTP_REFERER=reverse("instances:instance", args=[inst.id])) + for flags in (0, libvirt.VIR_DOMAIN_XML_INACTIVE): + self.assertEqual(cdroms(flags), []) + def test_pending_qos_change_is_shown_and_kept_by_the_next_edit(self): from lxml import etree, html diff --git a/instances/tests/test_new_disk_name.py b/instances/tests/test_new_disk_name.py index a7bab22f..8cc6d786 100644 --- a/instances/tests/test_new_disk_name.py +++ b/instances/tests/test_new_disk_name.py @@ -30,7 +30,8 @@ class NewDiskNameTestCase(TestCase): proxy = wvm.return_value proxy.get_disk_devices.side_effect = lambda **kw: list((config if kw.get("config") else live)[0]) proxy.get_media_devices.side_effect = lambda **kw: list((config if kw.get("config") else live)[1]) - proxy.get_disk_bus_types.return_value = ["virtio", "sata", "scsi"] + proxy.get_disk_bus_types.return_value = ["virtio", "sata", "scsi", "usb"] + proxy.get_iso_path.side_effect = lambda image: f"/iso/{image}" proxy.get_cache_modes.return_value = {"default": "", "none": ""} proxy.get_machine_type.return_value = "q35" proxy.get_status.return_value = status @@ -39,14 +40,14 @@ class NewDiskNameTestCase(TestCase): storage.return_value.get_volume_type.return_value = "file" storage.return_value.get_volume_format_type.return_value = "qcow2" create.return_value.create_volume.return_value = "/pool/data.qcow2" - self.client.post(reverse(f"instances:{view}", args=[self.instance.id]), data) - proxy.attach_disk.assert_called_once() + self.response = self.client.post(reverse(f"instances:{view}", args=[self.instance.id]), data, follow=True) + self.proxy = proxy return proxy.attach_disk.call_args VIEWS = ( - ("add_new_vol", {"storage": "default", "name": "data", "format": "qcow2", "size": "1", "cache": "default"}), - ("add_existing_vol", {"selected_storage": "default", "vols": "data.qcow2", "cache": "default"}), - ("add_cdrom", {}), + ("add_new_vol", {"storage": "default", "name": "data", "format": "qcow2", "size": "1", "cache": "default"}, 1), + ("add_existing_vol", {"selected_storage": "default", "vols": "data.qcow2", "cache": "default"}, 1), + ("add_cdrom", {}, 5), # shut off: a running VM takes USB and SCSI CD-ROMs only ) def test_a_name_either_definition_uses_is_not_given_again(self): @@ -58,12 +59,28 @@ class NewDiskNameTestCase(TestCase): ("virtio", ([vda, dev("vdb", "virtio")], []), ([vda], []), "vdc"), # not yet released by the guest ("virtio", ([vda], []), ([vda], []), "vdb"), # both the same, as on a stopped VM ) - for view, data in self.VIEWS: + for view, data, status in self.VIEWS: for bus, live, config, expected in cases: + if view == "add_cdrom" and bus == "virtio": # no virtio CD-ROM + continue with self.subTest(view=view, bus=bus, expected=expected): - self.assertEqual(self.attach(view, {**data, "bus": bus}, live, config).args[0], expected) + call = self.attach(view, {**data, "bus": bus}, live, config, status=status) + self.assertEqual(call.args[0], expected) - def test_a_cdrom_of_a_running_vm_is_on_the_bus_that_can_be_hotplugged(self): - for status, bus in ((1, "scsi"), (3, "scsi"), (5, "sata")): # running, paused, shut off + def test_a_cdrom_of_a_running_vm_is_on_a_bus_that_can_be_hotplugged(self): + # running, paused: USB first, which every guest sees; shut off: SATA + for status, bus in ((1, "usb"), (3, "usb"), (5, "sata")): with self.subTest(status=status): - self.assertEqual(self.attach("add_cdrom", {}, status=status).kwargs["target_bus"], bus) + call = self.attach("add_cdrom", {"media": "virtio-win.iso"}, status=status) + self.assertEqual((call.kwargs["target_bus"], call.args[1]), (bus, "/iso/virtio-win.iso")) + + def test_a_running_vm_refuses_a_sata_cdrom_and_a_usb_one_without_media(self): + for data in ({"bus": "sata", "media": "virtio-win.iso"}, {"bus": "usb", "media": ""}): + with self.subTest(data): + self.attach("add_cdrom", data, status=1) + self.proxy.attach_disk.assert_not_called() + self.assertTrue(list(self.response.context["messages"])) + + def test_a_cdrom_without_media_is_empty(self): + call = self.attach("add_cdrom", {"bus": "scsi", "media": ""}, status=1) + self.assertEqual((call.kwargs["target_bus"], call.args[1]), ("scsi", "")) diff --git a/instances/views.py b/instances/views.py index 397633ec..4926a993 100755 --- a/instances/views.py +++ b/instances/views.py @@ -307,6 +307,7 @@ def _instance_page(request, pk, instance, compute): vcpu_host = len(instance.vcpu_range) 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) networks_host = sorted(instance.proxy.get_networks()) interfaces_host = sorted(instance.proxy.get_ifaces()) nwfilters_host = instance.proxy.get_nwfilters() @@ -1575,31 +1576,49 @@ def detach_vol(request, pk): @serialize_instance_mutation def add_cdrom(request, pk): instance = get_instance(request.user, pk, perm_type="change") - if instance.status == 5: - default_bus = "ide" if instance.machine == "pc" else "sata" - else: # IDE and SATA drives cannot be plugged into a running VM, SCSI ones can - default_bus = "scsi" - bus = request.POST.get("bus", default_bus) - invalid = invalid_disk_options(instance, bus=bus) - if invalid: - return reject_disk_options(request, pk, invalid) + buses = cdrom_bus_choices(instance) + bus = request.POST.get("bus", buses[0] if buses else "") + image = request.POST.get("media", "") + if bus not in buses: + messages.error(request, _("This VM cannot take a CD-ROM on the %(bus)s bus now") % {"bus": bus}) + return _back(request, pk, "disks") + if bus == "usb" and not image: + messages.error(request, _("A USB CD-ROM needs an ISO image: libvirt has no empty USB drive")) + return _back(request, pk, "disks") target = utils.get_new_disk_dev( instance.media + instance.config_media, instance.disks + instance.config_disks, bus ) - with audit_vm(request, instance, f"add CD-ROM {target}"): - instance.proxy.attach_disk( - target, - "", - disk_device="cdrom", - cache_mode="none", - target_bus=bus, - readonly=True, - ) + try: + with audit_vm(request, instance, f"add CD-ROM {target}" + (f" with {image}" if image else "")): + instance.proxy.attach_disk( + target, + instance.proxy.get_iso_path(image) if image else "", + disk_device="cdrom", + cache_mode="none", + target_bus=bus, + readonly=True, + ) + except (libvirtError, util.OperationError) as err: + messages.error(request, err) return _back(request, pk, "disks") +def cdrom_bus_choices(instance): + """The buses a CD-ROM can be added on now, the default first. A running VM + takes USB and SCSI drives only; Windows sees USB, SATA and IDE ones + without a driver.""" + if instance.status != 5: + order = ("usb", "scsi") + elif instance.machine == "pc": + order = ("ide", "sata", "scsi", "usb") + else: + order = ("sata", "scsi", "usb") + offered = instance.proxy.get_disk_bus_types(instance.arch, instance.machine) + return [bus for bus in order if bus in offered] + + @require_POST @superuser_only @serialize_instance_mutation diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index 259f3dd5..36f2a520 100644 Binary files a/locale/tr/LC_MESSAGES/django.mo and b/locale/tr/LC_MESSAGES/django.mo differ diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index d535f790..56e6793c 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 10:56+0000\n" +"POT-Creation-Date: 2026-11-01 13:18+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/" @@ -271,6 +271,7 @@ msgstr "Parola Değiştir" #: admin/templates/admin/user_form.html:137 #: computes/templates/computes/form.html:45 #: console/templates/console-vnc.html:86 +#: instances/templates/add_instance_cdrom.html:38 #: instances/templates/add_instance_network_block.html:55 #: instances/templates/add_instance_owner_block.html:29 #: instances/templates/add_instance_volume.html:108 @@ -504,6 +505,7 @@ msgid "Add key to user form" msgstr "Kullanıcıya anahtar ekleme formu" #: accounts/templates/profile.html:55 +#: instances/templates/add_instance_cdrom.html:39 #: instances/templates/add_instance_network_block.html:56 #: instances/templates/add_instance_owner_block.html:30 #: instances/templates/create_flavors.html:20 @@ -2465,6 +2467,7 @@ msgid "Connect" msgstr "Bağlan" #: console/templates/console-vnc.html:98 +#: instances/templates/add_instance_cdrom.html:10 #: instances/templates/add_instance_network_block.html:11 #: instances/templates/add_instance_owner_block.html:11 #: instances/templates/add_instance_volume.html:15 @@ -2728,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:2408 +#: instances/jobs.py:685 instances/views.py:2427 msgid "A virtual machine with this name already exists" msgstr "Bu isimde bir sanal makine mevcut" @@ -2865,7 +2868,7 @@ msgid "Disk" msgstr "Disk" #: instances/models.py:223 instances/templates/create_instance_w2.html:309 -#: instances/templates/instances/settings_tab.html:167 +#: instances/templates/instances/settings_tab.html:165 msgid "CD-ROM" msgstr "CD-ROM" @@ -2946,6 +2949,45 @@ msgstr "Çöktü" msgid "PM suspended" msgstr "Güç yönetimiyle askıya alındı" +#: instances/templates/add_instance_cdrom.html:3 +#: instances/templates/add_instance_cdrom.html:9 +msgid "Add CD-ROM" +msgstr "CD-ROM Ekle" + +#: instances/templates/add_instance_cdrom.html:7 +msgid "Add CD-ROM form" +msgstr "CD-ROM ekleme formu" + +#: instances/templates/add_instance_cdrom.html:15 +#: instances/templates/add_instance_volume.html:90 +#: 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" +msgstr "Veriyolu" + +#: instances/templates/add_instance_cdrom.html:23 +#: instances/templates/instances/settings_tab.html:179 +msgid "ISO image" +msgstr "ISO imajı" + +#: instances/templates/add_instance_cdrom.html:28 +#: instances/templates/instances/settings_tab.html:166 +msgid "No media" +msgstr "Ortam yok" + +#: instances/templates/add_instance_cdrom.html:32 +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." + +#: 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)." + #: instances/templates/add_instance_network_block.html:4 #: instances/templates/add_instance_network_block.html:10 msgid "Add network device" @@ -3016,7 +3058,7 @@ msgid "Disk source" msgstr "Disk kaynağı" #: instances/templates/add_instance_volume.html:21 -#: instances/templates/create_instance_w2.html:131 instances/views.py:2427 +#: instances/templates/create_instance_w2.html:131 instances/views.py:2446 msgid "New disk" msgstr "Yeni disk" @@ -3076,13 +3118,6 @@ msgstr "Birim" msgid "Choose a pool first" msgstr "Önce bir havuz seçin" -#: instances/templates/add_instance_volume.html:90 -#: 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" -msgstr "Veriyolu" - #: instances/templates/add_instance_volume.html:98 #: instances/templates/instances/edit_instance_volume.html:45 msgid "Cache mode" @@ -3385,7 +3420,7 @@ msgstr "Özel CPU modelleri" msgid "Size (GiB)" msgstr "Boyut (GiB)" -#: instances/templates/create_instance_w2.html:158 instances/views.py:2428 +#: instances/templates/create_instance_w2.html:158 instances/views.py:2447 msgid "Existing volumes" msgstr "Mevcut birimler" @@ -3420,7 +3455,7 @@ msgstr "Aygıt" msgid "Remove" msgstr "Kaldır" -#: instances/templates/create_instance_w2.html:191 instances/views.py:2429 +#: instances/templates/create_instance_w2.html:191 instances/views.py:2448 msgid "Clone from template" msgstr "Şablondan klonla" @@ -3755,7 +3790,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:1075 +#: instances/templates/instances/access_tab.html:41 instances/views.py:1076 msgid "Reset root password" msgstr "Root parolasını sıfırla" @@ -4538,30 +4573,15 @@ msgstr "Sırayı uygula" #: instances/templates/instances/settings_tab.html:150 msgid "" -"The VM is running: CD-ROM drives added now use the SCSI bus. Shut it down to " -"detach other CD-ROM drives, or to detach or delete disks." -msgstr "" -"Sanal makine çalışıyor: şimdi eklenen CD-ROM sürücüleri SCSI veri yolunu " -"kullanır. Diğer CD-ROM sürücülerini ayırmak ya da diskleri ayırmak veya " -"silmek için sanal makineyi kapatın." +"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." #: instances/templates/instances/settings_tab.html:154 msgid "Removable media" msgstr "Çıkarılabilir ortam" -#: instances/templates/instances/settings_tab.html:155 -msgid "Add CD-ROM form" -msgstr "CD-ROM ekleme formu" - -#: instances/templates/instances/settings_tab.html:156 -msgid "Add CD-ROM" -msgstr "CD-ROM Ekle" - -#: instances/templates/instances/settings_tab.html:168 -msgid "No media" -msgstr "Ortam yok" - -#: instances/templates/instances/settings_tab.html:172 +#: instances/templates/instances/settings_tab.html:170 #: instances/templates/instances/settings_tab.html:249 msgid "Detach" msgstr "Ayır" @@ -4570,10 +4590,6 @@ msgstr "Ayır" msgid "Unmount" msgstr "Ayır" -#: instances/templates/instances/settings_tab.html:179 -msgid "ISO image" -msgstr "ISO imajı" - #: instances/templates/instances/settings_tab.html:191 msgid "Mount" msgstr "Bağla" @@ -5542,16 +5558,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:333 +#: instances/views.py:334 msgid "There is no other host to migrate to." msgstr "Taşınacak başka bir sunucu yok." -#: instances/views.py:335 +#: instances/views.py:336 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:337 +#: instances/views.py:338 msgid "" "A VM with snapshots cannot be migrated: its snapshots stay on this host. " "Delete them first." @@ -5559,7 +5575,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:339 +#: instances/views.py:340 msgid "" "A VM with host devices cannot be migrated: the destination may not have " "them. Remove them first." @@ -5567,11 +5583,11 @@ msgstr "" "Sunucu aygıtları olan bir sanal makine taşınamaz: hedefte bu aygıtlar " "olmayabilir. Önce onları kaldırın." -#: instances/views.py:605 +#: instances/views.py:606 msgid "The VM was moved meanwhile; try again." msgstr "Sanal makine bu arada taşındı; yeniden deneyin." -#: instances/views.py:636 +#: instances/views.py:637 msgid "" "The quota cannot be checked because a host of your virtual machines cannot " "be reached. Nothing was changed." @@ -5579,7 +5595,7 @@ msgstr "" "Sanal makinelerinizin bir sunucusuna erişilemediği için kota denetlenemiyor. " "Hiçbir şey değiştirilmedi." -#: instances/views.py:717 +#: instances/views.py:718 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 " @@ -5592,7 +5608,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:721 +#: instances/views.py:722 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 " @@ -5604,82 +5620,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:739 +#: instances/views.py:740 #, python-format msgid "Invalid disk options: %(options)s" msgstr "Geçersiz disk seçenekleri: %(options)s" -#: instances/views.py:749 instances/views.py:764 +#: instances/views.py:750 instances/views.py:765 msgid "Templates cannot be started." msgstr "Şablon makineler başlatılamaz." -#: instances/views.py:860 +#: instances/views.py:861 #, 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:866 +#: instances/views.py:867 #, 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:875 +#: instances/views.py:876 #, 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:997 +#: instances/views.py:998 #, 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:1003 instances/views.py:1017 instances/views.py:1026 +#: instances/views.py:1004 instances/views.py:1018 instances/views.py:1027 #, python-format msgid "gstfsd error: %(err)s" msgstr "gstfsd hatası: %(err)s" -#: instances/views.py:1027 +#: instances/views.py:1028 msgid "the reply is not signed with the key" msgstr "yanıt anahtarla imzalanmamış" -#: instances/views.py:1035 +#: instances/views.py:1036 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:1048 +#: instances/views.py:1049 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:1079 +#: instances/views.py:1080 msgid "Enter a password" msgstr "Bir parola girin" -#: instances/views.py:1093 +#: instances/views.py:1094 msgid "Select an SSH public key" msgstr "Bir SSH açık anahtarı seçin" -#: instances/views.py:1115 +#: instances/views.py:1116 #, python-format msgid "Installed new SSH public key %(keyname)s" msgstr "Yeni %(keyname)s SSH açık anahtarı yüklendi" -#: instances/views.py:1135 +#: instances/views.py:1136 msgid "Enter a whole number of vCPUs" msgstr "vCPU sayısı için bir tam sayı girin" -#: instances/views.py:1139 +#: instances/views.py:1140 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:1142 +#: instances/views.py:1143 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:1150 +#: instances/views.py:1151 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize CPU of '%(instance_name)s'!" @@ -5687,20 +5703,20 @@ msgstr "" "%(quota_msg)s kullanıcısı kotası doldu, '%(instance_name)s' in CPU su " "yeniden boyutlandırılamıyor!" -#: instances/views.py:1161 +#: instances/views.py:1162 #, 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:1180 +#: instances/views.py:1181 msgid "Enter a whole number for memory" msgstr "Bellek için bir tam sayı girin" -#: instances/views.py:1183 +#: instances/views.py:1184 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:1188 +#: instances/views.py:1189 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize memory of " @@ -5709,25 +5725,25 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in belleği yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1197 +#: instances/views.py:1198 #, 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:1217 +#: instances/views.py:1218 msgid "Enter disk sizes as whole GiB" msgstr "Disk boyutlarını tam GiB olarak girin" -#: instances/views.py:1221 +#: instances/views.py:1222 #, 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:1229 +#: instances/views.py:1230 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:1239 +#: instances/views.py:1240 #, python-format msgid "" "User %(quota_msg)s quota reached, cannot resize disks of '%(instance_name)s'!" @@ -5735,61 +5751,61 @@ msgstr "" "%(quota_msg)s kullanıcı kotası doldu, '%(instance_name)s' in diski yeniden " "boyutlandırılamıyor!" -#: instances/views.py:1254 +#: instances/views.py:1255 #, 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:1256 +#: instances/views.py:1257 #, python-format msgid "Disk is resized: %(dev)s" msgstr "Disk boyutu değiştirildi: %(dev)s" -#: instances/views.py:1350 +#: instances/views.py:1351 msgid "The VM's machine has no PCIe slots to add" msgstr "Sanal makinenin makine tipinde eklenecek PCIe yuvası yok" -#: instances/views.py:1355 +#: instances/views.py:1356 #, python-format msgid "%(count)s PCIe slots added" msgstr "%(count)s PCIe yuvası eklendi" -#: instances/views.py:1357 +#: instances/views.py:1358 #, 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:1488 +#: instances/views.py:1489 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:1493 +#: instances/views.py:1494 msgid "Volume is changed successfully." msgstr "Birim başarıyla değiştirildi." -#: instances/views.py:1511 +#: instances/views.py:1512 #, 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:1518 +#: instances/views.py:1519 #, 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:1533 +#: instances/views.py:1534 #, 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:1545 +#: instances/views.py:1546 #, python-format msgid "" "The guest has not released disk %(dev)s yet; volume %(vol)s was kept. Delete " @@ -5798,43 +5814,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:1556 +#: instances/views.py:1557 #, 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:1660 +#: instances/views.py:1583 +#, 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 +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 #, python-format msgid "Snapshot was not deleted: %(err)s" msgstr "Anlık görüntü silinmedi: %(err)s" -#: instances/views.py:1775 +#: instances/views.py:1794 msgid "Check at least one device to boot from." msgstr "Önyükleme için en az bir aygıt işaretleyin." -#: instances/views.py:1789 +#: instances/views.py:1808 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:1791 +#: instances/views.py:1810 msgid "Boot order changed successfully." msgstr "Ön yükleme sırası başarıyla değiştirildi." -#: instances/views.py:1811 +#: instances/views.py:1830 msgid "The XML is empty" msgstr "XML boş" -#: instances/views.py:1815 instances/views.py:2418 +#: instances/views.py:1834 instances/views.py:2437 #, python-format msgid "The XML is not well-formed: %(error)s" msgstr "XML düzgün biçimlendirilmemiş: %(error)s" -#: instances/views.py:1817 +#: instances/views.py:1836 #, 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:1821 +#: instances/views.py:1840 msgid "" "The definition has changed since the editor was opened. Reload the page to " "edit the current one." @@ -5842,35 +5867,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:1833 +#: instances/views.py:1852 msgid "The XML definition was saved." msgstr "XML tanımı kaydedildi." -#: instances/views.py:1835 +#: instances/views.py:1854 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:1902 +#: instances/views.py:1921 msgid "Select one network interface to change" msgstr "Değiştirilecek bir ağ arabirimi seçin" -#: instances/views.py:1940 +#: instances/views.py:1959 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:1942 +#: instances/views.py:1961 msgid "Network device changed" msgstr "Ağ aygıtı değiştirildi" -#: instances/views.py:1954 +#: instances/views.py:1973 msgid "No network selected" msgstr "Ağ seçilmedi" -#: instances/views.py:1974 +#: instances/views.py:1993 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 " @@ -5880,21 +5905,21 @@ msgstr "" "aygıtı sonra ekleyin; Diskler sekmesindeki PCIe yuvaları ekle, sonraki " "başlatmadan itibaren ona boş yuvalar verir." -#: instances/views.py:2011 instances/views.py:2017 +#: instances/views.py:2030 instances/views.py:2036 msgid "The VM has it" msgstr "Sanal makinede var" -#: instances/views.py:2048 +#: instances/views.py:2067 #, python-format msgid "The host has no USB device %(device)s" msgstr "Sunucuda %(device)s USB aygıtı yok" -#: instances/views.py:2069 +#: instances/views.py:2088 #, python-format msgid "The host has no PCI device %(device)s" msgstr "Sunucuda %(device)s PCI aygıtı yok" -#: instances/views.py:2087 +#: instances/views.py:2106 #, python-format msgid "" "Removal of PCI device %(address)s requested: the guest has not let it go " @@ -5904,20 +5929,20 @@ msgstr "" "bırakmadı; bırakana ya da sanal makine durana kadar çalışan sanal makinede " "kalır." -#: instances/views.py:2132 instances/views.py:2162 +#: instances/views.py:2151 instances/views.py:2181 msgid "Select a network interface and a QoS direction" msgstr "Bir ağ arabirimi ve bir QoS yönü seçin" -#: instances/views.py:2135 +#: instances/views.py:2154 msgid "QoS rates must be whole numbers" msgstr "QoS hızları tam sayı olmalıdır" -#: instances/views.py:2142 networks/views.py:229 +#: instances/views.py:2161 networks/views.py:229 #, python-format msgid "%(qos_dir)s QoS is set" msgstr "%(qos_dir)s QoS ayarlandı" -#: instances/views.py:2147 +#: instances/views.py:2166 #, python-format msgid "" "%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on." @@ -5925,12 +5950,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:2169 networks/views.py:255 +#: instances/views.py:2188 networks/views.py:255 #, python-format msgid "%(qos_dir)s QoS is deleted" msgstr "%(qos_dir)s QoS silindi" -#: instances/views.py:2174 +#: instances/views.py:2193 #, python-format msgid "" "%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is " @@ -5939,21 +5964,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:2202 +#: instances/views.py:2221 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:2204 +#: instances/views.py:2223 #, python-format msgid "%(user)s is already an owner" msgstr "%(user)s zaten bir sahip" -#: instances/views.py:2248 +#: instances/views.py:2267 #, python-format msgid "Console settings were not saved: %(errors)s" msgstr "Konsol ayarları kaydedilmedi: %(errors)s" -#: instances/views.py:2270 +#: instances/views.py:2289 msgid "" "Error setting console password. You should check that your instance have an " "graphic device." @@ -5961,67 +5986,67 @@ msgstr "" "Konsol parolası ayarlanırken hata oluştu. Sanal makinenin bir grafik " "aygıtına sahip olduğundan emin olunuz." -#: instances/views.py:2317 +#: instances/views.py:2336 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:2324 +#: instances/views.py:2343 #, 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:2406 +#: instances/views.py:2425 msgid "The XML has no name" msgstr "XML'de ad yok" -#: instances/views.py:2420 webvirtcloud/middleware.py:75 +#: instances/views.py:2439 webvirtcloud/middleware.py:75 #, python-format msgid "libvirt Error - %(exception)s" msgstr "libvirt Hatası - %(exception)s" -#: instances/views.py:2427 +#: instances/views.py:2446 msgid "An empty disk of the size you set" msgstr "Belirlediğiniz boyutta boş bir disk" -#: instances/views.py:2428 +#: instances/views.py:2447 msgid "Volumes already in a pool" msgstr "Bir havuzda zaten bulunan birimler" -#: instances/views.py:2429 +#: instances/views.py:2448 msgid "A copy of a template volume" msgstr "Bir şablon biriminin kopyası" -#: instances/views.py:2518 +#: instances/views.py:2537 msgid "You haven't defined any storage pools" msgstr "Hiç bir depolama havuzu tanımlamadınız" -#: instances/views.py:2520 +#: instances/views.py:2539 msgid "You haven't defined any network pools" msgstr "Hiç bir ağ havuzu tanımlamadınız" -#: instances/views.py:2583 +#: instances/views.py:2602 msgid "Flavor Created" msgstr "Flavor Oluşturuldu" -#: instances/views.py:2591 +#: instances/views.py:2610 msgid "Create Flavor" msgstr "Kullanıcı Oluştur" -#: instances/views.py:2606 +#: instances/views.py:2625 msgid "Flavor Updated" msgstr "Flavor Güncellendi" -#: instances/views.py:2612 +#: instances/views.py:2631 msgid "Update Flavor" msgstr "Flavor Güncelle" -#: instances/views.py:2624 +#: instances/views.py:2643 msgid "Flavor Deleted" msgstr "Flavor Silindi" -#: instances/views.py:2630 +#: instances/views.py:2649 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 00c6bebe..2983fa9f 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -1141,7 +1141,8 @@ class wvmInstance(wvmConnect): raise util.OperationError(f"Media of {dev} changed in the running VM only: {err}") from err raise - def mount_iso(self, dev, image): + def get_iso_path(self, image): + """The path of the ISO image of that name, in one of the active pools.""" paths = [] for storage in self.get_storages(only_actives=True): stg = self.get_storage(storage) @@ -1151,7 +1152,10 @@ class wvmInstance(wvmConnect): raise util.OperationError(f"ISO image {image} was not found") if len(paths) > 1: raise util.OperationError(f"ISO image {image} is in more than one storage pool") - self._set_cdrom_media(dev, paths[0]) + return paths[0] + + def mount_iso(self, dev, image): + self._set_cdrom_media(dev, self.get_iso_path(image)) def umount_iso(self, dev, image=None): self._set_cdrom_media(dev, None) @@ -1199,7 +1203,10 @@ class wvmInstance(wvmConnect): if detect_zeroes_mode is not None and detect_zeroes_mode != "default": additionals += f"detect_zeroes='{detect_zeroes_mode}' " - xml_disk = f"" + # a USB CD-ROM is a CD drive on usb-bot only: usb-storage, which libvirt + # takes where QEMU has no usb-bot (RHEL 9), shows the guest a hard disk + usb_cd = " model='usb-bot'" if disk_device == "cdrom" and target_bus == "usb" else "" + xml_disk = f"" if disk_device == "cdrom": xml_disk += f"" elif disk_device == "disk": diff --git a/vrtManager/tests/test_device_edits.py b/vrtManager/tests/test_device_edits.py index 0d0a9fbb..b4c3bb74 100644 --- a/vrtManager/tests/test_device_edits.py +++ b/vrtManager/tests/test_device_edits.py @@ -91,6 +91,16 @@ class DeviceEditsTestCase(unittest.TestCase): xml, _ = p.instance.attachDeviceFlags.call_args.args self.assertEqual(etree.fromstring(xml).find("source").attrib, {"dev": "/dev/vg/c"}) + def test_a_usb_cdrom_is_a_cd_drive_on_usb_bot(self): + # usb-storage, libvirt's choice where QEMU has no usb-bot, shows a hard disk + for bus, model in (("usb", "usb-bot"), ("sata", None)): + with self.subTest(bus=bus): + p = proxy(active=False) + p._definitions = lambda: [(PERSISTENT_XML, VIR_DOMAIN_AFFECT_CONFIG)] + p.attach_disk("sdb", "/iso/virtio-win.iso", disk_device="cdrom", target_bus=bus, readonly=True) + xml, _ = p.instance.attachDeviceFlags.call_args.args + self.assertEqual(etree.fromstring(xml).get("model"), model) + def test_a_bridge_nic_can_be_a_boot_device(self): p = proxy() p.set_bootorder({0: {"type": "network", "dev": "52:54:10:00:00:02"}, 1: {"type": "disk", "dev": "vdb"}})