From 415c2a250b1a0524cbd130082e0861dcd2da142b Mon Sep 17 00:00:00 2001 From: Kevin Adams Date: Sun, 24 May 2026 16:37:33 -0400 Subject: [PATCH] fix: unmap targetextent before extent delete in free_image (#259) When a VM has multiple disks on the same per-VM target, migrating one disk while the VM is running leaves the target in use for the other disks. TrueNAS refuses to delete an extent associated with an in-use target even with force=true. Fix: delete the targetextent (LUN mapping) first, then delete the extent. Removing the mapping severs this disk's association without touching the active session or other LUNs on the same target. Co-Authored-By: Claude Sonnet 4.6 --- perl5/PVE/Storage/Custom/TrueNAS.pm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/perl5/PVE/Storage/Custom/TrueNAS.pm b/perl5/PVE/Storage/Custom/TrueNAS.pm index feb875e..3c2306f 100644 --- a/perl5/PVE/Storage/Custom/TrueNAS.pm +++ b/perl5/PVE/Storage/Custom/TrueNAS.pm @@ -430,9 +430,18 @@ sub free_image { my $ext = _find_extent($scfg, $volname); if ($ext) { - # force=true tells TrueNAS to disconnect any remaining iSCSI session - # before deleting. For detached disks on a running VM, QEMU has - # already closed the libiscsi connection, so this is a no-op safety net. + # Step 1: unmap the LUN from the target. This must happen before the + # extent delete when the per-VM target still has other LUNs with active + # sessions (e.g. migrating one disk while the VM is running with others). + # Removing the targetextent severs just this LUN's association without + # touching the target session or other LUNs. + if (defined $ext->{targetextent_id}) { + eval { _api($scfg, 'DELETE', "/iscsi/targetextent/id/$ext->{targetextent_id}") }; + _log('warning', "free_image: could not remove targetextent $ext->{targetextent_id}: $@") if $@; + } + + # Step 2: delete the extent. force=true handles any residual session + # on a single-disk per-VM target (e.g. deleting a detached disk). eval { _api($scfg, 'DELETE', "/iscsi/extent/id/$ext->{extent_id}", { force => JSON::true });