use PVE::QMPClient;
use PVE::RPCEnvironment;
use PVE::GuestHelpers;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
use PVE::QemuServer::Memory;
use PVE::QemuServer::USB qw(parse_usb_device);
use PVE::QemuServer::Cloudinit;
foldersharing => {
type => 'boolean',
optional => 1,
+ default => '0',
description => "Enable folder sharing via SPICE. Needs Spice-WebDAV daemon installed in the VM."
},
videostreaming => {
type => 'string',
enum => ['off', 'all', 'filter'],
+ default => 'off',
optional => 1,
description => "Enable video streaming. Uses compression for detected video streams."
},
my $MAX_USB_DEVICES = 5;
my $MAX_NETS = 32;
my $MAX_UNUSED_DISKS = 256;
-my $MAX_HOSTPCI_DEVICES = 4;
+my $MAX_HOSTPCI_DEVICES = 16;
my $MAX_SERIAL_PORTS = 4;
my $MAX_PARALLEL_PORTS = 3;
my $MAX_NUMA = 8;
usb3 => {
optional => 1,
type => 'boolean',
- description => "Specifies whether if given host option is a USB3 device or port (this does currently not work reliably with spice redirection and is then ignored).",
+ description => "Specifies whether if given host option is a USB3 device or port.",
default => 0,
},
};
push @loc_res, "ivshmem" if $conf->{ivshmem};
foreach my $k (keys %$conf) {
- next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
+ next if $k =~ m/^usb/ && ($conf->{$k} =~ m/^spice(?![^,])/);
# sockets are safe: they will recreated be on the target side post-migrate
next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');
push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
# host pci devices
for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
- my $d = parse_hostpci($conf->{"hostpci$i"});
+ my $id = "hostpci$i";
+ my $d = parse_hostpci($conf->{$id});
next if !$d;
- my $pcie = $d->{pcie};
- if ($pcie) {
+ 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("hostpci${i}bus0");
+ $pciaddr = print_pcie_addr("${id}bus0");
} else {
- $pciaddr = print_pcie_addr("hostpci$i");
+ # 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("hostpci$i", $bridges, $arch, $machine_type);
+ $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type);
}
- my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
- my $romfile = $d->{romfile};
-
my $xvga = '';
if ($d->{'x-vga'}) {
- $xvga = ',x-vga=on';
+ $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
$kvm_off = 1;
$vga->{type} = 'none' if !defined($conf->{vga});
$gpu_passthrough = 1;
-
- if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
- $xvga = "";
- }
}
+
my $pcidevices = $d->{pciid};
my $multifunction = 1 if @$pcidevices > 1;
+
my $sysfspath;
if ($d->{mdev} && scalar(@$pcidevices) == 1) {
- my $id = $pcidevices->[0]->{id};
+ my $pci_id = $pcidevices->[0]->{id};
my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
- $sysfspath = "/sys/bus/pci/devices/0000:$id/$uuid";
+ $sysfspath = "/sys/bus/pci/devices/0000:$pci_id/$uuid";
} elsif ($d->{mdev}) {
- warn "ignoring mediated device with multifunction device\n";
+ warn "ignoring mediated device '$id' with multifunction device\n";
}
my $j=0;
- foreach my $pcidevice (@$pcidevices) {
-
- my $id = "hostpci$i";
- $id .= ".$j" if $multifunction;
- my $addr = $pciaddr;
- $addr .= ".$j" if $multifunction;
+ foreach my $pcidevice (@$pcidevices) {
my $devicestr = "vfio-pci";
+
if ($sysfspath) {
$devicestr .= ",sysfsdev=$sysfspath";
} else {
$devicestr .= ",host=$pcidevice->{id}";
}
- $devicestr .= ",id=$id$addr";
- if($j == 0){
- $devicestr .= "$rombar$xvga";
+ 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/$romfile" if $romfile;
+ $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
}
push @$devices, '-device', $devicestr;
}
# usb devices
- my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES);
+ my $usb_dev_features = {};
+ $usb_dev_features->{spice_usb3} = 1 if qemu_machine_feature_enabled($machine_type, $kvmver, 4, 1);
+
+ my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features);
push @$devices, @usbdevices if @usbdevices;
# serial devices
for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {
die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
+ # clean up leftover reboot request files
+ eval { clear_reboot_request($vmid); };
+ warn $@ if $@;
+
if (!$statefile && scalar(keys %{$conf->{pending}})) {
vmconfig_apply_pending($vmid, $conf, $storecfg);
$conf = PVE::QemuConfig->load_config($vmid); # update/reload
warn $@ if $@; # avoid errors - just warn
}
-# Note: use $nockeck to skip tests if VM configuration file exists.
-# We need that when migration VMs to other nodes (files already moved)
-# Note: we set $keepActive in vzdump stop mode - volumes need to stay active
-sub vm_stop {
- my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive, $migratedfrom) = @_;
-
- $force = 1 if !defined($force) && !$shutdown;
-
- if ($migratedfrom){
- my $pid = check_running($vmid, $nocheck, $migratedfrom);
- kill 15, $pid if $pid;
- my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
- vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0);
- return;
- }
-
- PVE::QemuConfig->lock_config($vmid, sub {
+# call only in locked context
+sub _do_vm_stop {
+ my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive) = @_;
- my $pid = check_running($vmid, $nocheck);
- return if !$pid;
+ my $pid = check_running($vmid, $nocheck);
+ return if !$pid;
- my $conf;
- if (!$nocheck) {
- $conf = PVE::QemuConfig->load_config($vmid);
- PVE::QemuConfig->check_lock($conf) if !$skiplock;
- if (!defined($timeout) && $shutdown && $conf->{startup}) {
- my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
- $timeout = $opts->{down} if $opts->{down};
- }
- PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
+ my $conf;
+ if (!$nocheck) {
+ $conf = PVE::QemuConfig->load_config($vmid);
+ PVE::QemuConfig->check_lock($conf) if !$skiplock;
+ if (!defined($timeout) && $shutdown && $conf->{startup}) {
+ my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
+ $timeout = $opts->{down} if $opts->{down};
}
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
+ }
- eval {
- if ($shutdown) {
- if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
- vm_qmp_command($vmid, {
+ eval {
+ if ($shutdown) {
+ if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
+ vm_qmp_command($vmid, {
execute => "guest-shutdown",
arguments => { timeout => $timeout }
}, $nocheck);
- } else {
- vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
- }
- } else {
- vm_qmp_command($vmid, { execute => "quit" }, $nocheck);
- }
- };
- my $err = $@;
-
- if (!$err) {
- $timeout = 60 if !defined($timeout);
-
- my $count = 0;
- while (($count < $timeout) && check_running($vmid, $nocheck)) {
- $count++;
- sleep 1;
- }
-
- if ($count >= $timeout) {
- if ($force) {
- warn "VM still running - terminating now with SIGTERM\n";
- kill 15, $pid;
- } else {
- die "VM quit/powerdown failed - got timeout\n";
- }
} else {
- vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
- return;
+ vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
}
} else {
- if ($force) {
- warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
- kill 15, $pid;
- } else {
- die "VM quit/powerdown failed\n";
- }
+ vm_qmp_command($vmid, { execute => "quit" }, $nocheck);
}
+ };
+ my $err = $@;
- # wait again
- $timeout = 10;
+ if (!$err) {
+ $timeout = 60 if !defined($timeout);
my $count = 0;
while (($count < $timeout) && check_running($vmid, $nocheck)) {
}
if ($count >= $timeout) {
- warn "VM still running - terminating now with SIGKILL\n";
- kill 9, $pid;
- sleep 1;
+ if ($force) {
+ warn "VM still running - terminating now with SIGTERM\n";
+ kill 15, $pid;
+ } else {
+ die "VM quit/powerdown failed - got timeout\n";
+ }
+ } else {
+ vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
+ return;
}
+ } else {
+ if ($force) {
+ warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
+ kill 15, $pid;
+ } else {
+ die "VM quit/powerdown failed\n";
+ }
+ }
+
+ # wait again
+ $timeout = 10;
+
+ my $count = 0;
+ while (($count < $timeout) && check_running($vmid, $nocheck)) {
+ $count++;
+ sleep 1;
+ }
+
+ if ($count >= $timeout) {
+ warn "VM still running - terminating now with SIGKILL\n";
+ kill 9, $pid;
+ sleep 1;
+ }
+
+ vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
+}
+
+# Note: use $nocheck to skip tests if VM configuration file exists.
+# We need that when migration VMs to other nodes (files already moved)
+# Note: we set $keepActive in vzdump stop mode - volumes need to stay active
+sub vm_stop {
+ my ($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive, $migratedfrom) = @_;
+
+ $force = 1 if !defined($force) && !$shutdown;
+
+ if ($migratedfrom){
+ my $pid = check_running($vmid, $nocheck, $migratedfrom);
+ kill 15, $pid if $pid;
+ my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
+ vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 0);
+ return;
+ }
+
+ PVE::QemuConfig->lock_config($vmid, sub {
+ _do_vm_stop($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive);
+ });
+}
+
+sub vm_reboot {
+ my ($vmid, $timeout) = @_;
+
+ PVE::QemuConfig->lock_config($vmid, sub {
+
+ # only reboot if running, as qmeventd starts it again on a stop event
+ return if !check_running($vmid);
+
+ create_reboot_request($vmid);
+
+ my $storecfg = PVE::Storage::config();
+ _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);
- vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
});
}
vm_mon_cmd($vmid, 'nbd-server-stop');
}
+sub create_reboot_request {
+ my ($vmid) = @_;
+ open(my $fh, '>', "/run/qemu-server/$vmid.reboot")
+ or die "failed to create reboot trigger file: $!\n";
+ close($fh);
+}
+
+sub clear_reboot_request {
+ my ($vmid) = @_;
+ my $path = "/run/qemu-server/$vmid.reboot";
+ my $res = 0;
+
+ $res = unlink($path);
+ die "could not remove reboot request for $vmid: $!"
+ if !$res && $! != POSIX::ENOENT;
+
+ return $res;
+}
+
# bash completion helper
sub complete_backup_archives {