X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=PVE%2FQemuServer.pm;h=efacc459de55ba1dfd0c25647b416a50843f669d;hb=0b953b8e3488cc2f117d6188bdfe2e1837595e5a;hp=510a9958c302b51412fd615fea2e4b4f5b4829a5;hpb=13cfe3b7a7ba89b4aa645fef7d80d257cf36acf8;p=qemu-server.git diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 510a995..efacc45 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -44,7 +44,7 @@ use PVE::QemuConfig; use PVE::QemuServer::Helpers qw(min_version config_aware_timeout); use PVE::QemuServer::Cloudinit; use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options); -use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive foreach_drive foreach_volid); +use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive); use PVE::QemuServer::Machine; use PVE::QemuServer::Memory; use PVE::QemuServer::Monitor qw(mon_cmd); @@ -97,6 +97,28 @@ PVE::JSONSchema::register_standard_option('pve-qemu-machine', { optional => 1, }); + +sub map_storage { + my ($map, $source) = @_; + + return $source if !defined($map); + + return $map->{entries}->{$source} + if $map->{entries} && defined($map->{entries}->{$source}); + + return $map->{default} if $map->{default}; + + # identity (fallback) + return $source; +} + +PVE::JSONSchema::register_standard_option('pve-targetstorage', { + description => "Mapping from source to target storages. Providing only a single storage ID maps all source storages to that storage. Providing the special value '1' will map each source storage to itself.", + type => 'string', + format => 'storagepair-list', + optional => 1, +}); + #no warnings 'redefine'; sub cgroups_write { @@ -567,8 +589,15 @@ EODESCR optional => 1, }), runningmachine => get_standard_option('pve-qemu-machine', { - description => "Specifies the Qemu machine type of the running vm. This is used internally for snapshots.", + description => "Specifies the QEMU machine type of the running vm. This is used internally for snapshots.", }), + runningcpu => { + description => "Specifies the QEMU '-cpu' parameter of the running vm. This is used internally for snapshots.", + optional => 1, + type => 'string', + pattern => $PVE::QemuServer::CPUConfig::qemu_cmdline_cpu_re, + format_description => 'QEMU -cpu parameter' + }, machine => get_standard_option('pve-qemu-machine'), arch => { description => "Virtual processor architecture. Defaults to the host.", @@ -817,6 +846,7 @@ my $net_fmt = { type => 'string', description => $net_fmt_bridge_descr, format_description => 'bridge', + pattern => '[-_.\w\d]+', optional => 1, }, queues => { @@ -1948,7 +1978,8 @@ sub json_config_properties { my $prop = shift; foreach my $opt (keys %$confdesc) { - next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || $opt eq 'runningmachine'; + next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || + $opt eq 'runningmachine' || $opt eq 'runningcpu'; $prop->{$opt} = $confdesc->{$opt}; } @@ -2007,7 +2038,7 @@ sub destroy_vm { if ($conf->{template}) { # check if any base image is still used by a linked clone - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive); @@ -2021,7 +2052,7 @@ sub destroy_vm { } # only remove disks owned by this VM - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive, 1); @@ -2310,7 +2341,7 @@ sub check_local_resources { sub check_storage_availability { my ($storecfg, $conf, $node) = @_; - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; my $volid = $drive->{file}; @@ -2333,7 +2364,7 @@ sub shared_nodes { my $nodehash = { map { $_ => 1 } @$nodelist }; my $nodename = nodename(); - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; my $volid = $drive->{file}; @@ -2365,7 +2396,7 @@ sub check_local_storage_availability { my $nodelist = PVE::Cluster::get_nodelist(); my $nodehash = { map { $_ => {} } @$nodelist }; - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; my $volid = $drive->{file}; @@ -2720,6 +2751,29 @@ sub conf_has_audio { }; } +sub audio_devs { + my ($audio, $audiopciaddr) = @_; + + my $devs = []; + + my $id = $audio->{dev_id}; + my $audiodev = "audiodev=$audio->{backend_id}"; + + if ($audio->{dev} eq 'AC97') { + push @$devs, '-device', "AC97,id=${id}${audiopciaddr},$audiodev"; + } elsif ($audio->{dev} =~ /intel\-hda$/) { + push @$devs, '-device', "$audio->{dev},id=${id}${audiopciaddr}"; + push @$devs, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0,$audiodev"; + push @$devs, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1,$audiodev"; + } else { + die "unkown audio device '$audio->{dev}', implement me!"; + } + + push @$devs, '-audiodev', "$audio->{backend},id=$audio->{backend_id}"; + + return $devs; +} + sub vga_conf_has_spice { my ($vga) = @_; @@ -2911,7 +2965,7 @@ sub query_understood_cpu_flags { } sub config_to_command { - my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_; + my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu) = @_; my $cmd = []; my $globalFlags = []; @@ -2942,13 +2996,15 @@ sub config_to_command { $machine_version =~ m/(\d+)\.(\d+)/; my ($machine_major, $machine_minor) = ($1, $2); - die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n" - if !PVE::QemuServer::min_version($kvmver, $machine_major, $machine_minor); - if (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) { + if ($kvmver =~ m/^\d+\.\d+\.(\d+)/ && $1 >= 90) { + warn "warning: Installed QEMU version ($kvmver) is a release candidate, ignoring version checks\n"; + } elsif (!min_version($kvmver, $machine_major, $machine_minor)) { + die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n" + } elsif (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) { my $max_pve_version = PVE::QemuServer::Machine::get_pve_version($machine_version); die "Installed qemu-server (max feature level for $machine_major.$machine_minor is pve$max_pve_version)" - . " is too old to run machine type '$machine_type', please upgrade node '$nodename'\n"; + ." is too old to run machine type '$machine_type', please upgrade node '$nodename'\n"; } # if a specific +pve version is required for a feature, use $version_guard @@ -3023,14 +3079,12 @@ sub config_to_command { } } - my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch); if ($conf->{bios} && $conf->{bios} eq 'ovmf') { - die "uefi base image not found\n" if ! -f $ovmf_code; + my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch); + die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code; - my $path; - my $format; - if (my $efidisk = $conf->{efidisk0}) { - my $d = parse_drive('efidisk0', $efidisk); + my ($path, $format); + if (my $d = parse_drive('efidisk0', $conf->{efidisk0})) { my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1); $format = $d->{format}; if ($storeid) { @@ -3219,22 +3273,10 @@ sub config_to_command { } } - if (my $audio = conf_has_audio($conf)) { - + if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) { my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type); - - my $id = $audio->{dev_id}; - if ($audio->{dev} eq 'AC97') { - push @$devices, '-device', "AC97,id=${id}${audiopciaddr}"; - } elsif ($audio->{dev} =~ /intel\-hda$/) { - push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}"; - push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0"; - push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1"; - } else { - die "unkown audio device '$audio->{dev}', implement me!"; - } - - push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}"; + my $audio_devs = audio_devs($audio, $audiopciaddr); + push @$devices, @$audio_devs; } my $sockets = 1; @@ -3292,7 +3334,6 @@ sub config_to_command { # time drift fix my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf}; - my $useLocaltime = $conf->{localtime}; if ($winversion >= 5) { # windows @@ -3311,13 +3352,17 @@ sub config_to_command { push @$rtcFlags, 'driftfix=slew' if $tdf; - if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) { + if ($conf->{startdate} && $conf->{startdate} ne 'now') { push @$rtcFlags, "base=$conf->{startdate}"; } elsif ($useLocaltime) { push @$rtcFlags, 'base=localtime'; } - push @$cmd, get_cpu_options($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough); + if ($forcecpu) { + push @$cmd, '-cpu', $forcecpu; + } else { + push @$cmd, get_cpu_options($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough); + } PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd); @@ -3365,7 +3410,7 @@ sub config_to_command { if ($qxlnum) { if ($qxlnum > 1) { if ($winversion){ - for(my $i = 1; $i < $qxlnum; $i++){ + for (my $i = 1; $i < $qxlnum; $i++){ push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges); } } else { @@ -3428,7 +3473,7 @@ sub config_to_command { push @$devices, '-iscsi', "initiator-name=$initiator"; } - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; if (PVE::Storage::parse_volume_id($drive->{file}, 1)) { @@ -3452,11 +3497,11 @@ sub config_to_command { } } - if($drive->{interface} eq 'virtio'){ + if ($drive->{interface} eq 'virtio'){ push @$cmd, '-object', "iothread,id=iothread-$ds" if $drive->{iothread}; } - if ($drive->{interface} eq 'scsi') { + if ($drive->{interface} eq 'scsi') { my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive); @@ -3481,13 +3526,13 @@ sub config_to_command { push @$devices, '-device', "$scsihw_type,id=$controller_prefix$controller$pciaddr$iothread$queues" if !$scsicontroller->{$controller}; $scsicontroller->{$controller}=1; - } + } if ($drive->{interface} eq 'sata') { - my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS); - $pciaddr = print_pci_addr("ahci$controller", $bridges, $arch, $machine_type); - push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller}; - $ahcicontroller->{$controller}=1; + my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS); + $pciaddr = print_pci_addr("ahci$controller", $bridges, $arch, $machine_type); + push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller}; + $ahcicontroller->{$controller}=1; } my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive); @@ -3496,22 +3541,22 @@ sub config_to_command { }); for (my $i = 0; $i < $MAX_NETS; $i++) { - next if !$conf->{"net$i"}; - my $d = parse_net($conf->{"net$i"}); - next if !$d; + next if !$conf->{"net$i"}; + my $d = parse_net($conf->{"net$i"}); + next if !$d; - $use_virtio = 1 if $d->{model} eq 'virtio'; + $use_virtio = 1 if $d->{model} eq 'virtio'; - if ($bootindex_hash->{n}) { - $d->{bootindex} = $bootindex_hash->{n}; - $bootindex_hash->{n} += 1; - } + if ($bootindex_hash->{n}) { + $d->{bootindex} = $bootindex_hash->{n}; + $bootindex_hash->{n} += 1; + } - my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i"); - push @$devices, '-netdev', $netdevfull; + my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i"); + push @$devices, '-netdev', $netdevfull; - my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type); - push @$devices, '-device', $netdevicefull; + my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type); + push @$devices, '-device', $netdevicefull; } if ($conf->{ivshmem}) { @@ -3569,12 +3614,9 @@ sub config_to_command { push @$machineFlags, "type=${machine_type_min}"; push @$cmd, @$devices; - push @$cmd, '-rtc', join(',', @$rtcFlags) - if scalar(@$rtcFlags); - push @$cmd, '-machine', join(',', @$machineFlags) - if scalar(@$machineFlags); - push @$cmd, '-global', join(',', @$globalFlags) - if scalar(@$globalFlags); + push @$cmd, '-rtc', join(',', @$rtcFlags) if scalar(@$rtcFlags); + push @$cmd, '-machine', join(',', @$machineFlags) if scalar(@$machineFlags); + push @$cmd, '-global', join(',', @$globalFlags) if scalar(@$globalFlags); if (my $vmstate = $conf->{vmstate}) { my $statepath = PVE::Storage::path($storecfg, $vmstate); @@ -4217,7 +4259,7 @@ sub qemu_volume_snapshot_delete { $running = undef; my $conf = PVE::QemuConfig->load_config($vmid); - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; $running = 1 if $drive->{file} eq $volid; }); @@ -4255,6 +4297,59 @@ sub set_migration_caps { mon_cmd($vmid, "migrate-set-capabilities", capabilities => $cap_ref); } +sub foreach_volid { + my ($conf, $func, @param) = @_; + + my $volhash = {}; + + my $test_volid = sub { + my ($key, $drive, $snapname) = @_; + + my $volid = $drive->{file}; + return if !$volid; + + $volhash->{$volid}->{cdrom} //= 1; + $volhash->{$volid}->{cdrom} = 0 if !drive_is_cdrom($drive); + + my $replicate = $drive->{replicate} // 1; + $volhash->{$volid}->{replicate} //= 0; + $volhash->{$volid}->{replicate} = 1 if $replicate; + + $volhash->{$volid}->{shared} //= 0; + $volhash->{$volid}->{shared} = 1 if $drive->{shared}; + + $volhash->{$volid}->{referenced_in_config} //= 0; + $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname); + + $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1 + if defined($snapname); + + my $size = $drive->{size}; + $volhash->{$volid}->{size} //= $size if $size; + + $volhash->{$volid}->{is_vmstate} //= 0; + $volhash->{$volid}->{is_vmstate} = 1 if $key eq 'vmstate'; + + $volhash->{$volid}->{is_unused} //= 0; + $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/; + }; + + my $include_opts = { + extra_keys => ['vmstate'], + include_unused => 1, + }; + + PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $test_volid); + foreach my $snapname (keys %{$conf->{snapshots}}) { + my $snap = $conf->{snapshots}->{$snapname}; + PVE::QemuConfig->foreach_volume_full($snap, $include_opts, $test_volid, $snapname); + } + + foreach my $volid (keys %$volhash) { + &$func($volid, $volhash->{$volid}, @param); + } +} + my $fast_plug_option = { 'lock' => 1, 'name' => 1, @@ -4710,11 +4805,11 @@ sub vmconfig_update_disk { } # called in locked context by incoming migration -sub vm_migrate_alloc_nbd_disks { - my ($storecfg, $vmid, $conf, $targetstorage, $replicated_volumes) = @_; +sub vm_migrate_get_nbd_disks { + my ($storecfg, $conf, $replicated_volumes) = @_; my $local_volumes = {}; - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive); @@ -4727,27 +4822,36 @@ sub vm_migrate_alloc_nbd_disks { my $scfg = PVE::Storage::storage_config($storecfg, $storeid); return if $scfg->{shared}; - $local_volumes->{$ds} = [$volid, $storeid, $volname]; + + # replicated disks re-use existing state via bitmap + my $use_existing = $replicated_volumes->{$volid} ? 1 : 0; + $local_volumes->{$ds} = [$volid, $storeid, $volname, $drive, $use_existing]; }); + return $local_volumes; +} + +# called in locked context by incoming migration +sub vm_migrate_alloc_nbd_disks { + my ($storecfg, $vmid, $source_volumes, $storagemap) = @_; my $format = undef; my $nbd = {}; - foreach my $opt (sort keys %$local_volumes) { - my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}}; - if ($replicated_volumes->{$volid}) { - # re-use existing, replicated volume with bitmap on source side - $nbd->{$opt} = $conf->{${opt}}; - print "re-using replicated volume: $opt - $volid\n"; + foreach my $opt (sort keys %$source_volumes) { + my ($volid, $storeid, $volname, $drive, $use_existing) = @{$source_volumes->{$opt}}; + + if ($use_existing) { + $nbd->{$opt}->{drivestr} = print_drive($drive); + $nbd->{$opt}->{volid} = $volid; + $nbd->{$opt}->{replicated} = 1; next; } - my $drive = parse_drive($opt, $conf->{$opt}); # If a remote storage is specified and the format of the original # volume is not available there, fall back to the default format. # Otherwise use the same format as the original. - if ($targetstorage && $targetstorage ne "1") { - $storeid = $targetstorage; + if (!$storagemap->{identity}) { + $storeid = map_storage($storagemap, $storeid); my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid); my $scfg = PVE::Storage::storage_config($storecfg, $storeid); my $fileFormat = qemu_img_format($scfg, $volname); @@ -4762,9 +4866,8 @@ sub vm_migrate_alloc_nbd_disks { $newdrive->{format} = $format; $newdrive->{file} = $newvolid; my $drivestr = print_drive($newdrive); - $nbd->{$opt} = $drivestr; - #pass drive to conf for command line - $conf->{$opt} = $drivestr; + $nbd->{$opt}->{drivestr} = $drivestr; + $nbd->{$opt}->{volid} = $newvolid; } return $nbd; @@ -4772,11 +4875,11 @@ sub vm_migrate_alloc_nbd_disks { # see vm_start_nolock for parameters, additionally: # migrate_opts: -# targetstorage = storageid/'1' - target storage for disks migrated over NBD +# storagemap = parsed storage map for allocating NBD disks sub vm_start { my ($storecfg, $vmid, $params, $migrate_opts) = @_; - PVE::QemuConfig->lock_config($vmid, sub { + return PVE::QemuConfig->lock_config($vmid, sub { my $conf = PVE::QemuConfig->load_config($vmid, $migrate_opts->{migratedfrom}); die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf); @@ -4788,10 +4891,17 @@ sub vm_start { die "VM $vmid already running\n" if check_running($vmid, undef, $migrate_opts->{migratedfrom}); - $migrate_opts->{nbd} = vm_migrate_alloc_nbd_disks($storecfg, $vmid, $conf, $migrate_opts->{targetstorage}, $migrate_opts->{replicated_volumes}) - if $migrate_opts->{targetstorage}; + if (my $storagemap = $migrate_opts->{storagemap}) { + my $replicated = $migrate_opts->{replicated_volumes}; + my $disks = vm_migrate_get_nbd_disks($storecfg, $conf, $replicated); + $migrate_opts->{nbd} = vm_migrate_alloc_nbd_disks($storecfg, $vmid, $disks, $storagemap); + + foreach my $opt (keys %{$migrate_opts->{nbd}}) { + $conf->{$opt} = $migrate_opts->{nbd}->{$opt}->{drivestr}; + } + } - vm_start_nolock($storecfg, $vmid, $conf, $params, $migrate_opts); + return vm_start_nolock($storecfg, $vmid, $conf, $params, $migrate_opts); }); } @@ -4800,11 +4910,12 @@ sub vm_start { # statefile => 'tcp', 'unix' for migration or path/volid for RAM state # skiplock => 0/1, skip checking for config lock # forcemachine => to force Qemu machine (rollback/migration) +# forcecpu => a QEMU '-cpu' argument string to override get_cpu_options # timeout => in seconds # paused => start VM in paused state (backup) # resume => resume from hibernation # migrate_opts: -# nbd => newly allocated volumes for NBD exports (vm_migrate_alloc_nbd_disks) +# nbd => volumes for NBD exports (vm_migrate_alloc_nbd_disks) # migratedfrom => source node # spice_ticket => used for spice migration, passed via tunnel/stdin # network => CIDR of migration network @@ -4820,6 +4931,8 @@ sub vm_start_nolock { my $migratedfrom = $migrate_opts->{migratedfrom}; my $migration_type = $migrate_opts->{type}; + my $res = {}; + # clean up leftover reboot request files eval { clear_reboot_request($vmid); }; warn $@ if $@; @@ -4839,13 +4952,16 @@ sub vm_start_nolock { PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1); my $forcemachine = $params->{forcemachine}; + my $forcecpu = $params->{forcecpu}; if ($resume) { - # enforce machine type on suspended vm to ensure HW compatibility + # enforce machine and CPU type on suspended vm to ensure HW compatibility $forcemachine = $conf->{runningmachine}; + $forcecpu = $conf->{runningcpu}; print "Resuming suspended VM\n"; } - my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine); + my ($cmd, $vollist, $spice_port) = + config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu); my $migration_ip; my $get_migration_ip = sub { @@ -5028,6 +5144,7 @@ sub vm_start_nolock { } print "migration listens on $migrate_uri\n" if $migrate_uri; + $res->{migrate_uri} = $migrate_uri; if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix') { eval { mon_cmd($vmid, "cont"); }; @@ -5055,10 +5172,19 @@ sub vm_start_nolock { $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}"; } + $res->{migrate_storage_uri} = $migrate_storage_uri; + foreach my $opt (sort keys %$nbd) { - my $drivestr = $nbd->{$opt}; + my $drivestr = $nbd->{$opt}->{drivestr}; + my $volid = $nbd->{$opt}->{volid}; mon_cmd($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true ); - print "storage migration listens on $migrate_storage_uri:exportname=drive-$opt volume:$drivestr\n"; + my $nbd_uri = "$migrate_storage_uri:exportname=drive-$opt"; + print "storage migration listens on $nbd_uri volume:$drivestr\n"; + print "re-using replicated volume: $opt - $volid\n" + if $nbd->{$opt}->{replicated}; + + $res->{drives}->{$opt} = $nbd->{$opt}; + $res->{drives}->{$opt}->{nbd_uri} = $nbd_uri; } } @@ -5070,6 +5196,7 @@ sub vm_start_nolock { if ($spice_port) { print "spice listens on port $spice_port\n"; + $res->{spice_port} = $spice_port; if ($migrate_opts->{spice_ticket}) { mon_cmd($vmid, "set_password", protocol => 'spice', password => $migrate_opts->{spice_ticket}); mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30"); @@ -5098,11 +5225,13 @@ sub vm_start_nolock { PVE::Storage::deactivate_volumes($storecfg, [$vmstate]); PVE::Storage::vdisk_free($storecfg, $vmstate); } - delete $conf->@{qw(lock vmstate runningmachine)}; + delete $conf->@{qw(lock vmstate runningmachine runningcpu)}; PVE::QemuConfig->write_config($vmid, $conf); } PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start'); + + return $res; } sub vm_commandline { @@ -5110,13 +5239,15 @@ sub vm_commandline { my $conf = PVE::QemuConfig->load_config($vmid); my $forcemachine; + my $forcecpu; if ($snapname) { my $snapshot = $conf->{snapshots}->{$snapname}; die "snapshot '$snapname' does not exist\n" if !defined($snapshot); - # check for a 'runningmachine' in snapshot - $forcemachine = $snapshot->{runningmachine} if $snapshot->{runningmachine}; + # check for machine or CPU overrides in snapshot + $forcemachine = $snapshot->{runningmachine}; + $forcecpu = $snapshot->{runningcpu}; $snapshot->{digest} = $conf->{digest}; # keep file digest for API @@ -5125,7 +5256,8 @@ sub vm_commandline { my $defaults = load_defaults(); - my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine); + my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, + $forcemachine, $forcecpu); return PVE::Tools::cmd2string($cmd); } @@ -5399,7 +5531,7 @@ sub vm_suspend { mon_cmd($vmid, "savevm-end"); PVE::Storage::deactivate_volumes($storecfg, [$vmstate]); PVE::Storage::vdisk_free($storecfg, $vmstate); - delete $conf->@{qw(vmstate runningmachine)}; + delete $conf->@{qw(vmstate runningmachine runningcpu)}; PVE::QemuConfig->write_config($vmid, $conf); }; warn $@ if $@; @@ -5536,7 +5668,7 @@ sub restore_file_archive { my $restore_cleanup_oldconf = sub { my ($storecfg, $vmid, $oldconf, $virtdev_hash) = @_; - foreach_drive($oldconf, sub { + PVE::QemuConfig->foreach_volume($oldconf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive, 1); @@ -5613,12 +5745,13 @@ my $parse_backup_hints = sub { my $drive = parse_drive($virtdev, $2); if (drive_is_cloudinit($drive)) { my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file}); + $storeid = $options->{storage} if defined ($options->{storage}); my $scfg = PVE::Storage::storage_config($storecfg, $storeid); my $format = qemu_img_format($scfg, $volname); # has 'raw' fallback $virtdev_hash->{$virtdev} = { format => $format, - storeid => $options->{storage} // $storeid, + storeid => $storeid, size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE, is_cloudinit => 1, }; @@ -6406,7 +6539,7 @@ sub foreach_storage_used_by_vm { my $sidhash = {}; - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive); @@ -6457,7 +6590,7 @@ sub template_create { my $storecfg = PVE::Storage::config(); - foreach_drive($conf, sub { + PVE::QemuConfig->foreach_volume($conf, sub { my ($ds, $drive) = @_; return if drive_is_cdrom($drive);