mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
A running VM got its new CD-ROM on SCSI, the one bus the panel could plug in then, and Windows does not see a virtio-scsi drive before the driver on that ISO is installed. Add CD-ROM is now a dialog that chooses the bus and the ISO: a running VM takes USB, first, which every guest sees and which hot-plugs, or SCSI; a shut-off one SATA (IDE on pc), SCSI or USB. libvirt has no empty USB drive, so a USB CD-ROM takes its ISO when it is added and is detached instead of ejected, also while the VM runs. A USB CD-ROM is a CD drive on QEMU's usb-bot only. Where QEMU has none (RHEL 9), libvirt took usb-storage, which shows the guest a hard disk that Windows does not read; the drive now asks for usb-bot, and such a host refuses it with libvirt's message.
86 lines
4.9 KiB
Python
86 lines
4.9 KiB
Python
"""A new disk or CD-ROM gets a name neither definition uses: on an active
|
|
VM a disk can be pending in the persistent definition only, or still in the
|
|
running VM only, and the attach goes to both."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
from computes.models import Compute
|
|
from django.contrib.auth import get_user_model
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from instances.models import Instance
|
|
|
|
|
|
def dev(name, bus):
|
|
return {"dev": name, "bus": bus, "image": f"{name}.qcow2", "storage": "default", "path": f"/pool/{name}.qcow2",
|
|
"format": "qcow2", "size": 1 << 30}
|
|
|
|
|
|
class NewDiskNameTestCase(TestCase):
|
|
def setUp(self):
|
|
self.client.force_login(get_user_model().objects.create_superuser("nd-admin", "nd@example.com", "x"))
|
|
compute = Compute.objects.create(name="nd", hostname="127.0.0.1:1", login="root", password="", type=1)
|
|
self.instance = Instance.objects.create(compute=compute, name="nd-vm", uuid="aaaaaaaa-5555-2222-3333-444444444444")
|
|
|
|
def attach(self, view, data, live=((), ()), config=((), ()), status=1):
|
|
"""The attach_disk call of view; live and config are (disks, media) of each definition."""
|
|
with patch("instances.models.wvmInstance") as wvm, patch("instances.views.wvmStorage") as storage, patch(
|
|
"instances.views.wvmCreate"
|
|
) as create:
|
|
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", "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
|
|
storage.return_value.get_volumes.return_value = ["data.qcow2"]
|
|
storage.return_value.get_type.return_value = "dir"
|
|
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.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"}, 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):
|
|
vda = dev("vda", "virtio")
|
|
cases = (
|
|
# bus, live (disks, media), persistent (disks, media), expected name
|
|
("scsi", ([vda], []), ([vda, dev("sda", "sata")], []), "sdb"), # pending sata rename; scsi shares sd
|
|
("sata", ([vda], []), ([vda], [dev("sda", "sata")]), "sdb"), # pending CD-ROM
|
|
("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, 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):
|
|
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_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):
|
|
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", ""))
|