fix: delete all targetextent associations in free_image (#265)

_find_extent returned only the first targetextent row for an extent.
If duplicate rows exist (from a prior failed alloc_image leaving an
orphan), free_image deleted one association and then hit a 422 on the
extent DELETE because TrueNAS still saw the second association as
keeping the target in use.

Return all rows in a new targetextents field and loop over them in
free_image so every association is removed before the extent DELETE.

Fixes: 422 Unprocessable Entity on free_image (SCALE 24.10, vm-103-disk-3)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kevin Adams 2026-05-27 17:31:02 -04:00
parent 334f67e473
commit 0dea6bc078
1 changed files with 11 additions and 10 deletions

View File

@ -365,8 +365,9 @@ sub _next_lun_id {
return int($lun);
}
# Returns { extent_id, targetextent_id, lun_id, target_id } for a volname,
# or undef if no extent exists.
# Returns { extent_id, targetextents, targetextent_id, lun_id, target_id } for a volname,
# or undef if no extent exists. targetextents is the full array of all associations
# (normally one, but may be >1 if duplicate rows exist from a prior failed alloc_image).
sub _find_extent {
my ($scfg, $volname) = @_;
@ -379,6 +380,7 @@ sub _find_extent {
return {
extent_id => $ext->{id},
targetextents => $tes,
targetextent_id => defined $te ? $te->{id} : undef,
lun_id => defined $te ? $te->{lunid} : 0,
target_id => defined $te ? $te->{target} : undef,
@ -508,14 +510,13 @@ sub free_image {
my $ext = _find_extent($scfg, $volname);
if ($ext) {
# 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 1: unmap ALL LUN associations for this extent. Normally there is
# exactly one targetextent row, but duplicate rows can exist if a prior
# alloc_image failed partway through and left an orphan. Deleting all of
# them before the extent DELETE prevents a 422 "target in use" rejection.
for my $te (@{ $ext->{targetextents} // [] }) {
eval { _api($scfg, 'DELETE', "/iscsi/targetextent/id/$te->{id}") };
_log('warning', "free_image: could not remove targetextent $te->{id}: $@") if $@;
}
# Step 2: delete the extent. force=true handles any residual session