all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Fiona Ebner <f.ebner@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH qemu-server 9/9] partially fix #7836: allow migration for drive with read-only flag when using blockdev
Date: Fri, 24 Jul 2026 16:32:30 +0200	[thread overview]
Message-ID: <20260724143240.211130-10-f.ebner@proxmox.com> (raw)
In-Reply-To: <20260724143240.211130-1-f.ebner@proxmox.com>

A read-only block node cannot serve as a writeable block export for
the NBD mirror target. With -blockdev, the issue can be resolved by
re-opening the node below throttle as writeable, exporting it, and
then re-opening it as read-only after finishing the migration.

Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
 src/PVE/API2/Qemu.pm           |  7 ++++--
 src/PVE/CLI/qm.pm              | 30 +++++++++++++++++++++++--
 src/PVE/QemuServer.pm          |  9 ++++++++
 src/PVE/QemuServer/BlockJob.pm | 40 ++++++++++++++++++++++++++++++++++
 4 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 28cbb9b0..7262c221 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -43,7 +43,6 @@ use PVE::QemuServer::MetaInfo;
 use PVE::QemuServer::Network;
 use PVE::QemuServer::OVMF;
 use PVE::QemuServer::PCI;
-use PVE::QemuServer::QMPHelpers;
 use PVE::QemuServer::RNG;
 use PVE::QemuServer::RunState;
 use PVE::QemuServer::USB;
@@ -6957,7 +6956,11 @@ __PACKAGE__->register_method({
                     return;
                 },
                 'nbdstop' => sub {
-                    PVE::QemuServer::QMPHelpers::nbd_stop($state->{vmid});
+                    PVE::QemuServer::BlockJob::nbd_stop_after_mirror(
+                        $state->{storecfg},
+                        $state->{vmid},
+                        $state->{conf},
+                    );
                     return;
                 },
                 'resume' => sub {
diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index 9a53a4f2..af6b3170 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -428,8 +428,34 @@ __PACKAGE__->register_method({
 
         my $vmid = $param->{vmid};
 
-        eval { PVE::QemuServer::QMPHelpers::nbd_stop($vmid) };
-        warn $@ if $@;
+        my $obtained_lock;
+
+        eval {
+            PVE::QemuConfig->lock_config(
+                $vmid,
+                sub {
+                    $obtained_lock = 1;
+                    my $storecfg = PVE::Storage::config();
+                    my $conf = PVE::QemuConfig->load_config($vmid);
+                    eval {
+                        PVE::QemuServer::BlockJob::nbd_stop_after_mirror(
+                            $storecfg, $vmid, $conf,
+                        );
+                    };
+                    warn $@ if $@;
+                },
+            );
+        };
+        if (my $err = $@) {
+            if (!$obtained_lock) {
+                # Fall back to just stopping the NBD server without checking disks.
+                warn "nbdstop: failed to obtain lock, skipping disk check - $@\n";
+                eval { PVE::QemuServer::QMPHelpers::nbd_stop($vmid) };
+                warn $@ if $@;
+            } else {
+                die $err;
+            }
+        }
 
         return;
     },
diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
index 6b03e170..7470a4a1 100644
--- a/src/PVE/QemuServer.pm
+++ b/src/PVE/QemuServer.pm
@@ -5929,6 +5929,10 @@ sub vm_start_nolock {
             my $drivestr = $nbd->{$opt}->{drivestr};
             my $volid = $nbd->{$opt}->{volid};
 
+            my $drive = parse_drive($opt, $drivestr);
+            die "$opt: internal error - unable to parse drive '$drivestr'\n" if !$drive;
+            my $drive_is_readonly = $drive->{ro};
+
             my $top = $block_info->{$opt}->{inserted};
             die "no block node found for drive '$opt'\n" if !$top;
             my $block_node = $top->{'node-name'};
@@ -5940,6 +5944,11 @@ sub vm_start_nolock {
                 my $children = { map { $_->{child} => $_ } $top->{children}->@* };
                 $block_node = $children->{file}->{'node-name'}
                     or die "drive '$opt': throttle node without file child node name\n";
+
+                if ($drive_is_readonly) { # re-open the node below throttle as writeable
+                    my $reopen_opts = { 'no-throttle' => 1, 'read-only' => 0 };
+                    PVE::QemuServer::Blockdev::reopen($storecfg, $vmid, $drive, $reopen_opts);
+                }
             }
 
             mon_cmd(
diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
index bce350da..1cf9149e 100644
--- a/src/PVE/QemuServer/BlockJob.pm
+++ b/src/PVE/QemuServer/BlockJob.pm
@@ -619,4 +619,44 @@ sub mirror {
     }
 }
 
+sub nbd_stop_after_mirror {
+    my ($storecfg, $vmid, $conf) = @_;
+
+    my $exports = [];
+
+    my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
+    if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
+        eval { $exports = mon_cmd($vmid, 'query-block-exports'); };
+        log_warn("stop nbd: unable to query block exports $@") if $@;
+    }
+
+    PVE::QemuServer::QMPHelpers::nbd_stop($vmid);
+
+    for my $export ($exports->@*) {
+        my ($opt) = $export->{id} =~ m/^drive-(.*)$/;
+        if (!defined($opt)) {
+            warn("stop nbd: got unexpected export ID '$export->{id}'");
+        } elsif (!PVE::QemuServer::Drive::is_valid_drivename($opt)) {
+            warn("stop nbd: got unexpected export ID '$export->{id}' - not a drive key");
+        } else {
+            my $drive = PVE::QemuServer::Drive::parse_drive($opt, $conf->{$opt});
+            if (!$drive) {
+                warn("stop nbd: $opt: unable to parse drive '$conf->{$opt}'");
+                next;
+            }
+            if ($drive->{ro}) {
+                # The node below throttle was opened as writeable to be able to serve as a target
+                # NBD export for mirror. Re-open as read-only now. Failure is not too important,
+                # because the guest sees the drive as read-only anways, because the top throttle
+                # node is.
+                my $reopen_opts = { 'no-throttle' => 1 };
+                eval { PVE::QemuServer::Blockdev::reopen($storecfg, $vmid, $drive, $reopen_opts); };
+                print "drive-$opt failed to re-apply read-only flag below throttle - $@\n" if $@;
+            }
+        }
+    }
+
+    return;
+}
+
 1;
-- 
2.47.3





      parent reply	other threads:[~2026-07-24 14:33 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-24 14:32 [PATCH-SERIES qemu/qemu-server 0/9] mirror: fix regressions with blockdev and allow migration with 'ro' flag Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu 1/9] add patch to allow mirror job to use non-root block node Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 2/9] partially fix #7299: fix regression with guest IO limits applying to mirror job Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 3/9] partially fix #7299: migrate: fix regression with guest IO limits applying to NBD export Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 4/9] blockdev: attach: support explicitly attaching as writeable Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 5/9] blockdev: implement reopen function Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 6/9] partially fix #7836: allow mirror for drive with read-only flag when using blockdev Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 7/9] qm: schema: declare 'nbdstop' command as being for internal use only Fiona Ebner
2026-07-24 14:32 ` [PATCH qemu-server 8/9] migrate: log messages from remote nbdstop command Fiona Ebner
2026-07-24 14:32 ` Fiona Ebner [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260724143240.211130-10-f.ebner@proxmox.com \
    --to=f.ebner@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal