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




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