public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
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




  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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal