From: Thomas Lamprecht <t.lamprecht@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH v3 storage 09/76] lvm: refuse to create a volume group on a device that holds data
Date: Fri, 6 Nov 2026 00:30:44 +0100 [thread overview]
Message-ID: <20261105233336.2178064-10-t.lamprecht@proxmox.com> (raw)
In-Reply-To: <20261105233336.2178064-1-t.lamprecht@proxmox.com>
Base-volume initialization must not destroy existing file systems,
partition tables, or volume groups. Probe immediately before writing
with bounded commands, refusing creation if a probe fails.
Read PV membership without LVM hints and require explicit evidence that
a PV is orphaned. Missing group metadata, including the unknown-name
sentinel, is not enough. A PV already in the requested group counts as
created; a confirmed orphan left by a failed attempt can be initialized
again.
Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
---
src/PVE/Diskmanage.pm | 16 ++
src/PVE/Storage/LVMPlugin.pm | 106 +++++++-
src/test/Makefile | 5 +-
src/test/lvm-fixtures/README.md | 20 ++
.../lab1-nomda-orphan-derived.colon | 2 +
src/test/lvm-fixtures/lab1-nomda-orphan.txt | 23 ++
src/test/run_test_lvm_create.pl | 243 ++++++++++++++++++
7 files changed, 407 insertions(+), 8 deletions(-)
create mode 100644 src/test/lvm-fixtures/README.md
create mode 100644 src/test/lvm-fixtures/lab1-nomda-orphan-derived.colon
create mode 100644 src/test/lvm-fixtures/lab1-nomda-orphan.txt
create mode 100755 src/test/run_test_lvm_create.pl
diff --git a/src/PVE/Diskmanage.pm b/src/PVE/Diskmanage.pm
index d5bd1473..4185d2f7 100644
--- a/src/PVE/Diskmanage.pm
+++ b/src/PVE/Diskmanage.pm
@@ -73,6 +73,22 @@ sub init_disk {
return 1;
}
+sub parse_wipefs_signatures {
+ my ($json) = @_;
+
+ my $probe = decode_json($json);
+ die "Malformed wipefs output\n"
+ if ref($probe) ne 'HASH' || ref($probe->{signatures}) ne 'ARRAY';
+ for my $signature ($probe->{signatures}->@*) {
+ die "Malformed wipefs signature\n"
+ if ref($signature) ne 'HASH'
+ || !defined($signature->{type})
+ || ref($signature->{type})
+ || $signature->{type} !~ m/^[a-zA-Z0-9_.+-]+$/;
+ }
+ return $probe->{signatures};
+}
+
sub disk_is_used {
my ($disk) = @_;
diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm
index eb9806b2..26908d7e 100644
--- a/src/PVE/Storage/LVMPlugin.pm
+++ b/src/PVE/Storage/LVMPlugin.pm
@@ -11,6 +11,7 @@ use JSON;
use List::Util qw(max);
use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
+use PVE::Diskmanage;
use PVE::Exception qw(raise_param_exc);
use PVE::Format qw(render_bytes render_duration);
use PVE::INotify;
@@ -55,25 +56,36 @@ my sub fork_cleanup_worker {
}
sub lvm_pv_info {
- my ($device) = @_;
+ my ($device, $require_pv) = @_;
die "no device specified" if !$device;
my $has_label = 0;
+ my $file_result;
- my $cmd = ['/usr/bin/file', '-L', '-s', $device];
+ my $cmd = ['/usr/bin/file', '-E', '-L', '-s', $device];
run_command(
$cmd,
+ timeout => 10,
outfunc => sub {
my $line = shift;
+ die "unable to probe '$device' - malformed file output\n"
+ if defined($file_result) || $line !~ m/^\Q$device\E:\s+\S/;
+ $file_result = $line;
$has_label = 1 if $line =~ m/LVM2/;
},
);
+ die "unable to probe '$device' - empty file output\n" if !defined($file_result);
- return undef if !$has_label;
+ return undef if !$has_label && !$require_pv;
$cmd = [
'/sbin/pvs',
+ '--nohints',
+ '--readonly',
+ '--devices',
+ $device,
+ '--binary',
'--separator',
':',
'--noheadings',
@@ -82,19 +94,30 @@ sub lvm_pv_info {
'--unbuffered',
'--nosuffix',
'--options',
- 'pv_name,pv_size,vg_name,pv_uuid',
+ 'pv_name,pv_size,vg_name,pv_uuid,pv_in_use',
$device,
];
my $pvinfo;
run_command(
$cmd,
+ timeout => 10,
outfunc => sub {
my $line = shift;
$line = trim($line);
- my ($pvname, $size, $vgname, $uuid) = split(':', $line);
+ my ($pvname, $size, $vgname, $uuid, $in_use, @extra) = split(':', $line, -1);
+ # LVM names a device-mapper device by its /dev/mapper link, not the dm node
+ my $listed = defined($pvname) ? abs_path($pvname) // $pvname : '';
+ die "unable to probe '$device' - malformed pvs output\n"
+ if @extra
+ || !defined($in_use)
+ || $in_use !~ m/^(?:0|1|unknown|)$/
+ || $listed ne (abs_path($device) // $device)
+ || $size !~ m/^\d+(?:\.\d+)?$/
+ || ($vgname ne '[unknown]' && $vgname !~ m/^[a-zA-Z0-9+_.-]*$/)
+ || $uuid !~ m/^[a-zA-Z0-9]{6}(?:-[a-zA-Z0-9]{4}){5}-[a-zA-Z0-9]{6}$/;
die "found multiple pvs entries for device '$device'\n"
if $pvinfo;
@@ -104,10 +127,12 @@ sub lvm_pv_info {
size => int($size),
vgname => $vgname,
uuid => $uuid,
+ in_use => $in_use,
};
},
);
+ die "unable to probe '$device' - no PV found\n" if !$pvinfo;
return $pvinfo;
}
@@ -124,13 +149,80 @@ sub clear_first_sector {
sub lvm_create_volume_group {
my ($device, $vgname, $shared) = @_;
- my $res = lvm_pv_info($device);
+ my $requested_device = $device;
+ $device = PVE::Diskmanage::verify_blockdev_path($device);
+ my $refuse_data = sub {
+ my (@types) = @_;
+ die "device '$device' contains data ("
+ . join(', ', sort @types)
+ . ") - wipe it first if it is no longer needed, for example with 'pvesh set /nodes/"
+ . PVE::INotify::nodename()
+ . "/disks/wipedisk --disk $requested_device'\n";
+ };
- if ($res->{vgname}) {
+ my $sysdir = '/sys/class/block/' . basename($device);
+ my $uuid = file_read_firstline("$sysdir/dm/uuid");
+ my $mounted = PVE::Diskmanage::is_mounted($device);
+ die "unable to create volume group - '$mounted' is mounted\n" if $mounted;
+ if (my $held = PVE::Diskmanage::has_holder($device)) {
+ # kpartx maps the partitions of a device-mapper device as its holders. Such holders indicate
+ # a partition table, not a user.
+ my @holders;
+ PVE::Tools::dir_glob_foreach("$sysdir/holders", '[^.].*', sub { push @holders, $_[0] });
+ my $partitions = defined($uuid) && @holders && !grep {
+ (file_read_firstline("/sys/class/block/$_/dm/uuid") // '') !~ m/^part\d+-\Q$uuid\E$/
+ } @holders;
+ $refuse_data->('partitions') if $partitions;
+ die "unable to create volume group - another block device uses '$held'\n";
+ }
+
+ my $size = file_read_firstline("$sysdir/size");
+ die "unable to probe '$device' - device is unavailable\n"
+ if !defined($size) || $size !~ m/^\d+$/ || !$size;
+ die "unable to probe '$device' - missing device mapper identity\n"
+ if basename($device) =~ m/^dm-\d+$/ && !defined($uuid);
+ if (defined($uuid) && $uuid =~ m/^mpath-/) {
+ # A queueing map without a running path can stall even a read-only probe.
+ my $running = 0;
+ PVE::Tools::dir_glob_foreach(
+ "$sysdir/slaves",
+ '[^.].*',
+ sub {
+ my $state = file_read_firstline("$sysdir/slaves/$_[0]/device/state");
+ $running = 1 if defined($state) && $state eq 'running';
+ },
+ );
+ die "unable to probe '$device' - multipath map has no running path\n" if !$running;
+ }
+
+ my $output = '';
+ run_command(
+ ['wipefs', '--no-act', '--json', $device],
+ timeout => 10,
+ outfunc => sub { $output .= "$_[0]\n" },
+ );
+ my $probe = eval { PVE::Diskmanage::parse_wipefs_signatures($output) };
+ if (my $err = $@) {
+ my $kind = $err eq "Malformed wipefs signature\n" ? 'signature' : 'output';
+ die "unable to probe '$device' - malformed wipefs $kind\n";
+ }
+ my %signatures = map { $_->{type} => 1 } $probe->@*;
+ my $res = lvm_pv_info($device, $signatures{LVM2_member});
+
+ # A PV without metadata copies can belong to a VG whose other devices are not visible. The
+ # sentinel is not a VG name or orphan proof, even if the use flag contradicts it.
+ die "device '$device' belongs to a volume group whose metadata is not visible on this node\n"
+ if $res
+ && ($res->{vgname} eq '[unknown]' || (!$res->{vgname} && $res->{in_use} ne '0'));
+
+ if ($res && $res->{vgname}) {
return if $res->{vgname} eq $vgname; # already created
die "device '$device' is already used by volume group '$res->{vgname}'\n";
}
+ delete $signatures{LVM2_member} if $res && !$res->{vgname};
+ $refuse_data->(keys %signatures) if %signatures;
+
clear_first_sector($device); # else pvcreate fails
# we use --metadatasize 250k, which reseults in "pe_start = 512"
diff --git a/src/test/Makefile b/src/test/Makefile
index 24abdebc..1cc8f00e 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -1,6 +1,6 @@
all: test
-test: test_iscsi test_zfspoolplugin test_lvmplugin test_lvm_lock_retry test_lvm_base_volume test_lvm_hints test_lvm_status test_status_command test_disklist test_bwlimit test_plugin test_ovf test_volume_access test_san_libraries
+test: test_iscsi test_zfspoolplugin test_lvmplugin test_lvm_create test_lvm_lock_retry test_lvm_base_volume test_lvm_hints test_lvm_status test_status_command test_disklist test_bwlimit test_plugin test_ovf test_volume_access test_san_libraries
test_zfspoolplugin: run_test_zfspoolplugin.pl
./run_test_zfspoolplugin.pl
@@ -9,6 +9,9 @@ test_lvmplugin: run_test_lvmplugin.pl run_test_lvm_throughput.pl
./run_test_lvmplugin.pl
./run_test_lvm_throughput.pl
+test_lvm_create: run_test_lvm_create.pl
+ ./run_test_lvm_create.pl
+
test_lvm_lock_retry: run_test_lvm_lock_retry.pl
./run_test_lvm_lock_retry.pl
diff --git a/src/test/lvm-fixtures/README.md b/src/test/lvm-fixtures/README.md
new file mode 100644
index 00000000..d024eacf
--- /dev/null
+++ b/src/test/lvm-fixtures/README.md
@@ -0,0 +1,20 @@
+# LVM missing-metadata fixtures
+
+`lab1-nomda-orphan.txt` is the unchanged lvm2 2.03.31 lab capture from
+`netapp-lab-20ee/drills/lvm-fixtures/lab1-nomda-orphan.txt`. It includes a genuine orphan and a PV
+with no metadata copies whose metadata-bearing peer is not visible on the observing node.
+
+The wipe and scan tests extract the JSON between `== devices B,C` and `== full nohints` without
+rewriting it. Both log warnings remain in the command output supplied to the parsers. The capture
+also contains a full-report row summary and hinted output; those sections are preserved for
+provenance, not passed to the JSON parsers.
+
+`lab1-nomda-orphan-derived.colon` is a derived create-probe fixture, not captured command output.
+It preserves the device names, VG names, UUIDs, and binary use flags from the captured JSON. The
+`pv_size` column is a synthetic 1024 KiB because the captured command did not request a size.
+Each line represents a separate device-restricted create probe, not a combined command response.
+
+These fixtures establish the `[unknown]` VG spelling with use flag `1` and an empty VG with use
+flag `0`. Contradictory flags, disappeared aliases, duplicate UUIDs, and malformed fields use
+synthetic tests. Neither additional VG sentinels nor a `[unknown]` device name are established by
+this capture.
diff --git a/src/test/lvm-fixtures/lab1-nomda-orphan-derived.colon b/src/test/lvm-fixtures/lab1-nomda-orphan-derived.colon
new file mode 100644
index 00000000..44fb154e
--- /dev/null
+++ b/src/test/lvm-fixtures/lab1-nomda-orphan-derived.colon
@@ -0,0 +1,2 @@
+/dev/mapper/360014056dbd1054071a4de3bd8460ae6:1024::YNV8yf-k7Ty-yCya-Jt9U-9Q5P-OMgB-EviHBt:0
+/dev/mapper/36001405c5a6706aeb1b40c89ba3363eb:1024:[unknown]:EqLIyc-SBrd-vjIE-TB66-ZRRA-JXvu-yHq011:1
diff --git a/src/test/lvm-fixtures/lab1-nomda-orphan.txt b/src/test/lvm-fixtures/lab1-nomda-orphan.txt
new file mode 100644
index 00000000..de1cd259
--- /dev/null
+++ b/src/test/lvm-fixtures/lab1-nomda-orphan.txt
@@ -0,0 +1,23 @@
+== devices B,C
+ {
+ "report": [
+ {
+ "pv": [
+ {"pv_name":"/dev/mapper/360014056dbd1054071a4de3bd8460ae6", "vg_name":"", "pv_uuid":"YNV8yf-k7Ty-yCya-Jt9U-9Q5P-OMgB-EviHBt", "pv_in_use":"0"},
+ {"pv_name":"/dev/mapper/36001405c5a6706aeb1b40c89ba3363eb", "vg_name":"[unknown]", "pv_uuid":"EqLIyc-SBrd-vjIE-TB66-ZRRA-JXvu-yHq011", "pv_in_use":"1"}
+ ]
+ }
+ ]
+ ,
+ "log": [
+ {"log_seq_num":"1", "log_type":"warn", "log_context":"processing", "log_object_type":"orphan", "log_object_name":"lvm2", "log_object_id":"", "log_object_group":"", "log_object_group_id":"", "log_message":"WARNING: PV /dev/mapper/36001405c5a6706aeb1b40c89ba3363eb is marked in use but no VG was found using it.", "log_errno":"0", "log_ret_code":"0"},
+ {"log_seq_num":"2", "log_type":"warn", "log_context":"processing", "log_object_type":"orphan", "log_object_name":"lvm2", "log_object_id":"", "log_object_group":"", "log_object_group_id":"", "log_message":"WARNING: PV /dev/mapper/36001405c5a6706aeb1b40c89ba3363eb might need repairing.", "log_errno":"0", "log_ret_code":"0"}
+ ]
+ }
+== full nohints (B, C rows)
+[{'pv_name': '/dev/mapper/360014056dbd1054071a4de3bd8460ae6', 'vg_name': '', 'pv_uuid': 'YNV8yf-k7Ty-yCya-Jt9U-9Q5P-OMgB-EviHBt', 'pv_in_use': '0'}, {'pv_name': '/dev/mapper/36001405c5a6706aeb1b40c89ba3363eb', 'vg_name': '[unknown]', 'pv_uuid': 'EqLIyc-SBrd-vjIE-TB66-ZRRA-JXvu-yHq011', 'pv_in_use': '1'}]
+
+== hinted B
+ WARNING: PV /dev/mapper/36001405c5a6706aeb1b40c89ba3363eb is marked in use but no VG was found using it.
+ WARNING: PV /dev/mapper/36001405c5a6706aeb1b40c89ba3363eb might need repairing.
+ /dev/mapper/36001405c5a6706aeb1b40c89ba3363eb [unknown] used
diff --git a/src/test/run_test_lvm_create.pl b/src/test/run_test_lvm_create.pl
new file mode 100755
index 00000000..2982cda4
--- /dev/null
+++ b/src/test/run_test_lvm_create.pl
@@ -0,0 +1,243 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use FindBin qw($Bin);
+use JSON;
+use Test::MockModule;
+use Test::More;
+
+use lib ('.', '..');
+use PVE::Storage::LVMPlugin;
+
+my $lvm = Test::MockModule->new('PVE::Storage::LVMPlugin');
+my $disk = Test::MockModule->new('PVE::Diskmanage');
+my $tools = Test::MockModule->new('PVE::Tools');
+my $inotify = Test::MockModule->new('PVE::INotify');
+$inotify->mock(nodename => sub { 'testnode' });
+my ($mounted, $held, $map, $running, $size, $signatures, $label, $pv, $fail, $bad_json);
+my ($holder_uuid, $pv_alias);
+my @writes;
+my @probes;
+$disk->mock(
+ verify_blockdev_path => sub { '/dev/sda' },
+ is_mounted => sub { $mounted },
+ has_holder => sub { $held },
+);
+$tools->mock(dir_glob_foreach => sub { $_[2]->($_[0] =~ m{/holders$} ? 'dm-5' : 'sdb') });
+$lvm->mock(
+ file_read_firstline => sub {
+ return $size if $_[0] =~ m{/size$};
+ return $holder_uuid if $_[0] =~ m{/dm-5/dm/uuid$};
+ return $map ? 'mpath-3600abc' : undef if $_[0] =~ m{/uuid$};
+ return $running ? 'running' : 'offline' if $_[0] =~ m{/state$};
+ die "unexpected read $_[0]";
+ },
+ clear_first_sector => sub { push @writes, 'clear' },
+ # pvs lists a multipath map by its /dev/mapper link
+ abs_path => sub { $_[0] eq $pv_alias ? '/dev/sda' : $_[0] },
+ run_command => sub {
+ my ($cmd, %opt) = @_;
+ my $tool = $cmd->[0];
+ if ($tool =~ m{(?:file|pvs|wipefs)$}) {
+ push @probes, $tool;
+ is($opt{timeout}, 10, "$tool has a timeout");
+ die "$tool failed or timed out\n" if ($fail // '') eq $tool;
+ if ($tool eq 'wipefs') {
+ $opt{outfunc}->($bad_json // encode_json({ signatures => $signatures }));
+ } elsif ($tool eq '/usr/bin/file') {
+ $opt{outfunc}->($label);
+ } else {
+ is_deeply(
+ [@$cmd[1 .. 6]],
+ [
+ '--nohints',
+ '--readonly',
+ '--devices',
+ '/dev/sda',
+ '--binary',
+ '--separator',
+ ],
+ 'PV lookup reads the candidate without hints or activation',
+ );
+ ok(
+ grep($_ eq 'pv_name,pv_size,vg_name,pv_uuid,pv_in_use', @$cmd),
+ 'PV use requested',
+ );
+ $opt{outfunc}->($pv) if defined($pv);
+ }
+ } else {
+ push @writes, $tool;
+ }
+ },
+);
+
+my sub reset_case {
+ ($mounted, $held, $map, $running, $size) = (undef, undef, 0, 1, 1024);
+ $signatures = [];
+ $label = '/dev/sda: data';
+ $pv = undef;
+ $pv_alias = '/dev/mapper/3600abc';
+ $fail = undef;
+ $bad_json = undef;
+ @writes = ();
+ @probes = ();
+}
+
+my sub create {
+ eval { PVE::Storage::LVMPlugin::lvm_create_volume_group('/dev/disk/by-id/test', 'test', 1) };
+ return $@;
+}
+
+reset_case();
+is(create(), '', 'blank device accepted');
+is_deeply(\@writes, ['clear', '/sbin/pvcreate', '/sbin/vgcreate'], 'writes only after probes');
+
+for my $case (
+ ['mount', sub { $mounted = '/dev/sda1' }, qr/is mounted/],
+ ['holder', sub { $held = '/dev/sda' }, qr{another block device uses '/dev/sda'}],
+ ['unavailable', sub { $size = 0 }, qr/unavailable/],
+ ['lost paths', sub { $map = 1; $running = 0 }, qr/no running path/],
+) {
+ reset_case();
+ $case->[1]->();
+ like(create(), $case->[2], "$case->[0] refused");
+ is_deeply(\@probes, [], "$case->[0]: no device reads");
+ is_deeply(\@writes, [], "$case->[0]: no writes");
+}
+
+reset_case();
+$map = 1;
+is(create(), '', 'map with a running slave accepted');
+
+reset_case();
+$signatures = [{ type => 'gpt' }, { type => 'ext4' }];
+is(
+ create(),
+ "device '/dev/sda' contains data (ext4, gpt) - wipe it first if it is no longer needed,"
+ . " for example with 'pvesh set /nodes/testnode/disks/wipedisk --disk /dev/disk/by-id/test'\n",
+ 'refusal names signatures, local node, and the caller device reference',
+);
+is_deeply(\@writes, [], 'signatures never overwritten');
+
+my $uuid = 'aaaaaa-bbbb-cccc-dddd-eeee-ffff-gggggg';
+for my $vg ('test', 'other', 'unknown', '') {
+ reset_case();
+ $signatures = [{ type => 'LVM2_member' }];
+ $label = '/dev/sda: LVM2 PV';
+ $pv = "/dev/sda:1024:$vg:$uuid:" . ($vg ? '1' : '0');
+ if ($vg && $vg ne 'test') {
+ like(create(), qr/already used by volume group/, 'foreign VG refused');
+ } else {
+ is(create(), '', $vg ? 'same VG accepted' : 'orphan PV accepted');
+ }
+ is(scalar(@writes), $vg ? 0 : 3, 'only the orphan PV is initialized');
+}
+
+for my $vg ('', '[unknown]') {
+ for my $used ('1', '', 'unknown', $vg ? ('0') : ()) {
+ reset_case();
+ $signatures = [{ type => 'LVM2_member' }];
+ $pv = "/dev/mapper/3600abc:1024:$vg:$uuid:$used";
+ like(
+ create(),
+ qr/belongs to a volume group whose metadata is not visible/,
+ "VG '$vg' with use flag '$used' refused before named-VG handling",
+ );
+ is_deeply(\@writes, [], 'uncertain orphan never initialized');
+ }
+}
+reset_case();
+$signatures = [{ type => 'LVM2_member' }];
+$pv = "/dev/sda:1024:[unknown]:$uuid:1";
+eval { PVE::Storage::LVMPlugin::lvm_create_volume_group('/dev/sda', '[unknown]', 1) };
+like($@, qr/metadata is not visible/, 'sentinel cannot match the requested VG as already created');
+is_deeply(\@writes, [], 'sentinel match causes no writes');
+
+subtest 'derived colon-format no-metadata PV and orphan probes' => sub {
+ my $fixture = PVE::Tools::file_get_contents(
+ "$Bin/lvm-fixtures/lab1-nomda-orphan-derived.colon",
+ );
+ my @lines = split('\n', $fixture);
+ is(scalar(@lines), 2, 'derived fixture contains one probe for each captured PV');
+ for my $line (@lines) {
+ reset_case();
+ $signatures = [{ type => 'LVM2_member' }];
+ $label = '/dev/sda: LVM2 PV';
+ $pv = $line;
+ my @fields = split(':', $line, -1);
+ $pv_alias = $fields[0];
+ if ($fields[2] eq '[unknown]') {
+ like(
+ create(),
+ qr/belongs to a volume group whose metadata is not visible/,
+ 'derived no-metadata probe receives the specific refusal',
+ );
+ is_deeply(\@writes, [], 'derived no-metadata probe causes no writes');
+ } else {
+ is(create(), '', 'derived genuine orphan probe permits creation');
+ is_deeply(
+ \@writes,
+ ['clear', '/sbin/pvcreate', '/sbin/vgcreate'],
+ 'only the derived genuine orphan reaches the mocked writes',
+ );
+ }
+ }
+};
+
+# kpartx maps the partitions of a map as its holders, which indicates a partition table
+reset_case();
+($map, $held, $holder_uuid) = (1, '/dev/sda', 'part1-mpath-3600abc');
+like(
+ create(),
+ qr{contains data \(partitions\) - wipe it first .* --disk /dev/disk/by-id/test'},
+ 'partition maps of the map refused as data',
+);
+is_deeply(\@writes, [], 'partition maps: no writes');
+($map, $held, $holder_uuid) = (1, '/dev/sda', 'LVM-otherlv');
+like(create(), qr{another block device uses '/dev/sda'}, 'another holder refused as a user');
+
+reset_case();
+$signatures = [{ type => 'LVM2_member' }];
+$label = '/dev/sda: LVM2 PV';
+$pv = "/dev/mapper/3600abc:1024:test:$uuid:1";
+is(create(), '', 'same VG on a map listed by its mapper name accepted');
+is(scalar(@writes), 0, 'same VG on a map: no writes');
+
+for my $case (
+ ['missing PV', sub { $pv = undef }, qr/no PV found/],
+ ['wrong PV', sub { $pv = "/dev/sdb:1024::$uuid:0" }, qr/malformed pvs/],
+ ['bad PV size', sub { $pv = "/dev/sda:nan::$uuid:0" }, qr/malformed pvs/],
+ ['missing PV use', sub { $pv = "/dev/sda:1024::$uuid" }, qr/malformed pvs/],
+ ['invalid PV use', sub { $pv = "/dev/sda:1024::$uuid:maybe" }, qr/malformed pvs/],
+ ['sentinel missing use', sub { $pv = "/dev/sda:1024:[unknown]:$uuid" }, qr/malformed pvs/],
+ [
+ 'sentinel invalid use',
+ sub { $pv = "/dev/sda:1024:[unknown]:$uuid:maybe" },
+ qr/malformed pvs/,
+ ],
+ ['invalid VG sentinel', sub { $pv = "/dev/sda:1024:[missing]:$uuid:0" }, qr/malformed pvs/],
+ ['missing device', sub { $pv = "[unknown]:1024:[unknown]:$uuid:1" }, qr/malformed pvs/],
+ [
+ 'extra signature',
+ sub { push @$signatures, { type => 'gpt' } },
+ qr/contains data \(gpt\)/,
+ ],
+ ['bad file', sub { $label = 'garbage' }, qr/malformed file/],
+ ['bad JSON', sub { $bad_json = '{}' }, qr/malformed wipefs/],
+ ['bad signature', sub { $signatures = [{}] }, qr/malformed wipefs/],
+ map {
+ my $tool = $_;
+ ["failed $tool", sub { $fail = $tool }, qr/failed or timed out/]
+ } ('wipefs', '/usr/bin/file', '/sbin/pvs'),
+) {
+ reset_case();
+ $signatures = [{ type => 'LVM2_member' }];
+ $pv = "/dev/sda:1024::$uuid:0";
+ $case->[1]->();
+ like(create(), $case->[2], "$case->[0] refused");
+ is_deeply(\@writes, [], "$case->[0]: no writes");
+}
+
+done_testing();
--
2.47.3
next prev parent reply other threads:[~2026-11-06 0:52 UTC|newest]
Thread overview: 77+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 23:30 [PATCH v3 proxmox,proxmox-perl-rs,cluster,storage,docs,manager 00/76] SAN: cluster-wide multipath, SAN views and a guided storage setup Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 proxmox 01/76] add proxmox-multipath crate Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 proxmox-perl-rs 02/76] pve-rs: add PVE::RS::Multipath bindings for multipathd status parsing Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 cluster 03/76] cfs: observe the multipath configuration files Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 04/76] storage: distinguish status-only activation in plugins Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 05/76] lvm: find a shared volume group created by another node Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 06/76] storage: bound LVM status commands Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 07/76] iscsi: rate-limit session rescans per target Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 08/76] iscsi: list the LUNs of each session, not of its SCSI host Thomas Lamprecht
2026-11-05 23:30 ` Thomas Lamprecht [this message]
2026-11-05 23:30 ` [PATCH v3 storage 10/76] lvm: refuse a base volume of a target that serves disk images Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 11/76] iscsi: refuse virtual devices other than multipath maps as volumes Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 12/76] diskmanage: collect disk transport type from lsblk Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 13/76] diskmanage: add helper to list multipath devices Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 14/76] diskmanage: qualify NVMe over fabrics transport Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 15/76] disks: list: add include-remote parameter Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 16/76] diskmanage: include iSCSI session devices in disk enumeration Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 17/76] diskmanage: link multipath member disks to their map device Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 18/76] iscsi: factor out session device map from device list Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 19/76] api: scan: add a SAN LUN inventory Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 20/76] pvesm: expose the " Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 21/76] disks: lvm: allow volume groups on SAN LUNs and multipath maps Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 22/76] multipath: add a library around multipathd Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 23/76] diskmanage: collect native NVMe multipath path state Thomas Lamprecht
2026-11-05 23:30 ` [PATCH v3 storage 24/76] api: scan: san-luns: report " Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 25/76] diskmanage: collect local block device users Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 26/76] api: add node-level iSCSI status and maintenance Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 27/76] api: iscsi: add session logout with last-path protection Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 28/76] multipath: add a cluster-wide configuration Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 29/76] iscsi: add an option to manage storage LUNs with multipath Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 30/76] lvm: activate a missing base volume before using the volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 31/76] multipath: apply the configuration on every node Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 32/76] iscsi: share the initiator identity reader Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 33/76] multipath: publish per-node status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 34/76] api: add endpoints for the cluster-wide multipath configuration Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 35/76] api: multipath: add the cluster-wide health status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 36/76] multipath: collect identities of non-iSCSI SCSI paths Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 37/76] api: scan: san-luns: report the multipath state of each LUN Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 38/76] api: scan: flag duplicate physical volumes on distinct LUNs Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 39/76] api: disks: recheck local users and retain wipe device references Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 40/76] iscsi: name plain LUNs after their identity link Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 41/76] api: disks: guard SAN wipes with stable identities and storage checks Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 storage 42/76] api: scan: refresh SAN usage with bounded probes Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 docs 43/76] storage: lvm: note the rescan after extending a shared volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 docs 44/76] storage: document the cluster-wide multipath management Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 45/76] api: ceph: osd: keep iSCSI LUNs out of OSD creation Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 46/76] pvestatd: track pending storage status commands across re-exec Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 47/76] ui: storage: factor out the scan picker error mask Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 48/76] ui: storage: nfs: show scan errors and empty results Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 49/76] ui: storage: allow changing the node for NFS/CIFS scans Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 50/76] ui: storage: add guided remote storage wizard with NFS support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 51/76] ui: storage wizard: add SMB/CIFS support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 52/76] ui: storage wizard: preserve settings after partial setup Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 53/76] ui: storage wizard: add Fibre Channel shared LVM setup Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 54/76] ui: storage wizard: reconcile unanswered storage creation Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 55/76] ui: storage wizard: add ZFS over iSCSI support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 56/76] ui: dc: storage: add remote storage wizard entry to the add menu Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 57/76] ui: node: add SAN LUNs panel Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 58/76] ui: san luns: show multipath path state Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 59/76] api: nodes: expose iSCSI status and maintenance Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 60/76] pvenode: add iscsi commands Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 61/76] ui: node: san: add iSCSI target and session management Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 62/76] pvestatd: apply the multipath configuration and report the node status Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 63/76] api: cluster: add the multipath endpoints Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 64/76] ui: dc: add a cluster-wide multipath view Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 65/76] ui: storage: iscsi: add the multipath option Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 66/76] ui: san luns: show multipath state and manage LUN entries Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 67/76] ui: storage wizard: add iSCSI support Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 68/76] ui: storage wizard: iscsi: show cluster initiator names Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 69/76] ui: storage wizard: iscsi: enable multipath for shared LVM Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 70/76] ui: node: san: wipe a LUN that no storage uses Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 71/76] ui: storage wizard: iscsi: reuse existing target connections Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 72/76] ui: storage wizard: fibre channel: enable multipath for a LUN Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 73/76] ui: storage wizard: let root@pam wipe a LUN with data Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 74/76] ui: storage wizard: do not offer LUNs in use for a new volume group Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 75/76] ui: san: show the local users of a LUN in its usage tooltip Thomas Lamprecht
2026-11-05 23:31 ` [PATCH v3 manager 76/76] ui: san: check the other nodes for users before wiping a LUN Thomas Lamprecht
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261105233336.2178064-10-t.lamprecht@proxmox.com \
--to=t.lamprecht@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox