diff --git a/instances/tests/test_new_disk_name.py b/instances/tests/test_new_disk_name.py
index cd1cf1b4..a7bab22f 100644
--- a/instances/tests/test_new_disk_name.py
+++ b/instances/tests/test_new_disk_name.py
@@ -22,8 +22,8 @@ class NewDiskNameTestCase(TestCase):
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 attached_name(self, view, data, live=((), ()), config=((), ())):
- """The name view attaches with; live and config are (disks, media) of each definition."""
+ 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:
@@ -33,6 +33,7 @@ class NewDiskNameTestCase(TestCase):
proxy.get_disk_bus_types.return_value = ["virtio", "sata", "scsi"]
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"
@@ -40,7 +41,7 @@ class NewDiskNameTestCase(TestCase):
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()
- return proxy.attach_disk.call_args.args[0]
+ return proxy.attach_disk.call_args
VIEWS = (
("add_new_vol", {"storage": "default", "name": "data", "format": "qcow2", "size": "1", "cache": "default"}),
@@ -60,4 +61,9 @@ class NewDiskNameTestCase(TestCase):
for view, data in self.VIEWS:
for bus, live, config, expected in cases:
with self.subTest(view=view, bus=bus, expected=expected):
- self.assertEqual(self.attached_name(view, {**data, "bus": bus}, live, config), expected)
+ self.assertEqual(self.attach(view, {**data, "bus": bus}, live, config).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
+ with self.subTest(status=status):
+ self.assertEqual(self.attach("add_cdrom", {}, status=status).kwargs["target_bus"], bus)
diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py
index c46864fd..3ea28e90 100644
--- a/instances/tests/test_pending_edits.py
+++ b/instances/tests/test_pending_edits.py
@@ -78,6 +78,14 @@ class PendingEditsTestCase(TestCase):
self.assertEqual([" ".join(t.split()) for t in doc.xpath("//label[@class='col-form-label']/text()")],
[f"{mac} Inbound", f"{mac} Outbound"])
+ def test_one_disk_card_spans_the_row_and_more_sit_side_by_side(self):
+ for disks, wide in (([disk("vda", "default", "")], ["col-12"]),
+ ([disk("vda", "default", ""), disk("vdb", "default", "")], ["col-12 col-md-6"] * 2)):
+ with self.subTest(count=len(disks)):
+ doc = self.page(live=disks, config=disks)
+ self.assertEqual(doc.xpath("//div[@id='disks']//div[article[starts-with(@aria-labelledby, 'disk-')]]/@class"),
+ wide)
+
def test_a_disk_with_a_pending_source_is_not_deleted(self):
# running: vda is A, its definition for the next start uses B
with patch("instances.models.wvmInstance") as wvm, patch("instances.views.wvmStorage") as storage:
@@ -99,8 +107,8 @@ class PendingEditsTestCase(TestCase):
def test_buttons_shown_as_disabled_cannot_submit(self):
# running: the CPU, options and video forms wait for a shutdown
doc = self.page(live=[disk("vda", "default", "")], config=[disk("vda", "default", "")])
+ self.assertGreater(len(doc.xpath("//button[@disabled]")), 5)
shown = doc.xpath("//button[contains(concat(' ', @class, ' '), ' disabled ')]")
- self.assertGreater(len(shown), 5)
self.assertEqual([b for b in shown if b.get("disabled") is None], [])
for action in ("resizevm_cpu", "change_options", "set_video_model", "detach_vol", "delete_vol"):
with self.subTest(action):
diff --git a/instances/views.py b/instances/views.py
index cc5c8568..31d2550a 100755
--- a/instances/views.py
+++ b/instances/views.py
@@ -1462,7 +1462,11 @@ def detach_vol(request, pk):
@serialize_instance_mutation
def add_cdrom(request, pk):
instance = get_instance(request.user, pk, perm_type="change")
- bus = request.POST.get("bus", "ide" if instance.machine == "pc" else "sata")
+ 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)
diff --git a/static/icon_cache/disc_None_None_None.svg b/static/icon_cache/disc_None_None_None.svg
new file mode 100644
index 00000000..3f123b94
--- /dev/null
+++ b/static/icon_cache/disc_None_None_None.svg
@@ -0,0 +1,11 @@
+
+