On June 27, 2025 5:57 pm, Fiona Ebner wrote: > Co-developed-by: Alexandre Derumier <alexandre.derum...@groupe-cyllene.com> > Signed-off-by: Fiona Ebner <f.eb...@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); _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel