use URI::Escape;
use UUID;
-use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
use PVE::DataCenterConfig;
use PVE::Exception qw(raise raise_param_exc);
use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
use PVE::Storage;
use PVE::SysFSTools;
use PVE::Systemd;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
+use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
use PVE::QMPClient;
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);
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
use PVE::QemuServer::USB qw(parse_usb_device);
my $have_sdn;
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 {
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.",
my $MAX_USB_DEVICES = 5;
my $MAX_NETS = 32;
-my $MAX_HOSTPCI_DEVICES = 16;
my $MAX_SERIAL_PORTS = 4;
my $MAX_PARALLEL_PORTS = 3;
my $MAX_NUMA = 8;
type => 'string',
description => $net_fmt_bridge_descr,
format_description => 'bridge',
+ pattern => '[-_.\w\d]+',
optional => 1,
},
queues => {
description => 'Whether this interface should be disconnected (like pulling the plug).',
optional => 1,
},
+ mtu => {
+ type => 'integer',
+ minimum => 1, maximum => 65520,
+ description => "Force MTU, for VirtIO only. Set to '1' to use the bridge MTU",
+ optional => 1,
+ },
};
my $netdesc = {
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
-my $hostpci_fmt = {
- host => {
- default_key => 1,
- type => 'string',
- pattern => qr/$PCIRE(;$PCIRE)*/,
- format_description => 'HOSTPCIID[;HOSTPCIID2...]',
- description => <<EODESCR,
-Host PCI device pass through. The PCI ID of a host's PCI device or a list
-of PCI virtual functions of the host. HOSTPCIID syntax is:
-
-'bus:dev.func' (hexadecimal numbers)
-
-You can us the 'lspci' command to list existing PCI devices.
-EODESCR
- },
- rombar => {
- type => 'boolean',
- description => "Specify whether or not the device's ROM will be visible in the guest's memory map.",
- optional => 1,
- default => 1,
- },
- romfile => {
- type => 'string',
- pattern => '[^,;]+',
- format_description => 'string',
- description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
- optional => 1,
- },
- pcie => {
- type => 'boolean',
- description => "Choose the PCI-express bus (needs the 'q35' machine model).",
- optional => 1,
- default => 0,
- },
- 'x-vga' => {
- type => 'boolean',
- description => "Enable vfio-vga device support.",
- optional => 1,
- default => 0,
- },
- 'mdev' => {
- type => 'string',
- format_description => 'string',
- pattern => '[^/\.:]+',
- optional => 1,
- description => <<EODESCR
-The type of mediated device to use.
-An instance of this type will be created on startup of the VM and
-will be cleaned up when the VM stops.
-EODESCR
- }
-};
-PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
-
-my $hostpcidesc = {
- optional => 1,
- type => 'string', format => 'pve-qm-hostpci',
- description => "Map host PCI devices into guest.",
- verbose_description => <<EODESCR,
-Map host PCI devices into guest.
-
-NOTE: This option allows direct access to host hardware. So it is no longer
-possible to migrate such machines - use with special care.
-
-CAUTION: Experimental! User reported problems with this option.
-EODESCR
-};
-PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
-
my $serialdesc = {
optional => 1,
type => 'string',
$confdesc->{"serial$i"} = $serialdesc;
}
-for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
- $confdesc->{"hostpci$i"} = $hostpcidesc;
+for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) {
+ $confdesc->{"hostpci$i"} = $PVE::QemuServer::PCI::hostpcidesc;
}
for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {
}
$tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
+ if (my $mtu = $net->{mtu}) {
+ if ($net->{model} eq 'virtio' && $net->{bridge}) {
+ my $bridge_mtu = PVE::Network::read_bridge_mtu($net->{bridge});
+ if ($mtu == 1) {
+ $mtu = $bridge_mtu;
+ } elsif ($mtu < 576) {
+ die "netdev $netid: MTU '$mtu' is smaller than the IP minimum MTU '576'\n";
+ } elsif ($mtu > $bridge_mtu) {
+ die "netdev $netid: MTU '$mtu' is bigger than the bridge MTU '$bridge_mtu'\n";
+ }
+ $tmpstr .= ",host_mtu=$mtu";
+ } else {
+ warn "WARN: netdev $netid: ignoring MTU '$mtu', not using VirtIO or no bridge configured.\n";
+ }
+ }
+
if ($use_old_bios_files) {
my $romfile;
if ($device eq 'virtio-net-pci') {
return $res;
}
-sub parse_hostpci {
- my ($value) = @_;
-
- return undef if !$value;
-
- my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
-
- my @idlist = split(/;/, $res->{host});
- delete $res->{host};
- foreach my $id (@idlist) {
- my $devs = PVE::SysFSTools::lspci($id);
- die "no PCI device found for '$id'\n" if !scalar(@$devs);
- push @{$res->{pciid}}, @$devs;
- }
- return $res;
-}
-
# netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>
sub parse_net {
my ($data) = @_;
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};
}
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);
}
# 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);
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};
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};
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};
};
}
+sub audio_devs {
+ my ($audio, $audiopciaddr, $machine_version) = @_;
+
+ my $devs = [];
+
+ my $id = $audio->{dev_id};
+ my $audiodev = "";
+ if (min_version($machine_version, 4, 2)) {
+ $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) = @_;
}
sub config_to_command {
- my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
+ my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu) = @_;
my $cmd = [];
my $globalFlags = [];
$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
}
}
- 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;
+ my ($path, $format);
if (my $efidisk = $conf->{efidisk0}) {
my $d = parse_drive('efidisk0', $efidisk);
my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
push @$devices, '-device', $kbd if defined($kbd);
}
- my $kvm_off = 0;
- my $gpu_passthrough;
-
- # host pci devices
- for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
- my $id = "hostpci$i";
- my $d = parse_hostpci($conf->{$id});
- next if !$d;
-
- if (my $pcie = $d->{pcie}) {
- die "q35 machine model is not enabled" if !$q35;
- # win7 wants to have the pcie devices directly on the pcie bus
- # instead of in the root port
- if ($winversion == 7) {
- $pciaddr = print_pcie_addr("${id}bus0");
- } else {
- # add more root ports if needed, 4 are present by default
- # by pve-q35 cfgs, rest added here on demand.
- if ($i > 3) {
- push @$devices, '-device', print_pcie_root_port($i);
- }
- $pciaddr = print_pcie_addr($id);
- }
- } else {
- $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type);
- }
-
- my $xvga = '';
- if ($d->{'x-vga'}) {
- $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
- $kvm_off = 1;
- $vga->{type} = 'none' if !defined($conf->{vga});
- $gpu_passthrough = 1;
- }
-
- my $pcidevices = $d->{pciid};
- my $multifunction = 1 if @$pcidevices > 1;
-
- my $sysfspath;
- if ($d->{mdev} && scalar(@$pcidevices) == 1) {
- my $pci_id = $pcidevices->[0]->{id};
- my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
- $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
- } elsif ($d->{mdev}) {
- warn "ignoring mediated device '$id' with multifunction device\n";
- }
-
- my $j=0;
- foreach my $pcidevice (@$pcidevices) {
- my $devicestr = "vfio-pci";
-
- if ($sysfspath) {
- $devicestr .= ",sysfsdev=$sysfspath";
- } else {
- $devicestr .= ",host=$pcidevice->{id}";
- }
-
- my $mf_addr = $multifunction ? ".$j" : '';
- $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";
-
- if ($j == 0) {
- $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
- $devicestr .= "$xvga";
- $devicestr .= ",multifunction=on" if $multifunction;
- $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
- }
-
- push @$devices, '-device', $devicestr;
- $j++;
- }
- }
+ # host pci device passthrough
+ my ($kvm_off, $gpu_passthrough, $legacy_igd) = PVE::QemuServer::PCI::print_hostpci_devices(
+ $vmid, $conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type);
# usb devices
my $usb_dev_features = {};
}
}
- 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, $machine_version);
+ push @$devices, @$audio_devs;
}
my $sockets = 1;
# time drift fix
my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
-
my $useLocaltime = $conf->{localtime};
if ($winversion >= 5) { # windows
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);
my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
if ($rng && &$version_guard(4, 1, 2)) {
+ check_rng_source($rng->{source});
+
my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
my $period = $rng->{period} // $rng_fmt->{period}->{default};
-
my $limiter_str = "";
if ($max_bytes) {
$limiter_str = ",max-bytes=$max_bytes,period=$period";
}
- # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
- die "cannot create VirtIO RNG device: source file '$rng->{source}' doesn't exist\n"
- if ! -e $rng->{source};
-
my $rng_addr = print_pci_addr("rng0", $bridges, $arch, $machine_type);
-
push @$devices, '-object', "rng-random,filename=$rng->{source},id=rng0";
push @$devices, '-device', "virtio-rng-pci,rng=rng0$limiter_str$rng_addr";
}
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 {
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)) {
}
}
- 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);
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);
});
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}) {
for my $k (sort {$b cmp $a} keys %$bridges) {
next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3
- $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
+
+ my $k_name = $k;
+ if ($k == 2 && $legacy_igd) {
+ $k_name = "$k-igd";
+ }
+ $pciaddr = print_pci_addr("pci.$k_name", undef, $arch, $machine_type);
+
my $devstr = "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr";
if ($q35) {
# add after -readconfig pve-q35.cfg
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);
return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
+sub check_rng_source {
+ my ($source) = @_;
+
+ # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
+ die "cannot create VirtIO RNG device: source file '$source' doesn't exist\n"
+ if ! -e $source;
+
+ my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
+ if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
+ # Needs to abort, otherwise QEMU crashes on first rng access.
+ # Note that rng_current cannot be changed to 'none' manually, so
+ # once the VM is past this point, it is no longer an issue.
+ die "Cannot start VM with passed-through RNG device: '/dev/hwrng'"
+ . " exists, but '$rng_current' is set to 'none'. Ensure that"
+ . " a compatible hardware-RNG is attached to the host.\n";
+ }
+}
+
sub spice_port {
my ($vmid) = @_;
my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
my %options = split(/[=,]/, $netdev);
+ if (defined(my $vhost = $options{vhost})) {
+ $options{vhost} = JSON::boolean(PVE::JSONSchema::parse_boolean($vhost));
+ }
+
+ if (defined(my $queues = $options{queues})) {
+ $options{queues} = $queues + 0;
+ }
+
mon_cmd($vmid, "netdev_add", %options);
return 1;
}
$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;
});
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,
vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive, $arch, $machine_type);
}
-# see vm_start_nolock for parameters
+# called in locked context by incoming migration
+sub vm_migrate_get_nbd_disks {
+ my ($storecfg, $conf, $replicated_volumes) = @_;
+
+ my $local_volumes = {};
+ PVE::QemuConfig->foreach_volume($conf, sub {
+ my ($ds, $drive) = @_;
+
+ return if drive_is_cdrom($drive);
+
+ my $volid = $drive->{file};
+
+ return if !$volid;
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ return if $scfg->{shared};
+
+ # 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 %$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;
+ }
+
+ # 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 (!$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);
+ $format = (grep {$fileFormat eq $_} @{$validFormats}) ? $fileFormat : $defFormat;
+ } else {
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ $format = qemu_img_format($scfg, $volname);
+ }
+
+ my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
+ my $newdrive = $drive;
+ $newdrive->{format} = $format;
+ $newdrive->{file} = $newvolid;
+ my $drivestr = print_drive($newdrive);
+ $nbd->{$opt}->{drivestr} = $drivestr;
+ $nbd->{$opt}->{volid} = $newvolid;
+ }
+
+ return $nbd;
+}
+
+# see vm_start_nolock for parameters, additionally:
+# migrate_opts:
+# 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);
- $params->{resume} = PVE::QemuConfig->has_lock($conf, 'suspended');
+ my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');
PVE::QemuConfig->check_lock($conf)
- if !($params->{skiplock} || $params->{resume});
+ if !($params->{skiplock} || $has_suspended_lock);
+
+ $params->{resume} = $has_suspended_lock || defined($conf->{vmstate});
die "VM $vmid already running\n" if check_running($vmid, undef, $migrate_opts->{migratedfrom});
- vm_start_nolock($storecfg, $vmid, $conf, $params, $migrate_opts);
+ 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};
+ }
+ }
+
+ return vm_start_nolock($storecfg, $vmid, $conf, $params, $migrate_opts);
});
}
# 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 => 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
# type => secure/insecure - tunnel over encrypted connection or plain-text
-# targetstorage = storageid/'1' - target storage for disks migrated over NBD
# nbd_proto_version => int, 0 for TCP, 1 for UNIX
# replicated_volumes = which volids should be re-used with bitmaps for nbd migration
sub vm_start_nolock {
my $migratedfrom = $migrate_opts->{migratedfrom};
my $migration_type = $migrate_opts->{type};
- my $targetstorage = $migrate_opts->{targetstorage};
+
+ my $res = {};
# clean up leftover reboot request files
eval { clear_reboot_request($vmid); };
# set environment variable useful inside network script
$ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
- my $local_volumes = {};
-
- if ($targetstorage) {
- foreach_drive($conf, sub {
- my ($ds, $drive) = @_;
-
- return if drive_is_cdrom($drive);
-
- my $volid = $drive->{file};
-
- return if !$volid;
-
- my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
-
- my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- return if $scfg->{shared};
- $local_volumes->{$ds} = [$volid, $storeid, $volname];
- });
-
- my $format = undef;
-
- foreach my $opt (sort keys %$local_volumes) {
-
- my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}};
- if ($migrate_opts->{replicated_volumes}->{$volid}) {
- # re-use existing, replicated volume with bitmap on source side
- $local_volumes->{$opt} = $conf->{${opt}};
- print "re-using replicated volume: $opt - $volid\n";
- 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;
- 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);
- $format = (grep {$fileFormat eq $_} @{$validFormats}) ? $fileFormat : $defFormat;
- } else {
- my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- $format = qemu_img_format($scfg, $volname);
- }
-
- my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
- my $newdrive = $drive;
- $newdrive->{format} = $format;
- $newdrive->{file} = $newvolid;
- my $drivestr = print_drive($newdrive);
- $local_volumes->{$opt} = $drivestr;
- #pass drive to conf for command line
- $conf->{$opt} = $drivestr;
- }
- }
-
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 {
}
# host pci devices
- for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
+ for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) {
my $d = parse_hostpci($conf->{"hostpci$i"});
next if !$d;
my $pcidevices = $d->{pciid};
}
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"); };
}
#start nbd server for storage migration
- if ($targetstorage) {
+ if (my $nbd = $migrate_opts->{nbd}) {
my $nbd_protocol_version = $migrate_opts->{nbd_proto_version} // 0;
my $migrate_storage_uri;
$migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}";
}
- foreach my $opt (sort keys %$local_volumes) {
- my $drivestr = $local_volumes->{$opt};
+ $res->{migrate_storage_uri} = $migrate_storage_uri;
+
+ foreach my $opt (sort keys %$nbd) {
+ 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;
}
}
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");
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 {
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
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);
}
return;
}
} else {
- if ($force) {
+ if (!check_running($vmid, $nocheck)) {
+ warn "Unexpected: VM shutdown command failed, but VM not running anymore..\n";
+ return;
+ }
+ if ($force) {
warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
kill 15, $pid;
} else {
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 $@;
sub restore_file_archive {
my ($archive, $vmid, $user, $opts) = @_;
- my $format = $opts->{format};
- my $comp;
-
- if ($archive =~ m/\.tgz$/ || $archive =~ m/\.tar\.gz$/) {
- $format = 'tar' if !$format;
- $comp = 'gzip';
- } elsif ($archive =~ m/\.tar$/) {
- $format = 'tar' if !$format;
- } elsif ($archive =~ m/.tar.lzo$/) {
- $format = 'tar' if !$format;
- $comp = 'lzop';
- } elsif ($archive =~ m/\.vma$/) {
- $format = 'vma' if !$format;
- } elsif ($archive =~ m/\.vma\.gz$/) {
- $format = 'vma' if !$format;
- $comp = 'gzip';
- } elsif ($archive =~ m/\.vma\.lzo$/) {
- $format = 'vma' if !$format;
- $comp = 'lzop';
- } else {
- $format = 'vma' if !$format; # default
- }
+ return restore_vma_archive($archive, $vmid, $user, $opts)
+ if $archive eq '-';
+
+ my $info = PVE::Storage::archive_info($archive);
+ my $format = $opts->{format} // $info->{format};
+ my $comp = $info->{compression};
# try to detect archive format
if ($format eq 'tar') {
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);
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,
};
my ($vmid, $conf, $volid_hash) = @_;
my $changes;
- my $prefix = "VM $vmid:";
+ my $prefix = "VM $vmid";
# used and unused disks
my $referenced = {};
return if drive_is_cdrom($drive);
return if !$volid_hash->{$volid};
- my ($updated, $old_size, $new_size) = PVE::QemuServer::Drive::update_disksize($drive, $volid_hash);
+ my ($updated, $msg) = PVE::QemuServer::Drive::update_disksize($drive, $volid_hash->{$volid}->{size});
if (defined($updated)) {
$changes = 1;
$conf->{$opt} = print_drive($updated);
- print "$prefix size of disk '$volid' ($opt) updated from $old_size to $new_size\n";
+ print "$prefix ($opt): $msg\n";
}
});
my $datastore = $scfg->{datastore};
my $username = $scfg->{username} // 'root@pam';
my $fingerprint = $scfg->{fingerprint};
+ my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
my $repo = "$username\@$server:$datastore";
+
+ # This is only used for `pbs-restore`!
my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
local $ENV{PBS_PASSWORD} = $password;
local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
my $path = PVE::Storage::path($storecfg, $volid);
+ # This is the ONLY user of the PBS_ env vars set on top of this function!
my $pbs_restore_cmd = [
'/usr/bin/pbs-restore',
'--repository', $repo,
'--verbose',
];
+ push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+ push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
+
if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
push @$pbs_restore_cmd, '--skip-zero';
}
}
if ($comp) {
- my $cmd;
- if ($comp eq 'gzip') {
- $cmd = ['zcat', $readfrom];
- } elsif ($comp eq 'lzop') {
- $cmd = ['lzop', '-d', '-c', $readfrom];
- } else {
- die "unknown compression method '$comp'\n";
- }
+ my $info = PVE::Storage::decompressor_info('vma', $comp);
+ my $cmd = $info->{decompressor};
+ push @$cmd, $readfrom;
$add_pipe->($cmd);
}
my $sidhash = {};
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
my $storecfg = PVE::Storage::config();
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
my $res = [];
foreach my $id (keys %$data) {
foreach my $item (@{$data->{$id}}) {
- next if $item->{format} !~ m/^vma\.(gz|lzo)$/;
+ next if $item->{format} !~ m/^vma\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})$/;
push @$res, $item->{volid} if defined($item->{volid});
}
}