From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id E9A331FF0E6 for ; Fri, 24 Jul 2026 16:32:59 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id B9D922154A; Fri, 24 Jul 2026 16:32:49 +0200 (CEST) From: Fiona Ebner To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server 6/9] partially fix #7836: allow mirror for drive with read-only flag when using blockdev Date: Fri, 24 Jul 2026 16:32:27 +0200 Message-ID: <20260724143240.211130-7-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260724143240.211130-1-f.ebner@proxmox.com> References: <20260724143240.211130-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1784903533534 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.207 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_LOW -0.7 Sender listed at https://www.dnswl.org/, low trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: ACAFWDRYQRL4GKB3YVBAEFS5F47OAY2H X-Message-ID-Hash: ACAFWDRYQRL4GKB3YVBAEFS5F47OAY2H X-MailFrom: f.ebner@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The switch to -blockdev in Proxmox VE 9 introduced a regression, making clone of a VM with a read-only drive fail. The reason is that the target node is also attached as read-only and thus cannot serve as the mirror target. To fix the issue, open the mirror target as writeable initially and in case the VM switched to the new drive, re-open as read-only later. Note that the drive device is always read-only for the guest, because the top throttle node is. Signed-off-by: Fiona Ebner --- src/PVE/QemuServer/BlockJob.pm | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm index 7be5a1e4..bce350da 100644 --- a/src/PVE/QemuServer/BlockJob.pm +++ b/src/PVE/QemuServer/BlockJob.pm @@ -80,6 +80,25 @@ sub qemu_blockjobs_cancel { } } +sub check_reopen_after_complete { + my ($job_info) = @_; + + my $reopen_info = $job_info->{'target-reopen-upon-complete'} or return; + + my ($storecfg, $vmid, $drive, $options) = $reopen_info->@{qw(storecfg vmid drive options)}; + + # Re-opening is currently only done to restore read-only below the throttle node. For the guest, + # the drive will be seen as read-only if the throttle node already is, so a failure here is not + # too bad. + eval { PVE::QemuServer::Blockdev::reopen($storecfg, $vmid, $drive, $options); }; + if (my $err = $@) { + my $drive_id = PVE::QemuServer::Drive::get_drive_id($drive); + print "$drive_id: failed to re-apply read-only flag below throttle - $err\n"; + } + + return; +} + # $completion can be either # 'complete': wait until all jobs are ready, job-complete them (default) # 'cancel': wait until all jobs are ready, block-job-cancel them @@ -210,6 +229,7 @@ sub monitor { if ($completion eq 'complete') { $detach_node_name = $jobs->{$job_id}->{'source-node-name'}; qmp_cmd($qmp_peer, 'job-complete', id => $job_id); + check_reopen_after_complete($jobs->{$job_id}); } elsif ($completion eq 'cancel') { $detach_node_name = $jobs->{$job_id}->{'target-node-name'}; qmp_cmd($qmp_peer, 'block-job-cancel', device => $job_id); @@ -519,6 +539,14 @@ sub blockdev_mirror { } } + # If the drive is configured as read-only, attach the mirror target as writeable initially and + # re-open it as read-only later. For the guest, the drive will be seen as read-only if the + # throttle node is, even in case mirror switches to the new target. + my $dest_is_read_only = $dest_drive->{ro}; + if ($dest_is_read_only) { + $attach_dest_opts->{'read-only'} = 0; + } + # Note that if 'aio' is not explicitly set, i.e. default, it can change if source and target # don't both allow or both not allow 'io_uring' as the default. my ($target_node_name) = @@ -531,6 +559,16 @@ sub blockdev_mirror { 'target-node-name' => $target_node_name, }; + if ($dest_is_read_only) { + delete($attach_dest_opts->{'read-only'}); # remove the override for re-opening later + $jobs->{$jobid}->{'target-reopen-upon-complete'} = { + storecfg => $storecfg, + vmid => $vmid, + drive => $dest_drive, + options => $attach_dest_opts, + }; + } + my $qmp_opts = common_mirror_qmp_options( $vmid, $device_id, $target_node_name, $source->{bitmap}, $options->{bwlimit}, ); -- 2.47.3