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 0CE0A1FF0E6 for ; Fri, 24 Jul 2026 16:33:08 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 22B6D2159F; Fri, 24 Jul 2026 16:32:50 +0200 (CEST) From: Fiona Ebner To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server 2/9] partially fix #7299: fix regression with guest IO limits applying to mirror job Date: Fri, 24 Jul 2026 16:32:23 +0200 Message-ID: <20260724143240.211130-3-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: 1784903533302 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.198 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: 3NHEVQIDOHXZSLI5CMAVZJ3S2MW3RI75 X-Message-ID-Hash: 3NHEVQIDOHXZSLI5CMAVZJ3S2MW3RI75 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.0.3, 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. 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 8da6f15d..ab930955 100644 --- a/src/PVE/QemuMigrate.pm +++ b/src/PVE/QemuMigrate.pm @@ -626,7 +626,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; @@ -817,9 +819,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 921f046c..7be5a1e4 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 @@ -242,12 +244,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.0.3, blockdev-mirror requires a root node. + if (!PVE::QemuServer::QMPHelpers::runs_at_least_qemu_version($vmid, 11, 0, 3)) { + 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, @@ -304,7 +338,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; @@ -498,7 +532,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 05eed1d9..9d6c495e 100755 --- a/src/test/run_qemu_migrate_tests.pl +++ b/src/test/run_qemu_migrate_tests.pl @@ -1367,8 +1367,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), @@ -1416,8 +1416,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