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 20C601FF0C0 for ; Wed, 04 Nov 2026 20:06:51 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 3EDBD21811; 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 15/38] tests: container: verify volume and bind mountpoints Date: Wed, 4 Nov 2026 20:03:22 +0100 Message-ID: <20261104190345.2573464-16-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: 1793819034668 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.222 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: OTK3Z672D7DJWZR3UCYPAF5CMO2NCD2P X-Message-ID-Hash: OTK3Z672D7DJWZR3UCYPAF5CMO2NCD2P 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: Cover volume mountpoints on LVM-thin and on a directory storage as well as a bind mountpoint from the host. All of them must be mounted in the running container, an online resize must grow the file system, and deleting a mountpoint must keep its volume as unused until that is deleted as well. Also destroy the container after its mountpoint storage was removed from the storage configuration, which must succeed and only warn about the volume it could not delete. Signed-off-by: Michael Köppl --- tests/pve_ct_mountpoints.pl | 198 ++++++++++++++++++++++++++++++++++++ tests/testcases.ron | 9 ++ 2 files changed, 207 insertions(+) create mode 100755 tests/pve_ct_mountpoints.pl diff --git a/tests/pve_ct_mountpoints.pl b/tests/pve_ct_mountpoints.pl new file mode 100755 index 0000000..d6cbe70 --- /dev/null +++ b/tests/pve_ct_mountpoints.pl @@ -0,0 +1,198 @@ +#!/usr/bin/perl + +=head1 NAME + +C - Test volume and bind mountpoints of a container. + +=head1 DESCRIPTION + +This testcase creates an unprivileged container with a volume mountpoint on +local-lvm, a bind mountpoint from the host, and a volume mountpoint on a +temporary directory storage. It verifies that all of them are mounted inside +the running container, resizes the local-lvm mountpoint online, and detaches +and deletes it again. + +Finally, the directory storage is removed from the storage configuration while +the container still has a mountpoint on it. Destroying the container 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(CONTAINER_TEMPLATE 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 = 'ct-mp'; +my $dir_storage_path = '/var/lib/ct-mp-store'; +my $bind_source = '/var/lib/ct-bind'; +my $bind_marker = 'test'; + +my $test_container = { + vmid => $client->get('/cluster/nextid'), + node => $node, + ostemplate => CONTAINER_TEMPLATE, + rootfs => 'local-lvm:2', + storage => 'local-lvm', + memory => 256, + unprivileged => 1, + features => 'nesting=1', + hostname => 'ct-mountpoints', + mp0 => 'local-lvm:1,mp=/data', + mp1 => "$bind_source,mp=/bind", + mp2 => "$dir_storage:1,mp=/dirstore", +}; + +my $vmid = $test_container->{vmid}; +my $mp0_volid; + +my $volume_exists = sub { + my ($storage, $volid) = @_; + my $content = $client->get("/nodes/$node/storage/$storage/content", { vmid => $vmid }); + return grep { $_->{volid} eq $volid } $content->@*; +}; + +Proxmox::Test::Testcase->new('pve_ct_mountpoints')->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 => 'rootdir', + }, + ); + }, + sub { + eval { $client->delete("/storage/$dir_storage") }; + $pve->run_command("rm -rf $dir_storage_path"); + }, +)->step( + 'create bind mount source', + sub { + $pve->run_command("mkdir -p $bind_source && echo $bind_marker > $bind_source/marker"); + }, + sub { + $pve->run_command("rm -rf $bind_source"); + }, +)->step( + 'create container', + sub { + my $upid = $client->post("/nodes/$node/lxc", $test_container); + $pve->task_ok($upid, "created container $vmid") + or die "creating container $vmid failed\n"; + }, + sub { + $pve->destroy_guest('lxc', $vmid); + }, +)->step( + 'verify mountpoint config', + sub { + my $conf = $client->get("/nodes/$node/lxc/$vmid/config", {}); + + like( + $conf->{mp0} // '', + qr/^local-lvm:[^,]+,mp=\/data\b/, + 'mp0 is a volume on local-lvm', + ); + like($conf->{mp1} // '', qr/^\Q$bind_source\E,mp=\/bind\b/, 'mp1 is a bind mount'); + like( + $conf->{mp2} // '', + qr/^\Q$dir_storage\E:[^,]+,mp=\/dirstore\b/, + "mp2 is a volume on $dir_storage", + ); + + ($mp0_volid) = ($conf->{mp0} // '') =~ m/^([^,]+)/; + }, +)->step( + 'start container', + sub { + $pve->start_guest('lxc', $vmid); + pass("container $vmid reports status 'running'"); + }, +)->step( + 'verify mountpoints inside container', + sub { + for my $path ('/data', '/bind', '/dirstore') { + is( + $pve->ct_exec($vmid, "findmnt -n -o TARGET $path"), + "$path\n", + "$path is mounted", + ); + } + + is( + $pve->ct_exec($vmid, 'cat /bind/marker'), + "$bind_marker\n", + 'bind mount shows the host file', + ); + + $pve->ct_exec($vmid, "sh -c 'echo e2e > /data/marker'"); + is($pve->ct_exec($vmid, 'cat /data/marker'), "e2e\n", 'volume mountpoint is writable'); + }, +)->step( + 'resize volume mountpoint online', + sub { + my $upid = + $client->put("/nodes/$node/lxc/$vmid/resize", { disk => 'mp0', size => '+1G' }); + $pve->task_ok($upid, 'resized mp0 by 1 GiB') or die "resizing mp0 failed\n"; + + my $conf = $client->get("/nodes/$node/lxc/$vmid/config", {}); + like($conf->{mp0} // '', qr/\bsize=2G\b/, 'config reports the new mp0 size'); + + # File system on the mountpoint grows as part of the resize, check that the container sees + # the change right away. + my ($size_mib) = $pve->ct_exec($vmid, 'df -B1M --output=size /data') =~ m/(\d+)\s*$/; + cmp_ok($size_mib // 0, '>', 1024, 'file system on /data has grown beyond 1 GiB'); + }, +)->step( + 'stop container', + sub { + $pve->stop_guest('lxc', $vmid); + }, +)->step( + 'detach and delete volume mountpoint', + sub { + $client->put("/nodes/$node/lxc/$vmid/config", { delete => 'mp0' }); + + my $conf = $client->get("/nodes/$node/lxc/$vmid/config", {}); + ok(!defined($conf->{mp0}), 'mp0 is gone from the config'); + my ($unused) = grep { m/^unused\d+$/ && $conf->{$_} eq $mp0_volid } keys $conf->%*; + ok(defined($unused), 'the mp0 volume is kept as an unused volume'); + + $client->put("/nodes/$node/lxc/$vmid/config", { delete => $unused }) + if defined($unused); + + $conf = $client->get("/nodes/$node/lxc/$vmid/config", {}); + ok(!(grep { m/^unused\d+$/ } keys $conf->%*), 'no unused volume is left in the config'); + ok(!$volume_exists->('local-lvm', $mp0_volid), 'the mp0 volume is deleted'); + }, +)->step( + 'destroy container with unavailable mountpoint storage', + sub { + $client->delete("/storage/$dir_storage"); + + my $upid = $client->delete("/nodes/$node/lxc/$vmid", {}); + my $status = $pve->wait_for_task($upid, 1); + like( + $status, + qr/^WARNINGS: \d+$/, + 'destroying the container succeeds with warnings about the unavailable storage', + ); + + ok(!$pve->guest_exists('lxc', $vmid), "container $vmid is gone"); + }, +)->run(); + +done_testing(); diff --git a/tests/testcases.ron b/tests/testcases.ron index 391eb09..79841ca 100644 --- a/tests/testcases.ron +++ b/tests/testcases.ron @@ -151,6 +151,15 @@ TestcaseConfig( ], tag: "extended", ), + Testcase( + name: "pve_ct_mountpoints", + run: "./tests/pve_ct_mountpoints.pl", + timeout: 600, + instances: [ + "pve1", + ], + tag: "extended", + ), Testcase( name: "pve_ha_resource_config", run: "./tests/pve_ha_resource_config.pl", -- 2.47.3