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