fix: parse_volname + free_image session/LUN teardown for CORE 13

parse_volname: implement for vm-/base- volume names so PVE can resolve
device paths and hotplug disks. Without this the base class falls through
to directory-volume parsing and fails with a 400 hotplug error.

free_image: TrueNAS CORE 13 holds iSCSI sessions in recovery state after
TCP disconnect, blocking targetextent deletion with 422 even seconds after
logout. Fix: log out the initiator session by SID, then restart the TrueNAS
iSCSI service to immediately clear server-side session state. Delete the
targetextent and extent, then restore the initiator session so other LUNs
on the same target remain accessible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kevin Adams 2026-05-24 08:12:41 -04:00
parent c6f662b88c
commit f521089e57
1 changed files with 48 additions and 2 deletions

View File

@ -352,6 +352,18 @@ sub _iscsi_ensure_session {
# ── PVE::Storage::Plugin interface ───────────────────────────────────────────
sub parse_volname {
my ($class, $volname) = @_;
if ($volname =~ /^(vm|base)-(\d+)-disk-\d+$/) {
my ($prefix, $vmid) = ($1, $2);
my $isBase = $prefix eq 'base' ? 1 : 0;
return ('images', $volname, $vmid, undef, undef, $isBase, 'raw');
}
die "unable to parse TrueNAS volume name '$volname'\n";
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
@ -425,10 +437,44 @@ sub free_image {
if ($ext) {
if (defined $ext->{targetextent_id}) {
# TrueNAS CORE 13 keeps iSCSI sessions in recovery state after TCP
# disconnect — a reload is not sufficient to clear them. A service
# restart immediately purges all server-side session state, which is
# the only reliable way to allow targetextent deletion.
my $target = _resolve_target($scfg);
my $portal = _portal($scfg);
my $iqn = $target->{iqn};
# Logout from the initiator side
my $sessions = '';
eval { run_command(['iscsiadm', '-m', 'session'],
outfunc => sub { $sessions .= shift . "\n" },
noerr => 1) };
if ($sessions =~ /\[(\d+)\][^\n]*\Q$iqn\E/) {
my $sid = $1;
_log('info', "free_image: logging out iSCSI session $sid");
eval { run_command(['iscsiadm', '-m', 'session', '-r', $sid, '--logout'],
noerr => 1) };
}
# Restart TrueNAS iSCSI service to immediately clear server-side sessions
_log('info', "free_image: restarting TrueNAS iSCSI service to clear session state");
_api($scfg, 'POST', '/service/restart', { service => 'iscsitarget' });
_api($scfg, 'DELETE', "/iscsi/targetextent/id/$ext->{targetextent_id}");
_api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}");
# Restore the session so remaining LUNs stay accessible
_log('info', "free_image: restoring iSCSI session");
eval { run_command(['iscsiadm', '-m', 'discovery', '-t', 'sendtargets',
'-p', "$portal:3260"], noerr => 1) };
eval { run_command(['iscsiadm', '-m', 'node', '-T', $iqn,
'-p', "$portal:3260", '--login'], noerr => 1) };
eval { run_command(['iscsiadm', '-m', 'session', '--rescan'], noerr => 1) };
} else {
_api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}");
_reload_iscsi($scfg);
}
_api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}");
_reload_iscsi($scfg);
} else {
_log('warning', "free_image: no iSCSI extent found for $volname — skipping extent removal");
}