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 9D1751FF0AF for ; Fri, 06 Nov 2026 01:52:14 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 0D66F2184C; Fri, 06 Nov 2026 01:51:00 +0100 (CET) From: Thomas Lamprecht To: pve-devel@lists.proxmox.com Subject: [PATCH v3 storage 16/76] diskmanage: include iSCSI session devices in disk enumeration Date: Fri, 6 Nov 2026 00:30:51 +0100 Message-ID: <20261105233336.2178064-17-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: 1793926178321 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.129 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) POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes PROLO_LEO1 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: MVQ56JY2ZUWKN62LYFJCT2B4QZHT6L7F X-Message-ID-Hash: MVQ56JY2ZUWKN62LYFJCT2B4QZHT6L7F 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: From: Dietmar Maurer Disk enumeration excluded iSCSI devices, making them unavailable to SAN inventory clients. Include them with transport 'iscsi' so clients can filter them as needed. The disk list still hides them unless include-remote is set. Signed-off-by: Dietmar Maurer [TL: keep iSCSI LUNs and their partitions out of local disk operations (GPT init, LVM, LVM-thin, ZFS, directory), as other nodes may use them; skip SMART queries, which can stall the disk list during recovery; drop the volume group creation note, covered by the explicit SAN opt-in; clarify the transport description] Signed-off-by: Thomas Lamprecht --- src/PVE/API2/Disks.pm | 3 +- src/PVE/Diskmanage.pm | 45 ++++++++++++------- src/test/disk_tests/iscsi/disklist | 1 + .../disk_tests/iscsi/disklist_expected.json | 19 ++++++++ src/test/disk_tests/iscsi/lsblk | 5 +++ src/test/disk_tests/iscsi/sda/device/model | 1 + src/test/disk_tests/iscsi/sda/device/vendor | 1 + .../disk_tests/iscsi/sda/queue/rotational | 1 + src/test/disk_tests/iscsi/sda/size | 1 + src/test/disk_tests/iscsi/sda_udevadm | 21 +++++++++ src/test/disklist_test.pm | 15 ++++--- 11 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 src/test/disk_tests/iscsi/disklist create mode 100644 src/test/disk_tests/iscsi/disklist_expected.json create mode 100644 src/test/disk_tests/iscsi/lsblk create mode 100644 src/test/disk_tests/iscsi/sda/device/model create mode 100644 src/test/disk_tests/iscsi/sda/device/vendor create mode 100644 src/test/disk_tests/iscsi/sda/queue/rotational create mode 100644 src/test/disk_tests/iscsi/sda/size create mode 100644 src/test/disk_tests/iscsi/sda_udevadm diff --git a/src/PVE/API2/Disks.pm b/src/PVE/API2/Disks.pm index ada836b6..fe383319 100644 --- a/src/PVE/API2/Disks.pm +++ b/src/PVE/API2/Disks.pm @@ -146,7 +146,8 @@ __PACKAGE__->register_method({ transport => { type => 'string', description => - "The device transport, for example 'sata', 'sas', 'fc', 'nvme', or 'usb'.", + "The device transport, for example 'sata', 'sas', 'usb', 'fc'," + . " 'iscsi', 'nvme', 'nvme-fc', or 'nvme-tcp'.", optional => 1, }, parent => { diff --git a/src/PVE/Diskmanage.pm b/src/PVE/Diskmanage.pm index 6ea9f2e3..be7d9828 100644 --- a/src/PVE/Diskmanage.pm +++ b/src/PVE/Diskmanage.pm @@ -89,6 +89,28 @@ sub parse_wipefs_signatures { return $probe->{signatures}; } +sub is_iscsi_device { + my ($dev) = @_; + + my $link = readlink("/sys/block/$dev"); + return defined($link) && $link =~ m{/session\d+/} ? 1 : 0; +} + +# Partitions inherit their containing disk's classification, as they have no transport of their own. +sub assert_local_device { + my ($disk, $disklist) = @_; + + my $dev = strip_dev($disk); + $disklist //= get_disks($dev, 1, 1); + my $entry = $disklist->{$dev} // die "'$disk' is not a valid local disk\n"; + + my $parent = is_partition("/dev/$dev") ? strip_dev(get_blockdev("/dev/$dev")) : $dev; + my $transport = $entry->{transport} // ($disklist->{$parent} // {})->{transport} // ''; + die "'$disk' is attached over iSCSI and not a local disk\n" + if $transport eq 'iscsi' || is_iscsi_device($parent); + return; +} + sub disk_is_used { my ($disk) = @_; @@ -98,9 +120,10 @@ sub disk_is_used { my $disklist = get_disks($dev, 1, 1); die "'$disk' is not a valid local disk\n" if !defined($disklist->{$dev}); - return 1 if $disklist->{$dev}->{used}; - return 0; + assert_local_device($disk, $disklist); + + return $disklist->{$dev}->{used} ? 1 : 0; } sub get_smart_data { @@ -493,16 +516,6 @@ sub dir_is_empty { return 1; } -sub is_iscsi { - my ($sysdir) = @_; - - if (-l $sysdir && readlink($sysdir) =~ m|host[^/]*/session[^/]*|) { - return 1; - } - - return 0; -} - # Returns the NVMe transport (pcie, fc, tcp, rdma, loop) of a namespace, either directly from # its controller, or from any controller of its subsystem for native NVMe multipath setups. sub get_nvme_transport { @@ -619,9 +632,6 @@ sub get_disks { my $sysdir = "/sys/block/$dev"; - # we do not want iscsi devices - return if is_iscsi($sysdir); - my $nvme_transport; $nvme_transport = get_nvme_transport($sysdir) if $dev =~ m/^nvme/; @@ -644,7 +654,10 @@ sub get_disks { } my ($health, $wearout) = ('UNKNOWN', 'N/A'); - if (!$nosmart) { + # SMART of an iSCSI LUN goes to the target, which can stall while its session recovers + my $tran = $lsblk_info->{$devpath}->{tran}; + my $iscsi = defined($tran) ? $tran eq 'iscsi' : is_iscsi_device($dev); + if (!$nosmart && !$iscsi) { eval { my $smartdata = get_smart_data($devpath, !is_ssdlike($type)); $health = $smartdata->{health} if $smartdata->{health}; diff --git a/src/test/disk_tests/iscsi/disklist b/src/test/disk_tests/iscsi/disklist new file mode 100644 index 00000000..9191c61b --- /dev/null +++ b/src/test/disk_tests/iscsi/disklist @@ -0,0 +1 @@ +sda diff --git a/src/test/disk_tests/iscsi/disklist_expected.json b/src/test/disk_tests/iscsi/disklist_expected.json new file mode 100644 index 00000000..ca334106 --- /dev/null +++ b/src/test/disk_tests/iscsi/disklist_expected.json @@ -0,0 +1,19 @@ +{ + "sda": { + "devpath": "/dev/sda", + "size": 10737418240, + "vendor": "LIO-ORG", + "model": "vdisk1", + "serial": "ISER1", + "wwn": "0x60014051111111111", + "gpt": 0, + "rpm": -1, + "type": "unknown", + "health": "UNKNOWN", + "wearout": "N/A", + "osdid": -1, + "osdid-list": null, + "by_id_link": "/dev/disk/by-id/scsi-360014051111111111", + "transport": "iscsi" + } +} diff --git a/src/test/disk_tests/iscsi/lsblk b/src/test/disk_tests/iscsi/lsblk new file mode 100644 index 00000000..526d27c5 --- /dev/null +++ b/src/test/disk_tests/iscsi/lsblk @@ -0,0 +1,5 @@ +{ + "blockdevices": [ + {"path":"/dev/sda", "parttype":null, "fstype":null, "tran":"iscsi"} + ] +} diff --git a/src/test/disk_tests/iscsi/sda/device/model b/src/test/disk_tests/iscsi/sda/device/model new file mode 100644 index 00000000..b14d6fe7 --- /dev/null +++ b/src/test/disk_tests/iscsi/sda/device/model @@ -0,0 +1 @@ +vdisk1 diff --git a/src/test/disk_tests/iscsi/sda/device/vendor b/src/test/disk_tests/iscsi/sda/device/vendor new file mode 100644 index 00000000..968831cd --- /dev/null +++ b/src/test/disk_tests/iscsi/sda/device/vendor @@ -0,0 +1 @@ +LIO-ORG diff --git a/src/test/disk_tests/iscsi/sda/queue/rotational b/src/test/disk_tests/iscsi/sda/queue/rotational new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/src/test/disk_tests/iscsi/sda/queue/rotational @@ -0,0 +1 @@ +1 diff --git a/src/test/disk_tests/iscsi/sda/size b/src/test/disk_tests/iscsi/sda/size new file mode 100644 index 00000000..14faeece --- /dev/null +++ b/src/test/disk_tests/iscsi/sda/size @@ -0,0 +1 @@ +20971520 diff --git a/src/test/disk_tests/iscsi/sda_udevadm b/src/test/disk_tests/iscsi/sda_udevadm new file mode 100644 index 00000000..ca9f7815 --- /dev/null +++ b/src/test/disk_tests/iscsi/sda_udevadm @@ -0,0 +1,21 @@ +P: /devices/platform/host5/session1/target5:0:0/5:0:0:0/block/sda +N: sda +S: disk/by-id/scsi-360014051111111111 +S: disk/by-id/wwn-0x60014051111111111 +S: disk/by-path/ip-192.168.1.10:3260-iscsi-iqn.2003-01.org.linux-iscsi.storage:target1-lun-0 +E: DEVLINKS=/dev/disk/by-id/scsi-360014051111111111 /dev/disk/by-id/wwn-0x60014051111111111 /dev/disk/by-path/ip-192.168.1.10:3260-iscsi-iqn.2003-01.org.linux-iscsi.storage:target1-lun-0 +E: DEVNAME=/dev/sda +E: DEVPATH=/devices/platform/host5/session1/target5:0:0/5:0:0:0/block/sda +E: DEVTYPE=disk +E: ID_BUS=scsi +E: ID_MODEL=vdisk1 +E: ID_PATH=ip-192.168.1.10:3260-iscsi-iqn.2003-01.org.linux-iscsi.storage:target1-lun-0 +E: ID_SCSI=1 +E: ID_SERIAL=36001405111111111 +E: ID_SERIAL_SHORT=ISER1 +E: ID_TYPE=disk +E: ID_VENDOR=LIO-ORG +E: ID_WWN=0x60014051111111111 +E: MAJOR=8 +E: MINOR=0 +E: SUBSYSTEM=block diff --git a/src/test/disklist_test.pm b/src/test/disklist_test.pm index 9e35430d..dd4e4d73 100644 --- a/src/test/disklist_test.pm +++ b/src/test/disklist_test.pm @@ -105,10 +105,6 @@ sub mocked_get_sysdir_size { return &$originalsub($param); } -sub mocked_is_iscsi { - return 0; -} - sub mocked_file_read_firstline { my ($path) = @_; @@ -242,6 +238,12 @@ sub test_disk_list { is_deeply($multipath, $expected_multipath, 'multipath list should be the same'); } + if ($testcasedir eq 'iscsi') { + eval { PVE::Diskmanage::assert_disk_unused('/dev/sda'); }; + $testcount++; + like($@, qr/attached over iSCSI/, 'local disk operations reject an iSCSI LUN'); + } + done_testing($testcount); }; } @@ -262,8 +264,6 @@ $diskmanage_module->mock('get_sysdir_info' => \&mocked_get_sysdir_info); print("\tMocked get_sysdir_info\n"); $diskmanage_module->mock('get_sysdir_size' => \&mocked_get_sysdir_size); print("\tMocked get_sysdir_size\n"); -$diskmanage_module->mock('is_iscsi' => \&mocked_is_iscsi); -print("\tMocked is_iscsi\n"); $diskmanage_module->mock('assert_blockdev' => sub { return 1; }); print("\tMocked assert_blockdev\n"); $diskmanage_module->mock( @@ -297,6 +297,9 @@ $diskmanage_module->mock( print("\tMocked abs_path\n"); $diskmanage_module->mock('check_bin' => sub { return 1; }); print("\tMocked check_bin\n"); +# the test cases name iSCSI LUNs through the lsblk transport, the sysfs of the host must not count +$diskmanage_module->mock('is_iscsi_device' => sub { return 0; }); +print("\tMocked is_iscsi_device\n"); my $tools_module = Test::MockModule->new('PVE::ProcFSTools', no_auto => 1); $tools_module->mock('parse_proc_mounts' => \&mocked_parse_proc_mounts); print("\tMocked parse_proc_mounts\n"); -- 2.47.3