[pve-devel] [PATCH qemu-server 29/31] command line: switch to blockdev starting with machine version 10.0
Fabian Grünbichler
f.gruenbichler at proxmox.com
Mon Jun 30 12:15:00 CEST 2025
On June 27, 2025 5:57 pm, Fiona Ebner wrote:
> Co-developed-by: Alexandre Derumier <alexandre.derumier at groupe-cyllene.com>
> Signed-off-by: Fiona Ebner <f.ebner at proxmox.com>
> ---
>
> Changes since last series:
> * Support for live restore and live import.
> * Use Blockdev::{attach,detach} helpers for hot{,un}plug.
> * Adapt to changes from previous patches.
> * Also switch for medium change.
>
> src/PVE/QemuServer.pm | 146 ++++++++++++++----
> src/PVE/QemuServer/BlockJob.pm | 32 ++--
> src/PVE/QemuServer/Blockdev.pm | 101 ++++++++----
> src/PVE/QemuServer/OVMF.pm | 21 ++-
> src/test/MigrationTest/QemuMigrateMock.pm | 5 +
> src/test/cfg2cmd/aio.conf.cmd | 42 +++--
> src/test/cfg2cmd/bootorder-empty.conf.cmd | 11 +-
> src/test/cfg2cmd/bootorder-legacy.conf.cmd | 11 +-
> src/test/cfg2cmd/bootorder.conf.cmd | 11 +-
> ...putype-icelake-client-deprecation.conf.cmd | 5 +-
> src/test/cfg2cmd/efi-raw-template.conf.cmd | 7 +-
> src/test/cfg2cmd/efi-raw.conf.cmd | 7 +-
> .../cfg2cmd/efi-secboot-and-tpm-q35.conf.cmd | 7 +-
> src/test/cfg2cmd/efi-secboot-and-tpm.conf.cmd | 7 +-
> src/test/cfg2cmd/efidisk-on-rbd.conf.cmd | 7 +-
> src/test/cfg2cmd/ide.conf.cmd | 15 +-
> src/test/cfg2cmd/q35-ide.conf.cmd | 15 +-
> .../q35-linux-hostpci-mapping.conf.cmd | 7 +-
> .../q35-linux-hostpci-multifunction.conf.cmd | 7 +-
> .../q35-linux-hostpci-template.conf.cmd | 10 +-
> ...q35-linux-hostpci-x-pci-overrides.conf.cmd | 7 +-
> src/test/cfg2cmd/q35-linux-hostpci.conf.cmd | 7 +-
> src/test/cfg2cmd/q35-simple.conf.cmd | 7 +-
> src/test/cfg2cmd/seabios_serial.conf.cmd | 5 +-
> src/test/cfg2cmd/sev-es.conf.cmd | 7 +-
> src/test/cfg2cmd/sev-std.conf.cmd | 7 +-
> src/test/cfg2cmd/simple-btrfs.conf.cmd | 14 +-
> src/test/cfg2cmd/simple-cifs.conf.cmd | 14 +-
> .../cfg2cmd/simple-disk-passthrough.conf.cmd | 9 +-
> src/test/cfg2cmd/simple-lvm.conf.cmd | 12 +-
> src/test/cfg2cmd/simple-lvmthin.conf.cmd | 12 +-
> src/test/cfg2cmd/simple-rbd.conf.cmd | 26 ++--
> src/test/cfg2cmd/simple-virtio-blk.conf.cmd | 5 +-
> .../cfg2cmd/simple-zfs-over-iscsi.conf.cmd | 14 +-
> src/test/cfg2cmd/simple1-template.conf.cmd | 8 +-
> src/test/cfg2cmd/simple1.conf.cmd | 5 +-
> src/test/run_config2command_tests.pl | 19 +++
> 37 files changed, 446 insertions(+), 206 deletions(-)
>
> diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
> index d9284843..3eb7f339 100644
> --- a/src/PVE/QemuServer.pm
> +++ b/src/PVE/QemuServer.pm
> @@ -3640,19 +3640,38 @@ sub config_to_command {
> }
>
> my $live_restore = $live_restore_backing->{$ds};
> - my $live_blockdev_name = undef;
> - if ($live_restore) {
> - $live_blockdev_name = $live_restore->{name};
> - push @$devices, '-blockdev', $live_restore->{blockdev};
> +
> + if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev
> + my $throttle_group = PVE::QemuServer::Blockdev::generate_throttle_group($drive);
> + push @$cmd, '-object', to_json($throttle_group, { canonical => 1 });
> +
> + my $extra_blockdev_options = {};
> + $extra_blockdev_options->{'live-restore'} = $live_restore if $live_restore;
> + # extra protection for templates, but SATA and IDE don't support it..
> + $extra_blockdev_options->{'read-only'} = 1 if drive_is_read_only($conf, $drive);
> +
> + if ($drive->{file} ne 'none') {
> + my $blockdev = PVE::QemuServer::Blockdev::generate_drive_blockdev(
> + $storecfg, $drive, $extra_blockdev_options,
> + );
> + push @$devices, '-blockdev', to_json($blockdev, { canonical => 1 });
> + }
> + } else {
> + my $live_blockdev_name = undef;
> + if ($live_restore) {
> + $live_blockdev_name = $live_restore->{name};
> + push @$devices, '-blockdev', $live_restore->{blockdev};
> + }
> +
> + my $drive_cmd =
> + print_drive_commandline_full($storecfg, $vmid, $drive, $live_blockdev_name);
> +
> + # extra protection for templates, but SATA and IDE don't support it..
> + $drive_cmd .= ',readonly=on' if drive_is_read_only($conf, $drive);
> +
> + push @$devices, '-drive', $drive_cmd;
> }
>
> - my $drive_cmd =
> - print_drive_commandline_full($storecfg, $vmid, $drive, $live_blockdev_name);
> -
> - # extra protection for templates, but SATA and IDE don't support it..
> - $drive_cmd .= ',readonly=on' if drive_is_read_only($conf, $drive);
> -
> - push @$devices, '-drive', $drive_cmd;
> push @$devices, '-device',
> print_drivedevice_full(
> $storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type,
> @@ -4050,28 +4069,63 @@ sub qemu_iothread_del {
> sub qemu_driveadd {
> my ($storecfg, $vmid, $device) = @_;
>
> - my $drive = print_drive_commandline_full($storecfg, $vmid, $device, undef);
> - $drive =~ s/\\/\\\\/g;
> - my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"", 60);
> + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
>
> - # If the command succeeds qemu prints: "OK"
> - return 1 if $ret =~ m/OK/s;
> + # for the switch to -blockdev
> + if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
isn't this part here basically Blockdev::attach?
> + my $throttle_group = PVE::QemuServer::Blockdev::generate_throttle_group($device);
> + mon_cmd($vmid, 'object-add', %$throttle_group);
>
> - die "adding drive failed: $ret\n";
> + eval {
> + my $blockdev =
> + PVE::QemuServer::Blockdev::generate_drive_blockdev($storecfg, $device, {});
> + mon_cmd($vmid, 'blockdev-add', %$blockdev);
> + };
> + if (my $err = $@) {
> + my $drive_id = PVE::QemuServer::Drive::get_drive_id($device);
> + eval { mon_cmd($vmid, 'object-del', id => "throttle-drive-$drive_id"); };
> + warn $@ if $@;
> + die $err;
> + }
> +
> + return 1;
> + } else {
> + my $drive = print_drive_commandline_full($storecfg, $vmid, $device, undef);
> + $drive =~ s/\\/\\\\/g;
> + my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"", 60);
> +
> + # If the command succeeds qemu prints: "OK"
> + return 1 if $ret =~ m/OK/s;
> +
> + die "adding drive failed: $ret\n";
> + }
> }
>
> sub qemu_drivedel {
> my ($vmid, $deviceid) = @_;
>
> - my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid", 10 * 60);
> - $ret =~ s/^\s+//;
> + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
>
> - return 1 if $ret eq "";
> + # for the switch to -blockdev
> + if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
and this here Blockdev::detach?
> + # QEMU recursively auto-removes the file children, i.e. file and format node below the top
> + # node and also implicit backing children referenced by a qcow2 image.
> + eval { mon_cmd($vmid, 'blockdev-del', 'node-name' => "drive-$deviceid"); };
> + die "deleting blockdev $deviceid failed : $@\n" if $@;
> + # FIXME ignore already removed scenario like below?
>
> - # NB: device not found errors mean the drive was auto-deleted and we ignore the error
> - return 1 if $ret =~ m/Device \'.*?\' not found/s;
> + mon_cmd($vmid, 'object-del', id => "throttle-drive-$deviceid");
> + } else {
> + my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid", 10 * 60);
> + $ret =~ s/^\s+//;
>
> - die "deleting drive $deviceid failed : $ret\n";
> + return 1 if $ret eq "";
> +
> + # NB: device not found errors mean the drive was auto-deleted and we ignore the error
> + return 1 if $ret =~ m/Device \'.*?\' not found/s;
> +
> + die "deleting drive $deviceid failed : $ret\n";
> + }
> }
>
> sub qemu_deviceaddverify {
> @@ -7006,10 +7060,22 @@ sub pbs_live_restore {
> print "restoring '$ds' to '$drive->{file}'\n";
>
> my $pbs_name = "drive-${confname}-pbs";
> - $live_restore_backing->{$confname} = {
> - name => $pbs_name,
> - blockdev => print_pbs_blockdev($pbs_conf, $pbs_name),
> - };
> +
> + $live_restore_backing->{$confname} = { name => $pbs_name };
> +
> + # add blockdev information
> + my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf, undef, $conf->{arch});
> + my $machine_version = PVE::QemuServer::Machine::extract_version(
> + $machine_type,
> + PVE::QemuServer::Helpers::kvm_user_version(),
> + );
> + if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev
> + $live_restore_backing->{$confname}->{blockdev} =
> + PVE::QemuServer::Blockdev::generate_pbs_blockdev($pbs_conf, $pbs_name);
> + } else {
> + $live_restore_backing->{$confname}->{blockdev} =
> + print_pbs_blockdev($pbs_conf, $pbs_name);
> + }
> }
>
> my $drives_streamed = 0;
> @@ -7086,6 +7152,8 @@ sub pbs_live_restore {
> sub live_import_from_files {
> my ($mapping, $vmid, $conf, $restore_options) = @_;
>
> + my $storecfg = PVE::Storage::config();
> +
> my $live_restore_backing = {};
> my $sources_to_remove = [];
> for my $dev (keys %$mapping) {
> @@ -7103,18 +7171,30 @@ sub live_import_from_files {
> die "invalid format '$format' for '$dev' mapping\n"
> if !grep { $format eq $_ } qw(raw qcow2 vmdk);
>
> - $live_restore_backing->{$dev} = {
> - name => "drive-$dev-restore",
> - blockdev => "driver=$format,node-name=drive-$dev-restore"
> + $live_restore_backing->{$dev} = { name => "drive-$dev-restore" };
> +
> + my $machine_type = PVE::QemuServer::Machine::get_vm_machine($conf, undef, $conf->{arch});
> + my $machine_version = PVE::QemuServer::Machine::extract_version(
> + $machine_type,
> + PVE::QemuServer::Helpers::kvm_user_version(),
> + );
> + if (min_version($machine_version, 10, 0)) { # for the switch to -blockdev
> + my ($interface, $index) = PVE::QemuServer::Drive::parse_drive_interface($dev);
> + my $drive = { file => $volid, interface => $interface, index => $index };
> + my $blockdev =
> + PVE::QemuServer::Blockdev::generate_drive_blockdev($storecfg, $drive, {});
> + $live_restore_backing->{$dev}->{blockdev} = $blockdev;
> + } else {
> + $live_restore_backing->{$dev}->{blockdev} =
> + "driver=$format,node-name=drive-$dev-restore"
> . ",read-only=on"
> - . ",file.driver=file,file.filename=$path",
> - };
> + . ",file.driver=file,file.filename=$path";
> + }
>
> my $source_volid = $info->{'delete-after-finish'};
> push $sources_to_remove->@*, $source_volid if defined($source_volid);
> }
>
> - my $storecfg = PVE::Storage::config();
> eval {
>
> # make sure HA doesn't interrupt our restore by stopping the VM
> diff --git a/src/PVE/QemuServer/BlockJob.pm b/src/PVE/QemuServer/BlockJob.pm
> index 212d6a4f..1f242cca 100644
> --- a/src/PVE/QemuServer/BlockJob.pm
> +++ b/src/PVE/QemuServer/BlockJob.pm
> @@ -527,20 +527,24 @@ sub mirror {
> my ($source, $dest, $jobs, $completion, $options) = @_;
>
> # for the switch to -blockdev
> -
> - my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive});
> - qemu_drive_mirror(
> - $source->{vmid},
> - $drive_id,
> - $dest->{volid},
> - $dest->{vmid},
> - $dest->{'zero-initialized'},
> - $jobs,
> - $completion,
> - $options->{'guest-agent'},
> - $options->{bwlimit},
> - $source->{bitmap},
> - );
> + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($source->{vmid});
> + if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
> + blockdev_mirror($source, $dest, $jobs, $completion, $options);
> + } else {
> + my $drive_id = PVE::QemuServer::Drive::get_drive_id($source->{drive});
> + qemu_drive_mirror(
> + $source->{vmid},
> + $drive_id,
> + $dest->{volid},
> + $dest->{vmid},
> + $dest->{'zero-initialized'},
> + $jobs,
> + $completion,
> + $options->{'guest-agent'},
> + $options->{bwlimit},
> + $source->{bitmap},
> + );
> + }
> }
>
> 1;
> diff --git a/src/PVE/QemuServer/Blockdev.pm b/src/PVE/QemuServer/Blockdev.pm
> index 4aea1abd..b0b88ea3 100644
> --- a/src/PVE/QemuServer/Blockdev.pm
> +++ b/src/PVE/QemuServer/Blockdev.pm
> @@ -579,18 +579,24 @@ my sub blockdev_change_medium {
> sub change_medium {
> my ($storecfg, $vmid, $qdev_id, $drive) = @_;
>
> - # force eject if locked
> - mon_cmd($vmid, "eject", force => JSON::true, id => "$qdev_id");
> + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
> + # for the switch to -blockdev
> + if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
> + blockdev_change_medium($storecfg, $vmid, $qdev_id, $drive);
> + } else {
> + # force eject if locked
> + mon_cmd($vmid, "eject", force => JSON::true, id => "$qdev_id");
>
> - my ($path, $format) = PVE::QemuServer::Drive::get_path_and_format($storecfg, $drive);
> + my ($path, $format) = PVE::QemuServer::Drive::get_path_and_format($storecfg, $drive);
>
> - if ($path) { # no path for 'none'
> - mon_cmd(
> - $vmid, "blockdev-change-medium",
> - id => "$qdev_id",
> - filename => "$path",
> - format => "$format",
> - );
> + if ($path) { # no path for 'none'
> + mon_cmd(
> + $vmid, "blockdev-change-medium",
> + id => "$qdev_id",
> + filename => "$path",
> + format => "$format",
> + );
> + }
> }
> }
>
> @@ -620,28 +626,59 @@ sub set_io_throttle {
>
> return if !PVE::QemuServer::Helpers::vm_running_locally($vmid);
>
> - mon_cmd(
> - $vmid, "block_set_io_throttle",
> - device => $deviceid,
> - bps => int($bps),
> - bps_rd => int($bps_rd),
> - bps_wr => int($bps_wr),
> - iops => int($iops),
> - iops_rd => int($iops_rd),
> - iops_wr => int($iops_wr),
> - bps_max => int($bps_max),
> - bps_rd_max => int($bps_rd_max),
> - bps_wr_max => int($bps_wr_max),
> - iops_max => int($iops_max),
> - iops_rd_max => int($iops_rd_max),
> - iops_wr_max => int($iops_wr_max),
> - bps_max_length => int($bps_max_length),
> - bps_rd_max_length => int($bps_rd_max_length),
> - bps_wr_max_length => int($bps_wr_max_length),
> - iops_max_length => int($iops_max_length),
> - iops_rd_max_length => int($iops_rd_max_length),
> - iops_wr_max_length => int($iops_wr_max_length),
> - );
> + my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
> + # for the switch to -blockdev
> + if (PVE::QemuServer::Machine::is_machine_version_at_least($machine_type, 10, 0)) {
> + mon_cmd(
> + $vmid,
> + 'qom-set',
> + path => "throttle-$deviceid",
> + property => "limits",
> + value => {
> + 'bps-total' => int($bps),
> + 'bps-read' => int($bps_rd),
> + 'bps-write' => int($bps_wr),
> + 'iops-total' => int($iops),
> + 'iops-read' => int($iops_rd),
> + 'iops-write' => int($iops_wr),
> + 'bps-total-max' => int($bps_max),
> + 'bps-read-max' => int($bps_rd_max),
> + 'bps-write-max' => int($bps_wr_max),
> + 'iops-total-max' => int($iops_max),
> + 'iops-read-max' => int($iops_rd_max),
> + 'iops-write-max' => int($iops_wr_max),
> + 'bps-total-max-length' => int($bps_max_length),
> + 'bps-read-max-length' => int($bps_rd_max_length),
> + 'bps-write-max-length' => int($bps_wr_max_length),
> + 'iops-total-max-length' => int($iops_max_length),
> + 'iops-read-max-length' => int($iops_rd_max_length),
> + 'iops-write-max-length' => int($iops_wr_max_length),
> + },
> + );
> + } else {
> + mon_cmd(
> + $vmid, "block_set_io_throttle",
> + device => $deviceid,
> + bps => int($bps),
> + bps_rd => int($bps_rd),
> + bps_wr => int($bps_wr),
> + iops => int($iops),
> + iops_rd => int($iops_rd),
> + iops_wr => int($iops_wr),
> + bps_max => int($bps_max),
> + bps_rd_max => int($bps_rd_max),
> + bps_wr_max => int($bps_wr_max),
> + iops_max => int($iops_max),
> + iops_rd_max => int($iops_rd_max),
> + iops_wr_max => int($iops_wr_max),
> + bps_max_length => int($bps_max_length),
> + bps_rd_max_length => int($bps_rd_max_length),
> + bps_wr_max_length => int($bps_wr_max_length),
> + iops_max_length => int($iops_max_length),
> + iops_rd_max_length => int($iops_rd_max_length),
> + iops_wr_max_length => int($iops_wr_max_length),
> + );
> + }
> }
>
> 1;
> diff --git a/src/PVE/QemuServer/OVMF.pm b/src/PVE/QemuServer/OVMF.pm
> index dde81eb7..a7239614 100644
> --- a/src/PVE/QemuServer/OVMF.pm
> +++ b/src/PVE/QemuServer/OVMF.pm
> @@ -3,7 +3,7 @@ package PVE::QemuServer::OVMF;
> use strict;
> use warnings;
>
> -use JSON;
> +use JSON qw(to_json);
>
> use PVE::RESTEnvironment qw(log_warn);
> use PVE::Storage;
> @@ -210,10 +210,21 @@ sub print_ovmf_commandline {
> }
> push $cmd->@*, '-bios', get_ovmf_files($hw_info->{arch}, undef, undef, $amd_sev_type);
> } else {
> - my ($code_drive_str, $var_drive_str) =
> - print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $hw_info, $version_guard);
> - push $cmd->@*, '-drive', $code_drive_str;
> - push $cmd->@*, '-drive', $var_drive_str;
> + if ($version_guard->(10, 0, 0)) { # for the switch to -blockdev
> + my ($code_blockdev, $vars_blockdev, $throttle_group) =
> + generate_ovmf_blockdev($conf, $storecfg, $vmid, $hw_info);
> +
> + push $cmd->@*, '-object', to_json($throttle_group, { canonical => 1 });
> + push $cmd->@*, '-blockdev', to_json($code_blockdev, { canonical => 1 });
> + push $cmd->@*, '-blockdev', to_json($vars_blockdev, { canonical => 1 });
> + push $machine_flags->@*, "pflash0=$code_blockdev->{'node-name'}",
> + "pflash1=$vars_blockdev->{'node-name'}";
> + } else {
> + my ($code_drive_str, $var_drive_str) =
> + print_ovmf_drive_commandlines($conf, $storecfg, $vmid, $hw_info, $version_guard);
> + push $cmd->@*, '-drive', $code_drive_str;
> + push $cmd->@*, '-drive', $var_drive_str;
> + }
> }
>
> return ($cmd, $machine_flags);
More information about the pve-devel
mailing list