use strict;
use warnings;
+
use POSIX;
use IO::Handle;
use IO::Select;
use PVE::QemuConfig;
use PVE::QMPClient;
use PVE::RPCEnvironment;
+use PVE::GuestHelpers;
use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
use PVE::QemuServer::Memory;
use PVE::QemuServer::USB qw(parse_usb_device);
use PVE::QemuServer::Cloudinit;
+use PVE::SysFSTools;
use PVE::Systemd;
use Time::HiRes qw(gettimeofday);
use File::Copy qw(copy);
optional => 1,
});
-PVE::JSONSchema::register_standard_option('pve-snapshot-name', {
- description => "The name of the snapshot.",
- type => 'string', format => 'pve-configid',
- maxLength => 40,
-});
-
PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
type => 'string',
enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
my $lock_dir = "/var/lock/qemu-server";
mkdir $lock_dir;
-my $pcisysfs = "/sys/bus/pci";
-
my $cpu_vendor_list = {
# Intel CPUs
486 => 'GenuineIntel',
optional => 1,
default => 0
},
+ 'hv-vendor-id' => {
+ type => 'string',
+ pattern => qr/[a-zA-Z0-9]{1,12}/,
+ format_description => 'vendor-id',
+ description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
+ optional => 1,
+ },
flags => {
description => "List of additional CPU flags separated by ';'."
. " Use '+FLAG' to enable, '-FLAG' to disable a flag."
default => 'std',
optional => 1,
default_key => 1,
- enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)],
+ enum => [qw(cirrus qxl qxl2 qxl3 qxl4 none serial0 serial1 serial2 serial3 std virtio vmware)],
},
memory => {
description => "Sets the VGA memory (in MiB). Has no effect with serial display.",
},
};
+my $ivshmem_fmt = {
+ size => {
+ type => 'integer',
+ minimum => 1,
+ description => "The size of the file in MB.",
+ },
+ name => {
+ type => 'string',
+ pattern => '[a-zA-Z0-9\-]+',
+ optional => 1,
+ format_description => 'string',
+ description => "The name of the file. Will be prefixed with 'pve-shm-'. Default is the VMID. Will be deleted when the VM is stopped.",
+ },
+};
+
my $confdesc = {
onboot => {
optional => 1,
optional => 1,
type => 'string',
description => "Lock/unlock the VM.",
- enum => [qw(migrate backup snapshot rollback)],
+ enum => [qw(backup clone create migrate rollback snapshot snapshot-delete suspending suspended)],
},
cpulimit => {
optional => 1,
default => "1 (autogenerated)",
optional => 1,
},
+ hookscript => {
+ type => 'string',
+ format => 'pve-volume-id',
+ optional => 1,
+ description => "Script that will be executed during various steps in the vms lifetime.",
+ },
+ ivshmem => {
+ type => 'string',
+ format => $ivshmem_fmt,
+ description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
+ optional => 1,
+ }
};
+my $cicustom_fmt = {
+ meta => {
+ type => 'string',
+ optional => 1,
+ description => 'Specify a custom file containing all meta data passed to the VM via cloud-init. This is provider specific meaning configdrive2 and nocloud differ.',
+ format => 'pve-volume-id',
+ format_description => 'volume',
+ },
+ network => {
+ type => 'string',
+ optional => 1,
+ description => 'Specify a custom file containing all network data passed to the VM via cloud-init.',
+ format => 'pve-volume-id',
+ format_description => 'volume',
+ },
+ user => {
+ type => 'string',
+ optional => 1,
+ description => 'Specify a custom file containing all user data passed to the VM via cloud-init.',
+ format => 'pve-volume-id',
+ format_description => 'volume',
+ },
+};
+PVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);
+
my $confdesc_cloudinit = {
citype => {
optional => 1,
type => 'string',
description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
},
+ cicustom => {
+ optional => 1,
+ type => 'string',
+ description => 'cloud-init: Specify custom files to replace the automatically generated ones at start.',
+ format => 'pve-qm-cicustom',
+ },
searchdomain => {
optional => 1,
type => 'string',
__EOD__
my $net_fmt = {
- macaddr => {
- type => 'string',
- pattern => qr/[0-9a-f]{2}(?::[0-9a-f]{2}){5}/i,
+ macaddr => get_standard_option('mac-addr', {
description => "MAC address. That address must be unique withing your network. This is automatically generated if not specified.",
- format_description => "XX:XX:XX:XX:XX:XX",
- optional => 1,
- },
+ }),
model => {
type => 'string',
description => "Network Card Model. The 'virtio' model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use 'e1000'.",
},
);
+my %wwn_fmt = (
+ wwn => {
+ type => 'string',
+ pattern => qr/^(0x)[0-9a-fA-F]{16}/,
+ format_description => 'wwn',
+ description => "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
+ optional => 1,
+ },
+);
+
my $add_throttle_desc = sub {
my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
my $d = {
%drivedesc_base,
%model_fmt,
%ssd_fmt,
+ %wwn_fmt,
};
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
%queues_fmt,
%scsiblock_fmt,
%ssd_fmt,
+ %wwn_fmt,
};
my $scsidesc = {
optional => 1,
my $sata_fmt = {
%drivedesc_base,
%ssd_fmt,
+ %wwn_fmt,
};
my $satadesc = {
optional => 1,
%queues_fmt,
%scsiblock_fmt,
%ssd_fmt,
+ %wwn_fmt,
};
my $efidisk_fmt = {
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-# NOTE: the match-groups of this regex are used in parse_hostpci
-my $PCIRE = qr/([a-f0-9]{2}:[a-f0-9]{2})(?:\.([a-f0-9]))?/;
+my $PCIRE = qr/[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
my $hostpci_fmt = {
host => {
default_key => 1,
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 $kernel_has_vhost_net = -c '/dev/vhost-net';
+sub kernel_has_vhost_net {
+ return -c '/dev/vhost-net';
+}
sub valid_drive_names {
# order is important - used to autoselect boot disk
if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
$device .= ",rotation_rate=1";
}
+ $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
} elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
my $maxdev = ($drive->{interface} eq 'sata') ? $MAX_SATA_DISKS : 2;
$device .= ",rotation_rate=1";
}
}
+ $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
} elsif ($drive->{interface} eq 'usb') {
die "implement me";
# -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0
my $vhostparam = '';
if (is_native($arch)) {
- $vhostparam = ',vhost=on' if $kernel_has_vhost_net && $net->{model} eq 'virtio';
+ $vhostparam = ',vhost=on' if kernel_has_vhost_net() && $net->{model} eq 'virtio';
}
my $vmname = $conf->{name} || "vm$vmid";
my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
my $type = $vga_map->{$vga->{type}};
- if ($type eq 'virtio-vga' && $arch eq 'aarch64') {
+ if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
$type = 'virtio-gpu';
}
my $vgamem_mb = $vga->{memory};
my @idlist = split(/;/, $res->{host});
delete $res->{host};
foreach my $id (@idlist) {
- if ($id =~ /^$PCIRE$/) {
- if (defined($2)) {
- push @{$res->{pciid}}, { id => $1, function => $2 };
- } else {
- my $pcidevices = lspci($1);
- $res->{pciid} = $pcidevices->{$1};
- }
- } else {
- # should have been caught by parse_property_string already
- die "failed to parse PCI id: $id\n";
+ if ($id =~ m/\./) { # full id 00:00.1
+ push @{$res->{pciid}}, {
+ id => $id,
+ };
+ } else { # partial id 00:00
+ $res->{pciid} = PVE::SysFSTools::lspci($id);
}
}
return $res;
}
}
-sub check_iommu_support{
- #fixme : need to check IOMMU support
- #http://www.linux-kvm.org/page/How_to_assign_devices_with_VT-d_in_KVM
-
- my $iommu=1;
- return $iommu;
-
-}
-
sub touch_config {
my ($vmid) = @_;
$loc_res = 1 if $conf->{hostusb}; # old syntax
$loc_res = 1 if $conf->{hostpci}; # old syntax
+ $loc_res = 1 if $conf->{ivshmem};
+
foreach my $k (keys %$conf) {
next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
# sockets are safe: they will recreated be on the target side post-migrate
type => 'number',
optional => 1,
},
+ lock => {
+ description => "The current config lock, if any.",
+ type => 'string',
+ optional => 1,
+ }
};
my $last_proc_pid_stat;
$d->{template} = PVE::QemuConfig->is_template($conf);
$d->{serial} = 1 if conf_has_serial($conf);
+ $d->{lock} = $conf->{lock} if $conf->{lock};
$res->{$vmid} = $d;
}
if ($arch eq 'aarch64') {
$cpu = 'cortex-a57';
}
+ my $hv_vendor_id;
if (my $cputype = $conf->{cpu}) {
my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
or die "Cannot parse cpu description: $cputype\n";
$cpu = $cpuconf->{cputype};
$kvm_off = 1 if $cpuconf->{hidden};
+ $hv_vendor_id = $cpuconf->{'hv-vendor-id'};
if (defined(my $flags = $cpuconf->{flags})) {
push @$cpuFlags, split(";", $flags);
push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
}
- add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
+ add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
push @$cmd, '-mon', "chardev=qmp,mode=control";
if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
- my $eventsocket = qmp_socket($vmid, 0, 'event');
- push @$cmd, '-chardev', "socket,id=qmp-event,path=$eventsocket,server,nowait";
+ push @$cmd, '-chardev', "socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5";
push @$cmd, '-mon', "chardev=qmp-event,mode=control";
}
$vga->{type} = 'qxl' if $qxlnum;
if (!$vga->{type}) {
- if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+ if ($arch eq 'aarch64') {
+ $vga->{type} = 'virtio';
+ } elsif (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
$vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
} else {
$vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
next if !$d;
my $pcie = $d->{pcie};
- if($pcie){
+ if ($pcie) {
die "q35 machine model is not enabled" if !$q35;
- $pciaddr = print_pcie_addr("hostpci$i");
- }else{
+ # 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");
+ } else {
+ $pciaddr = print_pcie_addr("hostpci$i");
+ }
+ } else {
$pciaddr = print_pci_addr("hostpci$i", $bridges, $arch, $machine_type);
}
if ($d->{'x-vga'}) {
$xvga = ',x-vga=on';
$kvm_off = 1;
- $vga->{type} = 'none';
+ $vga->{type} = 'none' if !defined($conf->{vga});
$gpu_passthrough = 1;
if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
}
my $pcidevices = $d->{pciid};
my $multifunction = 1 if @$pcidevices > 1;
+ my $sysfspath;
+ if ($d->{mdev} && scalar(@$pcidevices) == 1) {
+ my $id = $pcidevices->[0]->{id};
+ my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
+ $sysfspath = "/sys/bus/pci/devices/0000:$id/$uuid";
+ } elsif ($d->{mdev}) {
+ warn "ignoring mediated device with multifunction device\n";
+ }
my $j=0;
foreach my $pcidevice (@$pcidevices) {
$id .= ".$j" if $multifunction;
my $addr = $pciaddr;
$addr .= ".$j" if $multifunction;
- my $devicestr = "vfio-pci,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr";
+ my $devicestr = "vfio-pci";
+ if ($sysfspath) {
+ $devicestr .= ",sysfsdev=$sysfspath";
+ } else {
+ $devicestr .= ",host=$pcidevice->{id}";
+ }
+ $devicestr .= ",id=$id$addr";
if($j == 0){
$devicestr .= "$rombar$xvga";
push @$devices, '-device', $netdevicefull;
}
+ if ($conf->{ivshmem}) {
+ my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+
+ my $bus;
+ if ($q35) {
+ $bus = print_pcie_addr("ivshmem");
+ } else {
+ $bus = print_pci_addr("ivshmem", $bridges, $arch, $machine_type);
+ }
+
+ my $ivshmem_name = $ivshmem->{name} // $vmid;
+ my $path = '/dev/shm/pve-shm-' . $ivshmem_name;
+
+ push @$devices, '-device', "ivshmem-plain,memdev=ivshmem$bus,";
+ push @$devices, '-object', "memory-backend-file,id=ivshmem,share=on,mem-path=$path,size=$ivshmem->{size}M";
+ }
+
if (!$q35) {
# add pci bridges
if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
}
}
- # add custom args
- if ($conf->{args}) {
- my $aa = PVE::Tools::split_args($conf->{args});
- push @$cmd, @$aa;
- }
-
push @$cmd, @$devices;
push @$cmd, '-rtc', join(',', @$rtcFlags)
if scalar(@$rtcFlags);
push @$cmd, '-global', join(',', @$globalFlags)
if scalar(@$globalFlags);
+ if (my $vmstate = $conf->{vmstate}) {
+ my $statepath = PVE::Storage::path($storecfg, $vmstate);
+ PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+ push @$cmd, '-loadstate', $statepath;
+ }
+
+ # add custom args
+ if ($conf->{args}) {
+ my $aa = PVE::Tools::split_args($conf->{args});
+ push @$cmd, @$aa;
+ }
+
return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
} elsif ($deviceid =~ m/^(net)(\d+)$/) {
- return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
+ return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
- my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
- my $use_old_bios_files = undef;
- ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
+ my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+ my $use_old_bios_files = undef;
+ ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
- my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
- qemu_deviceadd($vmid, $netdevicefull);
- eval { qemu_deviceaddverify($vmid, $deviceid); };
+ my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
+ qemu_deviceadd($vmid, $netdevicefull);
+ eval {
+ qemu_deviceaddverify($vmid, $deviceid);
+ qemu_set_link_status($vmid, $deviceid, !$device->{link_down});
+ };
if (my $err = $@) {
eval { qemu_netdevdel($vmid, $deviceid); };
warn $@ if $@;
die $err;
- }
+ }
} elsif (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
sub qemu_iothread_del {
my($conf, $vmid, $deviceid) = @_;
- my $device = parse_drive($deviceid, $conf->{$deviceid});
+ my $confid = $deviceid;
+ if ($deviceid =~ m/^(?:virtioscsi|scsihw)(\d+)$/) {
+ $confid = 'scsi' . $1;
+ }
+ my $device = parse_drive($confid, $conf->{$confid});
if ($device->{iothread}) {
my $iothreads = vm_iothreads_list($vmid);
qemu_objectdel($vmid, "iothread-$deviceid") if $iothreads->{"iothread-$deviceid"};
'description' => 1,
'protection' => 1,
'vmstatestorage' => 1,
+ 'hookscript' => 1,
};
# hotplug changes in [PENDING]
die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
- PVE::QemuConfig->check_lock($conf) if !$skiplock;
+ my $is_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
+
+ PVE::QemuConfig->check_lock($conf)
+ if !($skiplock || $is_suspended);
die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
}
}
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);
+
+ if ($is_suspended) {
+ # enforce machine type on suspended vm to ensure HW compatibility
+ $forcemachine = $conf->{runningmachine};
+ print "Resuming suspended VM\n";
+ }
+
my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
my $migrate_port = 0;
next if !$d;
my $pcidevices = $d->{pciid};
foreach my $pcidevice (@$pcidevices) {
- my $pciid = $pcidevice->{id}.".".$pcidevice->{function};
+ my $pciid = $pcidevice->{id};
- my $info = pci_device_info("0000:$pciid");
- die "IOMMU not present\n" if !check_iommu_support();
+ my $info = PVE::SysFSTools::pci_device_info("0000:$pciid");
+ die "IOMMU not present\n" if !PVE::SysFSTools::check_iommu_support();
die "no pci device info for device '$pciid'\n" if !$info;
- die "can't unbind/bind pci group to vfio '$pciid'\n" if !pci_dev_group_bind_to_vfio($pciid);
- die "can't reset pci device '$pciid'\n" if $info->{has_fl_reset} and !pci_dev_reset($info);
+
+ if ($d->{mdev}) {
+ my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
+ PVE::SysFSTools::pci_create_mdev_device($pciid, $uuid, $d->{mdev});
+ } else {
+ die "can't unbind/bind pci group to vfio '$pciid'\n"
+ if !PVE::SysFSTools::pci_dev_group_bind_to_vfio($pciid);
+ die "can't reset pci device '$pciid'\n"
+ if $info->{has_fl_reset} and !PVE::SysFSTools::pci_dev_reset($info);
+ }
}
}
PVE::Storage::activate_volumes($storecfg, $vollist);
- if (!check_running($vmid, 1)) {
+ if (-d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
eval {
run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
outfunc => sub {}, errfunc => sub {});
my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
: $defaults->{cpuunits};
- my $start_timeout = $conf->{hugepages} ? 300 : 30;
+ my $start_timeout = ($conf->{hugepages} || $is_suspended) ? 300 : 30;
my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077);
my %properties = (
property => "guest-stats-polling-interval",
value => 2) if (!defined($conf->{balloon}) || $conf->{balloon});
+ if ($is_suspended && (my $vmstate = $conf->{vmstate})) {
+ print "Resumed VM, removing state\n";
+ delete $conf->@{qw(lock vmstate runningmachine)};
+ PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+ PVE::Storage::vdisk_free($storecfg, $vmstate);
+ PVE::QemuConfig->write_config($vmid, $conf);
+ }
+
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
});
}
}
sub vm_commandline {
- my ($storecfg, $vmid) = @_;
+ my ($storecfg, $vmid, $snapname) = @_;
my $conf = PVE::QemuConfig->load_config($vmid);
+ if ($snapname) {
+ my $snapshot = $conf->{snapshots}->{$snapname};
+ die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+ $snapshot->{digest} = $conf->{digest}; # keep file digest for API
+
+ $conf = $snapshot;
+ }
+
my $defaults = load_defaults();
my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
unlink "/var/run/qemu-server/${vmid}.$ext";
}
+ if ($conf->{ivshmem}) {
+ my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+ # just delete it for now, VMs which have this already open do not
+ # are affected, but new VMs will get a separated one. If this
+ # becomes an issue we either add some sort of ref-counting or just
+ # add a "don't delete on stop" flag to the ivshmem format.
+ unlink '/dev/shm/pve-shm-' . ($ivshmem->{name} // $vmid);
+ }
+
+ foreach my $key (keys %$conf) {
+ next if $key !~ m/^hostpci(\d+)$/;
+ my $hostpciindex = $1;
+ my $d = parse_hostpci($conf->{$key});
+ my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $hostpciindex);
+
+ foreach my $pci (@{$d->{pciid}}) {
+ my $pciid = $pci->{id};
+ PVE::SysFSTools::pci_cleanup_mdev_device($pciid, $uuid);
+ }
+ }
+
vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;
};
warn $@ if $@; # avoid errors - just warn
my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
$timeout = $opts->{down} if $opts->{down};
}
+ PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
}
$timeout = 60 if !defined($timeout);
}
sub vm_suspend {
- my ($vmid, $skiplock) = @_;
+ my ($vmid, $skiplock, $includestate, $statestorage) = @_;
+
+ my $conf;
+ my $path;
+ my $storecfg;
+ my $vmstate;
PVE::QemuConfig->lock_config($vmid, sub {
- my $conf = PVE::QemuConfig->load_config($vmid);
+ $conf = PVE::QemuConfig->load_config($vmid);
+ my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');
PVE::QemuConfig->check_lock($conf)
- if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
+ if !($skiplock || $is_backing_up);
- vm_mon_cmd($vmid, "stop");
+ die "cannot suspend to disk during backup\n"
+ if $is_backing_up && $includestate;
+
+ if ($includestate) {
+ $conf->{lock} = 'suspending';
+ my $date = strftime("%Y-%m-%d", localtime(time()));
+ $storecfg = PVE::Storage::config();
+ $vmstate = PVE::QemuConfig->__snapshot_save_vmstate($vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1);
+ $path = PVE::Storage::path($storecfg, $vmstate);
+ PVE::QemuConfig->write_config($vmid, $conf);
+ } else {
+ vm_mon_cmd($vmid, "stop");
+ }
});
+
+ if ($includestate) {
+ # save vm state
+ PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+
+ eval {
+ vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+ for(;;) {
+ my $state = vm_mon_cmd_nocheck($vmid, "query-savevm");
+ if (!$state->{status}) {
+ die "savevm not active\n";
+ } elsif ($state->{status} eq 'active') {
+ sleep(1);
+ next;
+ } elsif ($state->{status} eq 'completed') {
+ print "State saved, quitting\n";
+ last;
+ } elsif ($state->{status} eq 'failed' && $state->{error}) {
+ die "query-savevm failed with error '$state->{error}'\n"
+ } else {
+ die "query-savevm returned status '$state->{status}'\n";
+ }
+ }
+ };
+ my $err = $@;
+
+ PVE::QemuConfig->lock_config($vmid, sub {
+ $conf = PVE::QemuConfig->load_config($vmid);
+ if ($err) {
+ # cleanup, but leave suspending lock, to indicate something went wrong
+ eval {
+ vm_mon_cmd($vmid, "savevm-end");
+ PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+ PVE::Storage::vdisk_free($storecfg, $vmstate);
+ delete $conf->@{qw(vmstate runningmachine)};
+ PVE::QemuConfig->write_config($vmid, $conf);
+ };
+ warn $@ if $@;
+ die $err;
+ }
+
+ die "lock changed unexpectedly\n"
+ if !PVE::QemuConfig->has_lock($conf, 'suspending');
+
+ vm_qmp_command($vmid, { execute => "quit" });
+ $conf->{lock} = 'suspended';
+ PVE::QemuConfig->write_config($vmid, $conf);
+ });
+ }
}
sub vm_resume {
});
}
-# pci helpers
-
-sub file_write {
- my ($filename, $buf) = @_;
-
- my $fh = IO::File->new($filename, "w");
- return undef if !$fh;
-
- my $res = print $fh $buf;
-
- $fh->close();
-
- return $res;
-}
-
-sub pci_device_info {
- my ($name) = @_;
-
- my $res;
-
- return undef if $name !~ m/^([a-f0-9]{4}):([a-f0-9]{2}):([a-f0-9]{2})\.([a-f0-9])$/;
- my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
-
- my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
- return undef if !defined($irq) || $irq !~ m/^\d+$/;
-
- my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
- return undef if !defined($vendor) || $vendor !~ s/^0x//;
-
- my $product = file_read_firstline("$pcisysfs/devices/$name/device");
- return undef if !defined($product) || $product !~ s/^0x//;
-
- $res = {
- name => $name,
- vendor => $vendor,
- product => $product,
- domain => $domain,
- bus => $bus,
- slot => $slot,
- func => $func,
- irq => $irq,
- has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
- };
-
- return $res;
-}
-
-sub pci_dev_reset {
- my ($dev) = @_;
-
- my $name = $dev->{name};
-
- my $fn = "$pcisysfs/devices/$name/reset";
-
- return file_write($fn, "1");
-}
-
-sub pci_dev_bind_to_vfio {
- my ($dev) = @_;
-
- my $name = $dev->{name};
-
- my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
-
- if (!-d $vfio_basedir) {
- system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
- }
- die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
-
- my $testdir = "$vfio_basedir/$name";
- return 1 if -d $testdir;
-
- my $data = "$dev->{vendor} $dev->{product}";
- return undef if !file_write("$vfio_basedir/new_id", $data);
-
- my $fn = "$pcisysfs/devices/$name/driver/unbind";
- if (!file_write($fn, $name)) {
- return undef if -f $fn;
- }
-
- $fn = "$vfio_basedir/bind";
- if (! -d $testdir) {
- return undef if !file_write($fn, $name);
- }
-
- return -d $testdir;
-}
-
-sub pci_dev_group_bind_to_vfio {
- my ($pciid) = @_;
-
- my $vfio_basedir = "$pcisysfs/drivers/vfio-pci";
-
- if (!-d $vfio_basedir) {
- system("/sbin/modprobe vfio-pci >/dev/null 2>/dev/null");
- }
- die "Cannot find vfio-pci module!\n" if !-d $vfio_basedir;
-
- # get IOMMU group devices
- opendir(my $D, "$pcisysfs/devices/0000:$pciid/iommu_group/devices/") || die "Cannot open iommu_group: $!\n";
- my @devs = grep /^0000:/, readdir($D);
- closedir($D);
-
- foreach my $pciid (@devs) {
- $pciid =~ m/^([:\.\da-f]+)$/ or die "PCI ID $pciid not valid!\n";
-
- # pci bridges, switches or root ports are not supported
- # they have a pci_bus subdirectory so skip them
- next if (-e "$pcisysfs/devices/$pciid/pci_bus");
-
- my $info = pci_device_info($1);
- pci_dev_bind_to_vfio($info) || die "Cannot bind $pciid to vfio\n";
- }
-
- return 1;
-}
-
# vzdump restore implementaion
sub tar_archive_read_firstfile {
return if $line =~ m/^lock:/;
return if $line =~ m/^unused\d+:/;
return if $line =~ m/^parent:/;
- return if $line =~ m/^template:/; # restored VM is never a template
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
});
}
+sub convert_iscsi_path {
+ my ($path) = @_;
+
+ if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {
+ my $portal = $1;
+ my $target = $2;
+ my $lun = $3;
+
+ my $initiator_name = get_initiator_name();
+
+ return "file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,".
+ "file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw";
+ }
+
+ die "cannot convert iscsi path '$path', unkown format\n";
+}
+
sub qemu_img_convert {
my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized) = @_;
my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+ my $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+ my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+
my $cmd = [];
push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
- push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
- if ($is_zero_initialized) {
+
+ if ($src_is_iscsi) {
+ push @$cmd, '--image-opts';
+ $src_path = convert_iscsi_path($src_path);
+ } else {
+ push @$cmd, '-f', $src_format;
+ }
+
+ if ($dst_is_iscsi) {
+ push @$cmd, '--target-image-opts';
+ $dst_path = convert_iscsi_path($dst_path);
+ } else {
+ push @$cmd, '-O', $dst_format;
+ }
+
+ push @$cmd, $src_path;
+
+ if (!$dst_is_iscsi && $is_zero_initialized) {
push @$cmd, "zeroinit:$dst_path";
} else {
push @$cmd, $dst_path;
}
sub qemu_drive_mirror {
- my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
+ my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga, $bwlimit) = @_;
$jobs = {} if !$jobs;
my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target };
$opts->{format} = $format if $format;
- print "drive mirror is starting for drive-$drive\n";
-
- eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
+ if (defined($bwlimit)) {
+ $opts->{speed} = $bwlimit * 1024;
+ print "drive mirror is starting for drive-$drive with bandwidth limit: ${bwlimit} KB/s\n";
+ } else {
+ print "drive mirror is starting for drive-$drive\n";
+ }
+ # if a job already runs for this device we get an error, catch it for cleanup
+ eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); };
if (my $err = $@) {
eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
- die "mirroring error: $err";
+ warn "$@\n" if $@;
+ die "mirroring error: $err\n";
}
qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
sub clone_disk {
my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
- $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
+ $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga, $bwlimit) = @_;
my $newvolid;
my $name = undef;
if (drive_is_cloudinit($drive)) {
$name = "vm-$newvmid-cloudinit";
+ $snapname = undef;
# cloudinit only supports raw and qcow2 atm:
if ($dst_format eq 'qcow2') {
$name .= '.qcow2';
my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
if (!$running || $snapname) {
+ # TODO: handle bwlimits
qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
} else {
if $drive->{iothread};
}
- qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
+ qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga, $bwlimit);
}
}
return ($volid, $vars_size);
}
-sub lspci {
-
- my $devices = {};
-
- dir_glob_foreach("$pcisysfs/devices", '[a-f0-9]{4}:([a-f0-9]{2}:[a-f0-9]{2})\.([0-9])', sub {
- my (undef, $id, $function) = @_;
- my $res = { id => $id, function => $function};
- push @{$devices->{$id}}, $res;
- });
-
- # Entries should be sorted by functions.
- foreach my $id (keys %$devices) {
- my $dev = $devices->{$id};
- $devices->{$id} = [ sort { $a->{function} <=> $b->{function} } @$dev ];
- }
-
- return $devices;
-}
-
sub vm_iothreads_list {
my ($vmid) = @_;
}
sub add_hyperv_enlightenments {
- my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough) = @_;
+ my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
return if $winversion < 6;
return if $bios && $bios eq 'ovmf' && $winversion < 8;
- push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+ if ($gpu_passthrough || defined($hv_vendor_id)) {
+ $hv_vendor_id //= 'proxmox';
+ push @$cpuFlags , "hv_vendor_id=$hv_vendor_id";
+ }
if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
push @$cpuFlags , 'hv_spinlocks=0x1fff';