* [PATCH qemu-server v2 01/18] block job: fix variable name in documentation
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
` (16 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer/BlockJob.pm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index d7c78741..97ada28b 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -19,7 +19,7 @@ use PVE::QemuServer::RunState;
# If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this
# option is useful to get the error for failed jobs here. QEMU's job lock should make it impossible
# to see a job in 'concluded' state when auto-dismiss=true.
-# $info is the 'BlockJobInfo' for the job returned by query-block-jobs.
+# $qmp_info is the 'BlockJobInfo' for the job returned by query-block-jobs.
# $job is the information about the job recorded on the PVE-side.
# A block node $job->{'detach-node-name'} will be detached if present.
sub qemu_handle_concluded_blockjob {
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 01/18] block job: fix variable name in documentation Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
` (15 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Based on pre-existing defaults for similar commands, commands for
adding get 1 minute, commands for creating block jobs or removing get
10 minutes, since those might require in-flight IO to finish.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QMPClient.pm | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/PVE/QMPClient.pm b/src/PVE/QMPClient.pm
index 723123a6..4d4a2f82 100644
--- a/src/PVE/QMPClient.pm
+++ b/src/PVE/QMPClient.pm
@@ -120,6 +120,8 @@ sub cmd {
$timeout = 3 * 60;
} elsif (
$cmd->{execute} eq 'blockdev-add'
+ || $cmd->{execute} eq 'blockdev-insert-medium'
+ || $cmd->{execute} eq 'block-export-add'
|| $cmd->{execute} eq 'device_add'
|| $cmd->{execute} eq 'device_del'
|| $cmd->{execute} eq 'netdev_add'
@@ -130,8 +132,13 @@ sub cmd {
$timeout = 60;
} elsif (
$cmd->{execute} eq 'backup-cancel'
+ || $cmd->{execute} eq 'block-commit'
+ || $cmd->{execute} eq 'block-export-del'
+ || $cmd->{execute} eq 'block-stream'
|| $cmd->{execute} eq 'blockdev-del'
|| $cmd->{execute} eq 'blockdev-mirror'
+ || $cmd->{execute} eq 'blockdev-remove-medium'
+ || $cmd->{execute} eq 'blockdev-reopen'
|| $cmd->{execute} eq 'block-job-cancel'
|| $cmd->{execute} eq 'job-complete'
|| $cmd->{execute} eq 'drive-mirror'
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 01/18] block job: fix variable name in documentation Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
` (14 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
In preparation for supporting snapshot operations for drives which
are QSD FUSE exported. Having a central drive_uses_qsd_fuse() helper
makes it possible to consistently decide whether the QMP peer is the
QEMU storage daemon or the main QEMU instance.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 7 +++----
src/PVE/QemuServer/Drive.pm | 12 ++++++++++++
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index d87fc218..03a03efd 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -2828,12 +2828,11 @@ sub start_swtpm {
my $tpm = parse_drive("tpmstate0", $tpmdrive);
my ($storeid) = PVE::Storage::parse_volume_id($tpm->{file}, 1);
if ($storeid) {
- my $format = checked_volume_format($storecfg, $tpm->{file});
- if ($format eq 'raw') {
- $state = PVE::Storage::map_volume($storecfg, $tpm->{file});
- } else {
+ if (PVE::QemuServer::Drive::drive_uses_qsd_fuse($storecfg, $tpm)) {
PVE::QemuServer::QSD::start($vmid);
$state = PVE::QemuServer::QSD::add_fuse_export($vmid, $tpm, 'tpmstate0');
+ } else {
+ $state = PVE::Storage::map_volume($storecfg, $tpm->{file});
}
} else {
$state = $tpm->{file};
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index 10106ebd..f88504b2 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -1152,4 +1152,16 @@ sub detect_zeroes_cmdline_option {
return 'on';
}
+sub drive_uses_qsd_fuse {
+ my ($storecfg, $drive) = @_;
+
+ if ($drive->{interface} eq 'tpmstate') {
+ my ($storeid) = PVE::Storage::parse_volume_id($drive->{file}, 1);
+ my $format = checked_volume_format($storecfg, $drive->{file});
+ return $storeid && $format ne 'raw';
+ }
+
+ return;
+}
+
1;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (2 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper Fiona Ebner
` (13 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Avoid duplicating this information to ensure consistency and improve
readability.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 10 ++++------
src/PVE/QemuServer/Blockdev.pm | 4 ++--
src/PVE/QemuServer/Monitor.pm | 9 ++++++++-
src/PVE/VZDump/QemuServer.pm | 4 ++--
4 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 03a03efd..ba5acfe3 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -83,7 +83,7 @@ use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
use PVE::QemuServer::Network;
use PVE::QemuServer::OVMF;
use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
@@ -2707,7 +2707,7 @@ sub vmstatus {
my $statuscb = sub {
my ($vmid, $resp) = @_;
- my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
+ my $qmp_peer = vm_qmp_peer($vmid);
$qmpclient->queue_cmd($qmp_peer, $proxmox_support_cb, 'query-proxmox-support');
$qmpclient->queue_cmd($qmp_peer, $blockstatscb, 'query-blockstats');
@@ -2729,8 +2729,7 @@ sub vmstatus {
foreach my $vmid (keys %$list) {
next if $opt_vmid && ($vmid ne $opt_vmid);
next if !$res->{$vmid}->{pid}; # not running
- my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
- $qmpclient->queue_cmd($qmp_peer, $statuscb, 'query-status');
+ $qmpclient->queue_cmd(vm_qmp_peer($vmid), $statuscb, 'query-status');
}
$qmpclient->queue_execute(undef, 2);
@@ -3187,8 +3186,7 @@ sub config_to_command {
my $use_virtio = 0;
- my $qmpsocket =
- PVE::QemuServer::Helpers::qmp_socket({ name => "VM $vmid", id => $vmid, type => 'qmp' });
+ my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket(vm_qmp_peer($vmid));
push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server=on,wait=off";
push @$cmd, '-mon', "chardev=qmp,mode=control";
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index be907be8..80c1178e 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -14,7 +14,7 @@ use PVE::Storage;
use PVE::QemuServer::Drive qw(drive_is_cdrom);
use PVE::QemuServer::Helpers;
use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
use base qw(Exporter);
@@ -588,7 +588,7 @@ sub attach {
if ($options->{qsd}) {
$qmp_peer = { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
} else {
- $qmp_peer = { name => "VM $id", id => $id, type => 'qmp' };
+ $qmp_peer = vm_qmp_peer($id);
}
my $machine_version;
diff --git a/src/PVE/QemuServer/Monitor.pm b/src/PVE/QemuServer/Monitor.pm
index 423c40a9..e7f08e23 100644
--- a/src/PVE/QemuServer/Monitor.pm
+++ b/src/PVE/QemuServer/Monitor.pm
@@ -11,6 +11,7 @@ use base 'Exporter';
our @EXPORT_OK = qw(
mon_cmd
qmp_cmd
+ vm_qmp_peer
);
=head3 qmp_cmd
@@ -102,6 +103,12 @@ sub qmp_cmd {
return $res;
}
+sub vm_qmp_peer {
+ my ($vmid) = @_;
+
+ return { name => "VM $vmid", id => $vmid, type => 'qmp' };
+}
+
sub qsd_cmd {
my ($id, $execute, %params) = @_;
@@ -121,7 +128,7 @@ sub hmp_cmd {
my ($vmid, $cmdline, $timeout) = @_;
return qmp_cmd(
- { name => "VM $vmid", id => $vmid, type => 'qmp' }, 'human-monitor-command',
+ vm_qmp_peer($vmid), 'human-monitor-command',
'command-line' => $cmdline,
timeout => $timeout,
);
diff --git a/src/PVE/VZDump/QemuServer.pm b/src/PVE/VZDump/QemuServer.pm
index 55fb6dc4..0cdf6fed 100644
--- a/src/PVE/VZDump/QemuServer.pm
+++ b/src/PVE/VZDump/QemuServer.pm
@@ -34,7 +34,7 @@ use PVE::QemuServer::Blockdev;
use PVE::QemuServer::Drive qw(checked_volume_format);
use PVE::QemuServer::Helpers;
use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
use PVE::QemuServer::QMPHelpers;
use base qw (PVE::VZDump::Plugin);
@@ -995,7 +995,7 @@ sub archive_vma {
}
my $qmpclient = PVE::QMPClient->new();
- my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
+ my $qmp_peer = vm_qmp_peer($vmid);
my $backup_cb = sub {
my ($vmid, $resp) = @_;
$backup_job_uuid = $resp->{return}->{UUID};
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (3 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
` (12 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Avoid duplicating this information to ensure consistency and improve
readability.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer/Blockdev.pm | 9 ++-------
src/PVE/QemuServer/Monitor.pm | 10 ++++++++--
src/PVE/QemuServer/QSD.pm | 11 ++++-------
3 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 80c1178e..3c27e721 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -14,7 +14,7 @@ use PVE::Storage;
use PVE::QemuServer::Drive qw(drive_is_cdrom);
use PVE::QemuServer::Helpers;
use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd qsd_peer vm_qmp_peer);
use base qw(Exporter);
@@ -584,12 +584,7 @@ state image.
sub attach {
my ($storecfg, $id, $drive, $options) = @_;
- my $qmp_peer;
- if ($options->{qsd}) {
- $qmp_peer = { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
- } else {
- $qmp_peer = vm_qmp_peer($id);
- }
+ my $qmp_peer = $options->{qsd} ? qsd_peer($id) : vm_qmp_peer($id);
my $machine_version;
if ($options->{qsd}) { # qemu-storage-daemon runs with the installed binary version
diff --git a/src/PVE/QemuServer/Monitor.pm b/src/PVE/QemuServer/Monitor.pm
index e7f08e23..1b1ce4ea 100644
--- a/src/PVE/QemuServer/Monitor.pm
+++ b/src/PVE/QemuServer/Monitor.pm
@@ -11,6 +11,7 @@ use base 'Exporter';
our @EXPORT_OK = qw(
mon_cmd
qmp_cmd
+ qsd_peer
vm_qmp_peer
);
@@ -109,11 +110,16 @@ sub vm_qmp_peer {
return { name => "VM $vmid", id => $vmid, type => 'qmp' };
}
+sub qsd_peer {
+ my ($id) = @_;
+
+ return { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
+}
+
sub qsd_cmd {
my ($id, $execute, %params) = @_;
- return qmp_cmd({ name => "QEMU storage daemon $id", id => $id, type => 'qsd' },
- $execute, %params);
+ return qmp_cmd(qsd_peer($id), $execute, %params);
}
sub mon_cmd {
diff --git a/src/PVE/QemuServer/QSD.pm b/src/PVE/QemuServer/QSD.pm
index 9c30f7fd..bb85085a 100644
--- a/src/PVE/QemuServer/QSD.pm
+++ b/src/PVE/QemuServer/QSD.pm
@@ -11,7 +11,7 @@ use PVE::Tools;
use PVE::QemuServer::Blockdev;
use PVE::QemuServer::Helpers;
-use PVE::QemuServer::Monitor;
+use PVE::QemuServer::Monitor qw(qsd_peer);
=head3 start
@@ -22,13 +22,10 @@ Start a QEMU storage daemon instance with ID C<$id>.
=cut
sub start($id) {
- my $name = "QEMU storage daemon $id";
-
# If something is still mounted, that could block the new instance, try to clean up first.
PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);
- my $qmp_socket_path =
- PVE::QemuServer::Helpers::qmp_socket({ name => $name, id => $id, type => 'qsd' });
+ my $qmp_socket_path = PVE::QemuServer::Helpers::qmp_socket(qsd_peer($id));
my $pidfile = PVE::QemuServer::Helpers::qsd_pidfile_name($id);
my $cmd = [
@@ -45,7 +42,7 @@ sub start($id) {
PVE::Tools::run_command($cmd);
my $pid = PVE::QemuServer::Helpers::qsd_running_locally($id);
- syslog("info", "$name started with PID $pid.");
+ syslog("info", "QEMU storage daemon $id started with PID $pid.");
return;
}
@@ -134,7 +131,7 @@ sub quit($id) {
}
unlink PVE::QemuServer::Helpers::qsd_pidfile_name($id);
- unlink PVE::QemuServer::Helpers::qmp_socket({ name => $name, id => $id, type => 'qsd' });
+ unlink PVE::QemuServer::Helpers::qmp_socket(qsd_peer($id));
PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (4 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
` (11 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
The inserted node for the front-end device queried via
get_block_info() is the top node. The next commit will allow
get_node_name_below_throttle() to also query QSD, where there is no
front-end device. Part of the code can still be re-used, but the name
'inserted' would just be confusing.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer/Blockdev.pm | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 3c27e721..c96b76da 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -170,17 +170,17 @@ sub get_node_name_below_throttle {
my $block_info = get_block_info($vmid);
my $drive_id = $device_id =~ s/^drive-//r;
- my $inserted = $block_info->{$drive_id}->{inserted}
+ my $top = $block_info->{$drive_id}->{inserted}
or die "no block node inserted for drive '$drive_id'\n";
- if ($inserted->{drv} ne 'throttle') {
- die "$device_id: unexpected top node $inserted->{'node-name'} ($inserted->{drv})\n"
+ if ($top->{drv} ne 'throttle') {
+ die "$device_id: unexpected top node $top->{'node-name'} ($top->{drv})\n"
if $assert_top_is_throttle;
# before the switch to -blockdev, the top node was not throttle
- return $inserted->{'node-name'};
+ return $top->{'node-name'};
}
- my $children = { map { $_->{child} => $_ } $inserted->{children}->@* };
+ my $children = { map { $_->{child} => $_ } $top->{children}->@* };
if (my $node_name = $children->{file}->{'node-name'}) {
return $node_name;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (5 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 08/18] blockdev: switch detach() " Fiona Ebner
` (10 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
The get_block_info() function can only be used when the main QEMU
instance is the QMP peer, because it gets the information from the
front-end devices. In case of QSD, the relevant information can be
obtained with the 'query-named-block-nodes' QMP command.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
Changes in v2:
* also die right away in QSD case if no block node can be found
src/PVE/QemuServer.pm | 8 +++++---
src/PVE/QemuServer/BlockJob.pm | 2 +-
src/PVE/QemuServer/Blockdev.pm | 20 +++++++++++++++-----
src/PVE/QemuServer/VolumeChain.pm | 7 ++++---
4 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index ba5acfe3..0841750a 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7284,7 +7284,8 @@ sub pbs_live_restore {
# removes itself once all backing images vanish with 'auto-remove=on')
my $jobs = {};
for my $ds (sort keys %$restored_disks) {
- my $node_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $ds);
+ my $node_name =
+ PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $ds);
my $job_id = "restore-$ds";
mon_cmd(
$vmid, 'block-stream',
@@ -7402,8 +7403,9 @@ sub live_import_from_files {
# removes itself once all backing images vanish with 'auto-remove=on')
my $jobs = {};
for my $ds (sort keys %$live_restore_backing) {
- my $node_name =
- PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, "drive-$ds");
+ my $node_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
+ vm_qmp_peer($vmid), "drive-$ds",
+ );
my $job_id = "restore-$ds";
mon_cmd(
$vmid, 'block-stream',
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 97ada28b..59b33482 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -455,7 +455,7 @@ sub blockdev_mirror {
# Need to replace the node below the top node. This is not necessarily a format node, for
# example, it can also be a zeroinit node by a previous mirror! So query QEMU itself.
my $source_node_name =
- PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $device_id, 1);
+ PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $device_id, 1);
# Copy original drive config (aio, cache, discard, ...):
my $dest_drive = dclone($source->{drive});
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index c96b76da..495aff50 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -166,12 +166,22 @@ sub top_node_name {
}
sub get_node_name_below_throttle {
- my ($vmid, $device_id, $assert_top_is_throttle) = @_;
+ my ($qmp_peer, $device_id, $assert_top_is_throttle) = @_;
- my $block_info = get_block_info($vmid);
- my $drive_id = $device_id =~ s/^drive-//r;
- my $top = $block_info->{$drive_id}->{inserted}
- or die "no block node inserted for drive '$drive_id'\n";
+ my $top;
+ if ($qmp_peer->{type} eq 'qmp') { # get_block_info() only works if there are front-end devices.
+ my $block_info = get_block_info($qmp_peer->{id});
+ my $drive_id = $device_id =~ s/^drive-//r;
+ $top = $block_info->{$drive_id}->{inserted};
+ } else {
+ my $named_block_node_info = qmp_cmd($qmp_peer, 'query-named-block-nodes');
+ for my $info ($named_block_node_info->@*) {
+ next if $info->{'node-name'} ne $device_id;
+ $top = $info;
+ last;
+ }
+ }
+ die "no block node found for drive '$device_id'\n" if !$top;
if ($top->{drv} ne 'throttle') {
die "$device_id: unexpected top node $top->{'node-name'} ($top->{drv})\n"
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index e3790683..3613c008 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -11,7 +11,7 @@ use PVE::Storage;
use PVE::QemuServer::Blockdev qw(generate_file_blockdev generate_format_blockdev);
use PVE::QemuServer::BlockJob;
use PVE::QemuServer::Drive;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
sub blockdev_external_snapshot {
my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
@@ -113,8 +113,9 @@ sub blockdev_replace {
my $src_blockdev_name;
if ($src_snap eq 'current') {
# there might be other nodes on top like zeroinit, look up the current node below throttle
- $src_blockdev_name =
- PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $deviceid, 1);
+ $src_blockdev_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
+ vm_qmp_peer($vmid), $deviceid, 1,
+ );
} else {
$src_name_options = { 'snapshot-name' => $src_snap };
$src_blockdev_name =
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 08/18] blockdev: switch detach() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (6 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
` (9 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 6 +++---
src/PVE/QemuServer/BlockJob.pm | 4 ++--
src/PVE/QemuServer/Blockdev.pm | 22 +++++++++++-----------
src/PVE/QemuServer/VolumeChain.pm | 4 ++--
src/PVE/VZDump/QemuServer.pm | 2 +-
5 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 0841750a..a27a27c2 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4087,7 +4087,7 @@ sub qemu_drivedel {
# for the switch to -blockdev
if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
- PVE::QemuServer::Blockdev::detach($vmid, "drive-$deviceid");
+ PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "drive-$deviceid");
return 1;
} else {
my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid", 10 * 60);
@@ -7303,7 +7303,7 @@ sub pbs_live_restore {
. " to disconnect from Proxmox Backup Server\n";
for my $ds (sort keys %$restored_disks) {
- PVE::QemuServer::Blockdev::detach($vmid, "$ds-pbs");
+ PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "$ds-pbs");
}
close($qmeventd_fd);
@@ -7422,7 +7422,7 @@ sub live_import_from_files {
print "restore-drive jobs finished successfully, removing all tracking block devices\n";
for my $ds (sort keys %$live_restore_backing) {
- PVE::QemuServer::Blockdev::detach($vmid, "drive-$ds-restore");
+ PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "drive-$ds-restore");
}
close($qmeventd_fd);
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 59b33482..f76b1bde 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -34,7 +34,7 @@ sub qemu_handle_concluded_blockjob {
$job->{'detach-node-name'} = $job->{'target-node-name'} if $qmp_info->{error} || $job->{cancel};
if (my $node_name = $job->{'detach-node-name'}) {
- eval { PVE::QemuServer::Blockdev::detach($vmid, $node_name); };
+ eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name); };
log_warn($@) if $@;
}
@@ -509,7 +509,7 @@ sub blockdev_mirror {
if (my $err = $@) {
eval { qemu_blockjobs_cancel($vmid, $jobs) };
log_warn("unable to cancel block jobs - $@");
- eval { PVE::QemuServer::Blockdev::detach($vmid, $target_node_name); };
+ eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $target_node_name); };
log_warn("unable to delete blockdev '$target_node_name' - $@");
die "error starting blockdev mirrror - $err";
}
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 495aff50..b9430a56 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -637,16 +637,16 @@ sub attach {
=head3 detach
- detach($vmid, $node_name);
+ detach($qmp_peer, $node_name);
-Detach the block device C<$node_name> from the VM C<$vmid>. Also removes associated child block
-nodes.
+Detach the block device C<$node_name> from the QMP peer C<$qmp_peer>. Also removes associated child
+block nodes.
Parameters:
=over
-=item C<$vmid>: The ID of the virtual machine.
+=item C<$qmp_peer>: QMP peer information.
=item C<$node_name>: The node name identifying the block node in QEMU.
@@ -655,11 +655,11 @@ Parameters:
=cut
sub detach {
- my ($vmid, $node_name) = @_;
+ my ($qmp_peer, $node_name) = @_;
die "Blockdev::detach - no node name\n" if !$node_name;
- my $block_info = mon_cmd($vmid, "query-named-block-nodes");
+ my $block_info = qmp_cmd($qmp_peer, "query-named-block-nodes");
$block_info = { map { $_->{'node-name'} => $_ } $block_info->@* };
my $remove_throttle_group_id;
@@ -670,7 +670,7 @@ sub detach {
while ($node_name) {
last if !$block_info->{$node_name}; # already gone
- my $res = mon_cmd($vmid, 'blockdev-del', 'node-name' => "$node_name", noerr => 1);
+ my $res = qmp_cmd($qmp_peer, 'blockdev-del', 'node-name' => "$node_name", noerr => 1);
if (my $err = $res->{error}) {
last if $err =~ m/Failed to find node with node-name/; # already gone
die "deleting blockdev '$node_name' failed : $err\n";
@@ -684,7 +684,7 @@ sub detach {
}
if ($remove_throttle_group_id) {
- eval { mon_cmd($vmid, 'object-del', id => $remove_throttle_group_id); };
+ eval { qmp_cmd($qmp_peer, 'object-del', id => $remove_throttle_group_id); };
die "removing throttle group failed - $@\n" if $@;
}
@@ -694,7 +694,7 @@ sub detach {
sub detach_tpm_backup_node {
my ($vmid) = @_;
- detach($vmid, "drive-tpmstate0-backup");
+ detach(vm_qmp_peer($vmid), "drive-tpmstate0-backup");
}
sub detach_fleecing_block_nodes {
@@ -706,7 +706,7 @@ sub detach_fleecing_block_nodes {
next if !is_fleecing_top_node($node_name);
$log_func->('info', "detaching (old) fleecing image '$node_name'");
- eval { detach($vmid, $node_name) };
+ eval { detach(vm_qmp_peer($vmid), $node_name) };
$log_func->('warn', "error detaching (old) fleecing image '$node_name' - $@") if $@;
}
}
@@ -746,7 +746,7 @@ my sub blockdev_change_medium {
# force eject if locked
mon_cmd($vmid, "blockdev-open-tray", force => JSON::true, id => "$qdev_id");
mon_cmd($vmid, "blockdev-remove-medium", id => "$qdev_id");
- detach($vmid, "drive-$qdev_id");
+ detach(vm_qmp_peer($vmid), "drive-$qdev_id");
return if $drive->{file} eq 'none';
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 3613c008..1b8d199e 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -66,7 +66,7 @@ sub blockdev_external_snapshot {
sub blockdev_delete {
my ($storecfg, $vmid, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
- eval { PVE::QemuServer::Blockdev::detach($vmid, $fmt_blockdev->{'node-name'}); };
+ eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $fmt_blockdev->{'node-name'}); };
warn "detaching block node for $file_blockdev->{filename} failed - $@" if $@;
#delete the file (don't use vdisk_free as we don't want to delete all snapshot chain)
@@ -188,7 +188,7 @@ sub blockdev_replace {
}
# delete old file|fmt nodes
- eval { PVE::QemuServer::Blockdev::detach($vmid, $src_blockdev_name); };
+ eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $src_blockdev_name); };
warn "detaching block node for $src_snap failed - $@" if $@;
}
diff --git a/src/PVE/VZDump/QemuServer.pm b/src/PVE/VZDump/QemuServer.pm
index 0cdf6fed..be48be78 100644
--- a/src/PVE/VZDump/QemuServer.pm
+++ b/src/PVE/VZDump/QemuServer.pm
@@ -631,7 +631,7 @@ my sub detach_fleecing_images {
for my $di ($disks->@*) {
if (my $volid = $di->{'fleece-volid'}) {
my $node_name = "$di->{qmdevice}-fleecing";
- eval { PVE::QemuServer::Blockdev::detach($vmid, $node_name) };
+ eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name) };
}
}
}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (7 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 08/18] blockdev: switch detach() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
` (8 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 2 +-
src/PVE/QemuServer/VolumeChain.pm | 25 ++++++++++++-------------
2 files changed, 13 insertions(+), 14 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index a27a27c2..ddeb394c 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4427,7 +4427,7 @@ sub qemu_volume_snapshot_delete {
PVE::QemuServer::VolumeChain::blockdev_replace(
$storecfg,
- $vmid,
+ vm_qmp_peer($vmid),
$machine_version,
$attached_deviceid,
$drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 1b8d199e..01c5dd9b 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -11,7 +11,7 @@ use PVE::Storage;
use PVE::QemuServer::Blockdev qw(generate_file_blockdev generate_format_blockdev);
use PVE::QemuServer::BlockJob;
use PVE::QemuServer::Drive;
-use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
sub blockdev_external_snapshot {
my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
@@ -26,7 +26,7 @@ sub blockdev_external_snapshot {
#reopen current to snap
blockdev_replace(
$storecfg,
- $vmid,
+ vm_qmp_peer($vmid),
$machine_version,
$deviceid,
$drive,
@@ -95,7 +95,7 @@ my sub blockdev_relative_backing_file {
sub blockdev_replace {
my (
$storecfg,
- $vmid,
+ $qmp_peer,
$machine_version,
$deviceid,
$drive,
@@ -113,9 +113,8 @@ sub blockdev_replace {
my $src_blockdev_name;
if ($src_snap eq 'current') {
# there might be other nodes on top like zeroinit, look up the current node below throttle
- $src_blockdev_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
- vm_qmp_peer($vmid), $deviceid, 1,
- );
+ $src_blockdev_name =
+ PVE::QemuServer::Blockdev::get_node_name_below_throttle($qmp_peer, $deviceid, 1);
} else {
$src_name_options = { 'snapshot-name' => $src_snap };
$src_blockdev_name =
@@ -148,16 +147,16 @@ sub blockdev_replace {
);
$target_fmt_blockdev->{backing} = $parent_fmt_nodename;
}
- mon_cmd($vmid, 'blockdev-add', %$target_fmt_blockdev);
+ qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);
#reopen the current throttlefilter nodename with the target fmt nodename
my $throttle_blockdev = PVE::QemuServer::Blockdev::generate_throttle_blockdev(
$drive, $target_fmt_blockdev->{'node-name'}, {},
);
- mon_cmd($vmid, 'blockdev-reopen', options => [$throttle_blockdev]);
+ qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$throttle_blockdev]);
} else {
#intermediate snapshot
- mon_cmd($vmid, 'blockdev-add', %$target_fmt_blockdev);
+ qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);
#reopen the parent node with the new target fmt backing node
my $parent_file_blockdev = generate_file_blockdev(
@@ -173,14 +172,14 @@ sub blockdev_replace {
{ 'snapshot-name' => $parent_snap },
);
$parent_fmt_blockdev->{backing} = $target_fmt_blockdev->{'node-name'};
- mon_cmd($vmid, 'blockdev-reopen', options => [$parent_fmt_blockdev]);
+ qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$parent_fmt_blockdev]);
my $backing_file =
blockdev_relative_backing_file($target_file_blockdev, $parent_file_blockdev);
#change backing-file in qcow2 metadatas
- mon_cmd(
- $vmid, 'change-backing-file',
+ qmp_cmd(
+ $qmp_peer, 'change-backing-file',
device => $deviceid,
'image-node-name' => $parent_fmt_blockdev->{'node-name'},
'backing-file' => $backing_file,
@@ -188,7 +187,7 @@ sub blockdev_replace {
}
# delete old file|fmt nodes
- eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $src_blockdev_name); };
+ eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $src_blockdev_name); };
warn "detaching block node for $src_snap failed - $@" if $@;
}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (8 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
` (7 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 8 +++++++-
src/PVE/QemuServer/VolumeChain.pm | 10 +++++-----
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index ddeb394c..3c698c4c 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4356,7 +4356,13 @@ sub qemu_volume_snapshot {
my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);
my $parent_snap = $snapshots->{'current'}->{parent};
PVE::QemuServer::VolumeChain::blockdev_external_snapshot(
- $storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap,
+ $storecfg,
+ vm_qmp_peer($vmid),
+ $machine_version,
+ $deviceid,
+ $drive,
+ $snap,
+ $parent_snap,
);
} elsif ($do_snapshots_type eq 'storage') {
PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 01c5dd9b..8c0749de 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -14,7 +14,7 @@ use PVE::QemuServer::Drive;
use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
sub blockdev_external_snapshot {
- my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
+ my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
print "Creating a new current volume with $snap as backing snap\n";
@@ -26,7 +26,7 @@ sub blockdev_external_snapshot {
#reopen current to snap
blockdev_replace(
$storecfg,
- vm_qmp_peer($vmid),
+ $qmp_peer,
$machine_version,
$deviceid,
$drive,
@@ -53,11 +53,11 @@ sub blockdev_external_snapshot {
#backing need to be forced to undef in blockdev, to avoid reopen of backing-file on blockdev-add
$new_fmt_blockdev->{backing} = undef;
- mon_cmd($vmid, 'blockdev-add', %$new_fmt_blockdev);
+ qmp_cmd($qmp_peer, 'blockdev-add', %$new_fmt_blockdev);
print "blockdev-snapshot: reopen current with $snap backing image\n";
- mon_cmd(
- $vmid, 'blockdev-snapshot',
+ qmp_cmd(
+ $qmp_peer, 'blockdev-snapshot',
node => $snap_fmt_blockdev->{'node-name'},
overlay => $new_fmt_blockdev->{'node-name'},
);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (9 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
` (6 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer/BlockJob.pm | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index f76b1bde..9e8fbdd7 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -13,7 +13,7 @@ use PVE::Storage;
use PVE::QemuServer::Agent qw(qga_check_running);
use PVE::QemuServer::Blockdev;
use PVE::QemuServer::Drive qw(checked_volume_format);
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
use PVE::QemuServer::RunState;
# If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this
@@ -23,9 +23,9 @@ use PVE::QemuServer::RunState;
# $job is the information about the job recorded on the PVE-side.
# A block node $job->{'detach-node-name'} will be detached if present.
sub qemu_handle_concluded_blockjob {
- my ($vmid, $job_id, $qmp_info, $job) = @_;
+ my ($qmp_peer, $job_id, $qmp_info, $job) = @_;
- eval { mon_cmd($vmid, 'job-dismiss', id => $job_id); };
+ eval { qmp_cmd($qmp_peer, 'job-dismiss', id => $job_id); };
log_warn("$job_id: failed to dismiss job - $@") if $@;
# If there was an error or if the job was cancelled, always detach the target. This is correct
@@ -34,7 +34,7 @@ sub qemu_handle_concluded_blockjob {
$job->{'detach-node-name'} = $job->{'target-node-name'} if $qmp_info->{error} || $job->{cancel};
if (my $node_name = $job->{'detach-node-name'}) {
- eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name); };
+ eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $node_name); };
log_warn($@) if $@;
}
@@ -61,7 +61,7 @@ sub qemu_blockjobs_cancel {
foreach my $job (keys %$jobs) {
my $info = $running_jobs->{$job};
eval {
- qemu_handle_concluded_blockjob($vmid, $job, $info, $jobs->{$job})
+ qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job})
if $info && $info->{status} eq 'concluded';
};
log_warn($@) if $@; # only warn and proceed with canceling other jobs
@@ -120,8 +120,11 @@ sub monitor {
die "$job_id: '$op' has been cancelled\n" if !defined($job);
- qemu_handle_concluded_blockjob($vmid, $job_id, $job, $jobs->{$job_id})
- if $job && $job->{status} eq 'concluded';
+ if ($job && $job->{status} eq 'concluded') {
+ qemu_handle_concluded_blockjob(
+ vm_qmp_peer($vmid), $job_id, $job, $jobs->{$job_id},
+ );
+ }
my $busy = $job->{busy};
my $ready = $job->{ready};
@@ -353,7 +356,7 @@ sub qemu_drive_mirror_switch_to_active_mode {
my $info = $running_jobs->{$job};
if ($info->{status} eq 'concluded') {
- qemu_handle_concluded_blockjob($vmid, $job, $info, $jobs->{$job});
+ qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job});
# The 'concluded' state should occur here if and only if the job failed, so the
# 'die' below should be unreachable, but play it safe.
die "$job: expected job to have failed, but no error was set\n";
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (10 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
` (5 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/API2/Qemu.pm | 6 ++++--
src/PVE/QemuMigrate.pm | 4 ++--
src/PVE/QemuServer/BlockJob.pm | 16 ++++++++--------
3 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 59ff8181..dff4802d 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -35,7 +35,7 @@ use PVE::QemuServer::CPUConfig;
use PVE::QemuServer::Drive qw(checked_volume_format checked_parse_volname);
use PVE::QemuServer::Helpers;
use PVE::QemuServer::ImportDisk;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
@@ -4613,7 +4613,9 @@ __PACKAGE__->register_method({
PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
};
if (my $err = $@) {
- eval { PVE::QemuServer::BlockJob::qemu_blockjobs_cancel($vmid, $jobs) };
+ eval {
+ PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
+ };
sleep 1; # some storage like rbd need to wait before release volume - really?
foreach my $volid (@$newvollist) {
diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index f7ec3227..c5ddf829 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -33,7 +33,7 @@ use PVE::QemuServer::CPUConfig;
use PVE::QemuServer::Drive qw(checked_volume_format);
use PVE::QemuServer::Helpers qw(min_version);
use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::Network;
use PVE::QemuServer::QMPHelpers;
@@ -1606,7 +1606,7 @@ sub phase2_cleanup {
if ($self->{storage_migration}) {
eval {
PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(
- $vmid,
+ vm_qmp_peer($vmid),
$self->{storage_migration_jobs},
);
};
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 9e8fbdd7..d6eb2a8e 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -42,16 +42,16 @@ sub qemu_handle_concluded_blockjob {
}
sub qemu_blockjobs_cancel {
- my ($vmid, $jobs) = @_;
+ my ($qmp_peer, $jobs) = @_;
foreach my $job (keys %$jobs) {
print "$job: Cancelling block job\n";
- eval { mon_cmd($vmid, "block-job-cancel", device => $job); };
+ eval { qmp_cmd($qmp_peer, "block-job-cancel", device => $job); };
$jobs->{$job}->{cancel} = 1;
}
while (1) {
- my $stats = mon_cmd($vmid, "query-block-jobs");
+ my $stats = qmp_cmd($qmp_peer, "query-block-jobs");
my $running_jobs = {};
foreach my $stat (@$stats) {
@@ -61,7 +61,7 @@ sub qemu_blockjobs_cancel {
foreach my $job (keys %$jobs) {
my $info = $running_jobs->{$job};
eval {
- qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job})
+ qemu_handle_concluded_blockjob($qmp_peer, $job, $info, $jobs->{$job})
if $info && $info->{status} eq 'concluded';
};
log_warn($@) if $@; # only warn and proceed with canceling other jobs
@@ -184,7 +184,7 @@ sub monitor {
}
# if we clone a disk for a new target vm, we don't switch the disk
- qemu_blockjobs_cancel($vmid, $jobs);
+ qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
if ($should_fsfreeze) {
print "issuing guest agent 'guest-fsfreeze-thaw' command\n";
@@ -241,7 +241,7 @@ sub monitor {
my $err = $@;
if ($err) {
- eval { qemu_blockjobs_cancel($vmid, $jobs) };
+ eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
die "block job ($op) error: $err";
}
}
@@ -315,7 +315,7 @@ sub qemu_drive_mirror {
# if a job already runs for this device we get an error, catch it for cleanup
eval { mon_cmd($vmid, "drive-mirror", %$opts); };
if (my $err = $@) {
- eval { qemu_blockjobs_cancel($vmid, $jobs) };
+ eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
warn "$@\n" if $@;
die "mirroring error: $err\n";
}
@@ -510,7 +510,7 @@ sub blockdev_mirror {
# if a job already runs for this device we get an error, catch it for cleanup
eval { mon_cmd($vmid, "blockdev-mirror", $qmp_opts->%*); };
if (my $err = $@) {
- eval { qemu_blockjobs_cancel($vmid, $jobs) };
+ eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
log_warn("unable to cancel block jobs - $@");
eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $target_node_name); };
log_warn("unable to delete blockdev '$target_node_name' - $@");
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (11 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
` (4 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Take care to only allow a different destination ID when having the
main QEMU instance as a peer, because the required freeze/thaw or
suspend/resume can only be done then.
Also adds the missing $op argument in the signature of the mocked
function in the migration tests for completeness.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuMigrate.pm | 2 +-
src/PVE/QemuServer.pm | 12 ++++++---
src/PVE/QemuServer/BlockJob.pm | 33 ++++++++++++++---------
src/PVE/QemuServer/VolumeChain.pm | 8 ++++--
src/test/MigrationTest/QemuMigrateMock.pm | 2 +-
5 files changed, 38 insertions(+), 19 deletions(-)
diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index c5ddf829..0ebc0e73 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -1559,7 +1559,7 @@ sub phase2 {
# thus, this command changes to it to blockjob complete (see qapi docs)
eval {
PVE::QemuServer::BlockJob::monitor(
- $vmid, undef, $self->{storage_migration_jobs}, 'cancel',
+ vm_qmp_peer($vmid), undef, $self->{storage_migration_jobs}, 'cancel',
);
};
if (my $err = $@) {
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 3c698c4c..bcc6f553 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7303,7 +7303,9 @@ sub pbs_live_restore {
}
mon_cmd($vmid, 'cont');
- PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+ PVE::QemuServer::BlockJob::monitor(
+ vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+ );
print "restore-drive jobs finished successfully, removing all tracking block devices"
. " to disconnect from Proxmox Backup Server\n";
@@ -7423,7 +7425,9 @@ sub live_import_from_files {
}
mon_cmd($vmid, 'cont');
- PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+ PVE::QemuServer::BlockJob::monitor(
+ vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+ );
print "restore-drive jobs finished successfully, removing all tracking block devices\n";
@@ -7931,7 +7935,9 @@ sub clone_disk {
# if this is the case, we have to complete any block-jobs still there from
# previous drive-mirrors
if (($completion && $completion eq 'complete') && (scalar(keys %$jobs) > 0)) {
- PVE::QemuServer::BlockJob::monitor($vmid, $newvmid, $jobs, $completion, $qga);
+ PVE::QemuServer::BlockJob::monitor(
+ vm_qmp_peer($vmid), $newvmid, $jobs, $completion, $qga,
+ );
}
goto no_data_clone;
}
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index d6eb2a8e..76518b1f 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -84,7 +84,10 @@ sub qemu_blockjobs_cancel {
# 'skip': wait until all jobs are ready, return with block jobs in ready state
# 'auto': wait until all jobs disappear, only use for jobs which complete automatically
sub monitor {
- my ($vmid, $vmiddst, $jobs, $completion, $qga, $op) = @_;
+ my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;
+
+ die "drive mirror: different destination is only supported when peer is main QEMU instance\n"
+ if $vmiddst && $qmp_peer->{type} ne 'qmp';
$completion //= 'complete';
$op //= "mirror";
@@ -96,7 +99,7 @@ sub monitor {
while (1) {
die "block job ('$op') timed out\n" if $err_complete > 300;
- my $stats = mon_cmd($vmid, "query-block-jobs");
+ my $stats = qmp_cmd($qmp_peer, "query-block-jobs");
my $ctime = time();
my $running_jobs = {};
@@ -121,9 +124,7 @@ sub monitor {
die "$job_id: '$op' has been cancelled\n" if !defined($job);
if ($job && $job->{status} eq 'concluded') {
- qemu_handle_concluded_blockjob(
- vm_qmp_peer($vmid), $job_id, $job, $jobs->{$job_id},
- );
+ qemu_handle_concluded_blockjob($qmp_peer, $job_id, $job, $jobs->{$job_id});
}
my $busy = $job->{busy};
@@ -164,7 +165,8 @@ sub monitor {
# do the complete later (or has already been done)
last if $completion eq 'skip' || $completion eq 'auto';
- if ($vmiddst && $vmiddst != $vmid) {
+ if ($qmp_peer->{type} eq 'qmp' && $vmiddst && $vmiddst != $qmp_peer->{id}) {
+ my $vmid = $qmp_peer->{id};
my $should_fsfreeze = $qga && qga_check_running($vmid);
if ($should_fsfreeze) {
print "issuing guest agent 'guest-fsfreeze-freeze' command\n";
@@ -184,7 +186,7 @@ sub monitor {
}
# if we clone a disk for a new target vm, we don't switch the disk
- qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
+ qemu_blockjobs_cancel($qmp_peer, $jobs);
if ($should_fsfreeze) {
print "issuing guest agent 'guest-fsfreeze-thaw' command\n";
@@ -211,10 +213,10 @@ sub monitor {
eval {
if ($completion eq 'complete') {
$detach_node_name = $jobs->{$job_id}->{'source-node-name'};
- mon_cmd($vmid, 'job-complete', id => $job_id);
+ qmp_cmd($qmp_peer, 'job-complete', id => $job_id);
} elsif ($completion eq 'cancel') {
$detach_node_name = $jobs->{$job_id}->{'target-node-name'};
- mon_cmd($vmid, 'block-job-cancel', device => $job_id);
+ qmp_cmd($qmp_peer, 'block-job-cancel', device => $job_id);
} else {
die "invalid completion value: $completion\n";
}
@@ -241,7 +243,7 @@ sub monitor {
my $err = $@;
if ($err) {
- eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
+ eval { qemu_blockjobs_cancel($qmp_peer, $jobs) };
die "block job ($op) error: $err";
}
}
@@ -320,7 +322,7 @@ sub qemu_drive_mirror {
die "mirroring error: $err\n";
}
- monitor($vmid, $vmiddst, $jobs, $completion, $qga);
+ monitor(vm_qmp_peer($vmid), $vmiddst, $jobs, $completion, $qga);
}
# Callers should version guard this (only available with a binary >= QEMU 8.2)
@@ -516,7 +518,14 @@ sub blockdev_mirror {
log_warn("unable to delete blockdev '$target_node_name' - $@");
die "error starting blockdev mirrror - $err";
}
- monitor($vmid, $dest->{vmid}, $jobs, $completion, $options->{'guest-agent'}, 'mirror');
+ monitor(
+ vm_qmp_peer($vmid),
+ $dest->{vmid},
+ $jobs,
+ $completion,
+ $options->{'guest-agent'},
+ 'mirror',
+ );
}
sub mirror {
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 8c0749de..8dd29d61 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -255,7 +255,9 @@ sub blockdev_commit {
# 'block-commit' will complete automatically.
my $complete = $src_snap && $src_snap ne 'current' ? 'auto' : 'complete';
- PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, $complete, 0, 'commit');
+ PVE::QemuServer::BlockJob::monitor(
+ vm_qmp_peer($vmid), undef, $jobs, $complete, 0, 'commit',
+ );
blockdev_delete(
$storecfg, $vmid, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
@@ -329,7 +331,9 @@ sub blockdev_stream {
mon_cmd($vmid, 'block-stream', %$options);
$jobs->{$job_id} = {};
- PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+ PVE::QemuServer::BlockJob::monitor(
+ vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+ );
blockdev_delete($storecfg, $vmid, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap);
}
diff --git a/src/test/MigrationTest/QemuMigrateMock.pm b/src/test/MigrationTest/QemuMigrateMock.pm
index 8cd2da12..a12310ec 100644
--- a/src/test/MigrationTest/QemuMigrateMock.pm
+++ b/src/test/MigrationTest/QemuMigrateMock.pm
@@ -176,7 +176,7 @@ $qemu_server_blockjob_module->mock(
common_mirror_mock($source->{vmid}, $drive_id);
},
monitor => sub {
- my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
+ my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;
if (
$fail_config->{block_job_monitor}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (12 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
` (3 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
In preparation to allow snapshot operations for QSD-FUSE-exported
TPM states.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer/VolumeChain.pm | 20 ++++++++++++++++----
1 file changed, 16 insertions(+), 4 deletions(-)
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 8dd29d61..d2042614 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -64,9 +64,9 @@ sub blockdev_external_snapshot {
}
sub blockdev_delete {
- my ($storecfg, $vmid, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
+ my ($storecfg, $qmp_peer, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
- eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $fmt_blockdev->{'node-name'}); };
+ eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $fmt_blockdev->{'node-name'}); };
warn "detaching block node for $file_blockdev->{filename} failed - $@" if $@;
#delete the file (don't use vdisk_free as we don't want to delete all snapshot chain)
@@ -260,7 +260,12 @@ sub blockdev_commit {
);
blockdev_delete(
- $storecfg, $vmid, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
+ $storecfg,
+ vm_qmp_peer($vmid),
+ $drive,
+ $src_file_blockdev,
+ $src_fmt_blockdev,
+ $src_snap,
);
};
my $err = $@;
@@ -335,7 +340,14 @@ sub blockdev_stream {
vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
);
- blockdev_delete($storecfg, $vmid, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap);
+ blockdev_delete(
+ $storecfg,
+ vm_qmp_peer($vmid),
+ $drive,
+ $snap_file_blockdev,
+ $snap_fmt_blockdev,
+ $snap,
+ );
}
1;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (13 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
` (2 subsequent siblings)
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 2 +-
src/PVE/QemuServer/VolumeChain.pm | 25 +++++++++++++------------
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index bcc6f553..bb3f7529 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4446,7 +4446,7 @@ sub qemu_volume_snapshot_delete {
print "stream intermediate snapshot $snap to $childsnap\n";
PVE::QemuServer::VolumeChain::blockdev_stream(
$storecfg,
- $vmid,
+ vm_qmp_peer($vmid),
$machine_version,
$attached_deviceid,
$drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index d2042614..dad39479 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -284,8 +284,16 @@ sub blockdev_commit {
}
sub blockdev_stream {
- my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap, $target_snap) =
- @_;
+ my (
+ $storecfg,
+ $qmp_peer,
+ $machine_version,
+ $deviceid,
+ $drive,
+ $snap,
+ $parent_snap,
+ $target_snap,
+ ) = @_;
my $volid = $drive->{file};
$target_snap = undef if $target_snap eq 'current';
@@ -333,20 +341,13 @@ sub blockdev_stream {
$options->{'base-node'} = $parent_fmt_blockdev->{'node-name'};
$options->{'backing-file'} = $backing_file;
- mon_cmd($vmid, 'block-stream', %$options);
+ qmp_cmd($qmp_peer, 'block-stream', %$options);
$jobs->{$job_id} = {};
- PVE::QemuServer::BlockJob::monitor(
- vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
- );
+ PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, 'auto', 0, 'stream');
blockdev_delete(
- $storecfg,
- vm_qmp_peer($vmid),
- $drive,
- $snap_file_blockdev,
- $snap_fmt_blockdev,
- $snap,
+ $storecfg, $qmp_peer, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap,
);
}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() to use QMP peer
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (14 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 2 +-
src/PVE/QemuServer/VolumeChain.pm | 19 ++++++-------------
2 files changed, 7 insertions(+), 14 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index bb3f7529..04fde77b 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4421,7 +4421,7 @@ sub qemu_volume_snapshot_delete {
print "delete first snapshot $snap\n";
PVE::QemuServer::VolumeChain::blockdev_commit(
$storecfg,
- $vmid,
+ vm_qmp_peer($vmid),
$machine_version,
$attached_deviceid,
$drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index dad39479..91e78495 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -192,7 +192,7 @@ sub blockdev_replace {
}
sub blockdev_commit {
- my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $src_snap, $target_snap) = @_;
+ my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $src_snap, $target_snap) = @_;
my $volid = $drive->{file};
my $target_was_read_only;
@@ -229,7 +229,7 @@ sub blockdev_commit {
print "reopening internal read-only block node for '$target_snap' as writable\n";
$target_fmt_blockdev->{'read-only'} = JSON::false;
$target_file_blockdev->{'read-only'} = JSON::false;
- mon_cmd($vmid, 'blockdev-reopen', options => [$target_fmt_blockdev]);
+ qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]);
# For the guest, the drive is still read-only, because the top throttle node is.
}
@@ -241,7 +241,7 @@ sub blockdev_commit {
$opts->{'base-node'} = $target_fmt_blockdev->{'node-name'};
$opts->{'top-node'} = $src_fmt_blockdev->{'node-name'};
- mon_cmd($vmid, "block-commit", %$opts);
+ qmp_cmd($qmp_peer, "block-commit", %$opts);
$jobs->{$job_id} = {};
# If the 'current' state is committed to its backing snapshot, the job will not complete
@@ -255,17 +255,10 @@ sub blockdev_commit {
# 'block-commit' will complete automatically.
my $complete = $src_snap && $src_snap ne 'current' ? 'auto' : 'complete';
- PVE::QemuServer::BlockJob::monitor(
- vm_qmp_peer($vmid), undef, $jobs, $complete, 0, 'commit',
- );
+ PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, $complete, 0, 'commit');
blockdev_delete(
- $storecfg,
- vm_qmp_peer($vmid),
- $drive,
- $src_file_blockdev,
- $src_fmt_blockdev,
- $src_snap,
+ $storecfg, $qmp_peer, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
);
};
my $err = $@;
@@ -276,7 +269,7 @@ sub blockdev_commit {
print "re-applying read-only flag for internal block node for '$target_snap'\n";
$target_fmt_blockdev->{'read-only'} = JSON::true;
$target_file_blockdev->{'read-only'} = JSON::true;
- eval { mon_cmd($vmid, 'blockdev-reopen', options => [$target_fmt_blockdev]); };
+ eval { qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]); };
print "failed to re-apply read-only flag - $@\n" if $@;
}
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (15 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
2026-03-09 16:58 ` [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Adds a new drive_qmp_peer() helper to determine whether the drive is
managed by the QEMU instance or QSD instance for the VM.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/QemuServer.pm | 30 +++++++++++++-----------------
src/PVE/QemuServer/Drive.pm | 8 ++++++++
2 files changed, 21 insertions(+), 17 deletions(-)
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 04fde77b..1f7afaeb 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -83,7 +83,7 @@ use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory qw(get_current_memory);
use PVE::QemuServer::MetaInfo;
-use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
use PVE::QemuServer::Network;
use PVE::QemuServer::OVMF;
use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
@@ -4342,7 +4342,8 @@ sub qemu_volume_snapshot {
if ($do_snapshots_type eq 'internal') {
print "internal qemu snapshot\n";
- mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
+ my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+ qmp_cmd($qmp_peer, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
} elsif ($do_snapshots_type eq 'external') {
my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 0)) {
@@ -4355,14 +4356,9 @@ sub qemu_volume_snapshot {
print "external qemu snapshot\n";
my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);
my $parent_snap = $snapshots->{'current'}->{parent};
+ my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
PVE::QemuServer::VolumeChain::blockdev_external_snapshot(
- $storecfg,
- vm_qmp_peer($vmid),
- $machine_version,
- $deviceid,
- $drive,
- $snap,
- $parent_snap,
+ $storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap,
);
} elsif ($do_snapshots_type eq 'storage') {
PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
@@ -4390,8 +4386,9 @@ sub qemu_volume_snapshot_delete {
my $do_snapshots_type = do_snapshots_type($storecfg, $volid, $attached_deviceid, $running);
if ($do_snapshots_type eq 'internal') {
- mon_cmd(
- $vmid,
+ my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+ qmp_cmd(
+ $qmp_peer,
'blockdev-snapshot-delete-internal-sync',
device => $attached_deviceid,
name => $snap,
@@ -4415,13 +4412,15 @@ sub qemu_volume_snapshot_delete {
my $parentsnap = $snapshots->{$snap}->{parent};
my $childsnap = $snapshots->{$snap}->{child};
+ my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+
# if we delete the first snasphot, we commit because the first snapshot original base image, it should be big.
# improve-me: if firstsnap > child : commit, if firstsnap < child do a stream.
if (!$parentsnap) {
print "delete first snapshot $snap\n";
PVE::QemuServer::VolumeChain::blockdev_commit(
$storecfg,
- vm_qmp_peer($vmid),
+ $qmp_peer,
$machine_version,
$attached_deviceid,
$drive,
@@ -4433,7 +4432,7 @@ sub qemu_volume_snapshot_delete {
PVE::QemuServer::VolumeChain::blockdev_replace(
$storecfg,
- vm_qmp_peer($vmid),
+ $qmp_peer,
$machine_version,
$attached_deviceid,
$drive,
@@ -4446,7 +4445,7 @@ sub qemu_volume_snapshot_delete {
print "stream intermediate snapshot $snap to $childsnap\n";
PVE::QemuServer::VolumeChain::blockdev_stream(
$storecfg,
- vm_qmp_peer($vmid),
+ $qmp_peer,
$machine_version,
$attached_deviceid,
$drive,
@@ -7775,9 +7774,6 @@ sub restore_tar_archive {
sub do_snapshots_type {
my ($storecfg, $volid, $deviceid, $running) = @_;
- #always use storage snapshot for tpmstate
- return 'storage' if $deviceid && $deviceid =~ m/tpmstate0/;
-
#we use storage snapshot if vm is not running or if disk is unused;
return 'storage' if !$running || !$deviceid;
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index f88504b2..79afc146 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -13,6 +13,8 @@ use PVE::Storage;
use PVE::Storage::Common;
use PVE::JSONSchema qw(get_standard_option);
+use PVE::QemuServer::Monitor qw(qsd_peer vm_qmp_peer);
+
use base qw(Exporter);
our @EXPORT_OK = qw(
@@ -1164,4 +1166,10 @@ sub drive_uses_qsd_fuse {
return;
}
+sub drive_qmp_peer {
+ my ($storecfg, $vmid, $drive) = @_;
+
+ return drive_uses_qsd_fuse($storecfg, $drive) ? qsd_peer($vmid) : vm_qmp_peer($vmid);
+}
+
1;
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread* [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain
2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
` (16 preceding siblings ...)
2026-03-09 16:58 ` [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
To: pve-devel
Commit "snapshot: support live snapshot (remove) of qcow2 TPM drive on
storage with snapshot-as-volume-chain" prepared for this. It's not a
revert of commit c7d839df ("snapshot: prohibit live snapshot (remove)
of qcow2 TPM drive on storage with snapshot-as-volume-chain"), because
there is a single limitation remaining. That limitation is removing
the top-most snapshot live when the current image is exported via
FUSE, because exporting unshares the 'resize' permission, which would
be required by both 'block-commit' and 'block-stream', for example:
> QEMU storage daemon 100 qsd command 'block-commit' failed - Permission
> conflict on node '#block017': permissions 'resize' are both required
> by node 'drive-tpmstate0' (uses node '#block017' as 'file' child) and
> unshared by commit job 'commit-drive-tpmstate0' (uses node '#block017'
> as 'main node' child).
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/API2/Qemu.pm | 39 ++++++++++++++-------------------------
1 file changed, 14 insertions(+), 25 deletions(-)
diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index dff4802d..e162d6b1 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -729,11 +729,11 @@ my $check_cpu_model_access = sub {
}
};
-# TODO switch to doing internal snapshots only for TPM? Need a way to tell the storage. Also needs
-# handling for pre-existing as-volume-chain snapshots then. Or is there a way to make QSD+swtpm
-# compatible with using volume-chain live?
-my sub assert_tpm_snapshot_compat {
- my ($vmid, $conf, $op, $snap_conf) = @_;
+# The top-most snapshot for a FUSE-exported TPM state cannot be removed live, because exporting
+# unshares the 'resize' permission, which would be required by both 'block-commit' and
+# 'block-stream'.
+my sub assert_tpm_snapshot_delete_possible {
+ my ($vmid, $conf, $snap_conf, $snap_name) = @_;
return if !$conf->{tpmstate0};
return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);
@@ -742,19 +742,19 @@ my sub assert_tpm_snapshot_compat {
my $volid = $drive->{file};
my $storecfg = PVE::Storage::config();
- if ($snap_conf) {
- return if !$snap_conf->{tpmstate0};
- my $snap_drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $snap_conf->{tpmstate0});
- return if $volid ne $snap_drive->{file};
- }
+ return if $conf->{parent} ne $snap_name; # allowed if not top-most snapshot
+
+ return if !$snap_conf->{tpmstate0};
+ my $snap_drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $snap_conf->{tpmstate0});
+ return if $volid ne $snap_drive->{file};
my $format = PVE::QemuServer::Drive::checked_volume_format($storecfg, $volid);
my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);
if ($storeid && $format eq 'qcow2') {
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
if ($scfg && $scfg->{'snapshot-as-volume-chain'}) {
- die "snapshot $op of TPM state '$volid' on storage with 'snapshot-as-volume-chain' is"
- . " not yet supported while the VM is running.\n";
+ die "top-most snapshot of TPM state '$volid' on storage with 'snapshot-as-volume-chain'"
+ . " cannot be removed while the VM is running.\n";
}
}
}
@@ -6082,14 +6082,6 @@ __PACKAGE__->register_method({
0);
my $realcmd = sub {
- PVE::QemuConfig->lock_config(
- $vmid,
- sub {
- my $conf = PVE::QemuConfig->load_config($vmid);
- assert_tpm_snapshot_compat($vmid, $conf, 'create');
- },
- );
-
PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
PVE::QemuConfig->snapshot_create(
$vmid, $snapname, $param->{vmstate}, $param->{description},
@@ -6346,11 +6338,8 @@ __PACKAGE__->register_method({
$vmid,
sub {
my $conf = PVE::QemuConfig->load_config($vmid);
- assert_tpm_snapshot_compat(
- $vmid,
- $conf,
- 'delete',
- $conf->{snapshots}->{$snapname},
+ assert_tpm_snapshot_delete_possible(
+ $vmid, $conf, $conf->{snapshots}->{$snapname}, $snapname,
);
},
);
--
2.47.3
^ permalink raw reply [flat|nested] 19+ messages in thread