From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id C222F1FF0ED for ; Fri, 31 Jul 2026 12:22:31 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id F21D4215A6; Fri, 31 Jul 2026 12:22:03 +0200 (CEST) From: Dietmar Maurer To: pve-devel@lists.proxmox.com Subject: [RFC pve-storage 09/27] disks: lvm: allow creating volume groups on multipath devices Date: Fri, 31 Jul 2026 12:21:38 +0200 Message-ID: <20260731102156.3947857-10-dietmar@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260731102156.3947857-1-dietmar@proxmox.com> References: <20260731102156.3947857-1-dietmar@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 2 AWL -0.330 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: OFQXW7S5WYFSIDVGORL2I4GJOPXGZRVZ X-Message-ID-Hash: OFQXW7S5WYFSIDVGORL2I4GJOPXGZRVZ X-MailFrom: dietmar@zilli.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The device checks resolved the mapper symlink to the dm device, which get_disks does not know, so creating a volume group on a multipath mapped SAN LUN was rejected with 'not a valid local disk'. Accept multipath devices that are not in use, so the storage wizard can initialize a shared LVM volume group directly on a multipath LUN. Signed-off-by: Dietmar Maurer --- src/PVE/API2/Disks/LVM.pm | 8 +++++--- src/PVE/Diskmanage.pm | 27 +++++++++++++++++++++++---- src/test/disklist_test.pm | 19 +++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/PVE/API2/Disks/LVM.pm b/src/PVE/API2/Disks/LVM.pm index 9f8f951..35051fd 100644 --- a/src/PVE/API2/Disks/LVM.pm +++ b/src/PVE/API2/Disks/LVM.pm @@ -131,7 +131,8 @@ __PACKAGE__->register_method({ name => get_standard_option('pve-storage-id'), device => { type => 'string', - description => 'The block device you want to create the volume group on', + description => 'The block device you want to create the volume group on.' + . ' Multipath mapped devices are supported.', }, add_storage => { description => "Configure storage using the Volume Group", @@ -153,7 +154,8 @@ __PACKAGE__->register_method({ my $node = $param->{node}; $dev = PVE::Diskmanage::verify_blockdev_path($dev); - PVE::Diskmanage::assert_disk_unused($dev); + + PVE::Diskmanage::assert_disk_unused($dev, 1); my $storage_params = { type => 'lvm', @@ -176,7 +178,7 @@ __PACKAGE__->register_method({ my $worker = sub { PVE::Diskmanage::locked_disk_action(sub { - PVE::Diskmanage::assert_disk_unused($dev); + PVE::Diskmanage::assert_disk_unused($dev, 1); die "volume group with name '${name}' already exists on node '${node}'\n" if PVE::Storage::LVMPlugin::lvm_vgs()->{$name}; diff --git a/src/PVE/Diskmanage.pm b/src/PVE/Diskmanage.pm index 5ac04dd..ab5db06 100644 --- a/src/PVE/Diskmanage.pm +++ b/src/PVE/Diskmanage.pm @@ -74,14 +74,22 @@ sub init_disk { } sub disk_is_used { - my ($disk) = @_; + my ($disk, $allow_multipath) = @_; my $dev = $disk; $dev =~ s|^/dev/||; my $disklist = get_disks($dev, 1, 1); - die "'$disk' is not a valid local disk\n" if !defined($disklist->{$dev}); + if (!defined($disklist->{$dev})) { + if ($allow_multipath) { + my $multipath = lookup_multipath_map($disk); + return defined($multipath->{used}) ? 1 : 0 if defined($multipath); + } + + die "'$disk' is not a valid local disk\n"; + } + return 1 if $disklist->{$dev}->{used}; return 0; @@ -904,6 +912,17 @@ sub get_multipath_disks { return $disklist; } +# Returns the multipath device entry if the given path refers to a multipath mapped device. +sub lookup_multipath_map { + my ($devpath) = @_; + + my $path = abs_path($devpath) // $devpath; + my $dev = strip_dev($path); + return undef if $dev !~ m/^dm-\d+$/; + + return get_multipath_disks()->{$dev}; +} + sub get_partnum { my ($part_path) = @_; @@ -958,8 +977,8 @@ sub locked_disk_action { } sub assert_disk_unused { - my ($dev) = @_; - die "device '$dev' is already in use\n" if disk_is_used($dev); + my ($dev, $allow_multipath) = @_; + die "device '$dev' is already in use\n" if disk_is_used($dev, $allow_multipath); return; } diff --git a/src/test/disklist_test.pm b/src/test/disklist_test.pm index dc84510..d2ea3b2 100644 --- a/src/test/disklist_test.pm +++ b/src/test/disklist_test.pm @@ -236,6 +236,25 @@ sub test_disk_list { print Dumper($multipath) if $print; $testcount++; is_deeply($multipath, $expected_multipath, 'multipath list should be the same'); + + eval { PVE::Diskmanage::assert_disk_unused('/dev/dm-0'); }; + $testcount++; + like($@, qr/not a valid local disk/, + 'multipath devices require an explicit opt-in'); + + eval { PVE::Diskmanage::assert_disk_unused('/dev/mapper/mpatha', 1); }; + $testcount++; + is($@, '', 'unused multipath alias should pass'); + + for my $dev (qw(dm-1 dm-2 dm-3 dm-4)) { + eval { PVE::Diskmanage::assert_disk_unused("/dev/$dev", 1); }; + $testcount++; + like($@, qr/already in use/, "used multipath device $dev should fail"); + } + + eval { PVE::Diskmanage::assert_disk_unused('/dev/dm-99', 1); }; + $testcount++; + like($@, qr/not a valid local disk/, 'unknown device mapper device should fail'); } done_testing($testcount); -- 2.47.3