use strict;
use warnings;
+
use POSIX;
use IO::Handle;
use IO::Select;
use Fcntl;
use PVE::SafeSyslog;
use Storable qw(dclone);
+use MIME::Base64;
use PVE::Exception qw(raise raise_param_exc);
use PVE::Storage;
use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline dir_glob_foreach $IPV6RE);
use PVE::QemuConfig;
use PVE::QMPClient;
use PVE::RPCEnvironment;
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
+use PVE::GuestHelpers;
+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;
+use PVE::SysFSTools;
+use PVE::Systemd;
use Time::HiRes qw(gettimeofday);
use File::Copy qw(copy);
use URI::Escape;
-my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd';
-my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
+my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
+my $OVMF = {
+ x86_64 => [
+ "$EDK2_FW_BASE/OVMF_CODE.fd",
+ "$EDK2_FW_BASE/OVMF_VARS.fd"
+ ],
+ aarch64 => [
+ "$EDK2_FW_BASE/AAVMF_CODE.fd",
+ "$EDK2_FW_BASE/AAVMF_VARS.fd"
+ ],
+};
-my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
+my $qemu_snap_storage = { rbd => 1 };
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
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)],
optional => 1,
});
+PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
+ description => "Specifies the Qemu machine type.",
+ type => 'string',
+ pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?|virt(?:-\d+\.\d+)?)',
+ maxLength => 40,
+ optional => 1,
+});
+
#no warnings 'redefine';
sub cgroups_write {
my $lock_dir = "/var/lock/qemu-server";
mkdir $lock_dir;
-my $pcisysfs = "/sys/bus/pci";
-
my $cpu_vendor_list = {
# Intel CPUs
486 => 'GenuineIntel',
max => 'default',
};
-my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+my @supported_cpu_flags = (
+ 'pcid',
+ 'spec-ctrl',
+ 'ibpb',
+ 'ssbd',
+ 'virt-ssbd',
+ 'amd-ssbd',
+ 'amd-no-ssb',
+ 'pdpe1gb',
+ 'md-clear',
+ 'hv-tlbflush',
+ 'hv-evmcs',
+ 'aes'
+);
+my $cpu_flag = qr/[+-](@{[join('|', @supported_cpu_flags)]})/;
my $cpu_fmt = {
cputype => {
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."
- . " Currently supported flags: 'pcid', 'spec-ctrl'.",
+ . " Currently supported flags: @{[join(', ', @supported_cpu_flags)]}.",
format_description => '+FLAG[;-FLAG...]',
type => 'string',
pattern => qr/$cpu_flag(;$cpu_flag)*/,
};
PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);
+my $agent_fmt = {
+ enabled => {
+ description => "Enable/disable Qemu GuestAgent.",
+ type => 'boolean',
+ default => 0,
+ default_key => 1,
+ },
+ fstrim_cloned_disks => {
+ description => "Run fstrim after cloning/moving a disk.",
+ type => 'boolean',
+ optional => 1,
+ default => 0
+ },
+};
+
+my $vga_fmt = {
+ type => {
+ description => "Select the VGA type.",
+ type => 'string',
+ default => 'std',
+ optional => 1,
+ default_key => 1,
+ 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.",
+ type => 'integer',
+ optional => 1,
+ minimum => 4,
+ maximum => 512,
+ },
+};
+
+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 $audio_fmt = {
+ device => {
+ type => 'string',
+ enum => [qw(ich9-intel-hda intel-hda AC97)],
+ description => "Configure an audio device."
+ },
+ driver => {
+ type => 'string',
+ enum => ['spice'],
+ default => 'spice',
+ optional => 1,
+ description => "Driver backend for the audio device."
+ },
+};
+
+my $spice_enhancements_fmt = {
+ 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 $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,
shares => {
optional => 1,
type => 'integer',
- description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning",
+ description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning. Auto-ballooning is done by pvestatd.",
minimum => 0,
maximum => 50000,
default => 1000,
keyboard => {
optional => 1,
type => 'string',
- description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.".
+ description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.cfg' configuration file.".
"It should not be necessary to set it.",
enum => PVE::Tools::kvmkeymaplist(),
default => undef,
},
agent => {
optional => 1,
- type => 'boolean',
- description => "Enable/disable Qemu GuestAgent.",
- default => 0,
+ description => "Enable/disable Qemu GuestAgent and its properties.",
+ type => 'string',
+ format => $agent_fmt,
},
kvm => {
optional => 1,
},
vga => {
optional => 1,
- type => 'string',
- description => "Select the VGA type.",
- verbose_description => "Select the VGA type. If you want to use high resolution" .
- " modes (>= 1280x1024x16) then you should use the options " .
- "'std' or 'vmware'. Default is 'std' for win8/win7/w2k8, and " .
- "'cirrus' for other OS types. The 'qxl' option enables the SPICE " .
- "display sever. For win* OS you can select how many independent " .
- "displays you want, Linux guests can add displays them self. " .
- "You can also run without any graphic card, using a serial device" .
- " as terminal.",
- enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
+ type => 'string', format => $vga_fmt,
+ description => "Configure the VGA hardware.",
+ verbose_description => "Configure the VGA Hardware. If you want to use ".
+ "high resolution modes (>= 1280x1024x16) you may need to increase " .
+ "the vga memory option. Since QEMU 2.9 the default VGA display type " .
+ "is 'std' for all OS types besides some Windows versions (XP and " .
+ "older) which use 'cirrus'. The 'qxl' option enables the SPICE " .
+ "display server. For win* OS you can select how many independent " .
+ "displays you want, Linux guests can add displays them self.\n".
+ "You can also run without any graphic card, using a serial device as terminal.",
},
watchdog => {
optional => 1,
description => "Default storage for VM state volumes/files.",
optional => 1,
}),
- machine => {
- description => "Specific the Qemu machine type.",
- type => 'string',
- pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)',
- maxLength => 40,
+ runningmachine => get_standard_option('pve-qemu-machine', {
+ description => "Specifies the Qemu machine type of the running vm. This is used internally for snapshots.",
+ }),
+ machine => get_standard_option('pve-qemu-machine'),
+ arch => {
+ description => "Virtual processor architecture. Defaults to the host.",
optional => 1,
+ type => 'string',
+ enum => [qw(x86_64 aarch64)],
},
smbios1 => {
description => "Specify SMBIOS type 1 fields.",
type => 'string', format => 'pve-qm-smbios1',
- maxLength => 256,
+ maxLength => 512,
optional => 1,
},
protection => {
description => "Select BIOS implementation.",
default => 'seabios',
},
+ vmgenid => {
+ type => 'string',
+ pattern => '(?:[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}|[01])',
+ format_description => 'UUID',
+ description => "Set VM Generation ID. Use '1' to autogenerate on create or update, pass '0' to disable explicitly.",
+ verbose_description => "The VM generation ID (vmgenid) device exposes a".
+ " 128-bit integer value identifier to the guest OS. This allows to".
+ " notify the guest operating system when the virtual machine is".
+ " executed with a different configuration (e.g. snapshot execution".
+ " or creation from a template). The guest operating system notices".
+ " the change, and is then able to react as appropriate by marking".
+ " its copies of distributed databases as dirty, re-initializing its".
+ " random number generator, etc.\n".
+ "Note that auto-creation only works when done throug API/CLI create".
+ " or update methods, but not when manually editing the config file.",
+ 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,
+ },
+ audio0 => {
+ type => 'string',
+ format => $audio_fmt,
+ description => "Configure a audio device, useful in combination with QXL/Spice.",
+ optional => 1
+ },
+ spice_enhancements => {
+ type => 'string',
+ format => $spice_enhancements_fmt,
+ description => "Configure additional enhancements for SPICE.",
+ 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 => {
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',
my $MAX_SATA_DISKS = 6;
my $MAX_USB_DEVICES = 5;
my $MAX_NETS = 32;
-my $MAX_UNUSED_DISKS = 8;
-my $MAX_HOSTPCI_DEVICES = 4;
+my $MAX_UNUSED_DISKS = 256;
+my $MAX_HOSTPCI_DEVICES = 16;
my $MAX_SERIAL_PORTS = 4;
my $MAX_PARALLEL_PORTS = 3;
my $MAX_NUMA = 8;
__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 %ssd_fmt = (
+ ssd => {
+ type => 'boolean',
+ description => "Whether to expose this drive as an SSD, rather than a rotational hard disk.",
+ optional => 1,
+ },
+);
+
+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 = {
my $ide_fmt = {
%drivedesc_base,
%model_fmt,
+ %ssd_fmt,
+ %wwn_fmt,
};
PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
%iothread_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,
%model_fmt,
%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,
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
+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)
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);
verbose_description => <<EODESCR,
Map host PCI devices into guest.
-NOTE: This option allows direct access to host hardware. So it is no longer
+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.
my $kvm_api_version = 0;
sub kvm_version {
-
return $kvm_api_version if $kvm_api_version;
- my $fh = IO::File->new("</dev/kvm") ||
- return 0;
-
- if (my $v = $fh->ioctl(KVM_GET_API_VERSION(), 0)) {
- $kvm_api_version = $v;
- }
+ open my $fh, '<', '/dev/kvm'
+ or return undef;
- $fh->close();
+ # 0xae00 => KVM_GET_API_VERSION
+ $kvm_api_version = ioctl($fh, 0xae00, 0);
- return $kvm_api_version;
+ return $kvm_api_version;
}
-my $kvm_user_version;
+my $kvm_user_version = {};
+my $kvm_mtime = {};
sub kvm_user_version {
+ my ($binary) = @_;
+
+ $binary //= get_command_for_arch(get_host_arch()); # get the native arch by default
+ my $st = stat($binary);
- return $kvm_user_version if $kvm_user_version;
+ my $cachedmtime = $kvm_mtime->{$binary} // -1;
+ return $kvm_user_version->{$binary} if $kvm_user_version->{$binary} &&
+ $cachedmtime == $st->mtime;
- $kvm_user_version = 'unknown';
+ $kvm_user_version->{$binary} = 'unknown';
+ $kvm_mtime->{$binary} = $st->mtime;
my $code = sub {
my $line = shift;
if ($line =~ m/^QEMU( PC)? emulator version (\d+\.\d+(\.\d+)?)(\.\d+)?[,\s]/) {
- $kvm_user_version = $2;
+ $kvm_user_version->{$binary} = $2;
}
};
- eval { run_command("kvm -version", outfunc => $code); };
+ eval { run_command([$binary, '--version'], outfunc => $code); };
warn $@ if $@;
- return $kvm_user_version;
+ return $kvm_user_version->{$binary};
}
-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
}
sub print_tabletdevice_full {
- my ($conf) = @_;
+ my ($conf, $arch) = @_;
my $q35 = machine_type_is_q35($conf);
# we use uhci for old VMs because tablet driver was buggy in older qemu
- my $usbbus = $q35 ? "ehci" : "uhci";
+ my $usbbus;
+ if (machine_type_is_q35($conf) || $arch eq 'aarch64') {
+ $usbbus = 'ehci';
+ } else {
+ $usbbus = 'uhci';
+ }
return "usb-tablet,id=tablet,bus=$usbbus.0,port=1";
}
+sub print_keyboarddevice_full {
+ my ($conf, $arch, $machine) = @_;
+
+ return undef if $arch ne 'aarch64';
+
+ return "usb-kbd,id=keyboard,bus=ehci.0,port=2";
+}
+
sub print_drivedevice_full {
- my ($storecfg, $conf, $vmid, $drive, $bridges) = @_;
+ my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;
my $device = '';
my $maxdev = 0;
if ($drive->{interface} eq 'virtio') {
- my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}", $bridges);
+ my $pciaddr = print_pci_addr("$drive->{interface}$drive->{index}", $bridges, $arch, $machine_type);
$device = "virtio-blk-pci,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}$pciaddr";
$device .= ",iothread=iothread-$drive->{interface}$drive->{index}" if $drive->{iothread};
} elsif ($drive->{interface} eq 'scsi') {
$device = "scsi-$devicetype,bus=$controller_prefix$controller.0,channel=0,scsi-id=0,lun=$drive->{index},drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
}
- } elsif ($drive->{interface} eq 'ide'){
- $maxdev = 2;
+ 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;
my $controller = int($drive->{index} / $maxdev);
my $unit = $drive->{index} % $maxdev;
my $devicetype = ($drive->{media} && $drive->{media} eq 'cdrom') ? "cd" : "hd";
- $device = "ide-$devicetype,bus=ide.$controller,unit=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
- if ($devicetype eq 'hd' && (my $model = $drive->{model})) {
- $model = URI::Escape::uri_unescape($model);
- $device .= ",model=$model";
+ $device = "ide-$devicetype";
+ if ($drive->{interface} eq 'ide') {
+ $device .= ",bus=ide.$controller,unit=$unit";
+ } else {
+ $device .= ",bus=ahci$controller.$unit";
+ }
+ $device .= ",drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+
+ if ($devicetype eq 'hd') {
+ if (my $model = $drive->{model}) {
+ $model = URI::Escape::uri_unescape($model);
+ $device .= ",model=$model";
+ }
+ if ($drive->{ssd}) {
+ $device .= ",rotation_rate=1";
+ }
}
- } elsif ($drive->{interface} eq 'sata'){
- my $controller = int($drive->{index} / $MAX_SATA_DISKS);
- my $unit = $drive->{index} % $MAX_SATA_DISKS;
- $device = "ide-drive,bus=ahci$controller.$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+ $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
$device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex};
+ if (my $serial = $drive->{serial}) {
+ $serial = URI::Escape::uri_unescape($serial);
+ $device .= ",serial=$serial";
+ }
+
+
return $device;
}
my $path;
my $volid = $drive->{file};
my $format;
-
+
if (drive_is_cdrom($drive)) {
$path = get_iso_path($storecfg, $vmid, $volid);
} else {
}
}
- if (my $serial = $drive->{serial}) {
- $serial = URI::Escape::uri_unescape($serial);
- $opts .= ",serial=$serial";
- }
-
$opts .= ",format=$format" if $format && !$drive->{format};
my $cache_direct = 0;
}
sub print_netdevice_full {
- my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files) = @_;
+ my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files, $arch, $machine_type) = @_;
my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
$device = 'virtio-net-pci';
};
- my $pciaddr = print_pci_addr("$netid", $bridges);
+ my $pciaddr = print_pci_addr("$netid", $bridges, $arch, $machine_type);
my $tmpstr = "$device,mac=$net->{macaddr},netdev=$netid$pciaddr,id=$netid";
if ($net->{queues} && $net->{queues} > 1 && $net->{model} eq 'virtio'){
#Consider we have N queues, the number of vectors needed is 2*N + 2 (plus one config interrupt and control vq)
}
sub print_netdev_full {
- my ($vmid, $conf, $net, $netid, $hotplug) = @_;
+ my ($vmid, $conf, $arch, $net, $netid, $hotplug) = @_;
my $i = '';
if ($netid =~ m/^net(\d+)$/) {
if length($ifname) >= 16;
my $vhostparam = '';
- $vhostparam = ',vhost=on' if $kernel_has_vhost_net && $net->{model} eq 'virtio';
+ if (is_native($arch)) {
+ $vhostparam = ',vhost=on' if kernel_has_vhost_net() && $net->{model} eq 'virtio';
+ }
my $vmname = $conf->{name} || "vm$vmid";
return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
}
+my $vga_map = {
+ 'cirrus' => 'cirrus-vga',
+ 'std' => 'VGA',
+ 'vmware' => 'vmware-svga',
+ 'virtio' => 'virtio-vga',
+};
+
+sub print_vga_device {
+ my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
+
+ my $type = $vga_map->{$vga->{type}};
+ if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
+ $type = 'virtio-gpu';
+ }
+ my $vgamem_mb = $vga->{memory};
+ if ($qxlnum) {
+ $type = $id ? 'qxl' : 'qxl-vga';
+ }
+ die "no devicetype for $vga->{type}\n" if !$type;
+
+ my $memory = "";
+ if ($vgamem_mb) {
+ if ($vga->{type} eq 'virtio') {
+ my $bytes = PVE::Tools::convert_size($vgamem_mb, "mb" => "b");
+ $memory = ",max_hostmem=$bytes";
+ } elsif ($qxlnum) {
+ # from https://www.spice-space.org/multiple-monitors.html
+ $memory = ",vgamem_mb=$vga->{memory}";
+ my $ram = $vgamem_mb * 4;
+ my $vram = $vgamem_mb * 2;
+ $memory .= ",ram_size_mb=$ram,vram_size_mb=$vram";
+ } else {
+ $memory = ",vgamem_mb=$vga->{memory}";
+ }
+ } elsif ($qxlnum && $id) {
+ $memory = ",ram_size=67108864,vram_size=33554432";
+ }
+
+ my $q35 = machine_type_is_q35($conf);
+ my $vgaid = "vga" . ($id // '');
+ my $pciaddr;
+
+ if ($q35 && $vgaid eq 'vga') {
+ # the first display uses pcie.0 bus on q35 machines
+ $pciaddr = print_pcie_addr($vgaid, $bridges, $arch, $machine);
+ } else {
+ $pciaddr = print_pci_addr($vgaid, $bridges, $arch, $machine);
+ }
+
+ return "$type,id=${vgaid}${memory}${pciaddr}";
+}
+
sub drive_is_cloudinit {
my ($drive) = @_;
return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
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;
return $changes;
}
-# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str]
+# smbios: [manufacturer=str][,product=str][,version=str][,serial=str][,uuid=uuid][,sku=str][,family=str][,base64=bool]
my $smbios1_fmt = {
uuid => {
type => 'string',
},
version => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 version.",
optional => 1,
},
serial => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 serial number.",
optional => 1,
},
manufacturer => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 manufacturer.",
optional => 1,
},
product => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 product ID.",
optional => 1,
},
sku => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 SKU string.",
optional => 1,
},
family => {
type => 'string',
- pattern => '\S+',
- format_description => 'string',
+ pattern => '[A-Za-z0-9+\/]+={0,2}',
+ format_description => 'Base64 encoded string',
description => "Set SMBIOS1 family string.",
optional => 1,
},
+ base64 => {
+ type => 'boolean',
+ description => 'Flag to indicate that the SMBIOS values are base64 encoded',
+ optional => 1,
+ },
};
sub parse_smbios1 {
return $res;
}
+sub parse_guest_agent {
+ my ($value) = @_;
+
+ return {} if !defined($value->{agent});
+
+ my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $value->{agent}) };
+ warn $@ if $@;
+
+ # if the agent is disabled ignore the other potentially set properties
+ return {} if !$res->{enabled};
+ return $res;
+}
+
+sub parse_vga {
+ my ($value) = @_;
+
+ return {} if !$value;
+ my $res = eval { PVE::JSONSchema::parse_property_string($vga_fmt, $value) };
+ warn $@ if $@;
+ return $res;
+}
+
PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
sub verify_usb_device {
my ($value, $noerr) = @_;
my $prop = shift;
foreach my $opt (keys %$confdesc) {
- next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate';
+ next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || $opt eq 'runningmachine';
$prop->{$opt} = $confdesc->{$opt};
}
}
}
-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) = @_;
sub check_local_resources {
my ($conf, $noerr) = @_;
- my $loc_res = 0;
+ my @loc_res = ();
+
+ push @loc_res, "hostusb" if $conf->{hostusb}; # old syntax
+ push @loc_res, "hostpci" if $conf->{hostpci}; # old syntax
- $loc_res = 1 if $conf->{hostusb}; # old syntax
- $loc_res = 1 if $conf->{hostpci}; # old syntax
+ 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');
- $loc_res = 1 if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
+ push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
}
- die "VM uses local resources\n" if $loc_res && !$noerr;
+ die "VM uses local resources\n" if scalar @loc_res && !$noerr;
- return $loc_res;
+ return \@loc_res;
}
# check if used storages are available on all nodes (use by migrate)
return $nodehash
}
+sub check_local_storage_availability {
+ my ($conf, $storecfg) = @_;
+
+ my $nodelist = PVE::Cluster::get_nodelist();
+ my $nodehash = { map { $_ => {} } @$nodelist };
+
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+
+ my $volid = $drive->{file};
+ return if !$volid;
+
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
+ if ($storeid) {
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+
+ if ($scfg->{disable}) {
+ foreach my $node (keys %$nodehash) {
+ $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+ }
+ } elsif (my $avail = $scfg->{nodes}) {
+ foreach my $node (keys %$nodehash) {
+ if (!$avail->{$node}) {
+ $nodehash->{$node}->{unavailable_storages}->{$storeid} = 1;
+ }
+ }
+ }
+ }
+ });
+
+ foreach my $node (values %$nodehash) {
+ if (my $unavail = $node->{unavailable_storages}) {
+ $node->{unavailable_storages} = [ sort keys %$unavail ];
+ }
+ }
+
+ return $nodehash
+}
+
sub check_cmdline {
my ($pidfile, $pid) = @_;
my @param = split(/\0/, $line);
my $cmd = $param[0];
- return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m|qemu-system-x86_64$|);
+ return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-system-[^/]+$@);
for (my $i = 0; $i < scalar (@param); $i++) {
my $p = $param[$i];
return $drive->{size};
}
-my $last_proc_pid_stat;
-
-# get VM status information
-# This must be fast and should not block ($full == false)
-# We only query KVM using QMP if $full == true (this can be slow)
-sub vmstatus {
- my ($opt_vmid, $full) = @_;
-
- my $res = {};
-
- my $storecfg = PVE::Storage::config();
-
- my $list = vzlist();
- my $defaults = load_defaults();
-
- my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1);
-
- my $cpucount = $cpuinfo->{cpus} || 1;
-
- foreach my $vmid (keys %$list) {
- next if $opt_vmid && ($vmid ne $opt_vmid);
+our $vmstatus_return_properties = {
+ vmid => get_standard_option('pve-vmid'),
+ status => {
+ description => "Qemu process status.",
+ type => 'string',
+ enum => ['stopped', 'running'],
+ },
+ maxmem => {
+ description => "Maximum memory in bytes.",
+ type => 'integer',
+ optional => 1,
+ renderer => 'bytes',
+ },
+ maxdisk => {
+ description => "Root disk size in bytes.",
+ type => 'integer',
+ optional => 1,
+ renderer => 'bytes',
+ },
+ name => {
+ description => "VM name.",
+ type => 'string',
+ optional => 1,
+ },
+ qmpstatus => {
+ description => "Qemu QMP agent status.",
+ type => 'string',
+ optional => 1,
+ },
+ pid => {
+ description => "PID of running qemu process.",
+ type => 'integer',
+ optional => 1,
+ },
+ uptime => {
+ description => "Uptime.",
+ type => 'integer',
+ optional => 1,
+ renderer => 'duration',
+ },
+ cpus => {
+ description => "Maximum usable CPUs.",
+ type => 'number',
+ optional => 1,
+ },
+ lock => {
+ description => "The current config lock, if any.",
+ type => 'string',
+ optional => 1,
+ }
+};
+
+my $last_proc_pid_stat;
+
+# get VM status information
+# This must be fast and should not block ($full == false)
+# We only query KVM using QMP if $full == true (this can be slow)
+sub vmstatus {
+ my ($opt_vmid, $full) = @_;
+
+ my $res = {};
+
+ my $storecfg = PVE::Storage::config();
+
+ my $list = vzlist();
+ my $defaults = load_defaults();
+
+ my ($uptime) = PVE::ProcFSTools::read_proc_uptime(1);
+
+ my $cpucount = $cpuinfo->{cpus} || 1;
+
+ foreach my $vmid (keys %$list) {
+ next if $opt_vmid && ($vmid ne $opt_vmid);
my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
- my $d = {};
+ my $d = { vmid => $vmid };
$d->{pid} = $list->{$vmid}->{pid};
# fixme: better status?
$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;
}
my $volhash = {};
my $test_volid = sub {
- my ($volid, $is_cdrom, $replicate, $shared, $snapname) = @_;
+ my ($volid, $is_cdrom, $replicate, $shared, $snapname, $size) = @_;
return if !$volid;
$volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
if defined($snapname);
+ $volhash->{$volid}->{size} = $size if $size;
};
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
- $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef);
+ $test_volid->($drive->{file}, drive_is_cdrom($drive), $drive->{replicate} // 1, $drive->{shared}, undef, $drive->{size});
});
foreach my $snapname (keys %{$conf->{snapshots}}) {
return 0;
}
+sub conf_has_audio {
+ my ($conf, $id) = @_;
+
+ $id //= 0;
+ my $audio = $conf->{"audio$id"};
+ return undef if !defined($audio);
+
+ my $audioproperties = PVE::JSONSchema::parse_property_string($audio_fmt, $audio);
+ my $audiodriver = $audioproperties->{driver} // 'spice';
+
+ return {
+ dev => $audioproperties->{device},
+ dev_id => "audiodev$id",
+ backend => $audiodriver,
+ backend_id => "$audiodriver-backend${id}",
+ };
+}
+
sub vga_conf_has_spice {
my ($vga) = @_;
- return 0 if !$vga || $vga !~ m/^qxl([234])?$/;
+ my $vgaconf = parse_vga($vga);
+ my $vgatype = $vgaconf->{type};
+ return 0 if !$vgatype || $vgatype !~ m/^qxl([234])?$/;
return $1 || 1;
}
+my $host_arch; # FIXME: fix PVE::Tools::get_host_arch
+sub get_host_arch() {
+ $host_arch = (POSIX::uname())[4] if !$host_arch;
+ return $host_arch;
+}
+
+sub is_native($) {
+ my ($arch) = @_;
+ return get_host_arch() eq $arch;
+}
+
+my $default_machines = {
+ x86_64 => 'pc',
+ aarch64 => 'virt',
+};
+
+sub get_basic_machine_info {
+ my ($conf, $forcemachine) = @_;
+
+ my $arch = $conf->{arch} // get_host_arch();
+ my $machine = $forcemachine || $conf->{machine} || $default_machines->{$arch};
+ return ($arch, $machine);
+}
+
+sub get_ovmf_files($) {
+ my ($arch) = @_;
+
+ my $ovmf = $OVMF->{$arch}
+ or die "no OVMF images known for architecture '$arch'\n";
+
+ return @$ovmf;
+}
+
+my $Arch2Qemu = {
+ aarch64 => '/usr/bin/qemu-system-aarch64',
+ x86_64 => '/usr/bin/qemu-system-x86_64',
+};
+sub get_command_for_arch($) {
+ my ($arch) = @_;
+ return '/usr/bin/kvm' if is_native($arch);
+
+ my $cmd = $Arch2Qemu->{$arch}
+ or die "don't know how to emulate architecture '$arch'\n";
+ return $cmd;
+}
+
+sub get_cpu_options {
+ my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_;
+
+ my $cpuFlags = [];
+ my $ostype = $conf->{ostype};
+
+ my $cpu = $kvm ? "kvm64" : "qemu64";
+ 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 , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
+
+ push @$cpuFlags , '-x2apic'
+ if $conf->{ostype} && $conf->{ostype} eq 'solaris';
+
+ push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
+
+ push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') {
+
+ push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
+ push @$cpuFlags , '+kvm_pv_eoi' 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 @$cpuFlags, 'kvm=off' if $kvm_off;
+
+ if (my $cpu_vendor = $cpu_vendor_list->{$cpu}) {
+ push @$cpuFlags, "vendor=${cpu_vendor}"
+ if $cpu_vendor ne 'default';
+ } elsif ($arch ne 'aarch64') {
+ die "internal error"; # should not happen
+ }
+
+ $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
+
+ return ('-cpu', $cpu);
+}
+
sub config_to_command {
my ($storecfg, $vmid, $conf, $defaults, $forcemachine) = @_;
my $globalFlags = [];
my $machineFlags = [];
my $rtcFlags = [];
- my $cpuFlags = [];
my $devices = [];
my $pciaddr = '';
my $bridges = {};
- my $kvmver = kvm_user_version();
my $vernum = 0; # unknown
my $ostype = $conf->{ostype};
my $winversion = windows_version($ostype);
- my $kvm = $conf->{kvm} // 1;
+ my $kvm = $conf->{kvm};
- die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n" if (!$cpuinfo->{hvm} && $kvm);
+ my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+ my $kvm_binary = get_command_for_arch($arch);
+ my $kvmver = kvm_user_version($kvm_binary);
+ $kvm //= 1 if is_native($arch);
+
+ if ($kvm) {
+ die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n"
+ if !defined kvm_version();
+ }
if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
$vernum = $1*1000000+$2*1000;
my $q35 = machine_type_is_q35($conf);
my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
- my $machine_type = $forcemachine || $conf->{machine};
my $use_old_bios_files = undef;
($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
my $cpuunits = defined($conf->{cpuunits}) ?
$conf->{cpuunits} : $defaults->{cpuunits};
- push @$cmd, '/usr/bin/kvm';
+ push @$cmd, $kvm_binary;
push @$cmd, '-id', $vmid;
push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
push @$cmd, '-mon', "chardev=qmp,mode=control";
+ if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
+ push @$cmd, '-chardev', "socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5";
+ push @$cmd, '-mon', "chardev=qmp-event,mode=control";
+ }
push @$cmd, '-pidfile' , pidfile_name($vmid);
push @$cmd, '-daemonize';
if ($conf->{smbios1}) {
- push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
+ my $smbios_conf = parse_smbios1($conf->{smbios1});
+ if ($smbios_conf->{base64}) {
+ # Do not pass base64 flag to qemu
+ delete $smbios_conf->{base64};
+ my $smbios_string = "";
+ foreach my $key (keys %$smbios_conf) {
+ my $value;
+ if ($key eq "uuid") {
+ $value = $smbios_conf->{uuid}
+ } else {
+ $value = decode_base64($smbios_conf->{$key});
+ }
+ # qemu accepts any binary data, only commas need escaping by double comma
+ $value =~ s/,/,,/g;
+ $smbios_string .= "," . $key . "=" . $value if $value;
+ }
+ push @$cmd, '-smbios', "type=1" . $smbios_string;
+ } else {
+ push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
+ }
}
+ if ($conf->{vmgenid}) {
+ push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid};
+ }
+
+ 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;
+ die "uefi base image not found\n" if ! -f $ovmf_code;
my $path;
my $format;
} else {
warn "no efidisk configured! Using temporary efivars disk.\n";
$path = "/tmp/$vmid-ovmf.fd";
- PVE::Tools::file_copy($OVMF_VARS, $path, -s $OVMF_VARS);
+ PVE::Tools::file_copy($ovmf_vars, $path, -s $ovmf_vars);
$format = 'raw';
}
- push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$OVMF_CODE";
+ push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmf_code";
push @$cmd, '-drive', "if=pflash,unit=1,format=$format,id=drive-efidisk0,file=$path";
}
+ # load q35 config
+ if ($q35) {
+ # we use different pcie-port hardware for qemu >= 4.0 for passthrough
+ if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) {
+ push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';
+ } else {
+ push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
+ }
+ }
# add usb controllers
- my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $q35, $usbdesc->{format}, $MAX_USB_DEVICES);
+ my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_type, $usbdesc->{format}, $MAX_USB_DEVICES);
push @$devices, @usbcontrollers if @usbcontrollers;
- my $vga = $conf->{vga};
+ my $vga = parse_vga($conf->{vga});
- my $qxlnum = vga_conf_has_spice($vga);
- $vga = 'qxl' if $qxlnum;
+ my $qxlnum = vga_conf_has_spice($conf->{vga});
+ $vga->{type} = 'qxl' if $qxlnum;
- if (!$vga) {
- if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
- $vga = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
+ if (!$vga->{type}) {
+ 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 = ($winversion >= 6) ? 'std' : 'cirrus';
+ $vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
}
}
} else {
$tablet = $defaults->{tablet};
$tablet = 0 if $qxlnum; # disable for spice because it is not needed
- $tablet = 0 if $vga =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
+ $tablet = 0 if $vga->{type} =~ m/^serial\d+$/; # disable if we use serial terminal (no vga card)
}
- push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
+ if ($tablet) {
+ push @$devices, '-device', print_tabletdevice_full($conf, $arch) if $tablet;
+ my $kbd = print_keyboarddevice_full($conf, $arch);
+ 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 $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;
- $pciaddr = print_pcie_addr("hostpci$i");
- }else{
- $pciaddr = print_pci_addr("hostpci$i", $bridges);
+ # 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 $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 = 'none';
+ $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 $pci_id = $pcidevices->[0]->{id};
+ my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
+ $sysfspath = "/sys/bus/pci/devices/0000:$pci_id/$uuid";
+ } elsif ($d->{mdev}) {
+ warn "ignoring mediated device '$id' with multifunction device\n";
+ }
+
my $j=0;
- foreach my $pcidevice (@$pcidevices) {
+ foreach my $pcidevice (@$pcidevices) {
+ my $devicestr = "vfio-pci";
- my $id = "hostpci$i";
- $id .= ".$j" if $multifunction;
- my $addr = $pciaddr;
- $addr .= ".$j" if $multifunction;
- my $devicestr = "vfio-pci,host=$pcidevice->{id}.$pcidevice->{function},id=$id$addr";
+ if ($sysfspath) {
+ $devicestr .= ",sysfsdev=$sysfspath";
+ } else {
+ $devicestr .= ",host=$pcidevice->{id}";
+ }
- 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++) {
if ($path eq 'socket') {
my $socket = "/var/run/qemu-server/${vmid}.serial$i";
push @$devices, '-chardev', "socket,id=serial$i,path=$socket,server,nowait";
- push @$devices, '-device', "isa-serial,chardev=serial$i";
+ # On aarch64, serial0 is the UART device. Qemu only allows
+ # connecting UART devices via the '-serial' command line, as
+ # the device has a fixed slot on the hardware...
+ if ($arch eq 'aarch64' && $i == 0) {
+ push @$devices, '-serial', "chardev:serial$i";
+ } else {
+ push @$devices, '-device', "isa-serial,chardev=serial$i";
+ }
} else {
die "no such serial device\n" if ! -c $path;
push @$devices, '-chardev', "tty,id=serial$i,path=$path";
}
}
+ if (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 $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
- push @$cmd, '-vga', $vga if $vga && $vga !~ m/^serial\d+$/; # for kvm 77 and later
-
- if ($vga && $vga !~ m/^serial\d+$/ && $vga ne 'none'){
+ if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
+ push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, undef, $qxlnum, $bridges);
my $socket = vnc_socket($vmid);
- push @$cmd, '-vnc', "unix:$socket,x509,password";
+ push @$cmd, '-vnc', "unix:$socket,password";
} else {
+ push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
push @$cmd, '-nographic';
}
push @$machineFlags, "type=${machine_type}";
}
- if ($conf->{startdate}) {
+ if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) {
push @$rtcFlags, "base=$conf->{startdate}";
} elsif ($useLocaltime) {
push @$rtcFlags, 'base=localtime';
}
- my $cpu = $kvm ? "kvm64" : "qemu64";
- 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};
-
- if (defined(my $flags = $cpuconf->{flags})) {
- push @$cpuFlags, split(";", $flags);
- }
- }
-
- push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64';
-
- push @$cpuFlags , '-x2apic'
- if $conf->{ostype} && $conf->{ostype} eq 'solaris';
-
- push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
-
- push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
-
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
-
- push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
- push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
- }
-
- add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
-
- push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm;
-
- push @$cpuFlags, 'kvm=off' if $kvm_off;
-
- my $cpu_vendor = $cpu_vendor_list->{$cpu} ||
- die "internal error"; # should not happen
-
- push @$cpuFlags, "vendor=${cpu_vendor}"
- if $cpu_vendor ne 'default';
-
- $cpu .= "," . join(',', @$cpuFlags) if scalar(@$cpuFlags);
-
- push @$cmd, '-cpu', $cpu;
+ push @$cmd, get_cpu_options($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough);
PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd);
-
+
push @$cmd, '-S' if $conf->{freeze};
push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
- # enable sound
- #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
- #push @$cmd, '-soundhw', 'es1370';
- #push @$cmd, '-soundhw', $soundhw if $soundhw;
-
- if($conf->{agent}) {
+ if (parse_guest_agent($conf)->{enabled}) {
my $qgasocket = qmp_socket($vmid, 1);
- my $pciaddr = print_pci_addr("qga0", $bridges);
+ my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
if ($qxlnum > 1) {
if ($winversion){
for(my $i = 1; $i < $qxlnum; $i++){
- my $pciaddr = print_pci_addr("vga$i", $bridges);
- push @$cmd, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
+ push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, $i, $qxlnum, $bridges);
}
} else {
# assume other OS works like Linux
- push @$cmd, '-global', 'qxl-vga.ram_size=134217728';
- push @$cmd, '-global', 'qxl-vga.vram_size=67108864';
+ my ($ram, $vram) = ("134217728", "67108864");
+ if ($vga->{memory}) {
+ $ram = PVE::Tools::convert_size($qxlnum*4*$vga->{memory}, 'mb' => 'b');
+ $vram = PVE::Tools::convert_size($qxlnum*2*$vga->{memory}, 'mb' => 'b');
+ }
+ push @$cmd, '-global', "qxl-vga.ram_size=$ram";
+ push @$cmd, '-global', "qxl-vga.vram_size=$vram";
}
}
- my $pciaddr = print_pci_addr("spice", $bridges);
+ my $pciaddr = print_pci_addr("spice", $bridges, $arch, $machine_type);
my $nodename = PVE::INotify::nodename();
my $pfamily = PVE::Tools::get_host_address_family($nodename);
my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
$spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
- push @$devices, '-spice', "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
+ my $spice_enhancement = PVE::JSONSchema::parse_property_string($spice_enhancements_fmt, $conf->{spice_enhancements} // '');
+ if ($spice_enhancement->{foldersharing}) {
+ push @$devices, '-chardev', "spiceport,id=foldershare,name=org.spice-space.webdav.0";
+ push @$devices, '-device', "virtserialport,chardev=foldershare,name=org.spice-space.webdav.0";
+ }
+
+ my $spice_opts = "tls-port=${spice_port},addr=$localhost,tls-ciphers=HIGH,seamless-migration=on";
+ $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}" if $spice_enhancement->{videostreaming};
+ push @$devices, '-spice', "$spice_opts";
push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
push @$devices, '-device', "virtserialport,chardev=vdagent,name=com.redhat.spice.0";
+
}
# enable balloon by default, unless explicitly disabled
if (!defined($conf->{balloon}) || $conf->{balloon}) {
- $pciaddr = print_pci_addr("balloon0", $bridges);
+ $pciaddr = print_pci_addr("balloon0", $bridges, $arch, $machine_type);
push @$devices, '-device', "virtio-balloon-pci,id=balloon0$pciaddr";
}
if ($conf->{watchdog}) {
my $wdopts = parse_watchdog($conf->{watchdog});
- $pciaddr = print_pci_addr("watchdog", $bridges);
+ $pciaddr = print_pci_addr("watchdog", $bridges, $arch, $machine_type);
my $watchdog = $wdopts->{model} || 'i6300esb';
push @$devices, '-device', "$watchdog$pciaddr";
push @$devices, '-watchdog-action', $wdopts->{action} if $wdopts->{action};
my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive);
- $pciaddr = print_pci_addr("$controller_prefix$controller", $bridges);
+ $pciaddr = print_pci_addr("$controller_prefix$controller", $bridges, $arch, $machine_type);
my $scsihw_type = $scsihw =~ m/^virtio-scsi-single/ ? "virtio-scsi-pci" : $scsihw;
my $iothread = '';
my $queues = '';
if($conf->{scsihw} && $conf->{scsihw} eq "virtio-scsi-single" && $drive->{queues}){
$queues = ",num_queues=$drive->{queues}";
- }
+ }
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} / $MAX_SATA_DISKS);
- $pciaddr = print_pci_addr("ahci$controller", $bridges);
+ $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_full($storecfg, $vmid, $drive);
push @$devices, '-drive',$drive_cmd;
- push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
+ push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type);
});
for (my $i = 0; $i < $MAX_NETS; $i++) {
$bootindex_hash->{n} += 1;
}
- my $netdevfull = print_netdev_full($vmid,$conf,$d,"net$i");
+ 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);
+ 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}) {
+ 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)) {
$bridges->{3} = 1 if $scsihw =~ m/^virtio-scsi-single/;
while (my ($k, $v) = each %$bridges) {
- $pciaddr = print_pci_addr("pci.$k");
+ $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
unshift @$devices, '-device', "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr" if $k > 0;
}
}
- # 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;
}
}
sub qmp_socket {
- my ($vmid, $qga) = @_;
+ my ($vmid, $qga, $name) = @_;
my $sockettype = $qga ? 'qga' : 'qmp';
- return "${var_run_tmpdir}/$vmid.$sockettype";
+ my $ext = $name ? '-'.$name : '';
+ return "${var_run_tmpdir}/$vmid$ext.$sockettype";
}
sub pidfile_name {
my ($vmid) = @_;
my $res = vm_mon_cmd($vmid, 'query-pci');
+ my $devices_to_check = [];
my $devices = {};
foreach my $pcibus (@$res) {
- foreach my $device (@{$pcibus->{devices}}) {
- next if !$device->{'qdev_id'};
- if ($device->{'pci_bridge'}) {
- $devices->{$device->{'qdev_id'}} = 1;
- foreach my $bridge_device (@{$device->{'pci_bridge'}->{devices}}) {
- next if !$bridge_device->{'qdev_id'};
- $devices->{$bridge_device->{'qdev_id'}} = 1;
- $devices->{$device->{'qdev_id'}}++;
- }
- } else {
- $devices->{$device->{'qdev_id'}} = 1;
- }
+ push @$devices_to_check, @{$pcibus->{devices}},
+ }
+
+ while (@$devices_to_check) {
+ my $to_check = [];
+ for my $d (@$devices_to_check) {
+ $devices->{$d->{'qdev_id'}} = 1 if $d->{'qdev_id'};
+ next if !$d->{'pci_bridge'};
+
+ $devices->{$d->{'qdev_id'}} += scalar(@{$d->{'pci_bridge'}->{devices}});
+ push @$to_check, @{$d->{'pci_bridge'}->{devices}};
}
+ $devices_to_check = $to_check;
}
my $resblock = vm_mon_cmd($vmid, 'query-block');
}
sub vm_deviceplug {
- my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+ my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
my $q35 = machine_type_is_q35($conf);
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
- qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid); # add PCI bridge if we need it for the device
+ qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type); # add PCI bridge if we need it for the device
if ($deviceid eq 'tablet') {
- qemu_deviceadd($vmid, print_tabletdevice_full($conf));
+ qemu_deviceadd($vmid, print_tabletdevice_full($conf, $arch));
+
+ } elsif ($deviceid eq 'keyboard') {
+
+ qemu_deviceadd($vmid, print_keyboarddevice_full($conf, $arch));
} elsif ($deviceid =~ m/^usb(\d+)$/) {
qemu_iothread_add($vmid, $deviceid, $device);
qemu_driveadd($storecfg, $vmid, $device);
- my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
qemu_deviceadd($vmid, $devicefull);
eval { qemu_deviceaddverify($vmid, $deviceid); };
my $scsihw = defined($conf->{scsihw}) ? $conf->{scsihw} : "lsi";
- my $pciaddr = print_pci_addr($deviceid);
+ my $pciaddr = print_pci_addr($deviceid, undef, $arch, $machine_type);
my $scsihw_type = $scsihw eq 'virtio-scsi-single' ? "virtio-scsi-pci" : $scsihw;
my $devicefull = "$scsihw_type,id=$deviceid$pciaddr";
} elsif ($deviceid =~ m/^(scsi)(\d+)$/) {
- qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device);
+ qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device, $arch, $machine_type);
qemu_driveadd($storecfg, $vmid, $device);
- my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
eval { qemu_deviceadd($vmid, $devicefull); };
if (my $err = $@) {
eval { qemu_drivedel($vmid, $deviceid); };
} elsif ($deviceid =~ m/^(net)(\d+)$/) {
- return undef if !qemu_netdevadd($vmid, $conf, $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);
- 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+)$/) {
my $bridgeid = $2;
- my $pciaddr = print_pci_addr($deviceid);
+ my $pciaddr = print_pci_addr($deviceid, undef, $arch, $machine_type);
my $devicefull = "pci-bridge,id=pci.$bridgeid,chassis_nr=$bridgeid$pciaddr";
qemu_deviceadd($vmid, $devicefull);
die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
- if ($deviceid eq 'tablet') {
+ if ($deviceid eq 'tablet' || $deviceid eq 'keyboard') {
qemu_devicedel($vmid, $deviceid);
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"};
}
sub qemu_findorcreatescsihw {
- my ($storecfg, $conf, $vmid, $device) = @_;
+ my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $device);
my $devices_list = vm_devices_list($vmid);
if(!defined($devices_list->{$scsihwid})) {
- vm_deviceplug($storecfg, $conf, $vmid, $scsihwid, $device);
+ vm_deviceplug($storecfg, $conf, $vmid, $scsihwid, $device, $arch, $machine_type);
}
return 1;
}
sub qemu_add_pci_bridge {
- my ($storecfg, $conf, $vmid, $device) = @_;
+ my ($storecfg, $conf, $vmid, $device, $arch, $machine_type) = @_;
my $bridges = {};
my $bridgeid;
- print_pci_addr($device, $bridges);
+ print_pci_addr($device, $bridges, $arch, $machine_type);
while (my ($k, $v) = each %$bridges) {
$bridgeid = $k;
my $devices_list = vm_devices_list($vmid);
if (!defined($devices_list->{$bridge})) {
- vm_deviceplug($storecfg, $conf, $vmid, $bridge);
+ vm_deviceplug($storecfg, $conf, $vmid, $bridge, $arch, $machine_type);
}
return 1;
}
sub qemu_netdevadd {
- my ($vmid, $conf, $device, $deviceid) = @_;
+ my ($vmid, $conf, $arch, $device, $deviceid) = @_;
- my $netdev = print_netdev_full($vmid, $conf, $device, $deviceid, 1);
+ my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
my %options = split(/[=,]/, $netdev);
vm_mon_cmd($vmid, "netdev_add", %options);
}
sub qemu_usb_hotplug {
- my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+ my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
return if !$device;
my $devicelist = vm_devices_list($vmid);
if (!$devicelist->{xhci}) {
- my $pciaddr = print_pci_addr("xhci");
+ my $pciaddr = print_pci_addr("xhci", undef, $arch, $machine_type);
qemu_deviceadd($vmid, "nec-usb-xhci,id=xhci$pciaddr");
}
}
$d->{usb3} = $device->{usb3};
# add the new one
- vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d);
+ vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d, $arch, $machine_type);
}
sub qemu_cpu_hotplug {
my $running = check_running($vmid);
if ($running && do_snapshots_with_qemu($storecfg, $volid)){
- vm_mon_cmd($vmid, "snapshot-drive", device => $deviceid, name => $snap);
+ vm_mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
} else {
PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
}
my $running = check_running($vmid);
+ if($running) {
+
+ $running = undef;
+ my $conf = PVE::QemuConfig->load_config($vmid);
+ foreach_drive($conf, sub {
+ my ($ds, $drive) = @_;
+ $running = 1 if $drive->{file} eq $volid;
+ });
+ }
+
if ($running && do_snapshots_with_qemu($storecfg, $volid)){
- vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+ vm_mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
} else {
PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
}
'description' => 1,
'protection' => 1,
'vmstatestorage' => 1,
+ 'hookscript' => 1,
};
# hotplug changes in [PENDING]
my ($vmid, $conf, $storecfg, $selection, $errors) = @_;
my $defaults = load_defaults();
+ my ($arch, $machine_type) = get_basic_machine_info($conf, undef);
# commit values which do not have any impact on running VM first
# Note: those option cannot raise errors, we we do not care about
} elsif ($opt eq 'tablet') {
die "skip\n" if !$hotplug_features->{usb};
if ($defaults->{tablet}) {
- vm_deviceplug($storecfg, $conf, $vmid, $opt);
+ vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);
+ vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)
+ if $arch eq 'aarch64';
} else {
- vm_deviceunplug($vmid, $conf, $opt);
+ vm_deviceunplug($vmid, $conf, 'tablet');
+ vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';
}
} elsif ($opt =~ m/^usb\d+/) {
die "skip\n";
qemu_cpu_hotplug($vmid, $conf, undef);
} elsif ($opt eq 'balloon') {
# enable balloon device is not hotpluggable
- die "skip\n" if !defined($conf->{balloon}) || $conf->{balloon};
+ die "skip\n" if defined($conf->{balloon}) && $conf->{balloon} == 0;
+ # here we reset the ballooning value to memory
+ my $balloon = $conf->{memory} || $defaults->{memory};
+ vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
} elsif ($fast_plug_option->{$opt}) {
# do nothing
} elsif ($opt =~ m/^net(\d+)$/) {
} elsif ($opt eq 'tablet') {
die "skip\n" if !$hotplug_features->{usb};
if ($value == 1) {
- vm_deviceplug($storecfg, $conf, $vmid, $opt);
+ vm_deviceplug($storecfg, $conf, $vmid, 'tablet', $arch, $machine_type);
+ vm_deviceplug($storecfg, $conf, $vmid, 'keyboard', $arch, $machine_type)
+ if $arch eq 'aarch64';
} elsif ($value == 0) {
- vm_deviceunplug($vmid, $conf, $opt);
+ vm_deviceunplug($vmid, $conf, 'tablet');
+ vm_deviceunplug($vmid, $conf, 'keyboard') if $arch eq 'aarch64';
}
} elsif ($opt =~ m/^usb\d+$/) {
die "skip\n";
die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format}, $value) };
die "skip\n" if !$d;
- qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d);
+ qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d, $arch, $machine_type);
} elsif ($opt eq 'vcpus') {
die "skip\n" if !$hotplug_features->{cpu};
qemu_cpu_hotplug($vmid, $conf, $value);
} elsif ($opt =~ m/^net(\d+)$/) {
# some changes can be done without hotplug
vmconfig_update_net($storecfg, $conf, $hotplug_features->{network},
- $vmid, $opt, $value);
+ $vmid, $opt, $value, $arch, $machine_type);
} elsif (is_valid_drivename($opt)) {
# some changes can be done without hotplug
my $drive = parse_drive($opt, $value);
&$apply_pending_cloudinit($opt, $value);
}
vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
- $vmid, $opt, $value, 1);
+ $vmid, $opt, $value, 1, $arch, $machine_type);
} elsif ($opt =~ m/^memory$/) { #dimms
die "skip\n" if !$hotplug_features->{memory};
$value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
};
sub vmconfig_update_net {
- my ($storecfg, $conf, $hotplug, $vmid, $opt, $value) = @_;
+ my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;
my $newnet = parse_net($value);
}
if ($hotplug) {
- vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet);
+ vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet, $arch, $machine_type);
} else {
die "skip\n";
}
}
sub vmconfig_update_disk {
- my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $force) = @_;
+ my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $force, $arch, $machine_type) = @_;
# fixme: do we need force?
die "skip\n" if !$hotplug || $opt =~ m/(ide|sata)(\d+)/;
# hotplug new disks
PVE::Storage::activate_volumes($storecfg, [$drive->{file}]) if $drive->{file} !~ m|^/dev/.+|;
- vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive);
+ vm_deviceplug($storecfg, $conf, $vmid, $opt, $drive, $arch, $machine_type);
}
sub vm_start {
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);
+ # 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
}
}
+ 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)) {
- eval {
- run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
- outfunc => sub {}, errfunc => sub {});
- };
- }
+ eval {
+ run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
+ outfunc => sub {}, errfunc => sub {});
+ };
+ # Issues with the above 'stop' not being fully completed are extremely rare, a very low
+ # timeout should be more than enough here...
+ PVE::Systemd::wait_for_unit_removed("$vmid.scope", 5);
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 = (
}
$properties{timeout} = 10 if $statefile; # setting up the scope shoul be quick
+ my $run_qemu = sub {
+ PVE::Tools::run_fork sub {
+ PVE::Systemd::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
+ run_command($cmd, %run_params);
+ };
+ };
+
if ($conf->{hugepages}) {
my $code = sub {
PVE::QemuServer::Memory::hugepages_mount();
PVE::QemuServer::Memory::hugepages_allocate($hugepages_topology, $hugepages_host_topology);
- eval {
- PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
- run_command($cmd, %run_params);
- };
-
+ eval { $run_qemu->() };
if (my $err = $@) {
PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology);
die $err;
eval { PVE::QemuServer::Memory::hugepages_update_locked($code); };
} else {
- eval {
- PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
- run_command($cmd, %run_params);
- };
+ eval { $run_qemu->() };
}
if (my $err = $@) {
}
} else {
- if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
- vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
- if $conf->{balloon};
- }
+ vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+ if !$statefile && $conf->{balloon};
foreach my $opt (keys %$conf) {
next if $opt !~ m/^net\d+$/;
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');
});
}
my $res;
my $timeout;
- if ($cmd->{arguments} && $cmd->{arguments}->{timeout}) {
- $timeout = $cmd->{arguments}->{timeout};
- delete $cmd->{arguments}->{timeout};
+ if ($cmd->{arguments}) {
+ $timeout = delete $cmd->{arguments}->{timeout};
}
eval {
sub vm_human_monitor_command {
my ($vmid, $cmdline) = @_;
- my $res;
-
my $cmd = {
execute => 'human-monitor-command',
arguments => { 'command-line' => $cmdline},
}
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
}
-# Note: use $nockeck to skip tests if VM configuration file exists.
+# 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 $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, {
+ 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;
+ }
+ } 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 {
}
PVE::QemuConfig->lock_config($vmid, sub {
+ _do_vm_stop($storecfg, $vmid, $skiplock, $nocheck, $timeout, $shutdown, $force, $keepActive);
+ });
+}
- 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};
- }
- }
-
- $timeout = 60 if !defined($timeout);
-
- eval {
- if ($shutdown) {
- if (defined($conf) && $conf->{agent}) {
- vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck);
- } else {
- vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
- }
- } else {
- vm_qmp_command($vmid, { execute => "quit" }, $nocheck);
- }
- };
- my $err = $@;
-
- if (!$err) {
- my $count = 0;
- while (($count < $timeout) && check_running($vmid, $nocheck)) {
- $count++;
- sleep 1;
- }
+sub vm_reboot {
+ my ($vmid, $timeout) = @_;
- 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;
- }
- } else {
- if ($force) {
- warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
- kill 15, $pid;
- } else {
- die "VM quit/powerdown failed\n";
- }
- }
+ PVE::QemuConfig->lock_config($vmid, sub {
- # wait again
- $timeout = 10;
+ # only reboot if running, as qmeventd starts it again on a stop event
+ return if !check_running($vmid);
- my $count = 0;
- while (($count < $timeout) && check_running($vmid, $nocheck)) {
- $count++;
- sleep 1;
- }
+ create_reboot_request($vmid);
- if ($count >= $timeout) {
- warn "VM still running - terminating now with SIGKILL\n";
- kill 9, $pid;
- sleep 1;
- }
+ my $storecfg = PVE::Storage::config();
+ _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);
- vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, 1) if $conf;
});
}
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);
+
+ die "cannot suspend to disk during backup\n"
+ if $is_backing_up && $includestate;
- vm_mon_cmd($vmid, "stop");
+ 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 {
my ($vmid, $skiplock, $nocheck) = @_;
PVE::QemuConfig->lock_config($vmid, sub {
+ my $vm_mon_cmd = $nocheck ? \&vm_mon_cmd_nocheck : \&vm_mon_cmd;
+ my $res = $vm_mon_cmd->($vmid, 'query-status');
+ my $resume_cmd = 'cont';
+
+ if ($res->{status} && $res->{status} eq 'suspended') {
+ $resume_cmd = 'system_wakeup';
+ }
if (!$nocheck) {
PVE::QemuConfig->check_lock($conf)
if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
-
- vm_mon_cmd($vmid, "cont");
-
- } else {
- vm_mon_cmd_nocheck($vmid, "cont");
}
+
+ $vm_mon_cmd->($vmid, $resume_cmd);
});
}
my $conf = PVE::QemuConfig->load_config($vmid);
# there is no qmp command, so we use the human monitor command
- vm_human_monitor_command($vmid, "sendkey $key");
+ my $res = vm_human_monitor_command($vmid, "sendkey $key");
+ die $res if $res ne '';
});
}
});
}
-# 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*$/)) {
} else {
print $outfd $line;
}
+ } elsif (($line =~ m/^vmgenid: (.*)/)) {
+ my $vmgenid = $1;
+ if ($vmgenid ne '0') {
+ # always generate a new vmgenid if there was a valid one setup
+ $vmgenid = generate_uuid();
+ }
+ print $outfd "vmgenid: $vmgenid\n";
} elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {
my ($uuid, $uuid_str);
UUID::generate($uuid);
my ($vmid, $conf, $volid_hash) = @_;
my $changes;
+ my $prefix = "VM $vmid:";
# used and unused disks
my $referenced = {};
if ($new ne $conf->{$opt}) {
$changes = 1;
$conf->{$opt} = $new;
+ print "$prefix update disk '$opt' information.\n";
}
}
}
my $volid = $conf->{$opt};
my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
if ($referenced->{$volid} || ($path && $referencedpath->{$path})) {
+ print "$prefix remove entry '$opt', its volume '$volid' is in use.\n";
$changes = 1;
delete $conf->{$opt};
}
next if !$path; # just to be sure
next if $referencedpath->{$path};
$changes = 1;
- PVE::QemuConfig->add_unused_volume($conf, $volid);
+ my $key = PVE::QemuConfig->add_unused_volume($conf, $volid);
+ print "$prefix add unreferenced volume '$volid' as '$key' to config.\n";
$referencedpath->{$path} = 1; # avoid to add more than once (aliases)
}
}
sub rescan {
- my ($vmid, $nolock) = @_;
+ my ($vmid, $nolock, $dryrun) = @_;
my $cfg = PVE::Storage::config();
+ # FIXME: Remove once our RBD plugin can handle CT and VM on a single storage
+ # see: https://pve.proxmox.com/pipermail/pve-devel/2018-July/032900.html
+ foreach my $stor (keys %{$cfg->{ids}}) {
+ delete($cfg->{ids}->{$stor}) if ! $cfg->{ids}->{$stor}->{content}->{images};
+ }
+
+ print "rescan volumes...\n";
my $volid_hash = scan_volids($cfg, $vmid);
my $updatefn = sub {
my $changes = update_disksize($vmid, $conf, $vm_volids);
- PVE::QemuConfig->write_config($vmid, $conf) if $changes;
+ PVE::QemuConfig->write_config($vmid, $conf) if $changes && !$dryrun;
};
if (defined($vmid)) {
sub restore_vma_archive {
my ($archive, $vmid, $user, $opts, $comp) = @_;
- my $input = $archive eq '-' ? "<&STDIN" : undef;
my $readfrom = $archive;
- my $uncomp = '';
- if ($comp) {
+ my $cfg = PVE::Storage::config();
+ my $commands = [];
+ my $bwlimit = $opts->{bwlimit};
+
+ my $dbg_cmdstring = '';
+ my $add_pipe = sub {
+ my ($cmd) = @_;
+ push @$commands, $cmd;
+ $dbg_cmdstring .= ' | ' if length($dbg_cmdstring);
+ $dbg_cmdstring .= PVE::Tools::cmd2string($cmd);
$readfrom = '-';
- my $qarchive = PVE::Tools::shellquote($archive);
+ };
+
+ my $input = undef;
+ if ($archive eq '-') {
+ $input = '<&STDIN';
+ } else {
+ # If we use a backup from a PVE defined storage we also consider that
+ # storage's rate limit:
+ my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);
+ if (defined($volid)) {
+ my ($sid, undef) = PVE::Storage::parse_volume_id($volid);
+ my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);
+ if ($readlimit) {
+ print STDERR "applying read rate limit: $readlimit\n";
+ my $cstream = ['cstream', '-t', $readlimit*1024, '--', $readfrom];
+ $add_pipe->($cstream);
+ }
+ }
+ }
+
+ if ($comp) {
+ my $cmd;
if ($comp eq 'gzip') {
- $uncomp = "zcat $qarchive|";
+ $cmd = ['zcat', $readfrom];
} elsif ($comp eq 'lzop') {
- $uncomp = "lzop -d -c $qarchive|";
+ $cmd = ['lzop', '-d', '-c', $readfrom];
} else {
die "unknown compression method '$comp'\n";
}
-
+ $add_pipe->($cmd);
}
my $tmpdir = "/var/tmp/vzdumptmp$$";
open($fifofh, '>', $mapfifo) || die $!;
};
- my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+ $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
my $oldtimeout;
my $timeout = 5;
my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+ my %storage_limits;
+
my $print_devmap = sub {
my $virtdev_hash = {};
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
}
+ $storage_limits{$storeid} = $bwlimit;
+
$virtdev_hash->{$virtdev} = $devinfo->{$devname};
+ } elsif ($line =~ m/^((?:ide|sata|scsi)\d+):\s*(.*)\s*$/) {
+ my $virtdev = $1;
+ my $drive = parse_drive($virtdev, $2);
+ if (drive_is_cloudinit($drive)) {
+ my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+ my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+ my $format = qemu_img_format($scfg, $volname); # has 'raw' fallback
+
+ my $d = {
+ format => $format,
+ storeid => $opts->{storage} // $storeid,
+ size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE,
+ file => $drive->{file}, # to make drive_is_cloudinit check possible
+ name => "vm-$vmid-cloudinit",
+ is_cloudinit => 1,
+ };
+ $virtdev_hash->{$virtdev} = $d;
+ }
}
}
+ foreach my $key (keys %storage_limits) {
+ my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
+ next if !$limit;
+ print STDERR "rate limit for storage $key: $limit KiB/s\n";
+ $storage_limits{$key} = $limit * 1024;
+ }
+
foreach my $devname (keys %$devinfo) {
die "found no device mapping information for device '$devname'\n"
if !$devinfo->{$devname}->{virtdev};
}
- my $cfg = PVE::Storage::config();
-
# create empty/temp config
if ($oldconf) {
PVE::Tools::file_set_contents($conffile, "memory: 128\n");
foreach_drive($oldconf, sub {
my ($ds, $drive) = @_;
- return if drive_is_cdrom($drive);
+ return if !$drive->{is_cloudinit} && drive_is_cdrom($drive);
my $volid = $drive->{file};
-
return if !$volid || $volid =~ m|^/|;
my ($path, $owner) = PVE::Storage::path($cfg, $volid);
}
});
- # delete vmstate files
- # since after the restore we have no snapshots anymore
+ # delete vmstate files, after the restore we have no snapshots anymore
foreach my $snapname (keys %{$oldconf->{snapshots}}) {
my $snap = $oldconf->{snapshots}->{$snapname};
if ($snap->{vmstate}) {
foreach my $virtdev (sort keys %$virtdev_hash) {
my $d = $virtdev_hash->{$virtdev};
my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
- my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+ my $storeid = $d->{storeid};
+ my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+
+ my $map_opts = '';
+ if (my $limit = $storage_limits{$storeid}) {
+ $map_opts .= "throttling.bps=$limit:throttling.group=$storeid:";
+ }
# test if requested format is supported
- my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+ my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid);
my $supported = grep { $_ eq $d->{format} } @$validFormats;
$d->{format} = $defFormat if !$supported;
- my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
- $d->{format}, undef, $alloc_size);
+ my $name;
+ if ($d->{is_cloudinit}) {
+ $name = $d->{name};
+ $name .= ".$d->{format}" if $d->{format} ne 'raw';
+ }
+
+ my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid, $d->{format}, $name, $alloc_size);
print STDERR "new volume ID is '$volid'\n";
$d->{volid} = $volid;
- my $path = PVE::Storage::path($cfg, $volid);
- PVE::Storage::activate_volumes($cfg,[$volid]);
+ PVE::Storage::activate_volumes($cfg, [$volid]);
my $write_zeros = 1;
if (PVE::Storage::volume_has_feature($cfg, 'sparseinit', $volid)) {
$write_zeros = 0;
}
- print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+ if (!$d->{is_cloudinit}) {
+ my $path = PVE::Storage::path($cfg, $volid);
+
+ print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
- print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+ print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
+ }
$map->{$virtdev} = $volid;
}
}
};
- print "restore vma archive: $cmd\n";
- run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+ print "restore vma archive: $dbg_cmdstring\n";
+ run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
};
my $err = $@;
push @$vollist, $volid if $volid;
}
- my $cfg = PVE::Storage::config();
PVE::Storage::deactivate_volumes($cfg, $vollist);
unlink $mapfifo;
my ($storecfg, $volid) = @_;
my $storage_name = PVE::Storage::parse_volume_id($volid);
+ my $scfg = $storecfg->{ids}->{$storage_name};
- if ($qemu_snap_storage->{$storecfg->{ids}->{$storage_name}->{type}}
- && !$storecfg->{ids}->{$storage_name}->{krbd}){
+ if ($qemu_snap_storage->{$scfg->{type}} && !$scfg->{krbd}){
return 1;
}
}
sub qga_check_running {
- my ($vmid) = @_;
+ my ($vmid, $nowarn) = @_;
eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); };
if ($@) {
- warn "Qemu Guest Agent is not running - $@";
+ warn "Qemu Guest Agent is not running - $@" if !$nowarn;
return 0;
}
return 1;
});
}
+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, '-s', $snapname if($snapname && $src_format eq "qcow2");
- push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
- if ($is_zero_initialized) {
+ 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';
+
+ 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";
- # cloudinit only supports raw and qcow2 atm:
- if ($dst_format eq 'qcow2') {
- $name .= '.qcow2';
- } elsif ($dst_format ne 'raw') {
- die "clone: unhandled format for cloudinit image\n";
+ $snapname = undef;
+ # we only get here if it's supported by QEMU_FORMAT_RE, so just accept
+ if ($dst_format ne 'raw') {
+ $name .= ".$dst_format";
}
}
$newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
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);
}
}
my $current_major;
my $current_minor;
- if ($machine && $machine =~ m/^(pc(-i440fx|-q35)?-(\d+)\.(\d+))/) {
+ if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) {
$current_major = $3;
$current_minor = $4;
$current_minor = $2;
}
- return 1 if $current_major >= $version_major && $current_minor >= $version_minor;
-
-
+ return 1 if $current_major > $version_major ||
+ ($current_major == $version_major &&
+ $current_minor >= $version_minor);
}
sub qemu_machine_pxe {
- my ($vmid, $conf, $machine) = @_;
+ my ($vmid, $conf) = @_;
- $machine = PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine;
+ my $machine = PVE::QemuServer::get_current_qemu_machine($vmid);
- foreach my $opt (keys %$conf) {
- next if $opt !~ m/^net(\d+)$/;
- my $net = PVE::QemuServer::parse_net($conf->{$opt});
- next if !$net;
- my $romfile = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, 'qom-get', path => $opt, property => 'romfile');
- return $machine.".pxe" if $romfile =~ m/pxe/;
- last;
+ if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
+ $machine .= '.pxe';
}
return $machine;
return ($use_old_bios_files, $machine_type);
}
-sub create_efidisk {
- my ($storecfg, $storeid, $vmid, $fmt) = @_;
+sub create_efidisk($$$$$) {
+ my ($storecfg, $storeid, $vmid, $fmt, $arch) = @_;
- die "EFI vars default image not found\n" if ! -f $OVMF_VARS;
+ my (undef, $ovmf_vars) = get_ovmf_files($arch);
+ die "EFI vars default image not found\n" if ! -f $ovmf_vars;
- my $vars_size = PVE::Tools::convert_size(-s $OVMF_VARS, 'b' => 'kb');
+ my $vars_size = PVE::Tools::convert_size(-s $ovmf_vars, 'b' => 'kb');
my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $fmt, undef, $vars_size);
PVE::Storage::activate_volumes($storecfg, [$volid]);
my $path = PVE::Storage::path($storecfg, $volid);
eval {
- run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $OVMF_VARS, $path]);
+ run_command(['/usr/bin/qemu-img', 'convert', '-n', '-f', 'raw', '-O', $fmt, $ovmf_vars, $path]);
};
die "Copying EFI vars image failed: $@" if $@;
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';
if ($winversion >= 7) {
push @$cpuFlags , 'hv_relaxed';
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
+ push @$cpuFlags , 'hv_synic';
+ push @$cpuFlags , 'hv_stimer';
+ }
+
+ if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
+ push @$cpuFlags , 'hv_ipi';
+ }
}
}
return $firstdisk;
}
-sub generate_smbios1_uuid {
+sub generate_uuid {
my ($uuid, $uuid_str);
UUID::generate($uuid);
UUID::unparse($uuid, $uuid_str);
- return "uuid=$uuid_str";
+ return $uuid_str;
+}
+
+sub generate_smbios1_uuid {
+ return "uuid=".generate_uuid();
+}
+
+sub nbd_stop {
+ my ($vmid) = @_;
+
+ 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