From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 0F1861FF0C0 for ; Wed, 04 Nov 2026 20:07:02 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id BFDD62183A; Wed, 04 Nov 2026 20:05:24 +0100 (CET) From: =?UTF-8?q?Michael=20K=C3=B6ppl?= To: pve-devel@lists.proxmox.com Subject: [PATCH e2e-tests 21/38] tests: vm: verify adding, resizing, and removing disks Date: Wed, 4 Nov 2026 20:03:28 +0100 Message-ID: <20261104190345.2573464-22-m.koeppl@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261104190345.2573464-1-m.koeppl@proxmox.com> References: <20261104190345.2573464-1-m.koeppl@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793819035300 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.220 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: P2IWTTWNLVOIMPMW5U5ZWPL7D4NK66OA X-Message-ID-Hash: P2IWTTWNLVOIMPMW5U5ZWPL7D4NK66OA X-MailFrom: m.koeppl@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Check that disks on local-lvm and on a directory storage are attached to the running VM, and that a disk added while the VM runs is hotplugged instead of left pending. Resize it online, then remove it again, which must unplug it and keep its volume as unused until that is deleted explicitly. As for containers, destroying a VM whose disk storage was removed from the storage configuration must succeed with warnings. Signed-off-by: Michael Köppl --- tests/pve_vm_disks.pl | 195 ++++++++++++++++++++++++++++++++++++++++++ tests/testcases.ron | 9 ++ 2 files changed, 204 insertions(+) create mode 100755 tests/pve_vm_disks.pl diff --git a/tests/pve_vm_disks.pl b/tests/pve_vm_disks.pl new file mode 100755 index 0000000..1ff7704 --- /dev/null +++ b/tests/pve_vm_disks.pl @@ -0,0 +1,195 @@ +#!/usr/bin/perl + +=head1 NAME + +C - Test adding, resizing, and removing disks of a VM. + +=head1 DESCRIPTION + +This testcase creates a VM with a disk on local-lvm and a disk on a temporary +directory storage and verifies that both are attached to the running VM. While +the VM is running, it adds another disk on local-lvm, which must be hotplugged, +resizes it, and removes it again, which must unplug it and keep its volume as an +unused volume until it is deleted explicitly. + +Finally, the directory storage is removed from the storage configuration while +the VM still has a disk on it. Destroying the VM must still succeed and only +warn about the volume it could not delete. + +=cut + +use strict; +use warnings; + +use Test::More; + +use lib '.'; +use Proxmox::Test::Helper qw(get_pve_instance); +use Proxmox::Test::Testcase; + +my $pve = get_pve_instance('pve1'); +my $node = $pve->get_nodename(); +my $client = $pve->client(); + +my $dir_storage = 'vm-disks'; +my $dir_storage_path = '/var/lib/vm-disks-store'; + +my $test_vm = { + vmid => $client->get('/cluster/nextid'), + name => 'vm-disks', + memory => 256, + scsihw => 'virtio-scsi-pci', + scsi0 => 'local-lvm:1', + scsi1 => "$dir_storage:1", +}; + +my $vmid = $test_vm->{vmid}; +my $hotplug_volid; + +my $vm_config = sub { + return $client->get("/nodes/$node/qemu/$vmid/config", {}); +}; + +my $attached_disks = sub { + return $client->get("/nodes/$node/qemu/$vmid/status/current", {})->{blockstat} // {}; +}; + +my $pending = sub { + my ($key) = @_; + my ($entry) = + grep { $_->{key} eq $key } $client->get("/nodes/$node/qemu/$vmid/pending", {})->@*; + return $entry && (defined($entry->{pending}) || $entry->{delete}); +}; + +my $volume_size = sub { + my ($storage, $volid) = @_; + my $content = $client->get("/nodes/$node/storage/$storage/content", { vmid => $vmid }); + my ($volume) = grep { $_->{volid} eq $volid } $content->@*; + return $volume ? $volume->{size} : undef; +}; + +my $update_config = sub { + my ($params, $name) = @_; + my $upid = $client->post("/nodes/$node/qemu/$vmid/config", $params); + $pve->task_ok($upid, $name) or die "updating the config of VM $vmid failed\n"; +}; + +Proxmox::Test::Testcase->new('pve_vm_disks')->with([{}])->step( + 'create directory storage', + sub { + $pve->run_command("mkdir -p $dir_storage_path"); + $client->post( + '/storage', + { + storage => $dir_storage, + type => 'dir', + path => $dir_storage_path, + content => 'images', + }, + ); + }, + sub { + eval { $client->delete("/storage/$dir_storage") }; + $pve->run_command("rm -rf $dir_storage_path"); + }, +)->step( + 'create VM', + sub { + my $upid = $client->post("/nodes/$node/qemu", $test_vm); + $pve->task_ok($upid, "created VM $vmid") or die "creating VM $vmid failed\n"; + }, + sub { + $pve->destroy_guest('qemu', $vmid); + }, +)->step( + 'verify disk config', + sub { + my $conf = $vm_config->(); + + like($conf->{scsi0} // '', qr/^local-lvm:[^,]+,.*\bsize=1G\b/, 'scsi0 is on local-lvm'); + like( + $conf->{scsi1} // '', + qr/^\Q$dir_storage\E:[^,]+,.*\bsize=1G\b/, + "scsi1 is on $dir_storage", + ); + }, +)->step( + 'start VM', + sub { + $pve->start_guest('qemu', $vmid); + + my $attached = $attached_disks->(); + ok($attached->{scsi0}, 'scsi0 is attached to the running VM'); + ok($attached->{scsi1}, 'scsi1 is attached to the running VM'); + }, +)->step( + 'hotplug disk', + sub { + $update_config->({ scsi2 => 'local-lvm:1' }, 'added scsi2 to the running VM'); + + ($hotplug_volid) = ($vm_config->()->{scsi2} // '') =~ m/^([^,]+)/; + ok(defined($hotplug_volid), 'config has scsi2'); + ok(!$pending->('scsi2'), 'adding scsi2 is not pending'); + ok($attached_disks->()->{scsi2}, 'scsi2 is attached to the running VM'); + }, +)->step( + 'resize hotplugged disk', + sub { + my $upid = + $client->put("/nodes/$node/qemu/$vmid/resize", { disk => 'scsi2', size => '+1G' }); + $pve->task_ok($upid, 'resized scsi2 by 1 GiB') or die "resizing scsi2 failed\n"; + + like( + $vm_config->()->{scsi2} // '', + qr/\bsize=2G\b/, + 'config reports the new scsi2 size', + ); + is( + $volume_size->('local-lvm', $hotplug_volid), + 2 * 1024 * 1024 * 1024, + 'scsi2 volume has the new size', + ); + }, +)->step( + 'remove hotplugged disk', + sub { + $update_config->({ delete => 'scsi2' }, 'removed scsi2 from the running VM'); + + my $conf = $vm_config->(); + ok(!defined($conf->{scsi2}), 'scsi2 is gone from the config'); + ok(!$pending->('scsi2'), 'removing scsi2 is not pending'); + ok(!$attached_disks->()->{scsi2}, 'scsi2 is no longer attached to the running VM'); + + my ($unused) = grep { m/^unused\d+$/ && $conf->{$_} eq $hotplug_volid } keys $conf->%*; + ok(defined($unused), 'the scsi2 volume is kept as an unused volume'); + + $update_config->({ delete => $unused }, "deleted $unused") if defined($unused); + + $conf = $vm_config->(); + ok(!(grep { m/^unused\d+$/ } keys $conf->%*), 'no unused volume is left in the config'); + ok(!defined($volume_size->('local-lvm', $hotplug_volid)), + 'the scsi2 volume is deleted'); + }, +)->step( + 'stop VM', + sub { + $pve->stop_guest('qemu', $vmid); + }, +)->step( + 'destroy VM with unavailable disk storage', + sub { + $client->delete("/storage/$dir_storage"); + + my $upid = $client->delete("/nodes/$node/qemu/$vmid", {}); + my $status = $pve->wait_for_task($upid, 1); + like( + $status, + qr/^WARNINGS: \d+$/, + 'destroying the VM succeeds with warnings about the unavailable storage', + ); + + ok(!$pve->guest_exists('qemu', $vmid), "VM $vmid is gone"); + }, +)->run(); + +done_testing(); diff --git a/tests/testcases.ron b/tests/testcases.ron index e7f8917..b1a35b6 100644 --- a/tests/testcases.ron +++ b/tests/testcases.ron @@ -187,6 +187,15 @@ TestcaseConfig( ], tag: "extended", ), + Testcase( + name: "pve_vm_disks", + run: "./tests/pve_vm_disks.pl", + timeout: 600, + instances: [ + "pve1", + ], + tag: "extended", + ), Testcase( name: "pve_vm_snapshot_clone", run: "./tests/pve_vm_snapshot_clone.pl", -- 2.47.3