[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