public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain
@ 2026-04-23  9:35 Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 01/18] block job: fix variable name in documentation Fiona Ebner
                   ` (18 more replies)
  0 siblings, 19 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 UTC (permalink / raw)
  To: pve-devel

Changes in v3:
* Rebase on latest master (adapt to change from new perltidy version).

Changes in v2:
* Rebase on latest master (many modified functions moved to
  VolumeChain.pm).
* get_node_name_below_throttle(): also die after else branch if no
  top node can be found.

The QMP commands for snapshot operations can just be passed to the
qemu-storage-daemon for the FUSE-exported TPM state and it will handle
it just the same as the main QEMU instance.

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).

Most of the work is preparing the code interacting with the block
layer to support different QMP peers.

The first two commits are independent from the goal of the series, but
tangentially related.

Since this touches quite a bit of code, more testing would be very
appreciated!

qemu-server:

Fiona Ebner (18):
  block job: fix variable name in documentation
  qmp client: add default timeouts for more block commands
  drive: introduce drive_uses_qsd_fuse() helper
  monitor: add vm_qmp_peer() helper
  monitor: add qsd_peer() helper
  blockdev: rename variable in get_node_name_below_throttle() for
    readability
  blockdev: switch get_node_name_below_throttle() to use QMP peer
  blockdev: switch detach() to use QMP peer
  blockdev: switch blockdev_replace() to use QMP peer
  blockdev: switch blockdev_external_snapshot() to use QMP peer
  block job: switch qemu_handle_concluded_blockjob() to use QMP peer
  block job: switch qemu_blockjobs_cancel() to use QMP peer
  block job: switch qemu_drive_mirror_monitor() to use QMP peer
  blockdev: switch blockdev_delete() to use QMP peer
  blockdev: switch blockdev_stream() to use QMP peer
  blockdev: switch blockdev_commit() to use QMP peer
  snapshot: support live snapshot (remove) of qcow2 TPM drive on storage
    with snapshot-as-volume-chain
  fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with
    snapshot-as-volume-chain

 src/PVE/API2/Qemu.pm                      | 45 ++++++--------
 src/PVE/QMPClient.pm                      |  7 +++
 src/PVE/QemuMigrate.pm                    |  6 +-
 src/PVE/QemuServer.pm                     | 65 +++++++++++---------
 src/PVE/QemuServer/BlockJob.pm            | 62 +++++++++++--------
 src/PVE/QemuServer/Blockdev.pm            | 59 +++++++++---------
 src/PVE/QemuServer/Drive.pm               | 20 +++++++
 src/PVE/QemuServer/Monitor.pm             | 19 +++++-
 src/PVE/QemuServer/QSD.pm                 | 11 ++--
 src/PVE/QemuServer/VolumeChain.pm         | 73 ++++++++++++++---------
 src/PVE/VZDump/QemuServer.pm              |  6 +-
 src/test/MigrationTest/QemuMigrateMock.pm |  2 +-
 12 files changed, 222 insertions(+), 153 deletions(-)


Summary over all repositories:
  12 files changed, 222 insertions(+), 153 deletions(-)

-- 
Generated by git-murpp 0.5.0




^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH qemu-server v3 01/18] block job: fix variable name in documentation
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
@ 2026-04-23  9:35 ` Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
                   ` (17 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 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] 22+ messages in thread

* [PATCH qemu-server v3 02/18] qmp client: add default timeouts for more block commands
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 01/18] block job: fix variable name in documentation Fiona Ebner
@ 2026-04-23  9:35 ` Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
                   ` (16 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 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] 22+ messages in thread

* [PATCH qemu-server v3 03/18] drive: introduce drive_uses_qsd_fuse() helper
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 01/18] block job: fix variable name in documentation Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
@ 2026-04-23  9:35 ` Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
                   ` (15 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 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 2a469fff..476fef0b 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -2832,12 +2832,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] 22+ messages in thread

* [PATCH qemu-server v3 04/18] monitor: add vm_qmp_peer() helper
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (2 preceding siblings ...)
  2026-04-23  9:35 ` [PATCH qemu-server v3 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
@ 2026-04-23  9:35 ` Fiona Ebner
  2026-04-23  9:35 ` [PATCH qemu-server v3 05/18] monitor: add qsd_peer() helper Fiona Ebner
                   ` (14 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 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 476fef0b..76f32894 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -82,7 +82,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);
@@ -2711,7 +2711,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');
@@ -2733,8 +2733,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);
@@ -3189,8 +3188,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 32f92829..48994612 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);
@@ -996,7 +996,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] 22+ messages in thread

* [PATCH qemu-server v3 05/18] monitor: add qsd_peer() helper
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (3 preceding siblings ...)
  2026-04-23  9:35 ` [PATCH qemu-server v3 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
@ 2026-04-23  9:35 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
                   ` (13 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:35 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] 22+ messages in thread

* [PATCH qemu-server v3 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (4 preceding siblings ...)
  2026-04-23  9:35 ` [PATCH qemu-server v3 05/18] monitor: add qsd_peer() helper Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
                   ` (12 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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] 22+ messages in thread

* [PATCH qemu-server v3 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (5 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 08/18] blockdev: switch detach() " Fiona Ebner
                   ` (11 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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>
---
 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 76f32894..eea5ac3b 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7302,7 +7302,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',
@@ -7420,8 +7421,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 9aaf6bbe..08acbc05 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) = @_;
@@ -106,8 +106,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] 22+ messages in thread

* [PATCH qemu-server v3 08/18] blockdev: switch detach() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (6 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
                   ` (10 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 eea5ac3b..7d1ec232 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4089,7 +4089,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);
@@ -7321,7 +7321,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);
@@ -7440,7 +7440,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 08acbc05..e522e201 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -59,7 +59,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)
@@ -181,7 +181,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 48994612..ad65b5d0 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] 22+ messages in thread

* [PATCH qemu-server v3 09/18] blockdev: switch blockdev_replace() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (7 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 08/18] blockdev: switch detach() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
                   ` (9 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 | 32 ++++++++++++++++++-------------
 2 files changed, 20 insertions(+), 14 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 7d1ec232..88a5a863 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4440,7 +4440,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 e522e201..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) = @_;
@@ -25,7 +25,14 @@ sub blockdev_external_snapshot {
 
     #reopen current to snap
     blockdev_replace(
-        $storecfg, $vmid, $machine_version, $deviceid, $drive, 'current', $snap, $parent_snap,
+        $storecfg,
+        vm_qmp_peer($vmid),
+        $machine_version,
+        $deviceid,
+        $drive,
+        'current',
+        $snap,
+        $parent_snap,
     );
 
     #be sure to add drive in write mode
@@ -88,7 +95,7 @@ my sub blockdev_relative_backing_file {
 sub blockdev_replace {
     my (
         $storecfg,
-        $vmid,
+        $qmp_peer,
         $machine_version,
         $deviceid,
         $drive,
@@ -106,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 =
@@ -141,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(
@@ -166,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,
@@ -181,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] 22+ messages in thread

* [PATCH qemu-server v3 10/18] blockdev: switch blockdev_external_snapshot() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (8 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
                   ` (8 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 88a5a863..36cbfd59 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4369,7 +4369,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] 22+ messages in thread

* [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (9 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-24  9:51   ` Lukas Sichert
  2026-04-23  9:36 ` [PATCH qemu-server v3 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
                   ` (7 subsequent siblings)
  18 siblings, 1 reply; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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] 22+ messages in thread

* [PATCH qemu-server v3 12/18] block job: switch qemu_blockjobs_cancel() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (10 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
                   ` (6 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 dadc06b0..103aa6bc 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -36,7 +36,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;
@@ -4659,7 +4659,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 13e67e75..54b8a65d 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;
@@ -1618,7 +1618,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] 22+ messages in thread

* [PATCH qemu-server v3 13/18] block job: switch qemu_drive_mirror_monitor() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (11 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
                   ` (5 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 54b8a65d..bdf6770d 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -1571,7 +1571,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 36cbfd59..93f653c6 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7321,7 +7321,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";
@@ -7441,7 +7443,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";
 
@@ -7951,7 +7955,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] 22+ messages in thread

* [PATCH qemu-server v3 14/18] blockdev: switch blockdev_delete() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (12 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
                   ` (4 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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] 22+ messages in thread

* [PATCH qemu-server v3 15/18] blockdev: switch blockdev_stream() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (13 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
                   ` (3 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 93f653c6..9c6e2cdb 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4459,7 +4459,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] 22+ messages in thread

* [PATCH qemu-server v3 16/18] blockdev: switch blockdev_commit() to use QMP peer
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (14 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
                   ` (2 subsequent siblings)
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 9c6e2cdb..53c40eef 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4434,7 +4434,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] 22+ messages in thread

* [PATCH qemu-server v3 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (15 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23  9:36 ` [PATCH qemu-server v3 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
  2026-04-23 13:57 ` [PATCH-SERIES qemu-server v3 00/18] " Lukas Sichert
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 53c40eef..9dac4024 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -82,7 +82,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);
@@ -4355,7 +4355,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)) {
@@ -4368,14 +4369,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);
@@ -4403,8 +4399,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,
@@ -4428,13 +4425,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,
@@ -4446,7 +4445,7 @@ sub qemu_volume_snapshot_delete {
 
             PVE::QemuServer::VolumeChain::blockdev_replace(
                 $storecfg,
-                vm_qmp_peer($vmid),
+                $qmp_peer,
                 $machine_version,
                 $attached_deviceid,
                 $drive,
@@ -4459,7 +4458,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,
@@ -7793,9 +7792,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] 22+ messages in thread

* [PATCH qemu-server v3 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (16 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
@ 2026-04-23  9:36 ` Fiona Ebner
  2026-04-23 13:57 ` [PATCH-SERIES qemu-server v3 00/18] " Lukas Sichert
  18 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-23  9:36 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 103aa6bc..ca1e3ebe 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -730,11 +730,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);
@@ -743,19 +743,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";
         }
     }
 }
@@ -6136,14 +6136,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},
@@ -6400,11 +6392,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] 22+ messages in thread

* Re: [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain
  2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (17 preceding siblings ...)
  2026-04-23  9:36 ` [PATCH qemu-server v3 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
@ 2026-04-23 13:57 ` Lukas Sichert
  18 siblings, 0 replies; 22+ messages in thread
From: Lukas Sichert @ 2026-04-23 13:57 UTC (permalink / raw)
  To: Fiona Ebner, pve-devel

Everything works as expected.

My setup for testing:

I configured one PVE machine as an iSCSI target server and exported a
400 GB disk as a writable LUN. On a second PVE machine, in the GUI I
added the target LUN as iSCSI storage, created a volume group (VG) on
top of it, and then added a LVM storage based on that VG.

For testing, I used Windows Server 2022 Evaluation and Windows 11.

Before the patch:
-taking live snapshots was not possible
-removing any snapshot live was not possible

After the patch:
-taking live snapshots is possible
-removing live snapshots is possible, except for the top-most snapshot

Unrelated to the patch, works with and without it:
-rollbacks power off the VM and are therefore possible
-offline snapshots are still possible

I will go through the code to give a review soon.

Tested-by: Lukas Sichert <l.sichert@proxmox.com>

On 2026-04-23 11:35, Fiona Ebner <f.ebner@proxmox.com> wrote:

> Changes in v3:
> * Rebase on latest master (adapt to change from new perltidy version).
>
> Changes in v2:
> * Rebase on latest master (many modified functions moved to
>   VolumeChain.pm).
> * get_node_name_below_throttle(): also die after else branch if no
>   top node can be found.
>
> The QMP commands for snapshot operations can just be passed to the
> qemu-storage-daemon for the FUSE-exported TPM state and it will handle
> it just the same as the main QEMU instance.
>
> 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).
>
> Most of the work is preparing the code interacting with the block
> layer to support different QMP peers.
>
> The first two commits are independent from the goal of the series, but
> tangentially related.
>
> Since this touches quite a bit of code, more testing would be very
> appreciated!
>
> qemu-server:
>
> Fiona Ebner (18):
>   block job: fix variable name in documentation
>   qmp client: add default timeouts for more block commands
>   drive: introduce drive_uses_qsd_fuse() helper
>   monitor: add vm_qmp_peer() helper
>   monitor: add qsd_peer() helper
>   blockdev: rename variable in get_node_name_below_throttle() for
>     readability
>   blockdev: switch get_node_name_below_throttle() to use QMP peer
>   blockdev: switch detach() to use QMP peer
>   blockdev: switch blockdev_replace() to use QMP peer
>   blockdev: switch blockdev_external_snapshot() to use QMP peer
>   block job: switch qemu_handle_concluded_blockjob() to use QMP peer
>   block job: switch qemu_blockjobs_cancel() to use QMP peer
>   block job: switch qemu_drive_mirror_monitor() to use QMP peer
>   blockdev: switch blockdev_delete() to use QMP peer
>   blockdev: switch blockdev_stream() to use QMP peer
>   blockdev: switch blockdev_commit() to use QMP peer
>   snapshot: support live snapshot (remove) of qcow2 TPM drive on storage
>     with snapshot-as-volume-chain
>   fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with
>     snapshot-as-volume-chain
>
>  src/PVE/API2/Qemu.pm                      | 45 ++++++--------
>  src/PVE/QMPClient.pm                      |  7 +++
>  src/PVE/QemuMigrate.pm                    |  6 +-
>  src/PVE/QemuServer.pm                     | 65 +++++++++++---------
>  src/PVE/QemuServer/BlockJob.pm            | 62 +++++++++++--------
>  src/PVE/QemuServer/Blockdev.pm            | 59 +++++++++---------
>  src/PVE/QemuServer/Drive.pm               | 20 +++++++
>  src/PVE/QemuServer/Monitor.pm             | 19 +++++-
>  src/PVE/QemuServer/QSD.pm                 | 11 ++--
>  src/PVE/QemuServer/VolumeChain.pm         | 73 ++++++++++++++---------
>  src/PVE/VZDump/QemuServer.pm              |  6 +-
>  src/test/MigrationTest/QemuMigrateMock.pm |  2 +-
>  12 files changed, 222 insertions(+), 153 deletions(-)
>
>
> Summary over all repositories:
>   12 files changed, 222 insertions(+), 153 deletions(-)
>
> -- 
> Generated by git-murpp 0.5.0





^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() to use QMP peer
  2026-04-23  9:36 ` [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
@ 2026-04-24  9:51   ` Lukas Sichert
  2026-04-24 11:36     ` Fiona Ebner
  0 siblings, 1 reply; 22+ messages in thread
From: Lukas Sichert @ 2026-04-24  9:51 UTC (permalink / raw)
  To: Fiona Ebner, pve-devel

On 2026-04-23 11:36, Fiona Ebner <f.ebner@proxmox.com> wrote:

> 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); };
>
The 'vm_qmp_peer' command has just been introduced to this file in this
commit. I'm a bit confused how it is already being removed. Am I missing
something or is this a error, that happened during rebasing?

> +        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] 22+ messages in thread

* Re: [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() to use QMP peer
  2026-04-24  9:51   ` Lukas Sichert
@ 2026-04-24 11:36     ` Fiona Ebner
  0 siblings, 0 replies; 22+ messages in thread
From: Fiona Ebner @ 2026-04-24 11:36 UTC (permalink / raw)
  To: Lukas Sichert, pve-devel

Am 24.04.26 um 11:50 AM schrieb Lukas Sichert:
>> 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); };
>>
> The 'vm_qmp_peer' command has just been introduced to this file in this
> commit. I'm a bit confused how it is already being removed. Am I missing
> something or is this a error, that happened during rebasing?

Good catch, adding the import for vm_qmp_peer already needs to happen in
patch 07/18 "blockdev: switch get_node_name_below_throttle() to use QMP
peer".




^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-04-24 11:36 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-23  9:35 [PATCH-SERIES qemu-server v3 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
2026-04-23  9:35 ` [PATCH qemu-server v3 01/18] block job: fix variable name in documentation Fiona Ebner
2026-04-23  9:35 ` [PATCH qemu-server v3 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
2026-04-23  9:35 ` [PATCH qemu-server v3 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
2026-04-23  9:35 ` [PATCH qemu-server v3 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
2026-04-23  9:35 ` [PATCH qemu-server v3 05/18] monitor: add qsd_peer() helper Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 08/18] blockdev: switch detach() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
2026-04-24  9:51   ` Lukas Sichert
2026-04-24 11:36     ` Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
2026-04-23  9:36 ` [PATCH qemu-server v3 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
2026-04-23 13:57 ` [PATCH-SERIES qemu-server v3 00/18] " Lukas Sichert

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal