From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id F13441FF0B3 for ; Wed, 11 Nov 2026 13:02:32 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id AF1FE21680; Wed, 11 Nov 2026 13:02:20 +0100 (CET) From: Fiona Ebner To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server v2 2/9] partially fix #7299: fix regression with guest IO limits applying to mirror job Date: Wed, 11 Nov 2026 13:01:41 +0100 Message-ID: <20261111120211.110737-3-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261111120211.110737-1-f.ebner@proxmox.com> References: <20261111120211.110737-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: 1794398534683 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.281 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_MED -2.3 Sender listed at https://www.dnswl.org/, medium 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: UC7G4ZAPVR6FKFLUYBYM3P75OHMLZTZW X-Message-ID-Hash: UC7G4ZAPVR6FKFLUYBYM3P75OHMLZTZW 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: Since the switch to -blockdev was made in Proxmox VE 9, the root block node is now always a throttle node for optionally limiting the guest IO. Mirror jobs are accidentally affected by a set limit too, because the throttle root block node was passed as the source of the mirror job. Since pve-qemu-kvm 11.1.4, non-root nodes can be used for the mirror job too. Use the node node below throttle as the mirror source. During mirror completion, the throttle node on top is also drained by QEMU (which temporarily ignores the throttling). For migration, the dirty bitmap also needs to be created on the node below throttle. For the tests, it is necessary to mock the 'query-block' QMP command for the blockdev module. Signed-off-by: Fiona Ebner --- The QEMU binary version in the runs_at_least_qemu_version() check needs to be adapted when applying, to the pve-qemu version which actually does allow non-root nodes as a mirror source. Changes in v2: * Bump version in version check. src/PVE/QemuMigrate.pm | 8 ++++-- src/PVE/QemuServer/BlockJob.pm | 42 +++++++++++++++++++++++++++--- src/test/MigrationTest/Shared.pm | 35 +++++++++++++++++++++++++ src/test/run_qemu_migrate_tests.pl | 8 +++--- 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm index 8176afe5..f177f262 100644 --- a/src/PVE/QemuMigrate.pm +++ b/src/PVE/QemuMigrate.pm @@ -628,7 +628,9 @@ sub handle_replication { # start tracking before replication to get full delta + a few duplicates $self->log('info', "$drive: start tracking writes using block-dirty-bitmap '$bitmap'"); - mon_cmd($vmid, 'block-dirty-bitmap-add', node => "drive-$drive", name => $bitmap); + my $block_node_name = + PVE::QemuServer::BlockJob::get_mirror_source_node($vmid, "drive-$drive"); + mon_cmd($vmid, 'block-dirty-bitmap-add', node => "$block_node_name", name => $bitmap); # other info comes from target node in phase 2 $self->{target_drive}->{$drive}->{bitmap} = $bitmap; @@ -820,9 +822,11 @@ sub cleanup_bitmaps { my $bitmap = $self->{target_drive}->{$drive}->{bitmap}; next if !$bitmap; $self->log('info', "$drive: removing block-dirty-bitmap '$bitmap'"); + my $block_node_name = + PVE::QemuServer::BlockJob::get_mirror_source_node($self->{vmid}, "drive-$drive"); mon_cmd( $self->{vmid}, 'block-dirty-bitmap-remove', - node => "drive-$drive", + node => "$block_node_name", name => $bitmap, ); } diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm index 493dda33..2e4e275e 100644 --- a/src/PVE/QemuServer/BlockJob.pm +++ b/src/PVE/QemuServer/BlockJob.pm @@ -13,7 +13,9 @@ 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::Machine; use PVE::QemuServer::Monitor qw(mon_cmd qmp_cmd vm_qmp_peer); +use PVE::QemuServer::QMPHelpers; use PVE::QemuServer::RunState; # If the job was started with auto-dismiss=false, it's necessary to dismiss it manually. Using this @@ -244,12 +246,44 @@ sub monitor { } } +=pod + +=head3 get_mirror_source_node + + my $node_name = get_mirror_source_node($vmid, $device_id); + +Return the node for the drive device C<$device_id> that should serve as the mirror source. + +=cut + +sub get_mirror_source_node { + my ($vmid, $device_id) = @_; + + # Before QEMU binary 11.1.4, blockdev-mirror requires a root node. + if (!PVE::QemuServer::QMPHelpers::runs_at_least_qemu_version($vmid, 11, 1, 4)) { + return $device_id; + } + + # The switch to -blockdev happened with machine version 10.0. + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid); + if (!PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) { + return $device_id; + } + + # Avoid throttle limits intended for the guest applying to the mirror job (bug #7299). + return PVE::QemuServer::Blockdev::get_node_name_below_throttle( + vm_qmp_peer($vmid), $device_id, 1, + ); +} + my sub common_mirror_qmp_options { - my ($device_id, $qemu_target, $src_bitmap, $bwlimit) = @_; + my ($vmid, $device_id, $qemu_target, $src_bitmap, $bwlimit) = @_; + + my $source_node_name = get_mirror_source_node($vmid, $device_id); my $opts = { timeout => 10, - device => "$device_id", + device => "$source_node_name", sync => "full", target => $qemu_target, 'auto-dismiss' => JSON::false, @@ -306,7 +340,7 @@ sub qemu_drive_mirror { $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path; } - my $opts = common_mirror_qmp_options($device_id, $qemu_target, $src_bitmap, $bwlimit); + my $opts = common_mirror_qmp_options($vmid, $device_id, $qemu_target, $src_bitmap, $bwlimit); $opts->{mode} = "existing"; $opts->{format} = $format if $format; @@ -500,7 +534,7 @@ sub blockdev_mirror { }; my $qmp_opts = common_mirror_qmp_options( - $device_id, $target_node_name, $source->{bitmap}, $options->{bwlimit}, + $vmid, $device_id, $target_node_name, $source->{bitmap}, $options->{bwlimit}, ); $qmp_opts->{'job-id'} = "$jobid"; diff --git a/src/test/MigrationTest/Shared.pm b/src/test/MigrationTest/Shared.pm index a51e1692..c69a141a 100644 --- a/src/test/MigrationTest/Shared.pm +++ b/src/test/MigrationTest/Shared.pm @@ -159,6 +159,41 @@ $qemu_server_module->mock( }, ); +our $qemu_server_blockdev_module = Test::MockModule->new("PVE::QemuServer::Blockdev"); +$qemu_server_blockdev_module->mock( + mon_cmd => sub { + my ($vmid, $command, %params) = @_; + + if ($command eq 'query-block') { + my $vm_config = PVE::QemuConfig->load_config($vmid); + my $res = []; + PVE::QemuConfig->foreach_volume( + $vm_config, + sub { + my ($ds, $drive) = @_; + my $entry = { + qdev => "$ds", + inserted => { + drv => 'throttle', + 'node-name' => "drive-$ds", + children => [ + { + 'node-name' => "f-$ds", + child => 'file', + }, + ], + }, + }; + push($res->@*, $entry); + }, + ); + return $res; + } else { + die "mon_cmd (mocked) - implement me: $command"; + } + }, +); + our $qemu_server_ovmf_module = Test::MockModule->new("PVE::QemuServer::OVMF"); $qemu_server_ovmf_module->mock( get_efivars_size => sub { diff --git a/src/test/run_qemu_migrate_tests.pl b/src/test/run_qemu_migrate_tests.pl index fc2f1375..22a1febb 100755 --- a/src/test/run_qemu_migrate_tests.pl +++ b/src/test/run_qemu_migrate_tests.pl @@ -1418,8 +1418,8 @@ my $tests = [ target_volids => local_volids_for_vm(105), expected_calls => { %{$replicated_expected_calls_online}, - 'block-dirty-bitmap-add-drive-scsi0' => 1, - 'block-dirty-bitmap-add-drive-ide0' => 1, + 'block-dirty-bitmap-add-f-scsi0' => 1, + 'block-dirty-bitmap-add-f-ide0' => 1, }, expected => { source_volids => local_volids_for_vm(105), @@ -1467,8 +1467,8 @@ my $tests = [ target_volids => local_volids_for_vm(105), expected_calls => { %{$replicated_expected_calls_online}, - 'block-dirty-bitmap-add-drive-scsi0' => 1, - 'block-dirty-bitmap-add-drive-ide0' => 1, + 'block-dirty-bitmap-add-f-scsi0' => 1, + 'block-dirty-bitmap-add-f-ide0' => 1, }, expected => { source_volids => local_volids_for_vm(105), -- 2.47.3