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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.