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

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         | 66 +++++++++++++----------
 src/PVE/VZDump/QemuServer.pm              |  6 +--
 src/test/MigrationTest/QemuMigrateMock.pm |  2 +-
 12 files changed, 215 insertions(+), 153 deletions(-)


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

-- 
Generated by git-murpp 0.5.0




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

* [PATCH qemu-server v2 01/18] block job: fix variable name in documentation
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer/BlockJob.pm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index d7c78741..97ada28b 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -19,7 +19,7 @@ use PVE::QemuServer::RunState;
 # If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this
 # option is useful to get the error for failed jobs here. QEMU's job lock should make it impossible
 # to see a job in 'concluded' state when auto-dismiss=true.
-# $info is the 'BlockJobInfo' for the job returned by query-block-jobs.
+# $qmp_info is the 'BlockJobInfo' for the job returned by query-block-jobs.
 # $job is the information about the job recorded on the PVE-side.
 # A block node $job->{'detach-node-name'} will be detached if present.
 sub qemu_handle_concluded_blockjob {
-- 
2.47.3





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

* [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 01/18] block job: fix variable name in documentation Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Based on pre-existing defaults for similar commands, commands for
adding get 1 minute, commands for creating block jobs or removing get
10 minutes, since those might require in-flight IO to finish.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QMPClient.pm | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/PVE/QMPClient.pm b/src/PVE/QMPClient.pm
index 723123a6..4d4a2f82 100644
--- a/src/PVE/QMPClient.pm
+++ b/src/PVE/QMPClient.pm
@@ -120,6 +120,8 @@ sub cmd {
             $timeout = 3 * 60;
         } elsif (
             $cmd->{execute} eq 'blockdev-add'
+            || $cmd->{execute} eq 'blockdev-insert-medium'
+            || $cmd->{execute} eq 'block-export-add'
             || $cmd->{execute} eq 'device_add'
             || $cmd->{execute} eq 'device_del'
             || $cmd->{execute} eq 'netdev_add'
@@ -130,8 +132,13 @@ sub cmd {
             $timeout = 60;
         } elsif (
             $cmd->{execute} eq 'backup-cancel'
+            || $cmd->{execute} eq 'block-commit'
+            || $cmd->{execute} eq 'block-export-del'
+            || $cmd->{execute} eq 'block-stream'
             || $cmd->{execute} eq 'blockdev-del'
             || $cmd->{execute} eq 'blockdev-mirror'
+            || $cmd->{execute} eq 'blockdev-remove-medium'
+            || $cmd->{execute} eq 'blockdev-reopen'
             || $cmd->{execute} eq 'block-job-cancel'
             || $cmd->{execute} eq 'job-complete'
             || $cmd->{execute} eq 'drive-mirror'
-- 
2.47.3





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

* [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 01/18] block job: fix variable name in documentation Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 02/18] qmp client: add default timeouts for more block commands Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

In preparation for supporting snapshot operations for drives which
are QSD FUSE exported. Having a central drive_uses_qsd_fuse() helper
makes it possible to consistently decide whether the QMP peer is the
QEMU storage daemon or the main QEMU instance.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm       |  7 +++----
 src/PVE/QemuServer/Drive.pm | 12 ++++++++++++
 2 files changed, 15 insertions(+), 4 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index d87fc218..03a03efd 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -2828,12 +2828,11 @@ sub start_swtpm {
     my $tpm = parse_drive("tpmstate0", $tpmdrive);
     my ($storeid) = PVE::Storage::parse_volume_id($tpm->{file}, 1);
     if ($storeid) {
-        my $format = checked_volume_format($storecfg, $tpm->{file});
-        if ($format eq 'raw') {
-            $state = PVE::Storage::map_volume($storecfg, $tpm->{file});
-        } else {
+        if (PVE::QemuServer::Drive::drive_uses_qsd_fuse($storecfg, $tpm)) {
             PVE::QemuServer::QSD::start($vmid);
             $state = PVE::QemuServer::QSD::add_fuse_export($vmid, $tpm, 'tpmstate0');
+        } else {
+            $state = PVE::Storage::map_volume($storecfg, $tpm->{file});
         }
     } else {
         $state = $tpm->{file};
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index 10106ebd..f88504b2 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -1152,4 +1152,16 @@ sub detect_zeroes_cmdline_option {
     return 'on';
 }
 
+sub drive_uses_qsd_fuse {
+    my ($storecfg, $drive) = @_;
+
+    if ($drive->{interface} eq 'tpmstate') {
+        my ($storeid) = PVE::Storage::parse_volume_id($drive->{file}, 1);
+        my $format = checked_volume_format($storecfg, $drive->{file});
+        return $storeid && $format ne 'raw';
+    }
+
+    return;
+}
+
 1;
-- 
2.47.3





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

* [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (2 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 03/18] drive: introduce drive_uses_qsd_fuse() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper Fiona Ebner
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Avoid duplicating this information to ensure consistency and improve
readability.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm          | 10 ++++------
 src/PVE/QemuServer/Blockdev.pm |  4 ++--
 src/PVE/QemuServer/Monitor.pm  |  9 ++++++++-
 src/PVE/VZDump/QemuServer.pm   |  4 ++--
 4 files changed, 16 insertions(+), 11 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 03a03efd..ba5acfe3 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -83,7 +83,7 @@ use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos);
 use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::MetaInfo;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
 use PVE::QemuServer::Network;
 use PVE::QemuServer::OVMF;
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
@@ -2707,7 +2707,7 @@ sub vmstatus {
     my $statuscb = sub {
         my ($vmid, $resp) = @_;
 
-        my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
+        my $qmp_peer = vm_qmp_peer($vmid);
 
         $qmpclient->queue_cmd($qmp_peer, $proxmox_support_cb, 'query-proxmox-support');
         $qmpclient->queue_cmd($qmp_peer, $blockstatscb, 'query-blockstats');
@@ -2729,8 +2729,7 @@ sub vmstatus {
     foreach my $vmid (keys %$list) {
         next if $opt_vmid && ($vmid ne $opt_vmid);
         next if !$res->{$vmid}->{pid}; # not running
-        my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
-        $qmpclient->queue_cmd($qmp_peer, $statuscb, 'query-status');
+        $qmpclient->queue_cmd(vm_qmp_peer($vmid), $statuscb, 'query-status');
     }
 
     $qmpclient->queue_execute(undef, 2);
@@ -3187,8 +3186,7 @@ sub config_to_command {
 
     my $use_virtio = 0;
 
-    my $qmpsocket =
-        PVE::QemuServer::Helpers::qmp_socket({ name => "VM $vmid", id => $vmid, type => 'qmp' });
+    my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket(vm_qmp_peer($vmid));
     push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server=on,wait=off";
     push @$cmd, '-mon', "chardev=qmp,mode=control";
 
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index be907be8..80c1178e 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -14,7 +14,7 @@ use PVE::Storage;
 use PVE::QemuServer::Drive qw(drive_is_cdrom);
 use PVE::QemuServer::Helpers;
 use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 
 use base qw(Exporter);
 
@@ -588,7 +588,7 @@ sub attach {
     if ($options->{qsd}) {
         $qmp_peer = { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
     } else {
-        $qmp_peer = { name => "VM $id", id => $id, type => 'qmp' };
+        $qmp_peer = vm_qmp_peer($id);
     }
 
     my $machine_version;
diff --git a/src/PVE/QemuServer/Monitor.pm b/src/PVE/QemuServer/Monitor.pm
index 423c40a9..e7f08e23 100644
--- a/src/PVE/QemuServer/Monitor.pm
+++ b/src/PVE/QemuServer/Monitor.pm
@@ -11,6 +11,7 @@ use base 'Exporter';
 our @EXPORT_OK = qw(
     mon_cmd
     qmp_cmd
+    vm_qmp_peer
 );
 
 =head3 qmp_cmd
@@ -102,6 +103,12 @@ sub qmp_cmd {
     return $res;
 }
 
+sub vm_qmp_peer {
+    my ($vmid) = @_;
+
+    return { name => "VM $vmid", id => $vmid, type => 'qmp' };
+}
+
 sub qsd_cmd {
     my ($id, $execute, %params) = @_;
 
@@ -121,7 +128,7 @@ sub hmp_cmd {
     my ($vmid, $cmdline, $timeout) = @_;
 
     return qmp_cmd(
-        { name => "VM $vmid", id => $vmid, type => 'qmp' }, 'human-monitor-command',
+        vm_qmp_peer($vmid), 'human-monitor-command',
         'command-line' => $cmdline,
         timeout => $timeout,
     );
diff --git a/src/PVE/VZDump/QemuServer.pm b/src/PVE/VZDump/QemuServer.pm
index 55fb6dc4..0cdf6fed 100644
--- a/src/PVE/VZDump/QemuServer.pm
+++ b/src/PVE/VZDump/QemuServer.pm
@@ -34,7 +34,7 @@ use PVE::QemuServer::Blockdev;
 use PVE::QemuServer::Drive qw(checked_volume_format);
 use PVE::QemuServer::Helpers;
 use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
 use PVE::QemuServer::QMPHelpers;
 
 use base qw (PVE::VZDump::Plugin);
@@ -995,7 +995,7 @@ sub archive_vma {
         }
 
         my $qmpclient = PVE::QMPClient->new();
-        my $qmp_peer = { name => "VM $vmid", id => $vmid, type => 'qmp' };
+        my $qmp_peer = vm_qmp_peer($vmid);
         my $backup_cb = sub {
             my ($vmid, $resp) = @_;
             $backup_job_uuid = $resp->{return}->{UUID};
-- 
2.47.3





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

* [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (3 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 04/18] monitor: add vm_qmp_peer() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Avoid duplicating this information to ensure consistency and improve
readability.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer/Blockdev.pm |  9 ++-------
 src/PVE/QemuServer/Monitor.pm  | 10 ++++++++--
 src/PVE/QemuServer/QSD.pm      | 11 ++++-------
 3 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 80c1178e..3c27e721 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -14,7 +14,7 @@ use PVE::Storage;
 use PVE::QemuServer::Drive qw(drive_is_cdrom);
 use PVE::QemuServer::Helpers;
 use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd qsd_peer vm_qmp_peer);
 
 use base qw(Exporter);
 
@@ -584,12 +584,7 @@ state image.
 sub attach {
     my ($storecfg, $id, $drive, $options) = @_;
 
-    my $qmp_peer;
-    if ($options->{qsd}) {
-        $qmp_peer = { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
-    } else {
-        $qmp_peer = vm_qmp_peer($id);
-    }
+    my $qmp_peer = $options->{qsd} ? qsd_peer($id) : vm_qmp_peer($id);
 
     my $machine_version;
     if ($options->{qsd}) { # qemu-storage-daemon runs with the installed binary version
diff --git a/src/PVE/QemuServer/Monitor.pm b/src/PVE/QemuServer/Monitor.pm
index e7f08e23..1b1ce4ea 100644
--- a/src/PVE/QemuServer/Monitor.pm
+++ b/src/PVE/QemuServer/Monitor.pm
@@ -11,6 +11,7 @@ use base 'Exporter';
 our @EXPORT_OK = qw(
     mon_cmd
     qmp_cmd
+    qsd_peer
     vm_qmp_peer
 );
 
@@ -109,11 +110,16 @@ sub vm_qmp_peer {
     return { name => "VM $vmid", id => $vmid, type => 'qmp' };
 }
 
+sub qsd_peer {
+    my ($id) = @_;
+
+    return { name => "QEMU storage daemon $id", id => $id, type => 'qsd' };
+}
+
 sub qsd_cmd {
     my ($id, $execute, %params) = @_;
 
-    return qmp_cmd({ name => "QEMU storage daemon $id", id => $id, type => 'qsd' },
-        $execute, %params);
+    return qmp_cmd(qsd_peer($id), $execute, %params);
 }
 
 sub mon_cmd {
diff --git a/src/PVE/QemuServer/QSD.pm b/src/PVE/QemuServer/QSD.pm
index 9c30f7fd..bb85085a 100644
--- a/src/PVE/QemuServer/QSD.pm
+++ b/src/PVE/QemuServer/QSD.pm
@@ -11,7 +11,7 @@ use PVE::Tools;
 
 use PVE::QemuServer::Blockdev;
 use PVE::QemuServer::Helpers;
-use PVE::QemuServer::Monitor;
+use PVE::QemuServer::Monitor qw(qsd_peer);
 
 =head3 start
 
@@ -22,13 +22,10 @@ Start a QEMU storage daemon instance with ID C<$id>.
 =cut
 
 sub start($id) {
-    my $name = "QEMU storage daemon $id";
-
     # If something is still mounted, that could block the new instance, try to clean up first.
     PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);
 
-    my $qmp_socket_path =
-        PVE::QemuServer::Helpers::qmp_socket({ name => $name, id => $id, type => 'qsd' });
+    my $qmp_socket_path = PVE::QemuServer::Helpers::qmp_socket(qsd_peer($id));
     my $pidfile = PVE::QemuServer::Helpers::qsd_pidfile_name($id);
 
     my $cmd = [
@@ -45,7 +42,7 @@ sub start($id) {
     PVE::Tools::run_command($cmd);
 
     my $pid = PVE::QemuServer::Helpers::qsd_running_locally($id);
-    syslog("info", "$name started with PID $pid.");
+    syslog("info", "QEMU storage daemon $id started with PID $pid.");
 
     return;
 }
@@ -134,7 +131,7 @@ sub quit($id) {
     }
 
     unlink PVE::QemuServer::Helpers::qsd_pidfile_name($id);
-    unlink PVE::QemuServer::Helpers::qmp_socket({ name => $name, id => $id, type => 'qsd' });
+    unlink PVE::QemuServer::Helpers::qmp_socket(qsd_peer($id));
 
     PVE::QemuServer::Helpers::qsd_fuse_export_cleanup_files($id);
 
-- 
2.47.3





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

* [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (4 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 05/18] monitor: add qsd_peer() helper Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

The inserted node for the front-end device queried via
get_block_info() is the top node. The next commit will allow
get_node_name_below_throttle() to also query QSD, where there is no
front-end device. Part of the code can still be re-used, but the name
'inserted' would just be confusing.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer/Blockdev.pm | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 3c27e721..c96b76da 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -170,17 +170,17 @@ sub get_node_name_below_throttle {
 
     my $block_info = get_block_info($vmid);
     my $drive_id = $device_id =~ s/^drive-//r;
-    my $inserted = $block_info->{$drive_id}->{inserted}
+    my $top = $block_info->{$drive_id}->{inserted}
         or die "no block node inserted for drive '$drive_id'\n";
 
-    if ($inserted->{drv} ne 'throttle') {
-        die "$device_id: unexpected top node $inserted->{'node-name'} ($inserted->{drv})\n"
+    if ($top->{drv} ne 'throttle') {
+        die "$device_id: unexpected top node $top->{'node-name'} ($top->{drv})\n"
             if $assert_top_is_throttle;
         # before the switch to -blockdev, the top node was not throttle
-        return $inserted->{'node-name'};
+        return $top->{'node-name'};
     }
 
-    my $children = { map { $_->{child} => $_ } $inserted->{children}->@* };
+    my $children = { map { $_->{child} => $_ } $top->{children}->@* };
 
     if (my $node_name = $children->{file}->{'node-name'}) {
         return $node_name;
-- 
2.47.3





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

* [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (5 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 06/18] blockdev: rename variable in get_node_name_below_throttle() for readability Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 08/18] blockdev: switch detach() " Fiona Ebner
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

The get_block_info() function can only be used when the main QEMU
instance is the QMP peer, because it gets the information from the
front-end devices. In case of QSD, the relevant information can be
obtained with the 'query-named-block-nodes' QMP command.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---

Changes in v2:
* also die right away in QSD case if no block node can be found

 src/PVE/QemuServer.pm             |  8 +++++---
 src/PVE/QemuServer/BlockJob.pm    |  2 +-
 src/PVE/QemuServer/Blockdev.pm    | 20 +++++++++++++++-----
 src/PVE/QemuServer/VolumeChain.pm |  7 ++++---
 4 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index ba5acfe3..0841750a 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7284,7 +7284,8 @@ sub pbs_live_restore {
         # removes itself once all backing images vanish with 'auto-remove=on')
         my $jobs = {};
         for my $ds (sort keys %$restored_disks) {
-            my $node_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $ds);
+            my $node_name =
+                PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $ds);
             my $job_id = "restore-$ds";
             mon_cmd(
                 $vmid, 'block-stream',
@@ -7402,8 +7403,9 @@ sub live_import_from_files {
         # removes itself once all backing images vanish with 'auto-remove=on')
         my $jobs = {};
         for my $ds (sort keys %$live_restore_backing) {
-            my $node_name =
-                PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, "drive-$ds");
+            my $node_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
+                vm_qmp_peer($vmid), "drive-$ds",
+            );
             my $job_id = "restore-$ds";
             mon_cmd(
                 $vmid, 'block-stream',
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 97ada28b..59b33482 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -455,7 +455,7 @@ sub blockdev_mirror {
     # Need to replace the node below the top node. This is not necessarily a format node, for
     # example, it can also be a zeroinit node by a previous mirror! So query QEMU itself.
     my $source_node_name =
-        PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $device_id, 1);
+        PVE::QemuServer::Blockdev::get_node_name_below_throttle(vm_qmp_peer($vmid), $device_id, 1);
 
     # Copy original drive config (aio, cache, discard, ...):
     my $dest_drive = dclone($source->{drive});
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index c96b76da..495aff50 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -166,12 +166,22 @@ sub top_node_name {
 }
 
 sub get_node_name_below_throttle {
-    my ($vmid, $device_id, $assert_top_is_throttle) = @_;
+    my ($qmp_peer, $device_id, $assert_top_is_throttle) = @_;
 
-    my $block_info = get_block_info($vmid);
-    my $drive_id = $device_id =~ s/^drive-//r;
-    my $top = $block_info->{$drive_id}->{inserted}
-        or die "no block node inserted for drive '$drive_id'\n";
+    my $top;
+    if ($qmp_peer->{type} eq 'qmp') { # get_block_info() only works if there are front-end devices.
+        my $block_info = get_block_info($qmp_peer->{id});
+        my $drive_id = $device_id =~ s/^drive-//r;
+        $top = $block_info->{$drive_id}->{inserted};
+    } else {
+        my $named_block_node_info = qmp_cmd($qmp_peer, 'query-named-block-nodes');
+        for my $info ($named_block_node_info->@*) {
+            next if $info->{'node-name'} ne $device_id;
+            $top = $info;
+            last;
+        }
+    }
+    die "no block node found for drive '$device_id'\n" if !$top;
 
     if ($top->{drv} ne 'throttle') {
         die "$device_id: unexpected top node $top->{'node-name'} ($top->{drv})\n"
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index e3790683..3613c008 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -11,7 +11,7 @@ use PVE::Storage;
 use PVE::QemuServer::Blockdev qw(generate_file_blockdev generate_format_blockdev);
 use PVE::QemuServer::BlockJob;
 use PVE::QemuServer::Drive;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
 
 sub blockdev_external_snapshot {
     my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
@@ -113,8 +113,9 @@ sub blockdev_replace {
     my $src_blockdev_name;
     if ($src_snap eq 'current') {
         # there might be other nodes on top like zeroinit, look up the current node below throttle
-        $src_blockdev_name =
-            PVE::QemuServer::Blockdev::get_node_name_below_throttle($vmid, $deviceid, 1);
+        $src_blockdev_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
+            vm_qmp_peer($vmid), $deviceid, 1,
+        );
     } else {
         $src_name_options = { 'snapshot-name' => $src_snap };
         $src_blockdev_name =
-- 
2.47.3





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

* [PATCH qemu-server v2 08/18] blockdev: switch detach() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (6 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 07/18] blockdev: switch get_node_name_below_throttle() to use QMP peer Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm             |  6 +++---
 src/PVE/QemuServer/BlockJob.pm    |  4 ++--
 src/PVE/QemuServer/Blockdev.pm    | 22 +++++++++++-----------
 src/PVE/QemuServer/VolumeChain.pm |  4 ++--
 src/PVE/VZDump/QemuServer.pm      |  2 +-
 5 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 0841750a..a27a27c2 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4087,7 +4087,7 @@ sub qemu_drivedel {
 
     # for the switch to -blockdev
     if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
-        PVE::QemuServer::Blockdev::detach($vmid, "drive-$deviceid");
+        PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "drive-$deviceid");
         return 1;
     } else {
         my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid", 10 * 60);
@@ -7303,7 +7303,7 @@ sub pbs_live_restore {
             . " to disconnect from Proxmox Backup Server\n";
 
         for my $ds (sort keys %$restored_disks) {
-            PVE::QemuServer::Blockdev::detach($vmid, "$ds-pbs");
+            PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "$ds-pbs");
         }
 
         close($qmeventd_fd);
@@ -7422,7 +7422,7 @@ sub live_import_from_files {
         print "restore-drive jobs finished successfully, removing all tracking block devices\n";
 
         for my $ds (sort keys %$live_restore_backing) {
-            PVE::QemuServer::Blockdev::detach($vmid, "drive-$ds-restore");
+            PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), "drive-$ds-restore");
         }
 
         close($qmeventd_fd);
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 59b33482..f76b1bde 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -34,7 +34,7 @@ sub qemu_handle_concluded_blockjob {
     $job->{'detach-node-name'} = $job->{'target-node-name'} if $qmp_info->{error} || $job->{cancel};
 
     if (my $node_name = $job->{'detach-node-name'}) {
-        eval { PVE::QemuServer::Blockdev::detach($vmid, $node_name); };
+        eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name); };
         log_warn($@) if $@;
     }
 
@@ -509,7 +509,7 @@ sub blockdev_mirror {
     if (my $err = $@) {
         eval { qemu_blockjobs_cancel($vmid, $jobs) };
         log_warn("unable to cancel block jobs - $@");
-        eval { PVE::QemuServer::Blockdev::detach($vmid, $target_node_name); };
+        eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $target_node_name); };
         log_warn("unable to delete blockdev '$target_node_name' - $@");
         die "error starting blockdev mirrror - $err";
     }
diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
index 495aff50..b9430a56 100644
--- a/src/PVE/QemuServer/Blockdev.pm
+++ b/src/PVE/QemuServer/Blockdev.pm
@@ -637,16 +637,16 @@ sub attach {
 
 =head3 detach
 
-    detach($vmid, $node_name);
+    detach($qmp_peer, $node_name);
 
-Detach the block device C<$node_name> from the VM C<$vmid>. Also removes associated child block
-nodes.
+Detach the block device C<$node_name> from the QMP peer C<$qmp_peer>. Also removes associated child
+block nodes.
 
 Parameters:
 
 =over
 
-=item C<$vmid>: The ID of the virtual machine.
+=item C<$qmp_peer>: QMP peer information.
 
 =item C<$node_name>: The node name identifying the block node in QEMU.
 
@@ -655,11 +655,11 @@ Parameters:
 =cut
 
 sub detach {
-    my ($vmid, $node_name) = @_;
+    my ($qmp_peer, $node_name) = @_;
 
     die "Blockdev::detach - no node name\n" if !$node_name;
 
-    my $block_info = mon_cmd($vmid, "query-named-block-nodes");
+    my $block_info = qmp_cmd($qmp_peer, "query-named-block-nodes");
     $block_info = { map { $_->{'node-name'} => $_ } $block_info->@* };
 
     my $remove_throttle_group_id;
@@ -670,7 +670,7 @@ sub detach {
     while ($node_name) {
         last if !$block_info->{$node_name}; # already gone
 
-        my $res = mon_cmd($vmid, 'blockdev-del', 'node-name' => "$node_name", noerr => 1);
+        my $res = qmp_cmd($qmp_peer, 'blockdev-del', 'node-name' => "$node_name", noerr => 1);
         if (my $err = $res->{error}) {
             last if $err =~ m/Failed to find node with node-name/; # already gone
             die "deleting blockdev '$node_name' failed : $err\n";
@@ -684,7 +684,7 @@ sub detach {
     }
 
     if ($remove_throttle_group_id) {
-        eval { mon_cmd($vmid, 'object-del', id => $remove_throttle_group_id); };
+        eval { qmp_cmd($qmp_peer, 'object-del', id => $remove_throttle_group_id); };
         die "removing throttle group failed - $@\n" if $@;
     }
 
@@ -694,7 +694,7 @@ sub detach {
 sub detach_tpm_backup_node {
     my ($vmid) = @_;
 
-    detach($vmid, "drive-tpmstate0-backup");
+    detach(vm_qmp_peer($vmid), "drive-tpmstate0-backup");
 }
 
 sub detach_fleecing_block_nodes {
@@ -706,7 +706,7 @@ sub detach_fleecing_block_nodes {
         next if !is_fleecing_top_node($node_name);
 
         $log_func->('info', "detaching (old) fleecing image '$node_name'");
-        eval { detach($vmid, $node_name) };
+        eval { detach(vm_qmp_peer($vmid), $node_name) };
         $log_func->('warn', "error detaching (old) fleecing image '$node_name' - $@") if $@;
     }
 }
@@ -746,7 +746,7 @@ my sub blockdev_change_medium {
     # force eject if locked
     mon_cmd($vmid, "blockdev-open-tray", force => JSON::true, id => "$qdev_id");
     mon_cmd($vmid, "blockdev-remove-medium", id => "$qdev_id");
-    detach($vmid, "drive-$qdev_id");
+    detach(vm_qmp_peer($vmid), "drive-$qdev_id");
 
     return if $drive->{file} eq 'none';
 
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 3613c008..1b8d199e 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -66,7 +66,7 @@ sub blockdev_external_snapshot {
 sub blockdev_delete {
     my ($storecfg, $vmid, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
 
-    eval { PVE::QemuServer::Blockdev::detach($vmid, $fmt_blockdev->{'node-name'}); };
+    eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $fmt_blockdev->{'node-name'}); };
     warn "detaching block node for $file_blockdev->{filename} failed - $@" if $@;
 
     #delete the file (don't use vdisk_free as we don't want to delete all snapshot chain)
@@ -188,7 +188,7 @@ sub blockdev_replace {
     }
 
     # delete old file|fmt nodes
-    eval { PVE::QemuServer::Blockdev::detach($vmid, $src_blockdev_name); };
+    eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $src_blockdev_name); };
     warn "detaching block node for $src_snap failed - $@" if $@;
 }
 
diff --git a/src/PVE/VZDump/QemuServer.pm b/src/PVE/VZDump/QemuServer.pm
index 0cdf6fed..be48be78 100644
--- a/src/PVE/VZDump/QemuServer.pm
+++ b/src/PVE/VZDump/QemuServer.pm
@@ -631,7 +631,7 @@ my sub detach_fleecing_images {
     for my $di ($disks->@*) {
         if (my $volid = $di->{'fleece-volid'}) {
             my $node_name = "$di->{qmdevice}-fleecing";
-            eval { PVE::QemuServer::Blockdev::detach($vmid, $node_name) };
+            eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name) };
         }
     }
 }
-- 
2.47.3





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

* [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (7 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 08/18] blockdev: switch detach() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm             |  2 +-
 src/PVE/QemuServer/VolumeChain.pm | 25 ++++++++++++-------------
 2 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index a27a27c2..ddeb394c 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4427,7 +4427,7 @@ sub qemu_volume_snapshot_delete {
 
             PVE::QemuServer::VolumeChain::blockdev_replace(
                 $storecfg,
-                $vmid,
+                vm_qmp_peer($vmid),
                 $machine_version,
                 $attached_deviceid,
                 $drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 1b8d199e..01c5dd9b 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -11,7 +11,7 @@ use PVE::Storage;
 use PVE::QemuServer::Blockdev qw(generate_file_blockdev generate_format_blockdev);
 use PVE::QemuServer::BlockJob;
 use PVE::QemuServer::Drive;
-use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 
 sub blockdev_external_snapshot {
     my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
@@ -26,7 +26,7 @@ sub blockdev_external_snapshot {
     #reopen current to snap
     blockdev_replace(
         $storecfg,
-        $vmid,
+        vm_qmp_peer($vmid),
         $machine_version,
         $deviceid,
         $drive,
@@ -95,7 +95,7 @@ my sub blockdev_relative_backing_file {
 sub blockdev_replace {
     my (
         $storecfg,
-        $vmid,
+        $qmp_peer,
         $machine_version,
         $deviceid,
         $drive,
@@ -113,9 +113,8 @@ sub blockdev_replace {
     my $src_blockdev_name;
     if ($src_snap eq 'current') {
         # there might be other nodes on top like zeroinit, look up the current node below throttle
-        $src_blockdev_name = PVE::QemuServer::Blockdev::get_node_name_below_throttle(
-            vm_qmp_peer($vmid), $deviceid, 1,
-        );
+        $src_blockdev_name =
+            PVE::QemuServer::Blockdev::get_node_name_below_throttle($qmp_peer, $deviceid, 1);
     } else {
         $src_name_options = { 'snapshot-name' => $src_snap };
         $src_blockdev_name =
@@ -148,16 +147,16 @@ sub blockdev_replace {
             );
             $target_fmt_blockdev->{backing} = $parent_fmt_nodename;
         }
-        mon_cmd($vmid, 'blockdev-add', %$target_fmt_blockdev);
+        qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);
 
         #reopen the current throttlefilter nodename with the target fmt nodename
         my $throttle_blockdev = PVE::QemuServer::Blockdev::generate_throttle_blockdev(
             $drive, $target_fmt_blockdev->{'node-name'}, {},
         );
-        mon_cmd($vmid, 'blockdev-reopen', options => [$throttle_blockdev]);
+        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$throttle_blockdev]);
     } else {
         #intermediate snapshot
-        mon_cmd($vmid, 'blockdev-add', %$target_fmt_blockdev);
+        qmp_cmd($qmp_peer, 'blockdev-add', %$target_fmt_blockdev);
 
         #reopen the parent node with the new target fmt backing node
         my $parent_file_blockdev = generate_file_blockdev(
@@ -173,14 +172,14 @@ sub blockdev_replace {
             { 'snapshot-name' => $parent_snap },
         );
         $parent_fmt_blockdev->{backing} = $target_fmt_blockdev->{'node-name'};
-        mon_cmd($vmid, 'blockdev-reopen', options => [$parent_fmt_blockdev]);
+        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$parent_fmt_blockdev]);
 
         my $backing_file =
             blockdev_relative_backing_file($target_file_blockdev, $parent_file_blockdev);
 
         #change backing-file in qcow2 metadatas
-        mon_cmd(
-            $vmid, 'change-backing-file',
+        qmp_cmd(
+            $qmp_peer, 'change-backing-file',
             device => $deviceid,
             'image-node-name' => $parent_fmt_blockdev->{'node-name'},
             'backing-file' => $backing_file,
@@ -188,7 +187,7 @@ sub blockdev_replace {
     }
 
     # delete old file|fmt nodes
-    eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $src_blockdev_name); };
+    eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $src_blockdev_name); };
     warn "detaching block node for $src_snap failed - $@" if $@;
 }
 
-- 
2.47.3





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

* [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (8 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 09/18] blockdev: switch blockdev_replace() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm             |  8 +++++++-
 src/PVE/QemuServer/VolumeChain.pm | 10 +++++-----
 2 files changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index ddeb394c..3c698c4c 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4356,7 +4356,13 @@ sub qemu_volume_snapshot {
         my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);
         my $parent_snap = $snapshots->{'current'}->{parent};
         PVE::QemuServer::VolumeChain::blockdev_external_snapshot(
-            $storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap,
+            $storecfg,
+            vm_qmp_peer($vmid),
+            $machine_version,
+            $deviceid,
+            $drive,
+            $snap,
+            $parent_snap,
         );
     } elsif ($do_snapshots_type eq 'storage') {
         PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 01c5dd9b..8c0749de 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -14,7 +14,7 @@ use PVE::QemuServer::Drive;
 use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 
 sub blockdev_external_snapshot {
-    my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
+    my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap) = @_;
 
     print "Creating a new current volume with $snap as backing snap\n";
 
@@ -26,7 +26,7 @@ sub blockdev_external_snapshot {
     #reopen current to snap
     blockdev_replace(
         $storecfg,
-        vm_qmp_peer($vmid),
+        $qmp_peer,
         $machine_version,
         $deviceid,
         $drive,
@@ -53,11 +53,11 @@ sub blockdev_external_snapshot {
     #backing need to be forced to undef in blockdev, to avoid reopen of backing-file on blockdev-add
     $new_fmt_blockdev->{backing} = undef;
 
-    mon_cmd($vmid, 'blockdev-add', %$new_fmt_blockdev);
+    qmp_cmd($qmp_peer, 'blockdev-add', %$new_fmt_blockdev);
 
     print "blockdev-snapshot: reopen current with $snap backing image\n";
-    mon_cmd(
-        $vmid, 'blockdev-snapshot',
+    qmp_cmd(
+        $qmp_peer, 'blockdev-snapshot',
         node => $snap_fmt_blockdev->{'node-name'},
         overlay => $new_fmt_blockdev->{'node-name'},
     );
-- 
2.47.3





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

* [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (9 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 10/18] blockdev: switch blockdev_external_snapshot() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer/BlockJob.pm | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index f76b1bde..9e8fbdd7 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -13,7 +13,7 @@ use PVE::Storage;
 use PVE::QemuServer::Agent qw(qga_check_running);
 use PVE::QemuServer::Blockdev;
 use PVE::QemuServer::Drive qw(checked_volume_format);
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 use PVE::QemuServer::RunState;
 
 # If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this
@@ -23,9 +23,9 @@ use PVE::QemuServer::RunState;
 # $job is the information about the job recorded on the PVE-side.
 # A block node $job->{'detach-node-name'} will be detached if present.
 sub qemu_handle_concluded_blockjob {
-    my ($vmid, $job_id, $qmp_info, $job) = @_;
+    my ($qmp_peer, $job_id, $qmp_info, $job) = @_;
 
-    eval { mon_cmd($vmid, 'job-dismiss', id => $job_id); };
+    eval { qmp_cmd($qmp_peer, 'job-dismiss', id => $job_id); };
     log_warn("$job_id: failed to dismiss job - $@") if $@;
 
     # If there was an error or if the job was cancelled, always detach the target. This is correct
@@ -34,7 +34,7 @@ sub qemu_handle_concluded_blockjob {
     $job->{'detach-node-name'} = $job->{'target-node-name'} if $qmp_info->{error} || $job->{cancel};
 
     if (my $node_name = $job->{'detach-node-name'}) {
-        eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $node_name); };
+        eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $node_name); };
         log_warn($@) if $@;
     }
 
@@ -61,7 +61,7 @@ sub qemu_blockjobs_cancel {
         foreach my $job (keys %$jobs) {
             my $info = $running_jobs->{$job};
             eval {
-                qemu_handle_concluded_blockjob($vmid, $job, $info, $jobs->{$job})
+                qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job})
                     if $info && $info->{status} eq 'concluded';
             };
             log_warn($@) if $@; # only warn and proceed with canceling other jobs
@@ -120,8 +120,11 @@ sub monitor {
 
                 die "$job_id: '$op' has been cancelled\n" if !defined($job);
 
-                qemu_handle_concluded_blockjob($vmid, $job_id, $job, $jobs->{$job_id})
-                    if $job && $job->{status} eq 'concluded';
+                if ($job && $job->{status} eq 'concluded') {
+                    qemu_handle_concluded_blockjob(
+                        vm_qmp_peer($vmid), $job_id, $job, $jobs->{$job_id},
+                    );
+                }
 
                 my $busy = $job->{busy};
                 my $ready = $job->{ready};
@@ -353,7 +356,7 @@ sub qemu_drive_mirror_switch_to_active_mode {
 
             my $info = $running_jobs->{$job};
             if ($info->{status} eq 'concluded') {
-                qemu_handle_concluded_blockjob($vmid, $job, $info, $jobs->{$job});
+                qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job});
                 # The 'concluded' state should occur here if and only if the job failed, so the
                 # 'die' below should be unreachable, but play it safe.
                 die "$job: expected job to have failed, but no error was set\n";
-- 
2.47.3





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

* [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (10 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 11/18] block job: switch qemu_handle_concluded_blockjob() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
                   ` (5 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/API2/Qemu.pm           |  6 ++++--
 src/PVE/QemuMigrate.pm         |  4 ++--
 src/PVE/QemuServer/BlockJob.pm | 16 ++++++++--------
 3 files changed, 14 insertions(+), 12 deletions(-)

diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 59ff8181..dff4802d 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -35,7 +35,7 @@ use PVE::QemuServer::CPUConfig;
 use PVE::QemuServer::Drive qw(checked_volume_format checked_parse_volname);
 use PVE::QemuServer::Helpers;
 use PVE::QemuServer::ImportDisk;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
 use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::MetaInfo;
@@ -4613,7 +4613,9 @@ __PACKAGE__->register_method({
                 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
             };
             if (my $err = $@) {
-                eval { PVE::QemuServer::BlockJob::qemu_blockjobs_cancel($vmid, $jobs) };
+                eval {
+                    PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
+                };
                 sleep 1; # some storage like rbd need to wait before release volume - really?
 
                 foreach my $volid (@$newvollist) {
diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index f7ec3227..c5ddf829 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -33,7 +33,7 @@ use PVE::QemuServer::CPUConfig;
 use PVE::QemuServer::Drive qw(checked_volume_format);
 use PVE::QemuServer::Helpers qw(min_version);
 use PVE::QemuServer::Machine;
-use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
 use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::Network;
 use PVE::QemuServer::QMPHelpers;
@@ -1606,7 +1606,7 @@ sub phase2_cleanup {
     if ($self->{storage_migration}) {
         eval {
             PVE::QemuServer::BlockJob::qemu_blockjobs_cancel(
-                $vmid,
+                vm_qmp_peer($vmid),
                 $self->{storage_migration_jobs},
             );
         };
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index 9e8fbdd7..d6eb2a8e 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -42,16 +42,16 @@ sub qemu_handle_concluded_blockjob {
 }
 
 sub qemu_blockjobs_cancel {
-    my ($vmid, $jobs) = @_;
+    my ($qmp_peer, $jobs) = @_;
 
     foreach my $job (keys %$jobs) {
         print "$job: Cancelling block job\n";
-        eval { mon_cmd($vmid, "block-job-cancel", device => $job); };
+        eval { qmp_cmd($qmp_peer, "block-job-cancel", device => $job); };
         $jobs->{$job}->{cancel} = 1;
     }
 
     while (1) {
-        my $stats = mon_cmd($vmid, "query-block-jobs");
+        my $stats = qmp_cmd($qmp_peer, "query-block-jobs");
 
         my $running_jobs = {};
         foreach my $stat (@$stats) {
@@ -61,7 +61,7 @@ sub qemu_blockjobs_cancel {
         foreach my $job (keys %$jobs) {
             my $info = $running_jobs->{$job};
             eval {
-                qemu_handle_concluded_blockjob(vm_qmp_peer($vmid), $job, $info, $jobs->{$job})
+                qemu_handle_concluded_blockjob($qmp_peer, $job, $info, $jobs->{$job})
                     if $info && $info->{status} eq 'concluded';
             };
             log_warn($@) if $@; # only warn and proceed with canceling other jobs
@@ -184,7 +184,7 @@ sub monitor {
                     }
 
                     # if we clone a disk for a new target vm, we don't switch the disk
-                    qemu_blockjobs_cancel($vmid, $jobs);
+                    qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
 
                     if ($should_fsfreeze) {
                         print "issuing guest agent 'guest-fsfreeze-thaw' command\n";
@@ -241,7 +241,7 @@ sub monitor {
     my $err = $@;
 
     if ($err) {
-        eval { qemu_blockjobs_cancel($vmid, $jobs) };
+        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
         die "block job ($op) error: $err";
     }
 }
@@ -315,7 +315,7 @@ sub qemu_drive_mirror {
     # if a job already runs for this device we get an error, catch it for cleanup
     eval { mon_cmd($vmid, "drive-mirror", %$opts); };
     if (my $err = $@) {
-        eval { qemu_blockjobs_cancel($vmid, $jobs) };
+        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
         warn "$@\n" if $@;
         die "mirroring error: $err\n";
     }
@@ -510,7 +510,7 @@ sub blockdev_mirror {
     # if a job already runs for this device we get an error, catch it for cleanup
     eval { mon_cmd($vmid, "blockdev-mirror", $qmp_opts->%*); };
     if (my $err = $@) {
-        eval { qemu_blockjobs_cancel($vmid, $jobs) };
+        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
         log_warn("unable to cancel block jobs - $@");
         eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $target_node_name); };
         log_warn("unable to delete blockdev '$target_node_name' - $@");
-- 
2.47.3





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

* [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (11 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 12/18] block job: switch qemu_blockjobs_cancel() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Take care to only allow a different destination ID when having the
main QEMU instance as a peer, because the required freeze/thaw or
suspend/resume can only be done then.

Also adds the missing $op argument in the signature of the mocked
function in the migration tests for completeness.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuMigrate.pm                    |  2 +-
 src/PVE/QemuServer.pm                     | 12 ++++++---
 src/PVE/QemuServer/BlockJob.pm            | 33 ++++++++++++++---------
 src/PVE/QemuServer/VolumeChain.pm         |  8 ++++--
 src/test/MigrationTest/QemuMigrateMock.pm |  2 +-
 5 files changed, 38 insertions(+), 19 deletions(-)

diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index c5ddf829..0ebc0e73 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -1559,7 +1559,7 @@ sub phase2 {
         # thus, this command changes to it to blockjob complete (see qapi docs)
         eval {
             PVE::QemuServer::BlockJob::monitor(
-                $vmid, undef, $self->{storage_migration_jobs}, 'cancel',
+                vm_qmp_peer($vmid), undef, $self->{storage_migration_jobs}, 'cancel',
             );
         };
         if (my $err = $@) {
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 3c698c4c..bcc6f553 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -7303,7 +7303,9 @@ sub pbs_live_restore {
         }
 
         mon_cmd($vmid, 'cont');
-        PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+        PVE::QemuServer::BlockJob::monitor(
+            vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+        );
 
         print "restore-drive jobs finished successfully, removing all tracking block devices"
             . " to disconnect from Proxmox Backup Server\n";
@@ -7423,7 +7425,9 @@ sub live_import_from_files {
         }
 
         mon_cmd($vmid, 'cont');
-        PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+        PVE::QemuServer::BlockJob::monitor(
+            vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+        );
 
         print "restore-drive jobs finished successfully, removing all tracking block devices\n";
 
@@ -7931,7 +7935,9 @@ sub clone_disk {
             # if this is the case, we have to complete any block-jobs still there from
             # previous drive-mirrors
             if (($completion && $completion eq 'complete') && (scalar(keys %$jobs) > 0)) {
-                PVE::QemuServer::BlockJob::monitor($vmid, $newvmid, $jobs, $completion, $qga);
+                PVE::QemuServer::BlockJob::monitor(
+                    vm_qmp_peer($vmid), $newvmid, $jobs, $completion, $qga,
+                );
             }
             goto no_data_clone;
         }
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index d6eb2a8e..76518b1f 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -84,7 +84,10 @@ sub qemu_blockjobs_cancel {
 # 'skip': wait until all jobs are ready, return with block jobs in ready state
 # 'auto': wait until all jobs disappear, only use for jobs which complete automatically
 sub monitor {
-    my ($vmid, $vmiddst, $jobs, $completion, $qga, $op) = @_;
+    my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;
+
+    die "drive mirror: different destination is only supported when peer is main QEMU instance\n"
+        if $vmiddst && $qmp_peer->{type} ne 'qmp';
 
     $completion //= 'complete';
     $op //= "mirror";
@@ -96,7 +99,7 @@ sub monitor {
         while (1) {
             die "block job ('$op') timed out\n" if $err_complete > 300;
 
-            my $stats = mon_cmd($vmid, "query-block-jobs");
+            my $stats = qmp_cmd($qmp_peer, "query-block-jobs");
             my $ctime = time();
 
             my $running_jobs = {};
@@ -121,9 +124,7 @@ sub monitor {
                 die "$job_id: '$op' has been cancelled\n" if !defined($job);
 
                 if ($job && $job->{status} eq 'concluded') {
-                    qemu_handle_concluded_blockjob(
-                        vm_qmp_peer($vmid), $job_id, $job, $jobs->{$job_id},
-                    );
+                    qemu_handle_concluded_blockjob($qmp_peer, $job_id, $job, $jobs->{$job_id});
                 }
 
                 my $busy = $job->{busy};
@@ -164,7 +165,8 @@ sub monitor {
                 # do the complete later (or has already been done)
                 last if $completion eq 'skip' || $completion eq 'auto';
 
-                if ($vmiddst && $vmiddst != $vmid) {
+                if ($qmp_peer->{type} eq 'qmp' && $vmiddst && $vmiddst != $qmp_peer->{id}) {
+                    my $vmid = $qmp_peer->{id};
                     my $should_fsfreeze = $qga && qga_check_running($vmid);
                     if ($should_fsfreeze) {
                         print "issuing guest agent 'guest-fsfreeze-freeze' command\n";
@@ -184,7 +186,7 @@ sub monitor {
                     }
 
                     # if we clone a disk for a new target vm, we don't switch the disk
-                    qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs);
+                    qemu_blockjobs_cancel($qmp_peer, $jobs);
 
                     if ($should_fsfreeze) {
                         print "issuing guest agent 'guest-fsfreeze-thaw' command\n";
@@ -211,10 +213,10 @@ sub monitor {
                         eval {
                             if ($completion eq 'complete') {
                                 $detach_node_name = $jobs->{$job_id}->{'source-node-name'};
-                                mon_cmd($vmid, 'job-complete', id => $job_id);
+                                qmp_cmd($qmp_peer, 'job-complete', id => $job_id);
                             } elsif ($completion eq 'cancel') {
                                 $detach_node_name = $jobs->{$job_id}->{'target-node-name'};
-                                mon_cmd($vmid, 'block-job-cancel', device => $job_id);
+                                qmp_cmd($qmp_peer, 'block-job-cancel', device => $job_id);
                             } else {
                                 die "invalid completion value: $completion\n";
                             }
@@ -241,7 +243,7 @@ sub monitor {
     my $err = $@;
 
     if ($err) {
-        eval { qemu_blockjobs_cancel(vm_qmp_peer($vmid), $jobs) };
+        eval { qemu_blockjobs_cancel($qmp_peer, $jobs) };
         die "block job ($op) error: $err";
     }
 }
@@ -320,7 +322,7 @@ sub qemu_drive_mirror {
         die "mirroring error: $err\n";
     }
 
-    monitor($vmid, $vmiddst, $jobs, $completion, $qga);
+    monitor(vm_qmp_peer($vmid), $vmiddst, $jobs, $completion, $qga);
 }
 
 # Callers should version guard this (only available with a binary >= QEMU 8.2)
@@ -516,7 +518,14 @@ sub blockdev_mirror {
         log_warn("unable to delete blockdev '$target_node_name' - $@");
         die "error starting blockdev mirrror - $err";
     }
-    monitor($vmid, $dest->{vmid}, $jobs, $completion, $options->{'guest-agent'}, 'mirror');
+    monitor(
+        vm_qmp_peer($vmid),
+        $dest->{vmid},
+        $jobs,
+        $completion,
+        $options->{'guest-agent'},
+        'mirror',
+    );
 }
 
 sub mirror {
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 8c0749de..8dd29d61 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -255,7 +255,9 @@ sub blockdev_commit {
         # 'block-commit' will complete automatically.
         my $complete = $src_snap && $src_snap ne 'current' ? 'auto' : 'complete';
 
-        PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, $complete, 0, 'commit');
+        PVE::QemuServer::BlockJob::monitor(
+            vm_qmp_peer($vmid), undef, $jobs, $complete, 0, 'commit',
+        );
 
         blockdev_delete(
             $storecfg, $vmid, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
@@ -329,7 +331,9 @@ sub blockdev_stream {
     mon_cmd($vmid, 'block-stream', %$options);
     $jobs->{$job_id} = {};
 
-    PVE::QemuServer::BlockJob::monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+    PVE::QemuServer::BlockJob::monitor(
+        vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
+    );
 
     blockdev_delete($storecfg, $vmid, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap);
 }
diff --git a/src/test/MigrationTest/QemuMigrateMock.pm b/src/test/MigrationTest/QemuMigrateMock.pm
index 8cd2da12..a12310ec 100644
--- a/src/test/MigrationTest/QemuMigrateMock.pm
+++ b/src/test/MigrationTest/QemuMigrateMock.pm
@@ -176,7 +176,7 @@ $qemu_server_blockjob_module->mock(
         common_mirror_mock($source->{vmid}, $drive_id);
     },
     monitor => sub {
-        my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
+        my ($qmp_peer, $vmiddst, $jobs, $completion, $qga, $op) = @_;
 
         if (
             $fail_config->{block_job_monitor}
-- 
2.47.3





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

* [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (12 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 13/18] block job: switch qemu_drive_mirror_monitor() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

In preparation to allow snapshot operations for QSD-FUSE-exported
TPM states.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer/VolumeChain.pm | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index 8dd29d61..d2042614 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -64,9 +64,9 @@ sub blockdev_external_snapshot {
 }
 
 sub blockdev_delete {
-    my ($storecfg, $vmid, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
+    my ($storecfg, $qmp_peer, $drive, $file_blockdev, $fmt_blockdev, $snap) = @_;
 
-    eval { PVE::QemuServer::Blockdev::detach(vm_qmp_peer($vmid), $fmt_blockdev->{'node-name'}); };
+    eval { PVE::QemuServer::Blockdev::detach($qmp_peer, $fmt_blockdev->{'node-name'}); };
     warn "detaching block node for $file_blockdev->{filename} failed - $@" if $@;
 
     #delete the file (don't use vdisk_free as we don't want to delete all snapshot chain)
@@ -260,7 +260,12 @@ sub blockdev_commit {
         );
 
         blockdev_delete(
-            $storecfg, $vmid, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
+            $storecfg,
+            vm_qmp_peer($vmid),
+            $drive,
+            $src_file_blockdev,
+            $src_fmt_blockdev,
+            $src_snap,
         );
     };
     my $err = $@;
@@ -335,7 +340,14 @@ sub blockdev_stream {
         vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
     );
 
-    blockdev_delete($storecfg, $vmid, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap);
+    blockdev_delete(
+        $storecfg,
+        vm_qmp_peer($vmid),
+        $drive,
+        $snap_file_blockdev,
+        $snap_fmt_blockdev,
+        $snap,
+    );
 }
 
 1;
-- 
2.47.3





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

* [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (13 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 14/18] blockdev: switch blockdev_delete() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm             |  2 +-
 src/PVE/QemuServer/VolumeChain.pm | 25 +++++++++++++------------
 2 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index bcc6f553..bb3f7529 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4446,7 +4446,7 @@ sub qemu_volume_snapshot_delete {
             print "stream intermediate snapshot $snap to $childsnap\n";
             PVE::QemuServer::VolumeChain::blockdev_stream(
                 $storecfg,
-                $vmid,
+                vm_qmp_peer($vmid),
                 $machine_version,
                 $attached_deviceid,
                 $drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index d2042614..dad39479 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -284,8 +284,16 @@ sub blockdev_commit {
 }
 
 sub blockdev_stream {
-    my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $snap, $parent_snap, $target_snap) =
-        @_;
+    my (
+        $storecfg,
+        $qmp_peer,
+        $machine_version,
+        $deviceid,
+        $drive,
+        $snap,
+        $parent_snap,
+        $target_snap,
+    ) = @_;
 
     my $volid = $drive->{file};
     $target_snap = undef if $target_snap eq 'current';
@@ -333,20 +341,13 @@ sub blockdev_stream {
     $options->{'base-node'} = $parent_fmt_blockdev->{'node-name'};
     $options->{'backing-file'} = $backing_file;
 
-    mon_cmd($vmid, 'block-stream', %$options);
+    qmp_cmd($qmp_peer, 'block-stream', %$options);
     $jobs->{$job_id} = {};
 
-    PVE::QemuServer::BlockJob::monitor(
-        vm_qmp_peer($vmid), undef, $jobs, 'auto', 0, 'stream',
-    );
+    PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, 'auto', 0, 'stream');
 
     blockdev_delete(
-        $storecfg,
-        vm_qmp_peer($vmid),
-        $drive,
-        $snap_file_blockdev,
-        $snap_fmt_blockdev,
-        $snap,
+        $storecfg, $qmp_peer, $drive, $snap_file_blockdev, $snap_fmt_blockdev, $snap,
     );
 }
 
-- 
2.47.3





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

* [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() to use QMP peer
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (14 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 15/18] blockdev: switch blockdev_stream() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm             |  2 +-
 src/PVE/QemuServer/VolumeChain.pm | 19 ++++++-------------
 2 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index bb3f7529..04fde77b 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -4421,7 +4421,7 @@ sub qemu_volume_snapshot_delete {
             print "delete first snapshot $snap\n";
             PVE::QemuServer::VolumeChain::blockdev_commit(
                 $storecfg,
-                $vmid,
+                vm_qmp_peer($vmid),
                 $machine_version,
                 $attached_deviceid,
                 $drive,
diff --git a/src/PVE/QemuServer/VolumeChain.pm b/src/PVE/QemuServer/VolumeChain.pm
index dad39479..91e78495 100644
--- a/src/PVE/QemuServer/VolumeChain.pm
+++ b/src/PVE/QemuServer/VolumeChain.pm
@@ -192,7 +192,7 @@ sub blockdev_replace {
 }
 
 sub blockdev_commit {
-    my ($storecfg, $vmid, $machine_version, $deviceid, $drive, $src_snap, $target_snap) = @_;
+    my ($storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $src_snap, $target_snap) = @_;
 
     my $volid = $drive->{file};
     my $target_was_read_only;
@@ -229,7 +229,7 @@ sub blockdev_commit {
         print "reopening internal read-only block node for '$target_snap' as writable\n";
         $target_fmt_blockdev->{'read-only'} = JSON::false;
         $target_file_blockdev->{'read-only'} = JSON::false;
-        mon_cmd($vmid, 'blockdev-reopen', options => [$target_fmt_blockdev]);
+        qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]);
         # For the guest, the drive is still read-only, because the top throttle node is.
     }
 
@@ -241,7 +241,7 @@ sub blockdev_commit {
         $opts->{'base-node'} = $target_fmt_blockdev->{'node-name'};
         $opts->{'top-node'} = $src_fmt_blockdev->{'node-name'};
 
-        mon_cmd($vmid, "block-commit", %$opts);
+        qmp_cmd($qmp_peer, "block-commit", %$opts);
         $jobs->{$job_id} = {};
 
         # If the 'current' state is committed to its backing snapshot, the job will not complete
@@ -255,17 +255,10 @@ sub blockdev_commit {
         # 'block-commit' will complete automatically.
         my $complete = $src_snap && $src_snap ne 'current' ? 'auto' : 'complete';
 
-        PVE::QemuServer::BlockJob::monitor(
-            vm_qmp_peer($vmid), undef, $jobs, $complete, 0, 'commit',
-        );
+        PVE::QemuServer::BlockJob::monitor($qmp_peer, undef, $jobs, $complete, 0, 'commit');
 
         blockdev_delete(
-            $storecfg,
-            vm_qmp_peer($vmid),
-            $drive,
-            $src_file_blockdev,
-            $src_fmt_blockdev,
-            $src_snap,
+            $storecfg, $qmp_peer, $drive, $src_file_blockdev, $src_fmt_blockdev, $src_snap,
         );
     };
     my $err = $@;
@@ -276,7 +269,7 @@ sub blockdev_commit {
         print "re-applying read-only flag for internal block node for '$target_snap'\n";
         $target_fmt_blockdev->{'read-only'} = JSON::true;
         $target_file_blockdev->{'read-only'} = JSON::true;
-        eval { mon_cmd($vmid, 'blockdev-reopen', options => [$target_fmt_blockdev]); };
+        eval { qmp_cmd($qmp_peer, 'blockdev-reopen', options => [$target_fmt_blockdev]); };
         print "failed to re-apply read-only flag - $@\n" if $@;
     }
 
-- 
2.47.3





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

* [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (15 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 16/18] blockdev: switch blockdev_commit() " Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  2026-03-09 16:58 ` [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive " Fiona Ebner
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Adds a new drive_qmp_peer() helper to determine whether the drive is
managed by the QEMU instance or QSD instance for the VM.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/QemuServer.pm       | 30 +++++++++++++-----------------
 src/PVE/QemuServer/Drive.pm |  8 ++++++++
 2 files changed, 21 insertions(+), 17 deletions(-)

diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 04fde77b..1f7afaeb 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -83,7 +83,7 @@ use PVE::QemuServer::DriveDevice qw(print_drivedevice_full scsihw_infos);
 use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory qw(get_current_memory);
 use PVE::QemuServer::MetaInfo;
-use PVE::QemuServer::Monitor qw(mon_cmd vm_qmp_peer);
+use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer);
 use PVE::QemuServer::Network;
 use PVE::QemuServer::OVMF;
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
@@ -4342,7 +4342,8 @@ sub qemu_volume_snapshot {
 
     if ($do_snapshots_type eq 'internal') {
         print "internal qemu snapshot\n";
-        mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
+        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+        qmp_cmd($qmp_peer, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
     } elsif ($do_snapshots_type eq 'external') {
         my $machine_version = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
         if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_version, 10, 0)) {
@@ -4355,14 +4356,9 @@ sub qemu_volume_snapshot {
         print "external qemu snapshot\n";
         my $snapshots = PVE::Storage::volume_snapshot_info($storecfg, $volid);
         my $parent_snap = $snapshots->{'current'}->{parent};
+        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
         PVE::QemuServer::VolumeChain::blockdev_external_snapshot(
-            $storecfg,
-            vm_qmp_peer($vmid),
-            $machine_version,
-            $deviceid,
-            $drive,
-            $snap,
-            $parent_snap,
+            $storecfg, $qmp_peer, $machine_version, $deviceid, $drive, $snap, $parent_snap,
         );
     } elsif ($do_snapshots_type eq 'storage') {
         PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
@@ -4390,8 +4386,9 @@ sub qemu_volume_snapshot_delete {
     my $do_snapshots_type = do_snapshots_type($storecfg, $volid, $attached_deviceid, $running);
 
     if ($do_snapshots_type eq 'internal') {
-        mon_cmd(
-            $vmid,
+        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+        qmp_cmd(
+            $qmp_peer,
             'blockdev-snapshot-delete-internal-sync',
             device => $attached_deviceid,
             name => $snap,
@@ -4415,13 +4412,15 @@ sub qemu_volume_snapshot_delete {
         my $parentsnap = $snapshots->{$snap}->{parent};
         my $childsnap = $snapshots->{$snap}->{child};
 
+        my $qmp_peer = PVE::QemuServer::Drive::drive_qmp_peer($storecfg, $vmid, $drive);
+
         # if we delete the first snasphot, we commit because the first snapshot original base image, it should be big.
         # improve-me: if firstsnap > child : commit, if firstsnap < child do a stream.
         if (!$parentsnap) {
             print "delete first snapshot $snap\n";
             PVE::QemuServer::VolumeChain::blockdev_commit(
                 $storecfg,
-                vm_qmp_peer($vmid),
+                $qmp_peer,
                 $machine_version,
                 $attached_deviceid,
                 $drive,
@@ -4433,7 +4432,7 @@ sub qemu_volume_snapshot_delete {
 
             PVE::QemuServer::VolumeChain::blockdev_replace(
                 $storecfg,
-                vm_qmp_peer($vmid),
+                $qmp_peer,
                 $machine_version,
                 $attached_deviceid,
                 $drive,
@@ -4446,7 +4445,7 @@ sub qemu_volume_snapshot_delete {
             print "stream intermediate snapshot $snap to $childsnap\n";
             PVE::QemuServer::VolumeChain::blockdev_stream(
                 $storecfg,
-                vm_qmp_peer($vmid),
+                $qmp_peer,
                 $machine_version,
                 $attached_deviceid,
                 $drive,
@@ -7775,9 +7774,6 @@ sub restore_tar_archive {
 sub do_snapshots_type {
     my ($storecfg, $volid, $deviceid, $running) = @_;
 
-    #always use storage snapshot for tpmstate
-    return 'storage' if $deviceid && $deviceid =~ m/tpmstate0/;
-
     #we use storage snapshot if vm is not running or if disk is unused;
     return 'storage' if !$running || !$deviceid;
 
diff --git a/src/PVE/QemuServer/Drive.pm b/src/PVE/QemuServer/Drive.pm
index f88504b2..79afc146 100644
--- a/src/PVE/QemuServer/Drive.pm
+++ b/src/PVE/QemuServer/Drive.pm
@@ -13,6 +13,8 @@ use PVE::Storage;
 use PVE::Storage::Common;
 use PVE::JSONSchema qw(get_standard_option);
 
+use PVE::QemuServer::Monitor qw(qsd_peer vm_qmp_peer);
+
 use base qw(Exporter);
 
 our @EXPORT_OK = qw(
@@ -1164,4 +1166,10 @@ sub drive_uses_qsd_fuse {
     return;
 }
 
+sub drive_qmp_peer {
+    my ($storecfg, $vmid, $drive) = @_;
+
+    return drive_uses_qsd_fuse($storecfg, $drive) ? qsd_peer($vmid) : vm_qmp_peer($vmid);
+}
+
 1;
-- 
2.47.3





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

* [PATCH qemu-server v2 18/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain
  2026-03-09 16:58 [PATCH-SERIES qemu-server v2 00/18] fix #7066: api: allow live snapshot (remove) of qcow2 TPM drive with snapshot-as-volume-chain Fiona Ebner
                   ` (16 preceding siblings ...)
  2026-03-09 16:58 ` [PATCH qemu-server v2 17/18] snapshot: support live snapshot (remove) of qcow2 TPM drive on storage with snapshot-as-volume-chain Fiona Ebner
@ 2026-03-09 16:58 ` Fiona Ebner
  17 siblings, 0 replies; 19+ messages in thread
From: Fiona Ebner @ 2026-03-09 16:58 UTC (permalink / raw)
  To: pve-devel

Commit "snapshot: support live snapshot (remove) of qcow2 TPM drive on
storage with snapshot-as-volume-chain" prepared for this. It's not a
revert of commit c7d839df ("snapshot: prohibit live snapshot (remove)
of qcow2 TPM drive on storage with snapshot-as-volume-chain"), because
there is a single limitation remaining. That limitation is removing
the top-most snapshot live when the current image is exported via
FUSE, because exporting unshares the 'resize' permission, which would
be required by both 'block-commit' and 'block-stream', for example:

> QEMU storage daemon 100 qsd command 'block-commit' failed - Permission
> conflict on node '#block017': permissions 'resize' are both required
> by node 'drive-tpmstate0' (uses node '#block017' as 'file' child) and
> unshared by commit job 'commit-drive-tpmstate0' (uses node '#block017'
> as 'main node' child).

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/API2/Qemu.pm | 39 ++++++++++++++-------------------------
 1 file changed, 14 insertions(+), 25 deletions(-)

diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index dff4802d..e162d6b1 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -729,11 +729,11 @@ my $check_cpu_model_access = sub {
     }
 };
 
-# TODO switch to doing internal snapshots only for TPM? Need a way to tell the storage. Also needs
-# handling for pre-existing as-volume-chain snapshots then. Or is there a way to make QSD+swtpm
-# compatible with using volume-chain live?
-my sub assert_tpm_snapshot_compat {
-    my ($vmid, $conf, $op, $snap_conf) = @_;
+# The top-most snapshot for a FUSE-exported TPM state cannot be removed live, because exporting
+# unshares the 'resize' permission, which would be required by both 'block-commit' and
+# 'block-stream'.
+my sub assert_tpm_snapshot_delete_possible {
+    my ($vmid, $conf, $snap_conf, $snap_name) = @_;
 
     return if !$conf->{tpmstate0};
     return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);
@@ -742,19 +742,19 @@ my sub assert_tpm_snapshot_compat {
     my $volid = $drive->{file};
     my $storecfg = PVE::Storage::config();
 
-    if ($snap_conf) {
-        return if !$snap_conf->{tpmstate0};
-        my $snap_drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $snap_conf->{tpmstate0});
-        return if $volid ne $snap_drive->{file};
-    }
+    return if $conf->{parent} ne $snap_name; # allowed if not top-most snapshot
+
+    return if !$snap_conf->{tpmstate0};
+    my $snap_drive = PVE::QemuServer::Drive::parse_drive('tpmstate0', $snap_conf->{tpmstate0});
+    return if $volid ne $snap_drive->{file};
 
     my $format = PVE::QemuServer::Drive::checked_volume_format($storecfg, $volid);
     my ($storeid) = PVE::Storage::parse_volume_id($volid, 1);
     if ($storeid && $format eq 'qcow2') {
         my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
         if ($scfg && $scfg->{'snapshot-as-volume-chain'}) {
-            die "snapshot $op of TPM state '$volid' on storage with 'snapshot-as-volume-chain' is"
-                . " not yet supported while the VM is running.\n";
+            die "top-most snapshot of TPM state '$volid' on storage with 'snapshot-as-volume-chain'"
+                . " cannot be removed while the VM is running.\n";
         }
     }
 }
@@ -6082,14 +6082,6 @@ __PACKAGE__->register_method({
             0);
 
         my $realcmd = sub {
-            PVE::QemuConfig->lock_config(
-                $vmid,
-                sub {
-                    my $conf = PVE::QemuConfig->load_config($vmid);
-                    assert_tpm_snapshot_compat($vmid, $conf, 'create');
-                },
-            );
-
             PVE::Cluster::log_msg('info', $authuser, "snapshot VM $vmid: $snapname");
             PVE::QemuConfig->snapshot_create(
                 $vmid, $snapname, $param->{vmstate}, $param->{description},
@@ -6346,11 +6338,8 @@ __PACKAGE__->register_method({
                 $vmid,
                 sub {
                     my $conf = PVE::QemuConfig->load_config($vmid);
-                    assert_tpm_snapshot_compat(
-                        $vmid,
-                        $conf,
-                        'delete',
-                        $conf->{snapshots}->{$snapname},
+                    assert_tpm_snapshot_delete_possible(
+                        $vmid, $conf, $conf->{snapshots}->{$snapname}, $snapname,
                     );
                 },
             );
-- 
2.47.3





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

end of thread, other threads:[~2026-03-09 17:07 UTC | newest]

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

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