public inbox for pve-devel@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 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