1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/tests/test_new_disk_name.py
catborise 2a05576e18 feat(instances): a CD-ROM is added on the bus chosen, with its ISO
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.
2026-11-01 16:27:39 +03:00

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", ""))