fix: pre-flight check before service-stop fallback in free_image (#253)

Stopping the TrueNAS iSCSI service to clear session state (required on
CORE 13 when force=true is ignored) disrupts all LUNs on the target,
causing io-error on any other running VM using this storage.

Before triggering the service-stop path, scan /etc/pve/qemu-server/*.conf
for running VMs (PID file exists + process alive) that reference this
storage ID. If any are found, fail with a clear message naming the
blocking VMs rather than silently disrupting them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Kevin Adams 2026-05-24 11:29:14 -04:00
parent 178088197f
commit 5684b335a1
1 changed files with 41 additions and 3 deletions

View File

@ -428,6 +428,37 @@ sub alloc_image {
return $name;
}
# Returns a list of VMIDs that are currently running and have at least one
# disk on the given storage. Used to gate the service-stop fallback path.
sub _running_vms_on_storage {
my ($storeid) = @_;
my @blocking;
my $cfgdir = '/etc/pve/qemu-server';
opendir(my $dh, $cfgdir) or return ();
while (my $f = readdir($dh)) {
next unless $f =~ /^(\d+)\.conf$/;
my $vmid = $1;
# Check running first (cheap) — PID file exists and process is alive
my $pidfile = "/var/run/qemu-server/$vmid.pid";
next unless -f $pidfile;
open(my $pf, '<', $pidfile) or next;
my $pid = <$pf>; chomp $pid;
close($pf);
next unless $pid && kill(0, $pid);
# VM is running — check if it uses this storage
open(my $cf, '<', "$cfgdir/$f") or next;
while (<$cf>) {
if (/^\w+:\s+\Q$storeid\E:/) {
push @blocking, $vmid;
last;
}
}
close($cf);
}
closedir($dh);
return @blocking;
}
sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
@ -445,9 +476,16 @@ sub free_image {
$deleted = 1;
} elsif (defined $ext->{targetextent_id}) {
# force=true is ignored by TrueNAS CORE 13 — unconditional session check.
# POST /service/restart is async and iscsid reconnects in milliseconds,
# so we must: disable auto-reconnect → logout → wait for service to stop
# → delete → re-enable auto-reconnect → login.
# The only reliable delete window requires stopping the TrueNAS iSCSI
# service entirely, which disrupts ALL LUNs on ALL targets. Refuse if
# any other VM on this node has disks on this storage and is running —
# stopping the service would cause io-error on those VMs.
my @blocking = _running_vms_on_storage($storeid);
die "free_image: cannot delete '$volname' while other VMs are running on "
. "storage '$storeid': VM(s) " . join(', ', map { "#$_" } @blocking)
. ". Stop those VMs first, then retry.\n"
if @blocking;
_log('warning', "free_image: force delete failed — using service-stop fallback");
my $target = _resolve_target($scfg);