From: Michal Fox <me@dualfroz.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH manager] fix #7860: api: backup info: skip jobs with invalid guest selection
Date: Fri, 25 Sep 2026 05:30:16 +0000 [thread overview]
Message-ID: <20260925053016.7-1-me@dualfroz.com> (raw)
A backup job in pool mode keeps referencing its pool after the pool got
deleted. Resolving the guests of such a job dies with "pool '<name>'
does not exist", and since get_included_vmids() did not catch this, the
whole /cluster/backup-info/not-backed-up call failed. The GUI then hides
the "Show guests without backup job" button, so users lose exactly the
overview that would tell them some guests are no longer backed up.
Catch the error per job, warn about it and continue with the remaining
jobs. The broken job does not back up anything, so its guests correctly
show up as not covered by any backup job.
Add a test for get_included_vmids() covering this case.
Signed-off-by: Michal Fox <me@dualfroz.com>
---
PVE/API2/Cluster/BackupInfo.pm | 7 +++-
test/Makefile | 6 ++-
test/backup_info_test.pl | 71 ++++++++++++++++++++++++++++++++++
3 files changed, 82 insertions(+), 2 deletions(-)
create mode 100755 test/backup_info_test.pl
diff --git a/PVE/API2/Cluster/BackupInfo.pm b/PVE/API2/Cluster/BackupInfo.pm
index 4ef59ac3..1ac59662 100644
--- a/PVE/API2/Cluster/BackupInfo.pm
+++ b/PVE/API2/Cluster/BackupInfo.pm
@@ -25,7 +25,12 @@ sub get_included_vmids {
my $all_vmids = {};
for my $job ($legacy_jobs->@*, grep { $_->{type} eq 'vzdump' } values $jobs->{ids}->%*) {
- my $job_included_guests = PVE::VZDump::get_included_guests($job);
+ # a job referencing e.g. a deleted pool backs up nothing, but must not break the overview
+ my $job_included_guests = eval { PVE::VZDump::get_included_guests($job) };
+ if (my $err = $@) {
+ warn "ignoring backup job with invalid guest selection - $err";
+ next;
+ }
$all_vmids->{$_} = 1 for map { $_->@* } values %{$job_included_guests};
}
diff --git a/test/Makefile b/test/Makefile
index 2dfd20a4..adc1f17b 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -18,7 +18,7 @@ replication%.t: replication_test%.pl
./$<
.PHONY: test-vzdump
-test-vzdump: test-vzdump-guest-included test-vzdump-new
+test-vzdump: test-vzdump-guest-included test-vzdump-new test-vzdump-backup-info
.PHONY: test-vzdump-guest-included
test-vzdump-guest-included:
@@ -28,6 +28,10 @@ test-vzdump-guest-included:
test-vzdump-new:
./vzdump_new_test.pl
+.PHONY: test-vzdump-backup-info
+test-vzdump-backup-info:
+ ./backup_info_test.pl
+
.PHONY: test-osd
test-osd:
./OSD_test.pl
diff --git a/test/backup_info_test.pl b/test/backup_info_test.pl
new file mode 100755
index 00000000..54aece2e
--- /dev/null
+++ b/test/backup_info_test.pl
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib '..';
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::API2::Cluster::BackupInfo;
+
+my $vmlist = {
+ ids => {
+ 100 => { type => 'qemu', node => 'node1' },
+ 101 => { type => 'qemu', node => 'node1' },
+ 102 => { type => 'lxc', node => 'node1' },
+ },
+};
+
+my $pools = {
+ testpool => [100],
+};
+
+my $jobs_cfg = {
+ ids => {
+ 'backup-pool' => { type => 'vzdump', pool => 'testpool' },
+ 'backup-vmid' => { type => 'vzdump', vmid => '101' },
+ },
+};
+
+my $cluster_module = Test::MockModule->new('PVE::Cluster');
+$cluster_module->mock(get_vmlist => sub { return $vmlist });
+
+my $api2tools_module = Test::MockModule->new('PVE::API2Tools');
+$api2tools_module->mock(
+ get_resource_pool_guest_members => sub {
+ my ($pool) = @_;
+ die "pool '$pool' does not exist\n" if !$pools->{$pool};
+ return $pools->{$pool};
+ },
+);
+
+my $backup_info_module = Test::MockModule->new('PVE::API2::Cluster::BackupInfo');
+$backup_info_module->mock(
+ cfs_read_file => sub {
+ my ($filename) = @_;
+ return { jobs => [] } if $filename eq 'vzdump.cron';
+ return $jobs_cfg if $filename eq 'jobs.cfg';
+ die "unexpected file '$filename'\n";
+ },
+);
+
+is_deeply(
+ PVE::API2::Cluster::BackupInfo::get_included_vmids(),
+ { 100 => 1, 101 => 1 },
+ 'guests of all backup jobs are included',
+);
+
+$jobs_cfg->{ids}->{'backup-deleted-pool'} = { type => 'vzdump', pool => 'deletedpool' };
+
+my @warnings;
+my $included = eval {
+ local $SIG{__WARN__} = sub { push @warnings, $_[0] };
+ PVE::API2::Cluster::BackupInfo::get_included_vmids();
+};
+is($@, '', 'job referencing a deleted pool does not make the lookup fail');
+is_deeply($included, { 100 => 1, 101 => 1 }, 'guests of the other jobs are still included');
+like($warnings[0] // '', qr/pool 'deletedpool' does not exist/, 'broken job is warned about');
+
+done_testing();
--
2.43.0
reply other threads:[~2026-09-28 7:16 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260925053016.7-1-me@dualfroz.com \
--to=me@dualfroz.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox