From: Dietmar Maurer <dietmar@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [RFC pve-storage 08/27] api: scan: add san-luns method listing SAN LUN candidates
Date: Fri, 31 Jul 2026 12:21:37 +0200 [thread overview]
Message-ID: <20260731102156.3947857-9-dietmar@proxmox.com> (raw)
In-Reply-To: <20260731102156.3947857-1-dietmar@proxmox.com>
The storage wizard needs to offer the block devices of a SAN as
candidates for a shared LVM volume group. The node disk list alone
is not sufficient for that: aggregated multipath devices are missing
and mapping a device to its volume group and to a storage definition
that already uses it requires combining several calls.
Return one entry per whole disk or multipath mapped device with its
transport type and usage: unused devices are candidates for a new
volume group, devices holding an LVM physical volume report the
volume group name and, if the volume group is already referenced by
a storage definition, the storage ID, so a UI can filter those out.
LUNs belonging to the target of a configured iSCSI storage report
that storage as well, so a UI can warn before placing a volume group
on a LUN that already serves as raw VM disk storage. Member paths of
a multipath device are folded into the mapped device.
Also expose the method as 'pvesm scan san-luns'.
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
---
src/PVE/API2/Storage/Scan.pm | 175 +++++++++++++++++++++++++++++++++++
src/PVE/CLI/pvesm.pm | 8 ++
2 files changed, 183 insertions(+)
diff --git a/src/PVE/API2/Storage/Scan.pm b/src/PVE/API2/Storage/Scan.pm
index a58bdd8..9b944d5 100644
--- a/src/PVE/API2/Storage/Scan.pm
+++ b/src/PVE/API2/Storage/Scan.pm
@@ -5,10 +5,12 @@ use warnings;
# NOTE: This API endpoints are mounted by pve-manager's API2::Node module and pvesm CLI
+use PVE::Diskmanage;
use PVE::JSONSchema qw(get_standard_option);
use PVE::RESTHandler;
use PVE::SafeSyslog;
+use PVE::Storage::ISCSIPlugin;
use PVE::Storage::LVMPlugin;
use PVE::Storage::LvmThinPlugin;
use PVE::Storage::PBSPlugin;
@@ -49,6 +51,7 @@ __PACKAGE__->register_method({
{ method => 'lvm' },
{ method => 'nfs' },
{ method => 'pbs' },
+ { method => 'san-luns' },
{ method => 'zfs' },
];
@@ -373,6 +376,178 @@ __PACKAGE__->register_method({
},
});
+__PACKAGE__->register_method({
+ name => 'sanlunscan',
+ path => 'san-luns',
+ method => 'GET',
+ description => "List block devices (disks and multipath devices) together with their"
+ . " usage, for example as candidates for a SAN backed LVM volume group.",
+ protected => 1,
+ proxyto => "node",
+ permissions => {
+ check => ['perm', '/storage', ['Datastore.Allocate']],
+ },
+ parameters => {
+ additionalProperties => 0,
+ properties => {
+ node => get_standard_option('pve-node'),
+ },
+ },
+ returns => {
+ type => 'array',
+ items => {
+ type => "object",
+ properties => {
+ devpath => {
+ description => "The device path.",
+ type => 'string',
+ },
+ size => {
+ description => "The device size in bytes.",
+ type => 'integer',
+ optional => 1,
+ },
+ transport => {
+ description => "The bus type the device is attached with (for example"
+ . " sata, sas, usb, fc, iscsi, nvme, nvme-fc, nvme-tcp).",
+ type => 'string',
+ optional => 1,
+ },
+ vendor => { type => 'string', optional => 1 },
+ model => { type => 'string', optional => 1 },
+ serial => { type => 'string', optional => 1 },
+ wwn => { type => 'string', optional => 1 },
+ paths => {
+ description => "The number of paths of a multipath mapped device.",
+ type => 'integer',
+ optional => 1,
+ },
+ usage => {
+ description => "How the device is currently used. 'lvm' means it is an LVM"
+ . " physical volume, see the 'vgname' property.",
+ type => 'string',
+ enum => ['unused', 'lvm', 'other'],
+ },
+ vgname => {
+ description => "The name of the LVM volume group the device belongs to.",
+ type => 'string',
+ optional => 1,
+ },
+ 'used-by-storage' => {
+ description => "The ID of an existing storage definition that uses this"
+ . " device, either through the LVM volume group on it or because it"
+ . " is a LUN of the storage's iSCSI target.",
+ type => 'string',
+ optional => 1,
+ },
+ detail => {
+ description =>
+ "Details about other usage, for example the file system type.",
+ type => 'string',
+ optional => 1,
+ },
+ },
+ },
+ },
+ code => sub {
+ my ($param) = @_;
+
+ my $disks = PVE::Diskmanage::get_disks(undef, 1, 0);
+ my $multipath = PVE::Diskmanage::get_multipath_disks();
+
+ # map PV device to VG name, PVs may sit on a partition of a listed device
+ my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
+ my $pv2vg = {};
+ for my $vgname (sort keys %$vgs) {
+ for my $pv (@{ $vgs->{$vgname}->{pvs} // [] }) {
+ $pv2vg->{ $pv->{name} } = $vgname;
+ }
+ }
+ my $find_vg = sub {
+ my ($devpath) = @_;
+ return $pv2vg->{$devpath} if defined($pv2vg->{$devpath});
+ for my $pv (sort keys %$pv2vg) {
+ return $pv2vg->{$pv} if $pv =~ m/^\Q$devpath\Ep?\d+$/;
+ }
+ return undef;
+ };
+
+ my $cfg = PVE::Storage::config();
+ my $vg_used_by = {};
+ my $iscsi_target_used_by = {};
+ for my $sid (sort keys %{ $cfg->{ids} }) {
+ my $scfg = $cfg->{ids}->{$sid};
+ if (defined(my $vgname = $scfg->{vgname})) {
+ $vg_used_by->{$vgname} //= $sid;
+ }
+ if ($scfg->{type} eq 'iscsi' && defined(my $target = $scfg->{target})) {
+ $iscsi_target_used_by->{$target} //= $sid;
+ }
+ }
+
+ # map kernel device name to the storage using its iSCSI target
+ my $iscsi_used_by = {};
+ if (scalar(keys %$iscsi_target_used_by)) {
+ my $device_map = PVE::Storage::ISCSIPlugin::iscsi_session_device_map();
+ for my $bdev (sort keys %$device_map) {
+ my $sid = $iscsi_target_used_by->{ $device_map->{$bdev}->{target} } // next;
+ $iscsi_used_by->{$bdev} = $sid;
+ }
+ }
+
+ my $res = [];
+ my $add_entry = sub {
+ my ($disk) = @_;
+
+ my $entry = {
+ devpath => $disk->{devpath},
+ size => $disk->{size},
+ };
+ for my $key (qw(transport vendor model serial wwn paths)) {
+ $entry->{$key} = $disk->{$key} if defined($disk->{$key});
+ }
+
+ if (!defined($disk->{used})) {
+ $entry->{usage} = 'unused';
+ } elsif ($disk->{used} eq 'LVM') {
+ $entry->{usage} = 'lvm';
+ if (defined(my $vgname = $find_vg->($disk->{devpath}))) {
+ $entry->{vgname} = $vgname;
+ if (defined(my $sid = $vg_used_by->{$vgname})) {
+ $entry->{'used-by-storage'} = $sid;
+ }
+ }
+ } else {
+ $entry->{usage} = 'other';
+ $entry->{detail} = $disk->{used};
+ }
+
+ if (!defined($entry->{'used-by-storage'})) {
+ my @members =
+ map { $_ =~ s|^/dev/||r } ($disk->{slaves} // [$disk->{devpath}])->@*;
+ for my $member (@members) {
+ if (defined(my $sid = $iscsi_used_by->{$member})) {
+ $entry->{'used-by-storage'} = $sid;
+ last;
+ }
+ }
+ }
+
+ push @$res, $entry;
+ };
+
+ for my $dev (sort keys %$disks) {
+ my $disk = $disks->{$dev};
+ # member paths are represented by their multipath device
+ next if defined($disk->{multipath});
+ $add_entry->($disk);
+ }
+ $add_entry->($multipath->{$_}) for sort keys %$multipath;
+
+ return $res;
+ },
+});
+
__PACKAGE__->register_method({
name => 'zfsscan',
path => 'zfs',
diff --git a/src/PVE/CLI/pvesm.pm b/src/PVE/CLI/pvesm.pm
index 3edf31a..7348e8e 100755
--- a/src/PVE/CLI/pvesm.pm
+++ b/src/PVE/CLI/pvesm.pm
@@ -741,6 +741,14 @@ our $cmddef = {
$print_api_result,
$PVE::RESTHandler::standard_output_options,
],
+ 'san-luns' => [
+ "PVE::API2::Storage::Scan",
+ 'sanlunscan',
+ [],
+ { node => $nodename },
+ $print_api_result,
+ $PVE::RESTHandler::standard_output_options,
+ ],
zfs => [
"PVE::API2::Storage::Scan",
'zfsscan',
--
2.47.3
next prev parent reply other threads:[~2026-07-31 10:23 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 10:21 [RFC pve-storage/proxmox-widget-toolkit/pve-manager 00/27] add guided remote storage setup and SAN visibility Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 01/27] diskmanage: collect disk transport type from lsblk Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 02/27] diskmanage: add helper to list multipath devices Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 03/27] diskmanage: qualify NVMe over fabrics transport Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 04/27] disks: list: add include-remote parameter Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 05/27] diskmanage: include iSCSI session devices in disk enumeration Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 06/27] diskmanage: link multipath member disks to their map device Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 07/27] iscsi: factor out session device map from device list Dietmar Maurer
2026-07-31 10:21 ` Dietmar Maurer [this message]
2026-07-31 10:21 ` [RFC pve-storage 09/27] disks: lvm: allow creating volume groups on multipath devices Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 10/27] diskmanage: add helper querying multipath path state Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 11/27] diskmanage: add helper querying NVMe native " Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 12/27] api: scan: san-luns: report " Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 13/27] iscsi plugin: list sessions of all transports and capture transport Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-storage 14/27] api: add node-level iSCSI initiator target and session API Dietmar Maurer
2026-07-31 10:21 ` [RFC proxmox-widget-toolkit 15/27] disk selectors: allow opting into remote devices Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 16/27] ui: storage: allow switching the scan node of the NFS/CIFS scan combos Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 17/27] ui: storage: add guided remote storage wizard with NFS support Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 18/27] ui: storage wizard: add SMB/CIFS support Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 19/27] ui: storage wizard: add iSCSI support Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 20/27] ui: storage wizard: add FC-attached SAN (shared LVM) support Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 21/27] ui: storage wizard: add ZFS over iSCSI support Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 22/27] ui: dc: storage: add remote storage wizard entry to the add menu Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 23/27] ui: node: add SAN LUNs panel Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 24/27] ui: san luns: show multipath path state Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 25/27] api: nodes: add iSCSI initiator API endpoint Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 26/27] pvenode: add iscsi commands Dietmar Maurer
2026-07-31 10:21 ` [RFC pve-manager 27/27] ui: san luns: show iSCSI targets and sessions Dietmar Maurer
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=20260731102156.3947857-9-dietmar@proxmox.com \
--to=dietmar@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