From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 78F031FF0AF for ; Fri, 06 Nov 2026 01:50:36 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 194BD2170C; Fri, 06 Nov 2026 01:49:46 +0100 (CET) From: Thomas Lamprecht To: pve-devel@lists.proxmox.com Subject: [PATCH v3 storage 08/76] iscsi: list the LUNs of each session, not of its SCSI host Date: Fri, 6 Nov 2026 00:30:43 +0100 Message-ID: <20261105233336.2178064-9-t.lamprecht@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261105233336.2178064-1-t.lamprecht@proxmox.com> References: <20261105233336.2178064-1-t.lamprecht@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793926177781 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.291 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) PROLO_LEO2 0.1 Meta Catches all Leo drug variations so far RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: CKEMT6MVGNVGDTLYPL6XSEUR3E5PPRQG X-Message-ID-Hash: CKEMT6MVGNVGDTLYPL6XSEUR3E5PPRQG X-MailFrom: t.lamprecht@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 LUN list of a target is built from its sessions, but each session picked up every LUN of its SCSI host. Software iSCSI creates one host per session, so this did not matter there. Offload initiators can run sessions to several targets on one host; each of their targets then listed the LUNs of the other targets as well. Take the LUNs below the session's own SCSI target instead, which is where the iSCSI transport class of the kernel places them. Signed-off-by: Thomas Lamprecht --- src/PVE/Storage/ISCSIPlugin.pm | 53 +++++++++++------ src/test/Makefile | 3 +- src/test/run_iscsi_device_list_tests.pl | 78 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 20 deletions(-) create mode 100755 src/test/run_iscsi_device_list_tests.pl diff --git a/src/PVE/Storage/ISCSIPlugin.pm b/src/PVE/Storage/ISCSIPlugin.pm index 20605859..536b2e94 100644 --- a/src/PVE/Storage/ISCSIPlugin.pm +++ b/src/PVE/Storage/ISCSIPlugin.pm @@ -18,6 +18,7 @@ use base qw(PVE::Storage::Plugin); # iscsi helper function my $ISCSIADM = '/usr/bin/iscsiadm'; +our $SYSFS = '/sys'; my $found_iscsi_adm_exe; my sub assert_iscsi_support { @@ -283,11 +284,33 @@ sub load_stable_scsi_paths { return $stable_paths; } +# Offload initiators can run several sessions on one SCSI host, so the LUNs of a session are the +# ones below its own target, not all LUNs of its host. +my sub foreach_session_lun { + my ($sessiondir, $code) = @_; + + dir_glob_foreach( + $sessiondir, + 'target\d+:\d+:\d+', + sub { + my ($targetdir) = @_; + dir_glob_foreach( + "$sessiondir/$targetdir", + '\d+:(\d+):(\d+):(\d+)', + sub { + my ($hctl, $channel, $id, $lun) = @_; + $code->("$sessiondir/$targetdir/$hctl", $channel, $id, $lun); + }, + ); + }, + ); +} + sub iscsi_device_list { my $res = {}; - my $dirname = '/sys/class/iscsi_session'; + my $dirname = "$SYSFS/class/iscsi_session"; my $stable_paths = load_stable_scsi_paths(); @@ -300,39 +323,33 @@ sub iscsi_device_list { my $target = file_read_firstline("$dirname/$ent/targetname"); return if !$target; - my (undef, $host) = dir_glob_regex("$dirname/$ent/device", 'target(\d+):.*'); - return if !defined($host); - - dir_glob_foreach( - "/sys/bus/scsi/devices", - "$host:" . '(\d+):(\d+):(\d+)', + foreach_session_lun( + "$dirname/$ent/device", sub { - my ($tmp, $channel, $id, $lun) = @_; + my ($devdir, $channel, $id, $lun) = @_; - my $type = file_read_firstline("/sys/bus/scsi/devices/$tmp/type"); + my $type = file_read_firstline("$devdir/type"); return if !defined($type) || $type ne '0'; # list disks only my $bdev; - if (-d "/sys/bus/scsi/devices/$tmp/block") { # newer kernels - (undef, $bdev) = - dir_glob_regex("/sys/bus/scsi/devices/$tmp/block/", '([A-Za-z]\S*)'); + if (-d "$devdir/block") { # newer kernels + (undef, $bdev) = dir_glob_regex("$devdir/block/", '([A-Za-z]\S*)'); } else { - (undef, $bdev) = - dir_glob_regex("/sys/bus/scsi/devices/$tmp", 'block:(\S+)'); + (undef, $bdev) = dir_glob_regex($devdir, 'block:(\S+)'); } return if !$bdev; #check multipath - if (-d "/sys/block/$bdev/holders") { + if (-d "$SYSFS/block/$bdev/holders") { my $multipathdev = - dir_glob_regex("/sys/block/$bdev/holders", '[A-Za-z]\S*'); + dir_glob_regex("$SYSFS/block/$bdev/holders", '[A-Za-z]\S*'); $bdev = $multipathdev if $multipathdev; } my $blockdev = $stable_paths->{$bdev}; return if !$blockdev; - my $size = file_read_firstline("/sys/block/$bdev/size"); + my $size = file_read_firstline("$SYSFS/block/$bdev/size"); return if !$size; my $volid = "$channel.$id.$lun.$blockdev"; @@ -345,8 +362,6 @@ sub iscsi_device_list { 'id' => int($id), 'lun' => int($lun), }; - - #print "TEST: $target $session $host,$bus,$tg,$lun $blockdev\n"; }, ); diff --git a/src/test/Makefile b/src/test/Makefile index 404a1524..24abdebc 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -40,9 +40,10 @@ test_ovf: run_ovf_tests.pl test_volume_access: run_volume_access_tests.pl ./run_volume_access_tests.pl -test_iscsi: run_iscsi_discovery_tests.pl run_iscsi_rescan_tests.pl run_iscsi_session_tests.pl run_iscsi_volume_tests.pl +test_iscsi: run_iscsi_discovery_tests.pl run_iscsi_rescan_tests.pl run_iscsi_device_list_tests.pl run_iscsi_session_tests.pl run_iscsi_volume_tests.pl ./run_iscsi_discovery_tests.pl ./run_iscsi_rescan_tests.pl + ./run_iscsi_device_list_tests.pl ./run_iscsi_session_tests.pl ./run_iscsi_volume_tests.pl diff --git a/src/test/run_iscsi_device_list_tests.pl b/src/test/run_iscsi_device_list_tests.pl new file mode 100755 index 00000000..6df3a416 --- /dev/null +++ b/src/test/run_iscsi_device_list_tests.pl @@ -0,0 +1,78 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use File::Path qw(make_path); +use File::Temp qw(tempdir); +use Test::MockModule; +use Test::More; + +use lib ('.', '..'); +use PVE::Storage::ISCSIPlugin; + +my $root = tempdir(CLEANUP => 1); +local $PVE::Storage::ISCSIPlugin::SYSFS = "$root/sys"; + +my sub put { + my ($path, $value) = @_; + my ($dir) = "$root/sys/$path" =~ m{^(.*)/[^/]+$}; + make_path($dir); + open(my $fh, '>', "$root/sys/$path") or die $!; + print $fh "$value\n"; + close($fh) or die $!; +} + +# The sysfs layout of the kernel's iSCSI transport class: the SCSI target and its LUNs are children +# of the session device, and the SCSI bus lists the LUNs by H:C:T:L. +my sub session { + my ($sid, $host, $target, @luns) = @_; + my $sessiondir = "devices/platform/host$host/session$sid"; + make_path("$root/sys/$sessiondir"); + put("class/iscsi_session/session$sid/targetname", $target); + symlink("$root/sys/$sessiondir", "$root/sys/class/iscsi_session/session$sid/device") + or die $!; + for my $lun (@luns) { + my ($hctl, $dev) = @$lun; + my ($tdir) = $hctl =~ m/^(\d+:\d+:\d+):\d+$/; + my $devdir = "$sessiondir/target$tdir/$hctl"; + put("$devdir/type", '0'); + make_path("$root/sys/$devdir/block/$dev"); + make_path("$root/sys/bus/scsi/devices"); + symlink("$root/sys/$devdir", "$root/sys/bus/scsi/devices/$hctl") or die $!; + put("block/$dev/size", '2048'); + make_path("$root/sys/block/$dev/holders"); + } +} + +# An offload initiator with two sessions on one SCSI host, a software initiator session on a host +# of its own, and a session without LUNs. +session(1, 7, 'iqn.target-a', ['7:0:0:1', 'sda']); +session(2, 7, 'iqn.target-b', ['7:0:1:1', 'sdb'], ['7:0:1:2', 'sdc']); +session(3, 8, 'iqn.target-c', ['8:0:0:0', 'sdd']); +session(4, 9, 'iqn.target-d'); + +my $plugin = Test::MockModule->new('PVE::Storage::ISCSIPlugin'); +$plugin->mock( + load_stable_scsi_paths => sub { + return { map { $_ => "scsi-3$_" } qw(sda sdb sdc sdd) }; + }, +); + +my $list = PVE::Storage::ISCSIPlugin::iscsi_device_list(); +is_deeply( + { map { $_ => [sort keys $list->{$_}->%*] } keys %$list }, + { + 'iqn.target-a' => ['0.0.1.scsi-3sda'], + 'iqn.target-b' => ['0.1.1.scsi-3sdb', '0.1.2.scsi-3sdc'], + 'iqn.target-c' => ['0.0.0.scsi-3sdd'], + }, + 'each target lists the LUNs of its own session, also on a shared SCSI host', +); +is_deeply( + $list->{'iqn.target-b'}->{'0.1.2.scsi-3sdc'}, + { format => 'raw', size => 2048 * 512, vmid => 0, channel => 0, id => 1, lun => 2 }, + 'LUN address and size', +); + +done_testing(); -- 2.47.3