From 594b533a06ef181bd9b3247789b91d23371ee46a Mon Sep 17 00:00:00 2001 From: Kevin Adams Date: Sun, 24 May 2026 08:57:25 -0400 Subject: [PATCH] fix: volume_size_info override + free_image retry loop + remove rootdir content type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit volume_size_info: base class routes through filesystem_path() → get_subdir() which dies on block storage. Override to query TrueNAS API directly for volsize.parsed so create_efidisk and other callers get the correct zvol size. free_image retry loop: TrueNAS CORE 13 refuses targetextent deletion while any iSCSI session is active. Single logout+restart was racy — pvedaemon workers reconnect between the restart and DELETE when multiple disks are deleted concurrently (e.g. VM destroy). Retry up to 5 times with fresh logout+service restart each cycle and exponential backoff. Remove rootdir from plugindata content types: rootdir signals LXC container/directory storage and caused PVE to route TPM state allocation to TrueNAS, which immediately failed since TrueNAS has no filesystem path. Co-Authored-By: Claude Sonnet 4.6 --- perl5/PVE/Storage/Custom/TrueNAS.pm | 60 +++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/perl5/PVE/Storage/Custom/TrueNAS.pm b/perl5/PVE/Storage/Custom/TrueNAS.pm index a2754b2..347377a 100644 --- a/perl5/PVE/Storage/Custom/TrueNAS.pm +++ b/perl5/PVE/Storage/Custom/TrueNAS.pm @@ -35,7 +35,7 @@ sub type { return 'truenas'; } sub plugindata { return { - content => [ { images => 1, rootdir => 1 }, { images => 1 } ], + content => [ { images => 1 }, { images => 1 } ], format => [ { raw => 1 }, 'raw' ], # sensitive-properties intentionally omitted: PVE strips those keys from # $param before check_config and passes them only to on_add_hook/on_update_hook, @@ -445,23 +445,40 @@ sub free_image { 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) }; + # TrueNAS CORE 13 refuses targetextent deletion while any iSCSI session + # exists. We log out, restart the service to purge server-side state, + # then immediately attempt the DELETE. pvedaemon workers can race us + # and reconnect the session, so we retry up to 5 times with a fresh + # logout+restart cycle each time. + my $deleted = 0; + for my $attempt (1..5) { + # Log out any active session for this target + my $sessions = ''; + eval { run_command(['iscsiadm', '-m', 'session'], + outfunc => sub { $sessions .= shift . "\n" }, + noerr => 1) }; + for my $sid ($sessions =~ /\[(\d+)\][^\n]*\Q$iqn\E/g) { + _log('info', "free_image: logging out iSCSI session $sid (attempt $attempt)"); + eval { run_command(['iscsiadm', '-m', 'session', '-r', $sid, '--logout'], + noerr => 1) }; + } + + # Restart TrueNAS iSCSI service to clear all server-side session state + _log('info', "free_image: restarting TrueNAS iSCSI service (attempt $attempt)"); + eval { _api($scfg, 'POST', '/service/restart', { service => 'iscsitarget' }) }; + + eval { _api($scfg, 'DELETE', "/iscsi/targetextent/id/$ext->{targetextent_id}") }; + if ($@) { + _log('warning', "free_image: targetextent delete attempt $attempt failed: $@"); + sleep $attempt; # back off before retry + next; + } + $deleted = 1; + last; } + die "free_image: could not delete targetextent $ext->{targetextent_id} after 5 attempts\n" + unless $deleted; - # 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 @@ -526,6 +543,17 @@ sub list_images { return \@vols; } +sub volume_size_info { + my ($class, $scfg, $storeid, $volname, $timeout) = @_; + + # The base class volume_size_info calls filesystem_path() which calls + # get_subdir() which dies on block storage. Query TrueNAS directly instead. + my $zvol = _zvol_prefix($scfg) . "/$volname"; + my $enc = uri_escape($zvol, "^A-Za-z0-9\\-_.~"); + my $ds = _api($scfg, 'GET', "/pool/dataset/id/$enc") // {}; + return $ds->{volsize}{parsed} // 0; +} + sub path { my ($class, $scfg, $volname, $storeid, $snapname) = @_;