use UUID;
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
+use PVE::CGroup;
use PVE::DataCenterConfig;
use PVE::Exception qw(raise raise_param_exc);
+use PVE::Format qw(render_duration render_bytes);
use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
+use PVE::JSONSchema qw(get_standard_option parse_property_string);
use PVE::ProcFSTools;
+use PVE::PBSClient;
use PVE::RPCEnvironment;
use PVE::Storage;
use PVE::SysFSTools;
use PVE::QemuConfig;
use PVE::QemuServer::Helpers qw(min_version config_aware_timeout);
use PVE::QemuServer::Cloudinit;
+use PVE::QemuServer::CGroup;
use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options);
use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory;
use PVE::QemuServer::Monitor qw(mon_cmd);
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
use PVE::QemuServer::USB qw(parse_usb_device);
my $have_sdn;
#no warnings 'redefine';
-sub cgroups_write {
- my ($controller, $vmid, $option, $value) = @_;
-
- my $path = "/sys/fs/cgroup/$controller/qemu.slice/$vmid.scope/$option";
- PVE::ProcFSTools::write_proc_entry($path, $value);
-
-}
-
my $nodename_cache;
sub nodename {
$nodename_cache //= PVE::INotify::nodename();
default_key => 1,
},
fstrim_cloned_disks => {
- description => "Run fstrim after cloning/moving a disk.",
+ description => "Run fstrim after moving a disk or migrating the VM.",
type => 'boolean',
optional => 1,
default => 0
},
driver => {
type => 'string',
- enum => ['spice'],
+ enum => ['spice', 'none'],
default => 'spice',
optional => 1,
description => "Driver backend for the audio device."
wvista;; Microsoft Windows Vista
win7;; Microsoft Windows 7
win8;; Microsoft Windows 8/2012/2012r2
-win10;; Microsoft Windows 10/2016
+win10;; Microsoft Windows 10/2016/2019
l24;; Linux 2.4 Kernel
l26;; Linux 2.6 - 5.X Kernel
solaris;; Solaris/OpenSolaris/OpenIndiania kernel
},
boot => {
optional => 1,
- type => 'string',
- description => "Boot on floppy (a), hard disk (c), CD-ROM (d), or network (n).",
- pattern => '[acdn]{1,4}',
- default => 'cdn',
+ type => 'string', format => 'pve-qm-boot',
+ description => "Specify guest boot order. Use with 'order=', usage with"
+ . " no key or 'legacy=' is deprecated.",
},
bootdisk => {
optional => 1,
type => 'string', format => 'pve-qm-bootdisk',
- description => "Enable booting from specified disk.",
+ description => "Enable booting from specified disk. Deprecated: Use 'boot: order=foo;bar' instead.",
pattern => '(ide|sata|scsi|virtio)\d+',
},
smp => {
description => "Enable/disable hugepages memory.",
enum => [qw(any 2 1024)],
},
+ keephugepages => {
+ optional => 1,
+ type => 'boolean',
+ default => 0,
+ description => "Use together with hugepages. If enabled, hugepages will not not be deleted"
+ ." after VM shutdown and can be used for subsequent starts.",
+ },
vcpus => {
optional => 1,
type => 'integer',
localtime => {
optional => 1,
type => 'boolean',
- description => "Set the real time clock to local time. This is enabled by default if ostype indicates a Microsoft OS.",
+ description => "Set the real time clock to local time. This is enabled by default if ostype"
+ ." indicates a Microsoft OS.",
},
freeze => {
optional => 1,
optional => 1,
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.",
+ 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.\nYou can also run without any graphic card,"
+ ." using a serial device as terminal.",
},
watchdog => {
optional => 1,
type => 'string', format => 'pve-qm-watchdog',
description => "Create a virtual hardware watchdog device.",
- verbose_description => "Create a virtual hardware watchdog device. Once enabled" .
- " (by a guest action), the watchdog must be periodically polled " .
- "by an agent inside the guest or else the watchdog will reset " .
- "the guest (or execute the respective action specified)",
+ verbose_description => "Create a virtual hardware watchdog device. Once enabled (by a guest"
+ ." action), the watchdog must be periodically polled by an agent inside the guest or"
+ ." else the watchdog will reset the guest (or execute the respective action specified)",
},
startdate => {
optional => 1,
type => 'string',
typetext => "(now | YYYY-MM-DD | YYYY-MM-DDTHH:MM:SS)",
- description => "Set the initial date of the real time clock. Valid format for date are: 'now' or '2006-06-17T16:01:21' or '2006-06-17'.",
+ description => "Set the initial date of the real time clock. Valid format for date are:"
+ ."'now' or '2006-06-17T16:01:21' or '2006-06-17'.",
pattern => '(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)',
default => 'now',
},
type => 'boolean',
default => 1,
description => "Enable/disable the USB tablet device.",
- verbose_description => "Enable/disable the USB tablet device. This device is " .
- "usually needed to allow absolute mouse positioning with VNC. " .
- "Else the mouse runs out of sync with normal VNC clients. " .
- "If you're running lots of console-only guests on one host, " .
- "you may consider disabling this to save some context switches. " .
- "This is turned off by default if you use spice (-vga=qxl).",
+ verbose_description => "Enable/disable the USB tablet device. This device is usually needed"
+ ." to allow absolute mouse positioning with VNC. Else the mouse runs out of sync with"
+ ." normal VNC clients. If you're running lots of console-only guests on one host, you"
+ ." may consider disabling this to save some context switches. This is turned off by"
+ ." default if you use spice (`qm set <vmid> --vga qxl`).",
},
migrate_speed => {
optional => 1,
vmstate => {
optional => 1,
type => 'string', format => 'pve-volume-id',
- description => "Reference to a volume which stores the VM state. This is used internally for snapshots.",
+ description => "Reference to a volume which stores the VM state. This is used internally"
+ ." for snapshots.",
},
vmstatestorage => get_standard_option('pve-storage-id', {
description => "Default storage for VM state volumes/files.",
optional => 1,
}),
runningmachine => get_standard_option('pve-qemu-machine', {
- description => "Specifies the QEMU machine type of the running vm. This is used internally for snapshots.",
+ description => "Specifies the QEMU machine type of the running vm. This is used internally"
+ ." for snapshots.",
}),
runningcpu => {
- description => "Specifies the QEMU '-cpu' parameter of the running vm. This is used internally for snapshots.",
+ description => "Specifies the QEMU '-cpu' parameter of the running vm. This is used"
+ ." internally for snapshots.",
optional => 1,
type => 'string',
pattern => $PVE::QemuServer::CPUConfig::qemu_cmdline_cpu_re,
protection => {
optional => 1,
type => 'boolean',
- description => "Sets the protection flag of the VM. This will disable the remove VM and remove disk operations.",
+ description => "Sets the protection flag of the VM. This will disable the remove VM and"
+ ." remove disk operations.",
default => 0,
},
bios => {
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.",
+ 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 through API/CLI create or update methods"
+ .", but not when manually editing the config file.",
default => "1 (autogenerated)",
optional => 1,
},
ivshmem => {
type => 'string',
format => $ivshmem_fmt,
- description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
+ description => "Inter-VM shared memory. Useful for direct communication between VMs, or to"
+ ." the host.",
optional => 1,
},
audio0 => {
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.',
+ 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.',
+ 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.',
+ description => 'Specify a custom file containing all user data passed to the VM via'
+ .' cloud-init.',
format => 'pve-volume-id',
format_description => 'volume',
},
citype => {
optional => 1,
type => 'string',
- description => 'Specifies the cloud-init configuration format. The default depends on the configured operating system type (`ostype`. We use the `nocloud` format for Linux, and `configdrive2` for windows.',
- enum => ['configdrive2', 'nocloud'],
+ description => 'Specifies the cloud-init configuration format. The default depends on the'
+ .' configured operating system type (`ostype`. We use the `nocloud` format for Linux,'
+ .' and `configdrive2` for windows.',
+ enum => ['configdrive2', 'nocloud', 'opennebula'],
},
ciuser => {
optional => 1,
type => 'string',
- description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.",
+ description => "cloud-init: User name to change ssh keys and password for instead of the"
+ ." image's configured default user.",
},
cipassword => {
optional => 1,
type => 'string',
- description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
+ 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.',
+ description => 'cloud-init: Specify custom files to replace the automatically generated'
+ .' ones at start.',
format => 'pve-qm-cicustom',
},
searchdomain => {
optional => 1,
type => 'string',
- description => "cloud-init: Sets DNS search domains for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.",
+ description => "cloud-init: Sets DNS search domains for a container. Create will'
+ .' automatically use the setting from the host if neither searchdomain nor nameserver'
+ .' are set.",
},
nameserver => {
optional => 1,
type => 'string', format => 'address-list',
- description => "cloud-init: Sets DNS server IP address for a container. Create will automatically use the setting from the host if neither searchdomain nor nameserver are set.",
+ description => "cloud-init: Sets DNS server IP address for a container. Create will'
+ .' automatically use the setting from the host if neither searchdomain nor nameserver'
+ .' are set.",
},
sshkeys => {
optional => 1,
my $MAX_USB_DEVICES = 5;
my $MAX_NETS = 32;
-my $MAX_HOSTPCI_DEVICES = 16;
my $MAX_SERIAL_PORTS = 4;
my $MAX_PARALLEL_PORTS = 3;
my $MAX_NUMA = 8;
my $net_fmt = {
macaddr => get_standard_option('mac-addr', {
- description => "MAC address. That address must be unique withing your network. This is automatically generated if not specified.",
+ description => "MAC address. That address must be unique withing your network. This is"
+ ." automatically generated if not specified.",
}),
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'.",
+ 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'.",
enum => $nic_model_list,
default_key => 1,
},
description => 'Whether this interface should be disconnected (like pulling the plug).',
optional => 1,
},
+ mtu => {
+ type => 'integer',
+ minimum => 1, maximum => 65520,
+ description => "Force MTU, for VirtIO only. Set to '1' to use the bridge MTU",
+ optional => 1,
+ },
};
my $netdesc = {
IP addresses use CIDR notation, gateways are optional but need an IP of the same type specified.
-The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit gateway should be provided.
-For IPv6 the special string 'auto' can be used to use stateless autoconfiguration.
+The special string 'dhcp' can be used for IP addresses to use DHCP, in which case no explicit
+gateway should be provided.
+For IPv6 the special string 'auto' can be used to use stateless autoconfiguration. This requires
+cloud-init 19.4 or newer.
-If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using dhcp on IPv4.
+If cloud-init is enabled and neither an IPv4 nor an IPv6 address is specified, it defaults to using
+dhcp on IPv4.
EODESCR
};
PVE::JSONSchema::register_standard_option("pve-qm-ipconfig", $netdesc);
# if its neither 'none' nor 'cdrom' nor a path, check if its a volume-id
$volid = eval { PVE::JSONSchema::check_format('pve-volume-id', $volid, '') };
if ($@) {
- return undef if $noerr;
+ return if $noerr;
die $@;
}
return $volid;
You can use the 'lsusb -t' command to list existing usb devices.
-NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such
+machines - use with special care.
The value 'spice' can be used to add a usb redirection devices for spice.
EODESCR
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
-my $hostpci_fmt = {
- host => {
- default_key => 1,
- type => 'string',
- pattern => qr/$PCIRE(;$PCIRE)*/,
- format_description => 'HOSTPCIID[;HOSTPCIID2...]',
- description => <<EODESCR,
-Host PCI device pass through. The PCI ID of a host's PCI device or a list
-of PCI virtual functions of the host. HOSTPCIID syntax is:
-
-'bus:dev.func' (hexadecimal numbers)
-
-You can us the 'lspci' command to list existing PCI devices.
-EODESCR
- },
- rombar => {
- type => 'boolean',
- description => "Specify whether or not the device's ROM will be visible in the guest's memory map.",
- optional => 1,
- default => 1,
- },
- romfile => {
- type => 'string',
- pattern => '[^,;]+',
- format_description => 'string',
- description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
- optional => 1,
- },
- pcie => {
- type => 'boolean',
- description => "Choose the PCI-express bus (needs the 'q35' machine model).",
- optional => 1,
- default => 0,
- },
- 'x-vga' => {
- type => 'boolean',
- description => "Enable vfio-vga device support.",
- optional => 1,
- default => 0,
- },
- 'mdev' => {
- type => 'string',
- format_description => 'string',
- pattern => '[^/\.:]+',
- optional => 1,
- description => <<EODESCR
-The type of mediated device to use.
-An instance of this type will be created on startup of the VM and
-will be cleaned up when the VM stops.
-EODESCR
- }
-};
-PVE::JSONSchema::register_format('pve-qm-hostpci', $hostpci_fmt);
-
-my $hostpcidesc = {
- optional => 1,
- type => 'string', format => 'pve-qm-hostpci',
- description => "Map host PCI devices into guest.",
- verbose_description => <<EODESCR,
-Map host PCI devices into guest.
-
-NOTE: This option allows direct access to host hardware. So it is no longer
-possible to migrate such machines - use with special care.
-
-CAUTION: Experimental! User reported problems with this option.
-EODESCR
-};
-PVE::JSONSchema::register_standard_option("pve-qm-hostpci", $hostpcidesc);
-
my $serialdesc = {
optional => 1,
type => 'string',
host serial device (i.e. /dev/ttyS0), or create a unix socket on the
host side (use 'qm terminal' to open a terminal connection).
-NOTE: If you pass through a host serial device, it is no longer possible to migrate such machines - use with special care.
+NOTE: If you pass through a host serial device, it is no longer possible to migrate such machines -
+use with special care.
CAUTION: Experimental! User reported problems with this option.
EODESCR
verbose_description => <<EODESCR,
Map host parallel devices (n is 0 to 2).
-NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such machines - use with special care.
+NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such
+machines - use with special care.
CAUTION: Experimental! User reported problems with this option.
EODESCR
$confdesc->{"serial$i"} = $serialdesc;
}
-for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
- $confdesc->{"hostpci$i"} = $hostpcidesc;
+for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) {
+ $confdesc->{"hostpci$i"} = $PVE::QemuServer::PCI::hostpcidesc;
}
for my $key (keys %{$PVE::QemuServer::Drive::drivedesc_hash}) {
$confdesc->{"usb$i"} = $usbdesc;
}
+my $boot_fmt = {
+ legacy => {
+ optional => 1,
+ default_key => 1,
+ type => 'string',
+ description => "Boot on floppy (a), hard disk (c), CD-ROM (d), or network (n)."
+ . " Deprecated, use 'order=' instead.",
+ pattern => '[acdn]{1,4}',
+ format_description => "[acdn]{1,4}",
+
+ # note: this is also the fallback if boot: is not given at all
+ default => 'cdn',
+ },
+ order => {
+ optional => 1,
+ type => 'string',
+ format => 'pve-qm-bootdev-list',
+ format_description => "device[;device...]",
+ description => <<EODESC,
+The guest will attempt to boot from devices in the order they appear here.
+
+Disks, optical drives and passed-through storage USB devices will be directly
+booted from, NICs will load PXE, and PCIe devices will either behave like disks
+(e.g. NVMe) or load an option ROM (e.g. RAID controller, hardware NIC).
+
+Note that only devices in this list will be marked as bootable and thus loaded
+by the guest firmware (BIOS/UEFI). If you require multiple disks for booting
+(e.g. software-raid), you need to specify all of them here.
+
+Overrides the deprecated 'legacy=[acdn]*' value when given.
+EODESC
+ },
+};
+PVE::JSONSchema::register_format('pve-qm-boot', $boot_fmt);
+
+PVE::JSONSchema::register_format('pve-qm-bootdev', \&verify_bootdev);
+sub verify_bootdev {
+ my ($dev, $noerr) = @_;
+
+ return $dev if PVE::QemuServer::Drive::is_valid_drivename($dev) && $dev !~ m/^efidisk/;
+
+ my $check = sub {
+ my ($base) = @_;
+ return 0 if $dev !~ m/^$base\d+$/;
+ return 0 if !$confdesc->{$dev};
+ return 1;
+ };
+
+ return $dev if $check->("net");
+ return $dev if $check->("usb");
+ return $dev if $check->("hostpci");
+
+ return if $noerr;
+ die "invalid boot device '$dev'\n";
+}
+
+sub print_bootorder {
+ my ($devs) = @_;
+ return "" if !@$devs;
+ my $data = { order => join(';', @$devs) };
+ return PVE::JSONSchema::print_property_string($data, $boot_fmt);
+}
+
my $kvm_api_version = 0;
sub kvm_version {
return $kvm_api_version if $kvm_api_version;
- open my $fh, '<', '/dev/kvm'
- or return undef;
+ open my $fh, '<', '/dev/kvm' or return;
# 0xae00 => KVM_GET_API_VERSION
$kvm_api_version = ioctl($fh, 0xae00, 0);
+ close($fh);
return $kvm_api_version;
}
return $kvm_user_version->{$binary};
}
+my sub extract_version {
+ my ($machine_type, $version) = @_;
+ $version = kvm_user_version() if !defined($version);
+ PVE::QemuServer::Machine::extract_version($machine_type, $version)
+}
sub kernel_has_vhost_net {
return -c '/dev/vhost-net';
if (!($file eq 'none' || $file eq 'cdrom' ||
$file =~ m|^/dev/.+| || $file =~ m/^([^:]+):(.+)$/)) {
- return undef if $file =~ m|/|;
+ return if $file =~ m|/|;
if ($media && $media eq 'cdrom') {
$file = "local:iso/$file";
($drive->{file} !~ m/^([^:]+):(.+)$/) &&
($drive->{file} !~ m/^\d+$/)) {
my ($vtype, $volid) = PVE::Storage::path_to_volume_id($storecfg, $drive->{file});
- raise_param_exc({ $opt => "unable to associate path '$drive->{file}' to any storage"}) if !$vtype;
+ raise_param_exc({ $opt => "unable to associate path '$drive->{file}' to any storage"})
+ if !$vtype;
$drive->{media} = 'cdrom' if !$drive->{media} && $vtype eq 'iso';
verify_media_type($opt, $vtype, $drive->{media});
$drive->{file} = $volid;
return $value if parse_hotplug_features($value);
- return undef if $noerr;
+ return if $noerr;
die "unable to parse hotplug option\n";
}
my $ret = ioctl($fh, $SG_GET_VERSION_NUM, $versionbuf);
if (!$ret) {
die "scsi ioctl SG_GET_VERSION_NUM failoed - $!\n" if !$noerr;
- return undef;
+ return;
}
my $version = unpack("I", $versionbuf);
if ($version < 30000) {
die "scsi generic interface too old\n" if !$noerr;
- return undef;
+ return;
}
my $buf = "\x00" x 36;
$ret = ioctl($fh, $SG_IO, $packet);
if (!$ret) {
die "scsi ioctl SG_IO failed - $!\n" if !$noerr;
- return undef;
+ return;
}
my @res = unpack($sg_io_hdr_t, $packet);
if ($res[17] || $res[18]) {
die "scsi ioctl SG_IO status error - $!\n" if !$noerr;
- return undef;
+ return;
}
my $res = {};
sub path_is_scsi {
my ($path) = @_;
- my $fh = IO::File->new("+<$path") || return undef;
+ my $fh = IO::File->new("+<$path") || return;
my $res = scsi_inquiry($fh, 1);
close($fh);
sub print_keyboarddevice_full {
my ($conf, $arch, $machine) = @_;
- return undef if $arch ne 'aarch64';
+ return if $arch ne 'aarch64';
return "usb-kbd,id=keyboard,bus=ehci.0,port=2";
}
+my sub get_drive_id {
+ my ($drive) = @_;
+ return "$drive->{interface}$drive->{index}";
+}
+
sub print_drivedevice_full {
my ($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type) = @_;
my $device = '';
my $maxdev = 0;
+ my $drive_id = get_drive_id($drive);
if ($drive->{interface} eq 'virtio') {
- 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};
+ my $pciaddr = print_pci_addr("$drive_id", $bridges, $arch, $machine_type);
+ $device = "virtio-blk-pci,drive=drive-$drive_id,id=${drive_id}${pciaddr}";
+ $device .= ",iothread=iothread-$drive_id" if $drive->{iothread};
} elsif ($drive->{interface} eq 'scsi') {
my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive);
}
# for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
- my $version = PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());
+ my $version = extract_version($machine_type, kvm_user_version());
if ($path =~ m/^iscsi\:\/\// &&
!min_version($version, 4, 1)) {
$devicetype = 'generic';
}
if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)){
- $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,scsi-id=$unit,drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+ $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,scsi-id=$unit";
} else {
- $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}";
+ $device = "scsi-$devicetype,bus=$controller_prefix$controller.0,channel=0,scsi-id=0"
+ .",lun=$drive->{index}";
}
+ $device .= ",drive=drive-$drive_id,id=$drive_id";
if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
$device .= ",rotation_rate=1";
} else {
$device .= ",bus=ahci$controller.$unit";
}
- $device .= ",drive=drive-$drive->{interface}$drive->{index},id=$drive->{interface}$drive->{index}";
+ $device .= ",drive=drive-$drive_id,id=$drive_id";
if ($devicetype eq 'hd') {
if (my $model = $drive->{model}) {
sub get_initiator_name {
my $initiator;
- my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return undef;
+ my $fh = IO::File->new('/etc/iscsi/initiatorname.iscsi') || return;
while (defined(my $line = <$fh>)) {
next if $line !~ m/^\s*InitiatorName\s*=\s*([\.\-:\w]+)/;
$initiator = $1;
}
sub print_drive_commandline_full {
- my ($storecfg, $vmid, $drive) = @_;
+ my ($storecfg, $vmid, $drive, $pbs_name) = @_;
my $path;
my $volid = $drive->{file};
- my $format;
+ my $format = $drive->{format};
+ my $drive_id = get_drive_id($drive);
if (drive_is_cdrom($drive)) {
$path = get_iso_path($storecfg, $vmid, $volid);
+ die "$drive_id: cannot back cdrom drive with PBS snapshot\n" if $pbs_name;
} else {
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid, 1);
if ($storeid) {
$path = PVE::Storage::path($storecfg, $volid);
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- $format = qemu_img_format($scfg, $volname);
+ $format //= qemu_img_format($scfg, $volname);
} else {
$path = $volid;
- $format = "raw";
+ $format //= "raw";
}
}
+ my $is_rbd = $path =~ m/^rbd:/;
+
my $opts = '';
- my @qemu_drive_options = qw(heads secs cyls trans media format cache rerror werror aio discard);
+ my @qemu_drive_options = qw(heads secs cyls trans media cache rerror werror aio discard);
foreach my $o (@qemu_drive_options) {
$opts .= ",$o=$drive->{$o}" if defined($drive->{$o});
}
}
}
- $opts .= ",format=$format" if $format && !$drive->{format};
+ if ($pbs_name) {
+ $format = "rbd" if $is_rbd;
+ die "$drive_id: Proxmox Backup Server backed drive cannot auto-detect the format\n"
+ if !$format;
+ $opts .= ",format=alloc-track,file.driver=$format";
+ } elsif ($format) {
+ $opts .= ",format=$format";
+ }
my $cache_direct = 0;
# This used to be our default with discard not being specified:
$detectzeroes = 'on';
}
- $opts .= ",detect-zeroes=$detectzeroes" if $detectzeroes;
+
+ # note: 'detect-zeroes' works per blockdev and we want it to persist
+ # after the alloc-track is removed, so put it on 'file' directly
+ my $dz_param = $pbs_name ? "file.detect-zeroes" : "detect-zeroes";
+ $opts .= ",$dz_param=$detectzeroes" if $detectzeroes;
}
- my $pathinfo = $path ? "file=$path," : '';
+ if ($pbs_name) {
+ $opts .= ",backing=$pbs_name";
+ $opts .= ",auto-remove=on";
+ }
+
+ # my $file_param = $pbs_name ? "file.file.filename" : "file";
+ my $file_param = "file";
+ if ($pbs_name) {
+ # non-rbd drivers require the underlying file to be a seperate block
+ # node, so add a second .file indirection
+ $file_param .= ".file" if !$is_rbd;
+ $file_param .= ".filename";
+ }
+ my $pathinfo = $path ? "$file_param=$path," : '';
return "${pathinfo}if=none,id=drive-$drive->{interface}$drive->{index}$opts";
}
+sub print_pbs_blockdev {
+ my ($pbs_conf, $pbs_name) = @_;
+ my $blockdev = "driver=pbs,node-name=$pbs_name,read-only=on";
+ $blockdev .= ",repository=$pbs_conf->{repository}";
+ $blockdev .= ",snapshot=$pbs_conf->{snapshot}";
+ $blockdev .= ",archive=$pbs_conf->{archive}";
+ $blockdev .= ",keyfile=$pbs_conf->{keyfile}" if $pbs_conf->{keyfile};
+ return $blockdev;
+}
+
sub print_netdevice_full {
my ($vmid, $conf, $net, $netid, $bridges, $use_old_bios_files, $arch, $machine_type) = @_;
- my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
-
my $device = $net->{model};
if ($net->{model} eq 'virtio') {
$device = 'virtio-net-pci';
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)
+ # Consider we have N queues, the number of vectors needed is 2 * N + 2, i.e., one per in
+ # and out of each queue plus one config interrupt and control vector queue
my $vectors = $net->{queues} * 2 + 2;
$tmpstr .= ",vectors=$vectors,mq=on";
}
$tmpstr .= ",bootindex=$net->{bootindex}" if $net->{bootindex} ;
+ if (my $mtu = $net->{mtu}) {
+ if ($net->{model} eq 'virtio' && $net->{bridge}) {
+ my $bridge_mtu = PVE::Network::read_bridge_mtu($net->{bridge});
+ if ($mtu == 1) {
+ $mtu = $bridge_mtu;
+ } elsif ($mtu < 576) {
+ die "netdev $netid: MTU '$mtu' is smaller than the IP minimum MTU '576'\n";
+ } elsif ($mtu > $bridge_mtu) {
+ die "netdev $netid: MTU '$mtu' is bigger than the bridge MTU '$bridge_mtu'\n";
+ }
+ $tmpstr .= ",host_mtu=$mtu";
+ } else {
+ warn "WARN: netdev $netid: ignoring MTU '$mtu', not using VirtIO or no bridge configured.\n";
+ }
+ }
+
if ($use_old_bios_files) {
my $romfile;
if ($device eq 'virtio-net-pci') {
my $script = $hotplug ? "pve-bridge-hotplug" : "pve-bridge";
if ($net->{bridge}) {
- $netdev = "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/$script,downscript=/var/lib/qemu-server/pve-bridgedown$vhostparam";
+ $netdev = "type=tap,id=$netid,ifname=${ifname},script=/var/lib/qemu-server/$script"
+ .",downscript=/var/lib/qemu-server/pve-bridgedown$vhostparam";
} else {
$netdev = "type=user,id=$netid,hostname=$vmname";
}
$memory = ",ram_size=67108864,vram_size=33554432";
}
+ my $edidoff = "";
+ if ($type eq 'VGA' && windows_version($conf->{ostype})) {
+ $edidoff=",edid=off" if (!defined($conf->{bios}) || $conf->{bios} ne 'ovmf');
+ }
+
my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
my $vgaid = "vga" . ($id // '');
my $pciaddr;
$pciaddr = print_pci_addr($vgaid, $bridges, $arch, $machine);
}
- return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}";
+ return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}${edidoff}";
}
sub parse_number_sets {
sub parse_numa {
my ($data) = @_;
- my $res = PVE::JSONSchema::parse_property_string($numa_fmt, $data);
+ my $res = parse_property_string($numa_fmt, $data);
$res->{cpus} = parse_number_sets($res->{cpus}) if defined($res->{cpus});
$res->{hostnodes} = parse_number_sets($res->{hostnodes}) if defined($res->{hostnodes});
return $res;
}
-sub parse_hostpci {
- my ($value) = @_;
-
- return undef if !$value;
-
- my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
-
- my @idlist = split(/;/, $res->{host});
- delete $res->{host};
- foreach my $id (@idlist) {
- my $devs = PVE::SysFSTools::lspci($id);
- die "no PCI device found for '$id'\n" if !scalar(@$devs);
- push @{$res->{pciid}}, @$devs;
- }
- return $res;
-}
-
# netX: e1000=XX:XX:XX:XX:XX:XX,bridge=vmbr0,rate=<mbps>
sub parse_net {
my ($data) = @_;
- my $res = eval { PVE::JSONSchema::parse_property_string($net_fmt, $data) };
+ my $res = eval { parse_property_string($net_fmt, $data) };
if ($@) {
warn $@;
- return undef;
+ return;
}
if (!defined($res->{macaddr})) {
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
sub parse_ipconfig {
my ($data) = @_;
- my $res = eval { PVE::JSONSchema::parse_property_string($ipconfig_fmt, $data) };
+ my $res = eval { parse_property_string($ipconfig_fmt, $data) };
if ($@) {
warn $@;
- return undef;
+ return;
}
if ($res->{gw} && !$res->{ip}) {
warn 'gateway specified without specifying an IP address';
- return undef;
+ return;
}
if ($res->{gw6} && !$res->{ip6}) {
warn 'IPv6 gateway specified without specifying an IPv6 address';
- return undef;
+ return;
}
if ($res->{gw} && $res->{ip} eq 'dhcp') {
warn 'gateway specified together with DHCP';
- return undef;
+ return;
}
if ($res->{gw6} && $res->{ip6} !~ /^$IPV6RE/) {
# gw6 + auto/dhcp
warn "IPv6 gateway specified together with $res->{ip6} address";
- return undef;
+ return;
}
if (!$res->{ip} && !$res->{ip6}) {
}
}
- return undef;
+ return;
}
sub vmconfig_register_unused_drive {
sub parse_smbios1 {
my ($data) = @_;
- my $res = eval { PVE::JSONSchema::parse_property_string($smbios1_fmt, $data) };
+ my $res = eval { parse_property_string($smbios1_fmt, $data) };
warn $@ if $@;
return $res;
}
sub parse_watchdog {
my ($value) = @_;
- return undef if !$value;
+ return if !$value;
- my $res = eval { PVE::JSONSchema::parse_property_string($watchdog_fmt, $value) };
+ my $res = eval { parse_property_string($watchdog_fmt, $value) };
warn $@ if $@;
return $res;
}
sub parse_guest_agent {
- my ($value) = @_;
+ my ($conf) = @_;
- return {} if !defined($value->{agent});
+ return {} if !defined($conf->{agent});
- my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $value->{agent}) };
+ my $res = eval { parse_property_string($agent_fmt, $conf->{agent}) };
warn $@ if $@;
# if the agent is disabled ignore the other potentially set properties
return $res;
}
+sub get_qga_key {
+ my ($conf, $key) = @_;
+ return undef if !defined($conf->{agent});
+
+ my $agent = parse_guest_agent($conf);
+ return $agent->{$key};
+}
+
sub parse_vga {
my ($value) = @_;
return {} if !$value;
- my $res = eval { PVE::JSONSchema::parse_property_string($vga_fmt, $value) };
+ my $res = eval { parse_property_string($vga_fmt, $value) };
warn $@ if $@;
return $res;
}
sub parse_rng {
my ($value) = @_;
- return undef if !$value;
+ return if !$value;
- my $res = eval { PVE::JSONSchema::parse_property_string($rng_fmt, $value) };
+ my $res = eval { parse_property_string($rng_fmt, $value) };
warn $@ if $@;
return $res;
}
return $value if parse_usb_device($value);
- return undef if $noerr;
+ return if $noerr;
die "unable to parse usb device\n";
}
}
sub destroy_vm {
- my ($storecfg, $vmid, $skiplock, $replacement_conf) = @_;
+ my ($storecfg, $vmid, $skiplock, $replacement_conf, $purge_unreferenced) = @_;
my $conf = PVE::QemuConfig->load_config($vmid);
});
}
- # only remove disks owned by this VM
- PVE::QemuConfig->foreach_volume($conf, sub {
+ # only remove disks owned by this VM (referenced in the config)
+ PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive, 1);
warn "Could not remove disk '$volid', check manually: $@" if $@;
});
- # also remove unused disk
- my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
- PVE::Storage::foreach_volid($vmdisks, sub {
- my ($volid, $sid, $volname, $d) = @_;
- eval { PVE::Storage::vdisk_free($storecfg, $volid) };
- warn $@ if $@;
- });
+ if ($purge_unreferenced) { # also remove unreferenced disk
+ my $vmdisks = PVE::Storage::vdisk_list($storecfg, undef, $vmid, undef, 'images');
+ PVE::Storage::foreach_volid($vmdisks, sub {
+ my ($volid, $sid, $volname, $d) = @_;
+ eval { PVE::Storage::vdisk_free($storecfg, $volid) };
+ warn $@ if $@;
+ });
+ }
if (defined $replacement_conf) {
PVE::QemuConfig->write_config($vmid, $replacement_conf);
sub parse_vm_config {
my ($filename, $raw) = @_;
- return undef if !defined($raw);
+ return if !defined($raw);
my $res = {
digest => Digest::SHA::sha1_hex($raw),
$conf->{$key} = $value;
}
+ } else {
+ warn "vm $vmid - unable to parse config: $line\n";
}
}
}
foreach my $key (sort keys %$conf) {
- next if $key eq 'digest' || $key eq 'description' || $key eq 'pending' || $key eq 'snapshots';
+ next if $key =~ /^(digest|description|pending|snapshots)$/;
$raw .= "$key: $conf->{$key}\n";
}
return $raw;
type => 'string',
optional => 1,
},
+ 'running-machine' => {
+ description => "The currently running machine type (if running).",
+ type => 'string',
+ optional => 1,
+ },
+ 'running-qemu' => {
+ description => "The currently running QEMU version (if running).",
+ type => 'string',
+ optional => 1,
+ },
};
my $last_proc_pid_stat;
$res->{$vmid}->{diskwrite} = $totalwrbytes;
};
+ my $machinecb = sub {
+ my ($vmid, $resp) = @_;
+ my $data = $resp->{'return'} || [];
+
+ $res->{$vmid}->{'running-machine'} =
+ PVE::QemuServer::Machine::current_from_query_machines($data);
+ };
+
+ my $versioncb = sub {
+ my ($vmid, $resp) = @_;
+ my $data = $resp->{'return'} // {};
+ my $version = 'unknown';
+
+ if (my $v = $data->{qemu}) {
+ $version = $v->{major} . "." . $v->{minor} . "." . $v->{micro};
+ }
+
+ $res->{$vmid}->{'running-qemu'} = $version;
+ };
+
my $statuscb = sub {
my ($vmid, $resp) = @_;
$qmpclient->queue_cmd($vmid, $blockstatscb, 'query-blockstats');
+ $qmpclient->queue_cmd($vmid, $machinecb, 'query-machines');
+ $qmpclient->queue_cmd($vmid, $versioncb, 'query-version');
# this fails if ballon driver is not loaded, so this must be
# the last commnand (following command are aborted if this fails).
$qmpclient->queue_cmd($vmid, $ballooncb, 'query-balloon');
$qmpclient->queue_execute(undef, 2);
+ foreach my $vmid (keys %$list) {
+ next if $opt_vmid && ($vmid ne $opt_vmid);
+ next if !$res->{$vmid}->{pid}; #not running
+
+ # we can't use the $qmpclient since it might have already aborted on
+ # 'query-balloon', but this might also fail for older versions...
+ my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+ $res->{$vmid}->{'proxmox-support'} = $qemu_support // {};
+ }
+
foreach my $vmid (keys %$list) {
next if $opt_vmid && ($vmid ne $opt_vmid);
$res->{$vmid}->{qmpstatus} = $res->{$vmid}->{status} if !$res->{$vmid}->{qmpstatus};
$id //= 0;
my $audio = $conf->{"audio$id"};
- return undef if !defined($audio);
+ return if !defined($audio);
- my $audioproperties = PVE::JSONSchema::parse_property_string($audio_fmt, $audio);
+ my $audioproperties = parse_property_string($audio_fmt, $audio);
my $audiodriver = $audioproperties->{driver} // 'spice';
return {
aarch64 => 'virt',
};
+sub get_installed_machine_version {
+ my ($kvmversion) = @_;
+ $kvmversion = kvm_user_version() if !defined($kvmversion);
+ $kvmversion =~ m/^(\d+\.\d+)/;
+ return $1;
+}
+
+sub windows_get_pinned_machine_version {
+ my ($machine, $base_version, $kvmversion) = @_;
+
+ my $pin_version = $base_version;
+ if (!defined($base_version) ||
+ !PVE::QemuServer::Machine::can_run_pve_machine_version($base_version, $kvmversion)
+ ) {
+ $pin_version = get_installed_machine_version($kvmversion);
+ }
+ if (!$machine || $machine eq 'pc') {
+ $machine = "pc-i440fx-$pin_version";
+ } elsif ($machine eq 'q35') {
+ $machine = "pc-q35-$pin_version";
+ } elsif ($machine eq 'virt') {
+ $machine = "virt-$pin_version";
+ } else {
+ warn "unknown machine type '$machine', not touching that!\n";
+ }
+
+ return $machine;
+}
+
sub get_vm_machine {
my ($conf, $forcemachine, $arch, $add_pve_version, $kvmversion) = @_;
my $machine = $forcemachine || $conf->{machine};
if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
+ $kvmversion //= kvm_user_version();
+ # we must pin Windows VMs without a specific version to 5.1, as 5.2 fixed a bug in ACPI
+ # layout which confuses windows quite a bit and may result in various regressions..
+ # see: https://lists.gnu.org/archive/html/qemu-devel/2021-02/msg08484.html
+ if (windows_version($conf->{ostype})) {
+ $machine = windows_get_pinned_machine_version($machine, '5.1', $kvmversion);
+ }
$arch //= 'x86_64';
$machine ||= $default_machines->{$arch};
if ($add_pve_version) {
- $kvmversion //= kvm_user_version();
my $pvever = PVE::QemuServer::Machine::get_pve_version($kvmversion);
$machine .= "+pve$pvever";
}
}
- if ($add_pve_version && $machine !~ m/\+pve\d+$/) {
+ if ($add_pve_version && $machine !~ m/\+pve\d+?(?:\.pxe)?$/) {
+ my $is_pxe = $machine =~ m/^(.*?)\.pxe$/;
+ $machine = $1 if $is_pxe;
+
# for version-pinned machines that do not include a pve-version (e.g.
# pc-q35-4.1), we assume 0 to keep them stable in case we bump
$machine .= '+pve0';
+
+ $machine .= '.pxe' if $is_pxe;
}
return $machine;
}
sub config_to_command {
- my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu) = @_;
+ my ($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu,
+ $pbs_backing) = @_;
my $cmd = [];
my $globalFlags = [];
my $add_pve_version = min_version($kvmver, 4, 1);
my $machine_type = get_vm_machine($conf, $forcemachine, $arch, $add_pve_version);
- my $machine_version = PVE::QemuServer::Machine::extract_version($machine_type, $kvmver);
+ my $machine_version = extract_version($machine_type, $kvmver);
$kvm //= 1 if is_native($arch);
$machine_version =~ m/(\d+)\.(\d+)/;
if ($kvmver =~ m/^\d+\.\d+\.(\d+)/ && $1 >= 90) {
warn "warning: Installed QEMU version ($kvmver) is a release candidate, ignoring version checks\n";
} elsif (!min_version($kvmver, $machine_major, $machine_minor)) {
- die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n"
+ die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type',"
+ ." please upgrade node '$nodename'\n"
} elsif (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) {
my $max_pve_version = PVE::QemuServer::Machine::get_pve_version($machine_version);
- die "Installed qemu-server (max feature level for $machine_major.$machine_minor is pve$max_pve_version)"
- ." is too old to run machine type '$machine_type', please upgrade node '$nodename'\n";
+ die "Installed qemu-server (max feature level for $machine_major.$machine_minor is"
+ ." pve$max_pve_version) is too old to run machine type '$machine_type', please upgrade"
+ ." node '$nodename'\n";
}
# if a specific +pve version is required for a feature, use $version_guard
return 1;
};
- if ($kvm) {
- die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n"
- if !defined kvm_version();
+ if ($kvm && !defined kvm_version()) {
+ die "KVM virtualisation configured, but not available. Either disable in VM configuration"
+ ." or enable in BIOS.\n";
}
my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
push @$cmd, '-name', $vmname;
+ push @$cmd, '-no-shutdown';
+
my $use_virtio = 0;
my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
}
# add usb controllers
- my @usbcontrollers = PVE::QemuServer::USB::get_usb_controllers($conf, $bridges, $arch, $machine_type, $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 = parse_vga($conf->{vga});
push @$devices, '-device', $kbd if defined($kbd);
}
- my $kvm_off = 0;
- my $gpu_passthrough;
-
- # host pci devices
- for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
- my $id = "hostpci$i";
- my $d = parse_hostpci($conf->{$id});
- next if !$d;
-
- if (my $pcie = $d->{pcie}) {
- die "q35 machine model is not enabled" if !$q35;
- # win7 wants to have the pcie devices directly on the pcie bus
- # instead of in the root port
- if ($winversion == 7) {
- $pciaddr = print_pcie_addr("${id}bus0");
- } else {
- # add more root ports if needed, 4 are present by default
- # by pve-q35 cfgs, rest added here on demand.
- if ($i > 3) {
- push @$devices, '-device', print_pcie_root_port($i);
- }
- $pciaddr = print_pcie_addr($id);
- }
- } else {
- $pciaddr = print_pci_addr($id, $bridges, $arch, $machine_type);
- }
-
- my $xvga = '';
- if ($d->{'x-vga'}) {
- $xvga = ',x-vga=on' if !($conf->{bios} && $conf->{bios} eq 'ovmf');
- $kvm_off = 1;
- $vga->{type} = 'none' if !defined($conf->{vga});
- $gpu_passthrough = 1;
- }
+ my $bootorder = device_bootorder($conf);
- my $pcidevices = $d->{pciid};
- my $multifunction = 1 if @$pcidevices > 1;
-
- my $sysfspath;
- if ($d->{mdev} && scalar(@$pcidevices) == 1) {
- my $pci_id = $pcidevices->[0]->{id};
- my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
- $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
- } elsif ($d->{mdev}) {
- warn "ignoring mediated device '$id' with multifunction device\n";
- }
-
- my $j=0;
- foreach my $pcidevice (@$pcidevices) {
- my $devicestr = "vfio-pci";
-
- if ($sysfspath) {
- $devicestr .= ",sysfsdev=$sysfspath";
- } else {
- $devicestr .= ",host=$pcidevice->{id}";
- }
-
- my $mf_addr = $multifunction ? ".$j" : '';
- $devicestr .= ",id=${id}${mf_addr}${pciaddr}${mf_addr}";
-
- if ($j == 0) {
- $devicestr .= ',rombar=0' if defined($d->{rombar}) && !$d->{rombar};
- $devicestr .= "$xvga";
- $devicestr .= ",multifunction=on" if $multifunction;
- $devicestr .= ",romfile=/usr/share/kvm/$d->{romfile}" if $d->{romfile};
- }
-
- push @$devices, '-device', $devicestr;
- $j++;
- }
- }
+ # host pci device passthrough
+ my ($kvm_off, $gpu_passthrough, $legacy_igd) = PVE::QemuServer::PCI::print_hostpci_devices(
+ $vmid, $conf, $devices, $vga, $winversion, $q35, $bridges, $arch, $machine_type, $bootorder);
# usb devices
my $usb_dev_features = {};
$usb_dev_features->{spice_usb3} = 1 if min_version($machine_version, 4, 0);
- my @usbdevices = PVE::QemuServer::USB::get_usb_devices($conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features);
+ my @usbdevices = PVE::QemuServer::USB::get_usb_devices(
+ $conf, $usbdesc->{format}, $MAX_USB_DEVICES, $usb_dev_features, $bootorder);
push @$devices, @usbdevices if @usbdevices;
+
# serial devices
for (my $i = 0; $i < $MAX_SERIAL_PORTS; $i++) {
if (my $path = $conf->{"serial$i"}) {
}
push @$cmd, '-nodefaults';
- my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
-
- my $bootindex_hash = {};
- my $i = 1;
- foreach my $o (split(//, $bootorder)) {
- $bootindex_hash->{$o} = $i*100;
- $i++;
- }
-
push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg";
push @$cmd, '-no-acpi' if defined($conf->{acpi}) && $conf->{acpi} == 0;
push @$cmd, '-no-reboot' if defined($conf->{reboot}) && $conf->{reboot} == 0;
if ($vga->{type} && $vga->{type} !~ m/^serial\d+$/ && $vga->{type} ne 'none'){
- push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, undef, $qxlnum, $bridges);
+ push @$devices, '-device', print_vga_device(
+ $conf, $vga, $arch, $machine_version, $machine_type, undef, $qxlnum, $bridges);
my $socket = PVE::QemuServer::Helpers::vnc_socket($vmid);
push @$cmd, '-vnc', "unix:$socket,password";
} else {
}
}
- my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
- if ($rng && &$version_guard(4, 1, 2)) {
+ my $rng = $conf->{rng0} ? parse_rng($conf->{rng0}) : undef;
+ if ($rng && $version_guard->(4, 1, 2)) {
check_rng_source($rng->{source});
my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
if ($qxlnum > 1) {
if ($winversion){
for (my $i = 1; $i < $qxlnum; $i++){
- push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges);
+ push @$devices, '-device', print_vga_device(
+ $conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges);
}
} else {
# assume other OS works like Linux
my $localhost = PVE::Network::addr_to_ip($nodeaddrs[0]->{addr});
$spice_port = PVE::Tools::next_spice_port($pfamily, $localhost);
- my $spice_enhancement = PVE::JSONSchema::parse_property_string($spice_enhancements_fmt, $conf->{spice_enhancements} // '');
+ my $spice_enhancement_str = $conf->{spice_enhancements} // '';
+ my $spice_enhancement = parse_property_string($spice_enhancements_fmt, $spice_enhancement_str);
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};
+ $spice_opts .= ",streaming-video=$spice_enhancement->{videostreaming}"
+ if $spice_enhancement->{videostreaming};
+
push @$devices, '-spice', "$spice_opts";
}
$use_virtio = 1 if $ds =~ m/^virtio/;
- if (drive_is_cdrom ($drive)) {
- if ($bootindex_hash->{d}) {
- $drive->{bootindex} = $bootindex_hash->{d};
- $bootindex_hash->{d} += 1;
- }
- } else {
- if ($bootindex_hash->{c}) {
- $drive->{bootindex} = $bootindex_hash->{c} if $conf->{bootdisk} && ($conf->{bootdisk} eq $ds);
- $bootindex_hash->{c} += 1;
- }
- }
+ $drive->{bootindex} = $bootorder->{$ds} if $bootorder->{$ds};
if ($drive->{interface} eq 'virtio'){
push @$cmd, '-object', "iothread,id=iothread-$ds" if $drive->{iothread};
$queues = ",num_queues=$drive->{queues}";
}
- push @$devices, '-device', "$scsihw_type,id=$controller_prefix$controller$pciaddr$iothread$queues" if !$scsicontroller->{$controller};
+ push @$devices, '-device', "$scsihw_type,id=$controller_prefix$controller$pciaddr$iothread$queues"
+ if !$scsicontroller->{$controller};
$scsicontroller->{$controller}=1;
}
if ($drive->{interface} eq 'sata') {
my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS);
$pciaddr = print_pci_addr("ahci$controller", $bridges, $arch, $machine_type);
- push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr" if !$ahcicontroller->{$controller};
+ push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr"
+ if !$ahcicontroller->{$controller};
$ahcicontroller->{$controller}=1;
}
- my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive);
+ my $pbs_conf = $pbs_backing->{$ds};
+ my $pbs_name = undef;
+ if ($pbs_conf) {
+ $pbs_name = "drive-$ds-pbs";
+ push @$devices, '-blockdev', print_pbs_blockdev($pbs_conf, $pbs_name);
+ }
+
+ my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive, $pbs_name);
+ $drive_cmd .= ',readonly' if PVE::QemuConfig->is_template($conf);
+
push @$devices, '-drive',$drive_cmd;
- push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type);
+ push @$devices, '-device', print_drivedevice_full(
+ $storecfg, $conf, $vmid, $drive, $bridges, $arch, $machine_type);
});
for (my $i = 0; $i < $MAX_NETS; $i++) {
- next if !$conf->{"net$i"};
- my $d = parse_net($conf->{"net$i"});
- next if !$d;
+ my $netname = "net$i";
+
+ next if !$conf->{$netname};
+ my $d = parse_net($conf->{$netname});
+ next if !$d;
- $use_virtio = 1 if $d->{model} eq 'virtio';
+ $use_virtio = 1 if $d->{model} eq 'virtio';
- if ($bootindex_hash->{n}) {
- $d->{bootindex} = $bootindex_hash->{n};
- $bootindex_hash->{n} += 1;
- }
+ $d->{bootindex} = $bootorder->{$netname} if $bootorder->{$netname};
- my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i");
- push @$devices, '-netdev', $netdevfull;
+ my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, $netname);
+ push @$devices, '-netdev', $netdevfull;
- my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type);
- push @$devices, '-device', $netdevicefull;
+ my $netdevicefull = print_netdevice_full(
+ $vmid, $conf, $d, $netname, $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 $ivshmem = parse_property_string($ivshmem_fmt, $conf->{ivshmem});
my $bus;
if ($q35) {
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";
+ push @$devices, '-object', "memory-backend-file,id=ivshmem,share=on,mem-path=$path"
+ .",size=$ivshmem->{size}M";
}
# pci.4 is nested in pci.1
for my $k (sort {$b cmp $a} keys %$bridges) {
next if $q35 && $k < 4; # q35.cfg already includes bridges up to 3
- $pciaddr = print_pci_addr("pci.$k", undef, $arch, $machine_type);
+
+ my $k_name = $k;
+ if ($k == 2 && $legacy_igd) {
+ $k_name = "$k-igd";
+ }
+ $pciaddr = print_pci_addr("pci.$k_name", undef, $arch, $machine_type);
+
my $devstr = "pci-bridge,id=pci.$k,chassis_nr=$k$pciaddr";
if ($q35) {
# add after -readconfig pve-q35.cfg
my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
- # Needs to abort, otherwise QEMU crashes on first rng access.
- # Note that rng_current cannot be changed to 'none' manually, so
- # once the VM is past this point, it is no longer an issue.
- die "Cannot start VM with passed-through RNG device: '/dev/hwrng'"
- . " exists, but '$rng_current' is set to 'none'. Ensure that"
- . " a compatible hardware-RNG is attached to the host.\n";
+ # Needs to abort, otherwise QEMU crashes on first rng access. Note that rng_current cannot
+ # be changed to 'none' manually, so once the VM is past this point, it's no longer an issue.
+ die "Cannot start VM with passed-through RNG device: '/dev/hwrng' exists, but"
+ ." '$rng_current' is set to 'none'. Ensure that a compatible hardware-RNG is attached"
+ ." to the host.\n";
}
}
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
- qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type); # add PCI bridge if we need it for the device
+ # add PCI bridge if we need it for the device
+ qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid, $arch, $machine_type);
if ($deviceid eq 'tablet') {
} elsif ($deviceid =~ m/^usb(\d+)$/) {
die "usb hotplug currently not reliable\n";
- # since we can't reliably hot unplug all added usb devices
- # and usb passthrough disables live migration
- # we disable usb hotplugging for now
- qemu_deviceadd($vmid, PVE::QemuServer::USB::print_usbdevice_full($conf, $deviceid, $device));
+ # since we can't reliably hot unplug all added usb devices and usb
+ # passthrough breaks live migration we disable usb hotplugging for now
+ #qemu_deviceadd($vmid, PVE::QemuServer::USB::print_usbdevice_full($conf, $deviceid, $device));
} elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
qemu_iothread_add($vmid, $deviceid, $device);
qemu_driveadd($storecfg, $vmid, $device);
- my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $arch, $machine_type);
qemu_deviceadd($vmid, $devicefull);
eval { qemu_deviceaddverify($vmid, $deviceid); };
qemu_findorcreatescsihw($storecfg,$conf, $vmid, $device, $arch, $machine_type);
qemu_driveadd($storecfg, $vmid, $device);
- my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, $arch, $machine_type);
+ my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, $device, undef, $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, $arch, $device, $deviceid);
+ return if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
my $machine_type = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);
my $use_old_bios_files = undef;
($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
- my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
+ my $netdevicefull = print_netdevice_full(
+ $vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
qemu_deviceadd($vmid, $netdevicefull);
eval {
qemu_deviceaddverify($vmid, $deviceid);
my $devices_list = vm_devices_list($vmid);
return 1 if !defined($devices_list->{$deviceid});
- die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
+ my $bootdisks = PVE::QemuServer::Drive::get_bootdisks($conf);
+ die "can't unplug bootdisk '$deviceid'\n" if grep {$_ eq $deviceid} @$bootdisks;
if ($deviceid eq 'tablet' || $deviceid eq 'keyboard') {
} elsif ($deviceid =~ m/^usb\d+$/) {
die "usb hotplug currently not reliable\n";
- # when unplugging usb devices this way,
- # there may be remaining usb controllers/hubs
- # so we disable it for now
- qemu_devicedel($vmid, $deviceid);
- qemu_devicedelverify($vmid, $deviceid);
+ # when unplugging usb devices this way, there may be remaining usb
+ # controllers/hubs so we disable it for now
+ #qemu_devicedel($vmid, $deviceid);
+ #qemu_devicedelverify($vmid, $deviceid);
} elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
my %options = split(/[=,]/, $netdev);
+ if (defined(my $vhost = $options{vhost})) {
+ $options{vhost} = JSON::boolean(PVE::JSONSchema::parse_boolean($vhost));
+ }
+
+ if (defined(my $queues = $options{queues})) {
+ $options{queues} = $queues + 0;
+ }
+
mon_cmd($vmid, "netdev_add", %options);
return 1;
}
}
-# old code, only used to shutdown old VM after update
-sub __read_avail {
- my ($fh, $timeout) = @_;
-
- my $sel = new IO::Select;
- $sel->add($fh);
-
- my $res = '';
- my $buf;
-
- my @ready;
- while (scalar (@ready = $sel->can_read($timeout))) {
- my $count;
- if ($count = $fh->sysread($buf, 8192)) {
- if ($buf =~ /^(.*)\(qemu\) $/s) {
- $res .= $1;
- last;
- } else {
- $res .= $buf;
- }
- } else {
- if (!defined($count)) {
- die "$!\n";
- }
- last;
- }
- }
-
- die "monitor read timeout\n" if !scalar(@ready);
-
- return $res;
-}
-
sub qemu_block_resize {
my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
my $padding = (1024 - $size % 1024) % 1024;
$size = $size + $padding;
- mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
-
+ mon_cmd(
+ $vmid,
+ "block_resize",
+ device => $deviceid,
+ size => int($size),
+ timeout => 60,
+ );
}
sub qemu_volume_snapshot {
}
sub set_migration_caps {
- my ($vmid) = @_;
+ my ($vmid, $savevm) = @_;
+
+ my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+
+ my $bitmap_prop = $savevm ? 'pbs-dirty-bitmap-savevm' : 'pbs-dirty-bitmap-migration';
+ my $dirty_bitmaps = $qemu_support->{$bitmap_prop} ? 1 : 0;
my $cap_ref = [];
"xbzrle" => 1,
"x-rdma-pin-all" => 0,
"zero-blocks" => 0,
- "compress" => 0
+ "compress" => 0,
+ "dirty-bitmaps" => $dirty_bitmaps,
};
my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
$volhash->{$volid}->{is_unused} //= 0;
$volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
+
+ $volhash->{$volid}->{drivename} = $key if is_valid_drivename($key);
};
my $include_opts = {
my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
+ my $cgroup = PVE::QemuServer::CGroup->new($vmid);
my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
foreach my $opt (sort keys %$pending_delete_hash) {
next if $selection && !$selection->{$opt};
}
} elsif ($opt =~ m/^usb\d+/) {
die "skip\n";
- # since we cannot reliably hot unplug usb devices
- # we are disabling it
- die "skip\n" if !$hotplug_features->{usb} || $conf->{$opt} =~ m/spice/i;
- vm_deviceunplug($vmid, $conf, $opt);
+ # since we cannot reliably hot unplug usb devices we are disabling it
+ #die "skip\n" if !$hotplug_features->{usb} || $conf->{$opt} =~ m/spice/i;
+ #vm_deviceunplug($vmid, $conf, $opt);
} elsif ($opt eq 'vcpus') {
die "skip\n" if !$hotplug_features->{cpu};
qemu_cpu_hotplug($vmid, $conf, undef);
die "skip\n" if !$hotplug_features->{memory};
PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt);
} elsif ($opt eq 'cpuunits') {
- cgroups_write("cpu", $vmid, "cpu.shares", $defaults->{cpuunits});
+ $cgroup->change_cpu_shares(undef, $defaults->{cpuunits});
} elsif ($opt eq 'cpulimit') {
- cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", -1);
+ $cgroup->change_cpu_quota(-1, 100000);
} else {
die "skip\n";
}
$conf->{$opt} = delete $conf->{pending}->{$opt};
}
+ my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
+ foreach my $opt (sort keys %$pending_delete_hash) {
+ next if !grep { $_ eq $opt } @cloudinit_opts;
+ PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
+ delete $conf->{$opt};
+ }
+
my $new_conf = { %$conf };
$new_conf->{$key} = $value;
PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid);
}
} elsif ($opt =~ m/^usb\d+$/) {
die "skip\n";
- # since we cannot reliably hot unplug usb devices
- # we are disabling it
- 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, $arch, $machine_type);
+ # since we cannot reliably hot unplug usb devices we disable it for now
+ #die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
+ #my $d = eval { parse_property_string($usbdesc->{format}, $value) };
+ #die "skip\n" if !$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);
die "skip\n" if !$hotplug_features->{memory};
$value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
} elsif ($opt eq 'cpuunits') {
- cgroups_write("cpu", $vmid, "cpu.shares", $conf->{pending}->{$opt});
+ $cgroup->change_cpu_shares($conf->{pending}->{$opt}, $defaults->{cpuunits});
} elsif ($opt eq 'cpulimit') {
my $cpulimit = $conf->{pending}->{$opt} == 0 ? -1 : int($conf->{pending}->{$opt} * 100000);
- cgroups_write("cpu", $vmid, "cpu.cfs_quota_us", $cpulimit);
+ $cgroup->change_cpu_quota($cpulimit, 100000);
} else {
die "skip\n"; # skip non-hot-pluggable options
}
}
}
- return undef;
+ return;
}
sub vmconfig_delete_or_detach_drive {
my $drive = parse_drive($opt, $value);
- if ($conf->{$opt}) {
-
- if (my $old_drive = parse_drive($opt, $conf->{$opt})) {
-
- my $media = $drive->{media} || 'disk';
- my $oldmedia = $old_drive->{media} || 'disk';
- die "unable to change media type\n" if $media ne $oldmedia;
+ if ($conf->{$opt} && (my $old_drive = parse_drive($opt, $conf->{$opt}))) {
+ my $media = $drive->{media} || 'disk';
+ my $oldmedia = $old_drive->{media} || 'disk';
+ die "unable to change media type\n" if $media ne $oldmedia;
- if (!drive_is_cdrom($old_drive)) {
+ if (!drive_is_cdrom($old_drive)) {
- if ($drive->{file} ne $old_drive->{file}) {
+ if ($drive->{file} ne $old_drive->{file}) {
- die "skip\n" if !$hotplug;
+ die "skip\n" if !$hotplug;
- # unplug and register as unused
- vm_deviceunplug($vmid, $conf, $opt);
- vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive)
+ # unplug and register as unused
+ vm_deviceunplug($vmid, $conf, $opt);
+ vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive)
- } else {
- # update existing disk
-
- # skip non hotpluggable value
- if (safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
- safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
- safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
- safe_string_ne($drive->{cache}, $old_drive->{cache}) ||
- safe_string_ne($drive->{ssd}, $old_drive->{ssd})) {
- die "skip\n";
- }
+ } else {
+ # update existing disk
+
+ # skip non hotpluggable value
+ if (safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
+ safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
+ safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
+ safe_string_ne($drive->{cache}, $old_drive->{cache}) ||
+ safe_string_ne($drive->{ssd}, $old_drive->{ssd})) {
+ die "skip\n";
+ }
- # apply throttle
- if (safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
- safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
- safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
- safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
- safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
- safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) ||
- safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max}) ||
- safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max}) ||
- safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
- safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
- safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
- safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max}) ||
- safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length}) ||
- safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length}) ||
- safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length}) ||
- safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length}) ||
- safe_num_ne($drive->{iops_rd_max_length}, $old_drive->{iops_rd_max_length}) ||
- safe_num_ne($drive->{iops_wr_max_length}, $old_drive->{iops_wr_max_length})) {
-
- qemu_block_set_io_throttle($vmid,"drive-$opt",
- ($drive->{mbps} || 0)*1024*1024,
- ($drive->{mbps_rd} || 0)*1024*1024,
- ($drive->{mbps_wr} || 0)*1024*1024,
- $drive->{iops} || 0,
- $drive->{iops_rd} || 0,
- $drive->{iops_wr} || 0,
- ($drive->{mbps_max} || 0)*1024*1024,
- ($drive->{mbps_rd_max} || 0)*1024*1024,
- ($drive->{mbps_wr_max} || 0)*1024*1024,
- $drive->{iops_max} || 0,
- $drive->{iops_rd_max} || 0,
- $drive->{iops_wr_max} || 0,
- $drive->{bps_max_length} || 1,
- $drive->{bps_rd_max_length} || 1,
- $drive->{bps_wr_max_length} || 1,
- $drive->{iops_max_length} || 1,
- $drive->{iops_rd_max_length} || 1,
- $drive->{iops_wr_max_length} || 1);
+ # apply throttle
+ if (safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
+ safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
+ safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
+ safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
+ safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
+ safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) ||
+ safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max}) ||
+ safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max}) ||
+ safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
+ safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
+ safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
+ safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max}) ||
+ safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length}) ||
+ safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length}) ||
+ safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length}) ||
+ safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length}) ||
+ safe_num_ne($drive->{iops_rd_max_length}, $old_drive->{iops_rd_max_length}) ||
+ safe_num_ne($drive->{iops_wr_max_length}, $old_drive->{iops_wr_max_length})) {
+
+ qemu_block_set_io_throttle(
+ $vmid,"drive-$opt",
+ ($drive->{mbps} || 0)*1024*1024,
+ ($drive->{mbps_rd} || 0)*1024*1024,
+ ($drive->{mbps_wr} || 0)*1024*1024,
+ $drive->{iops} || 0,
+ $drive->{iops_rd} || 0,
+ $drive->{iops_wr} || 0,
+ ($drive->{mbps_max} || 0)*1024*1024,
+ ($drive->{mbps_rd_max} || 0)*1024*1024,
+ ($drive->{mbps_wr_max} || 0)*1024*1024,
+ $drive->{iops_max} || 0,
+ $drive->{iops_rd_max} || 0,
+ $drive->{iops_wr_max} || 0,
+ $drive->{bps_max_length} || 1,
+ $drive->{bps_rd_max_length} || 1,
+ $drive->{bps_wr_max_length} || 1,
+ $drive->{iops_max_length} || 1,
+ $drive->{iops_rd_max_length} || 1,
+ $drive->{iops_wr_max_length} || 1,
+ );
- }
+ }
- return 1;
- }
+ return 1;
+ }
- } else { # cdrom
+ } else { # cdrom
- if ($drive->{file} eq 'none') {
- mon_cmd($vmid, "eject", force => JSON::true, id => "$opt");
- if (drive_is_cloudinit($old_drive)) {
- vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);
- }
- } else {
- my $path = get_iso_path($storecfg, $vmid, $drive->{file});
+ if ($drive->{file} eq 'none') {
+ mon_cmd($vmid, "eject", force => JSON::true, id => "$opt");
+ if (drive_is_cloudinit($old_drive)) {
+ vmconfig_register_unused_drive($storecfg, $vmid, $conf, $old_drive);
+ }
+ } else {
+ my $path = get_iso_path($storecfg, $vmid, $drive->{file});
- # force eject if locked
- mon_cmd($vmid, "eject", force => JSON::true, id => "$opt");
+ # force eject if locked
+ mon_cmd($vmid, "eject", force => JSON::true, id => "$opt");
- if ($path) {
- mon_cmd($vmid, "blockdev-change-medium",
- id => "$opt", filename => "$path");
- }
+ if ($path) {
+ mon_cmd($vmid, "blockdev-change-medium",
+ id => "$opt", filename => "$path");
}
-
- return 1;
}
+
+ return 1;
}
}
$format = qemu_img_format($scfg, $volname);
}
- my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
+ my $size = $drive->{size} / 1024;
+ my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, $size);
my $newdrive = $drive;
$newdrive->{format} = $format;
$newdrive->{file} = $newvolid;
return PVE::QemuConfig->lock_config($vmid, sub {
my $conf = PVE::QemuConfig->load_config($vmid, $migrate_opts->{migratedfrom});
- die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
+ die "you can't start a vm if it's a template\n"
+ if !$params->{skiptemplate} && PVE::QemuConfig->is_template($conf);
+
+ my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');
+ my $has_backup_lock = PVE::QemuConfig->has_lock($conf, 'backup');
- $params->{resume} = PVE::QemuConfig->has_lock($conf, 'suspended');
+ my $running = check_running($vmid, undef, $migrate_opts->{migratedfrom});
+
+ if ($has_backup_lock && $running) {
+ # a backup is currently running, attempt to start the guest in the
+ # existing QEMU instance
+ return vm_resume($vmid);
+ }
PVE::QemuConfig->check_lock($conf)
- if !($params->{skiplock} || $params->{resume});
+ if !($params->{skiplock} || $has_suspended_lock);
+
+ $params->{resume} = $has_suspended_lock || defined($conf->{vmstate});
- die "VM $vmid already running\n" if check_running($vmid, undef, $migrate_opts->{migratedfrom});
+ die "VM $vmid already running\n" if $running;
if (my $storagemap = $migrate_opts->{storagemap}) {
my $replicated = $migrate_opts->{replicated_volumes};
# params:
# statefile => 'tcp', 'unix' for migration or path/volid for RAM state
# skiplock => 0/1, skip checking for config lock
+# skiptemplate => 0/1, skip checking whether VM is template
# forcemachine => to force Qemu machine (rollback/migration)
# forcecpu => a QEMU '-cpu' argument string to override get_cpu_options
# timeout => in seconds
# paused => start VM in paused state (backup)
# resume => resume from hibernation
+# pbs-backing => {
+# sata0 => {
+# repository
+# snapshot
+# keyfile
+# archive
+# },
+# virtio2 => ...
+# }
# migrate_opts:
# nbd => volumes for NBD exports (vm_migrate_alloc_nbd_disks)
# migratedfrom => source node
print "Resuming suspended VM\n";
}
- my ($cmd, $vollist, $spice_port) =
- config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine, $forcecpu);
+ my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid,
+ $conf, $defaults, $forcemachine, $forcecpu, $params->{'pbs-backing'});
my $migration_ip;
my $get_migration_ip = sub {
}
# host pci devices
- for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++) {
+ for (my $i = 0; $i < $PVE::QemuServer::PCI::MAX_HOSTPCI_DEVICES; $i++) {
my $d = parse_hostpci($conf->{"hostpci$i"});
next if !$d;
my $pcidevices = $d->{pciid};
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"
+ 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);
+ die "can't reset PCI device '$pciid'\n"
+ if $info->{has_fl_reset} && !PVE::SysFSTools::pci_dev_reset($info);
}
}
}
my %properties = (
Slice => 'qemu.slice',
- KillMode => 'none',
- CPUShares => $cpuunits
+ KillMode => 'none'
);
+ if (PVE::CGroup::cgroup_mode() == 2) {
+ $properties{CPUWeight} = $cpuunits;
+ } else {
+ $properties{CPUShares} = $cpuunits;
+ }
+
if (my $cpulimit = $conf->{cpulimit}) {
$properties{CPUQuota} = int($cpulimit * 100);
}
eval { $run_qemu->() };
if (my $err = $@) {
- PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology);
+ PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology)
+ if !$conf->{keephugepages};
die $err;
}
- PVE::QemuServer::Memory::hugepages_pre_deallocate($hugepages_topology);
+ PVE::QemuServer::Memory::hugepages_pre_deallocate($hugepages_topology)
+ if !$conf->{keephugepages};
};
eval { PVE::QemuServer::Memory::hugepages_update_locked($code); };
my $pfamily = PVE::Tools::get_host_address_family($nodename);
my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily);
- mon_cmd($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${storage_migrate_port}" } } );
+ mon_cmd($vmid, "nbd-server-start", addr => {
+ type => 'inet',
+ data => {
+ host => "${localip}",
+ port => "${storage_migrate_port}",
+ },
+ });
$localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
$migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}";
}
print "spice listens on port $spice_port\n";
$res->{spice_port} = $spice_port;
if ($migrate_opts->{spice_ticket}) {
- mon_cmd($vmid, "set_password", protocol => 'spice', password => $migrate_opts->{spice_ticket});
+ mon_cmd($vmid, "set_password", protocol => 'spice', password =>
+ $migrate_opts->{spice_ticket});
mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
}
}
}
if ($conf->{ivshmem}) {
- my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+ my $ivshmem = 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
eval {
if ($shutdown) {
- if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
+ if (defined($conf) && get_qga_key($conf, 'enabled')) {
mon_cmd($vmid, "guest-shutdown", timeout => $timeout);
} else {
mon_cmd($vmid, "system_powerdown");
}
- $vmstate = PVE::QemuConfig->__snapshot_save_vmstate($vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1);
+ $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 {
PVE::Storage::activate_volumes($storecfg, [$vmstate]);
eval {
+ set_migration_caps($vmid, 1);
mon_cmd($vmid, "savevm-start", statefile => $path);
for(;;) {
my $state = mon_cmd($vmid, "query-savevm");
PVE::QemuConfig->lock_config($vmid, sub {
my $res = mon_cmd($vmid, 'query-status');
my $resume_cmd = 'cont';
+ my $reset = 0;
- if ($res->{status} && $res->{status} eq 'suspended') {
- $resume_cmd = 'system_wakeup';
+ if ($res->{status}) {
+ return if $res->{status} eq 'running'; # job done, go home
+ $resume_cmd = 'system_wakeup' if $res->{status} eq 'suspended';
+ $reset = 1 if $res->{status} eq 'shutdown';
}
if (!$nocheck) {
if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
}
+ if ($reset) {
+ # required if a VM shuts down during a backup and we get a resume
+ # request before the backup finishes for example
+ mon_cmd($vmid, "system_reset");
+ }
mon_cmd($vmid, $resume_cmd);
});
}
my $name;
if ($d->{is_cloudinit}) {
$name = "vm-$vmid-cloudinit";
- $name .= ".$d->{format}" if $d->{format} ne 'raw';
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ if ($scfg->{path}) {
+ $name .= ".$d->{format}";
+ }
}
- my $volid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $d->{format}, $name, $alloc_size);
+ my $volid = PVE::Storage::vdisk_alloc(
+ $storecfg, $storeid, $vmid, $d->{format}, $name, $alloc_size);
print STDERR "new volume ID is '$volid'\n";
$d->{volid} = $volid;
return $map;
};
-my $restore_update_config_line = sub {
- my ($outfd, $cookie, $vmid, $map, $line, $unique) = @_;
+sub restore_update_config_line {
+ my ($cookie, $map, $line, $unique) = @_;
- return if $line =~ m/^\#qmdump\#/;
- return if $line =~ m/^\#vzdump\#/;
- return if $line =~ m/^lock:/;
- return if $line =~ m/^unused\d+:/;
- return if $line =~ m/^parent:/;
+ return '' if $line =~ m/^\#qmdump\#/;
+ return '' if $line =~ m/^\#vzdump\#/;
+ return '' if $line =~ m/^lock:/;
+ return '' if $line =~ m/^unused\d+:/;
+ return '' if $line =~ m/^parent:/;
+
+ my $res = '';
my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
};
my $netstr = print_net($net);
- print $outfd "net$cookie->{netcount}: $netstr\n";
+ $res .= "net$cookie->{netcount}: $netstr\n";
$cookie->{netcount}++;
}
} elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
my $net = parse_net($netstr);
$net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if $net->{macaddr};
$netstr = print_net($net);
- print $outfd "$id: $netstr\n";
+ $res .= "$id: $netstr\n";
} elsif ($line =~ m/^((ide|scsi|virtio|sata|efidisk)\d+):\s*(\S+)\s*$/) {
my $virtdev = $1;
my $value = $3;
my $di = parse_drive($virtdev, $value);
if (defined($di->{backup}) && !$di->{backup}) {
- print $outfd "#$line";
+ $res .= "#$line";
} elsif ($map->{$virtdev}) {
delete $di->{format}; # format can change on restore
$di->{file} = $map->{$virtdev};
$value = print_drive($di);
- print $outfd "$virtdev: $value\n";
+ $res .= "$virtdev: $value\n";
} else {
- print $outfd $line;
+ $res .= $line;
}
} elsif (($line =~ m/^vmgenid: (.*)/)) {
my $vmgenid = $1;
# always generate a new vmgenid if there was a valid one setup
$vmgenid = generate_uuid();
}
- print $outfd "vmgenid: $vmgenid\n";
+ $res .= "vmgenid: $vmgenid\n";
} elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {
my ($uuid, $uuid_str);
UUID::generate($uuid);
UUID::unparse($uuid, $uuid_str);
my $smbios1 = parse_smbios1($2);
$smbios1->{uuid} = $uuid_str;
- print $outfd $1.print_smbios1($smbios1)."\n";
+ $res .= $1.print_smbios1($smbios1)."\n";
} else {
- print $outfd $line;
+ $res .= $line;
}
-};
+
+ return $res;
+}
my $restore_deactivate_volumes = sub {
my ($storecfg, $devinfo) = @_;
}
};
+# FIXME For PVE 7.0, remove $content_type and always use 'images'
sub scan_volids {
- my ($cfg, $vmid) = @_;
+ my ($cfg, $vmid, $content_type) = @_;
- my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid);
+ my $info = PVE::Storage::vdisk_list($cfg, undef, $vmid, undef, $content_type);
my $volid_hash = {};
foreach my $storeid (keys %$info) {
my ($vmid, $conf, $volid_hash) = @_;
my $changes;
- my $prefix = "VM $vmid:";
+ my $prefix = "VM $vmid";
# used and unused disks
my $referenced = {};
my $volid = $drive->{file};
return if !$volid;
+ my $volume = $volid_hash->{$volid};
# mark volid as "in-use" for next step
$referenced->{$volid} = 1;
- if ($volid_hash->{$volid} &&
- (my $path = $volid_hash->{$volid}->{path})) {
+ if ($volume && (my $path = $volume->{path})) {
$referencedpath->{$path} = 1;
}
return if drive_is_cdrom($drive);
- return if !$volid_hash->{$volid};
+ return if !$volume;
- my ($updated, $old_size, $new_size) = PVE::QemuServer::Drive::update_disksize($drive, $volid_hash);
+ my ($updated, $msg) = PVE::QemuServer::Drive::update_disksize($drive, $volume->{size});
if (defined($updated)) {
$changes = 1;
$conf->{$opt} = print_drive($updated);
- print "$prefix size of disk '$volid' ($opt) updated from $old_size to $new_size\n";
+ print "$prefix ($opt): $msg\n";
}
});
my $volid = $drive->{file};
return if !$volid;
- my $path = $volid_hash->{$volid}->{path} if $volid_hash->{$volid};
+ my $path;
+ $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;
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 $volid_hash = scan_volids($cfg, $vmid, 'images');
my $updatefn = sub {
my ($vmid) = @_;
my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive);
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
- my $server = $scfg->{server};
- my $datastore = $scfg->{datastore};
- my $username = $scfg->{username} // 'root@pam';
my $fingerprint = $scfg->{fingerprint};
+ my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
+
+ my $repo = PVE::PBSClient::get_repository($scfg);
- my $repo = "$username\@$server:$datastore";
+ # This is only used for `pbs-restore` and the QEMU PBS driver (live-restore)
my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
local $ENV{PBS_PASSWORD} = $password;
local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
mkpath $tmpdir;
my $conffile = PVE::QemuConfig->config_file($vmid);
- my $tmpfn = "$conffile.$$.tmp";
# disable interrupts (always do cleanups)
local $SIG{INT} =
local $SIG{TERM} =
# Note: $oldconf is undef if VM does not exists
my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+ my $new_conf_raw = '';
my $rpcenv = PVE::RPCEnvironment::get();
my $devinfo = {};
}
}
- my $is_qemu_server_backup = scalar(grep { $_->{filename} eq 'qemu-server.conf.blob' } @{$index->{files}});
+ my $is_qemu_server_backup = scalar(
+ grep { $_->{filename} eq 'qemu-server.conf.blob' } @{$index->{files}}
+ );
if (!$is_qemu_server_backup) {
die "backup does not look like a qemu-server backup (missing 'qemu-server.conf' file)\n";
}
}
my $fh = IO::File->new($cfgfn, "r") ||
- "unable to read qemu-server.conf - $!\n";
+ die "unable to read qemu-server.conf - $!\n";
my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $storecfg, $fh, $devinfo, $options);
# allocate volumes
my $map = $restore_allocate_devices->($storecfg, $virtdev_hash, $vmid);
- foreach my $virtdev (sort keys %$virtdev_hash) {
- my $d = $virtdev_hash->{$virtdev};
- next if $d->{is_cloudinit}; # no need to restore cloudinit
+ if (!$options->{live}) {
+ foreach my $virtdev (sort keys %$virtdev_hash) {
+ my $d = $virtdev_hash->{$virtdev};
+ next if $d->{is_cloudinit}; # no need to restore cloudinit
- my $volid = $d->{volid};
+ my $volid = $d->{volid};
- my $path = PVE::Storage::path($storecfg, $volid);
+ my $path = PVE::Storage::path($storecfg, $volid);
- my $pbs_restore_cmd = [
- '/usr/bin/pbs-restore',
- '--repository', $repo,
- $pbs_backup_name,
- "$d->{devname}.img.fidx",
- $path,
- '--verbose',
- ];
+ my $pbs_restore_cmd = [
+ '/usr/bin/pbs-restore',
+ '--repository', $repo,
+ $pbs_backup_name,
+ "$d->{devname}.img.fidx",
+ $path,
+ '--verbose',
+ ];
- if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
- push @$pbs_restore_cmd, '--skip-zero';
- }
+ push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+ push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
- my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
- print "restore proxmox backup image: $dbg_cmdstring\n";
- run_command($pbs_restore_cmd);
+ if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
+ push @$pbs_restore_cmd, '--skip-zero';
+ }
+
+ my $dbg_cmdstring = PVE::Tools::cmd2string($pbs_restore_cmd);
+ print "restore proxmox backup image: $dbg_cmdstring\n";
+ run_command($pbs_restore_cmd);
+ }
}
$fh->seek(0, 0) || die "seek failed - $!\n";
- my $outfd = new IO::File ($tmpfn, "w") ||
- die "unable to write config for VM $vmid\n";
-
my $cookie = { netcount => 0 };
while (defined(my $line = <$fh>)) {
- $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $options->{unique});
+ $new_conf_raw .= restore_update_config_line(
+ $cookie,
+ $map,
+ $line,
+ $options->{unique},
+ );
}
$fh->close();
- $outfd->close();
};
my $err = $@;
- $restore_deactivate_volumes->($storecfg, $devinfo);
+ if ($err || !$options->{live}) {
+ $restore_deactivate_volumes->($storecfg, $devinfo);
+ }
rmtree $tmpdir;
if ($err) {
- unlink $tmpfn;
$restore_destroy_volumes->($storecfg, $devinfo);
die $err;
}
- rename($tmpfn, $conffile) ||
- die "unable to commit configuration file '$conffile'\n";
+ if ($options->{live}) {
+ # keep lock during live-restore
+ $new_conf_raw .= "\nlock: create";
+ }
+
+ PVE::Tools::file_set_contents($conffile, $new_conf_raw);
PVE::Cluster::cfs_update(); # make sure we read new file
eval { rescan($vmid, 1); };
warn $@ if $@;
+
+ PVE::AccessControl::add_vm_to_pool($vmid, $options->{pool}) if $options->{pool};
+
+ if ($options->{live}) {
+ # enable interrupts
+ local $SIG{INT} =
+ local $SIG{TERM} =
+ local $SIG{QUIT} =
+ local $SIG{HUP} =
+ local $SIG{PIPE} = sub { die "got signal ($!) - abort\n"; };
+
+ my $conf = PVE::QemuConfig->load_config($vmid);
+ die "cannot do live-restore for template\n" if PVE::QemuConfig->is_template($conf);
+
+ pbs_live_restore($vmid, $conf, $storecfg, $devinfo, $repo, $keyfile, $pbs_backup_name);
+
+ PVE::QemuConfig->remove_lock($vmid, "create");
+ }
+}
+
+sub pbs_live_restore {
+ my ($vmid, $conf, $storecfg, $restored_disks, $repo, $keyfile, $snap) = @_;
+
+ print "Starting VM for live-restore\n";
+
+ my $pbs_backing = {};
+ for my $ds (keys %$restored_disks) {
+ $ds =~ m/^drive-(.*)$/;
+ $pbs_backing->{$1} = {
+ repository => $repo,
+ snapshot => $snap,
+ archive => "$ds.img.fidx",
+ };
+ $pbs_backing->{$1}->{keyfile} = $keyfile if -e $keyfile;
+ }
+
+ my $drives_streamed = 0;
+ eval {
+ # make sure HA doesn't interrupt our restore by stopping the VM
+ if (PVE::HA::Config::vm_is_ha_managed($vmid)) {
+ run_command(['ha-manager', 'set', "vm:$vmid", '--state', 'started']);
+ }
+
+ # start VM with backing chain pointing to PBS backup, environment vars for PBS driver
+ # in QEMU (PBS_PASSWORD and PBS_FINGERPRINT) are already set by our caller
+ vm_start_nolock($storecfg, $vmid, $conf, {paused => 1, 'pbs-backing' => $pbs_backing}, {});
+
+ my $qmeventd_fd = register_qmeventd_handle($vmid);
+
+ # begin streaming, i.e. data copy from PBS to target disk for every vol,
+ # this will effectively collapse the backing image chain consisting of
+ # [target <- alloc-track -> PBS snapshot] to just [target] (alloc-track
+ # removes itself once all backing images vanish with 'auto-remove=on')
+ my $jobs = {};
+ for my $ds (sort keys %$restored_disks) {
+ my $job_id = "restore-$ds";
+ mon_cmd($vmid, 'block-stream',
+ 'job-id' => $job_id,
+ device => "$ds",
+ );
+ $jobs->{$job_id} = {};
+ }
+
+ mon_cmd($vmid, 'cont');
+ qemu_drive_mirror_monitor($vmid, undef, $jobs, 'auto', 0, 'stream');
+
+ print "restore-drive jobs finished successfully, removing all tracking block devices"
+ ." to disconnect from Proxmox Backup Server\n";
+
+ for my $ds (sort keys %$restored_disks) {
+ mon_cmd($vmid, 'blockdev-del', 'node-name' => "$ds-pbs");
+ }
+
+ close($qmeventd_fd);
+ };
+
+ my $err = $@;
+
+ if ($err) {
+ warn "An error occured during live-restore: $err\n";
+ _do_vm_stop($storecfg, $vmid, 1, 1, 10, 0, 1);
+ die "live-restore failed\n";
+ }
}
sub restore_vma_archive {
my $mapfifo = "/var/tmp/vzdumptmp$$.fifo";
POSIX::mkfifo($mapfifo, 0600);
my $fifofh;
-
- my $openfifo = sub {
- open($fifofh, '>', $mapfifo) || die $!;
- };
+ my $openfifo = sub { open($fifofh, '>', $mapfifo) or die $! };
$add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
my $rpcenv = PVE::RPCEnvironment::get();
my $conffile = PVE::QemuConfig->config_file($vmid);
- my $tmpfn = "$conffile.$$.tmp";
# Note: $oldconf is undef if VM does not exist
my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
+ my $new_conf_raw = '';
my %storage_limits;
# we can read the config - that is already extracted
my $fh = IO::File->new($cfgfn, "r") ||
- "unable to read qemu-server.conf - $!\n";
+ die "unable to read qemu-server.conf - $!\n";
my $fwcfgfn = "$tmpdir/qemu-server.fw";
if (-f $fwcfgfn) {
my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $cfg, $fh, $devinfo, $opts);
- 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 $info (values %{$virtdev_hash}) {
+ my $storeid = $info->{storeid};
+ next if defined($storage_limits{$storeid});
+
+ my $limit = PVE::Storage::get_bandwidth_limit('restore', [$storeid], $bwlimit) // 0;
+ print STDERR "rate limit for storage $storeid: $limit KiB/s\n" if $limit;
+ $storage_limits{$storeid} = $limit * 1024;
}
foreach my $devname (keys %$devinfo) {
$fh->seek(0, 0) || die "seek failed - $!\n";
- my $outfd = new IO::File ($tmpfn, "w") ||
- die "unable to write config for VM $vmid\n";
-
my $cookie = { netcount => 0 };
while (defined(my $line = <$fh>)) {
- $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ $new_conf_raw .= restore_update_config_line(
+ $cookie,
+ $map,
+ $line,
+ $opts->{unique},
+ );
}
$fh->close();
- $outfd->close();
};
eval {
$oldtimeout = undef;
alarm($tmp);
close($fifofh);
+ $fifofh = undef;
}
};
$restore_deactivate_volumes->($cfg, $devinfo);
+ close($fifofh) if $fifofh;
unlink $mapfifo;
rmtree $tmpdir;
if ($err) {
- unlink $tmpfn;
$restore_destroy_volumes->($cfg, $devinfo);
die $err;
}
- rename($tmpfn, $conffile) ||
- die "unable to commit configuration file '$conffile'\n";
+ PVE::Tools::file_set_contents($conffile, $new_conf_raw);
PVE::Cluster::cfs_update(); # make sure we read new file
eval { rescan($vmid, 1); };
warn $@ if $@;
+
+ PVE::AccessControl::add_vm_to_pool($vmid, $opts->{pool}) if $opts->{pool};
}
sub restore_tar_archive {
if ($archive ne '-') {
my $firstfile = tar_archive_read_firstfile($archive);
- die "ERROR: file '$archive' dos not lock like a QemuServer vzdump backup\n"
+ die "ERROR: file '$archive' does not look like a QemuServer vzdump backup\n"
if $firstfile ne 'qemu-server.conf';
}
local $ENV{VZDUMP_USER} = $user;
my $conffile = PVE::QemuConfig->config_file($vmid);
- my $tmpfn = "$conffile.$$.tmp";
+ my $new_conf_raw = '';
# disable interrupts (always do cleanups)
local $SIG{INT} =
my $confsrc = "$tmpdir/qemu-server.conf";
- my $srcfd = new IO::File($confsrc, "r") ||
- die "unable to open file '$confsrc'\n";
-
- my $outfd = new IO::File ($tmpfn, "w") ||
- die "unable to write config for VM $vmid\n";
+ my $srcfd = IO::File->new($confsrc, "r") || die "unable to open file '$confsrc'\n";
my $cookie = { netcount => 0 };
while (defined (my $line = <$srcfd>)) {
- $restore_update_config_line->($outfd, $cookie, $vmid, $map, $line, $opts->{unique});
+ $new_conf_raw .= restore_update_config_line(
+ $cookie,
+ $map,
+ $line,
+ $opts->{unique},
+ );
}
$srcfd->close();
- $outfd->close();
};
if (my $err = $@) {
- unlink $tmpfn;
tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
die $err;
}
rmtree $tmpdir;
- rename $tmpfn, $conffile ||
- die "unable to commit configuration file '$conffile'\n";
+ PVE::Tools::file_set_contents($conffile, $new_conf_raw);
PVE::Cluster::cfs_update(); # make sure we read new file
my $storage_name = PVE::Storage::parse_volume_id($volid);
my $scfg = $storecfg->{ids}->{$storage_name};
+ die "could not find storage '$storage_name'\n" if !defined($scfg);
if ($qemu_snap_storage->{$scfg->{type}} && !$scfg->{krbd}){
return 1;
return 1;
}
- return undef;
+ return;
}
sub qga_check_running {
if($line =~ m/\((\S+)\/100\%\)/){
my $percent = $1;
my $transferred = int($size * $percent / 100);
- my $remaining = $size - $transferred;
+ my $total_h = render_bytes($size, 1);
+ my $transferred_h = render_bytes($transferred, 1);
- print "transferred: $transferred bytes remaining: $remaining bytes total: $size bytes progression: $percent %\n";
+ print "transferred $transferred_h of $total_h ($percent%)";
}
};
# 'complete': wait until all jobs are ready, block-job-complete them (default)
# 'cancel': wait until all jobs are ready, block-job-cancel them
# 'skip': wait until all jobs are ready, return with block jobs in ready state
+# 'auto': wait until all jobs disappear, only use for jobs which complete automatically
sub qemu_drive_mirror_monitor {
- my ($vmid, $vmiddst, $jobs, $completion, $qga) = @_;
+ my ($vmid, $vmiddst, $jobs, $completion, $qga, $op) = @_;
$completion //= 'complete';
+ $op //= "mirror";
eval {
my $err_complete = 0;
+ my $starttime = time ();
while (1) {
- die "storage migration timed out\n" if $err_complete > 300;
+ die "block job ('$op') timed out\n" if $err_complete > 300;
my $stats = mon_cmd($vmid, "query-block-jobs");
+ my $ctime = time();
- my $running_mirror_jobs = {};
- foreach my $stat (@$stats) {
- next if $stat->{type} ne 'mirror';
- $running_mirror_jobs->{$stat->{device}} = $stat;
+ my $running_jobs = {};
+ for my $stat (@$stats) {
+ next if $stat->{type} ne $op;
+ $running_jobs->{$stat->{device}} = $stat;
}
my $readycounter = 0;
- foreach my $job (keys %$jobs) {
+ for my $job_id (sort keys %$jobs) {
+ my $job = $running_jobs->{$job_id};
- if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
- print "$job : finished\n";
- delete $jobs->{$job};
+ my $vanished = !defined($job);
+ my $complete = defined($jobs->{$job_id}->{complete}) && $vanished;
+ if($complete || ($vanished && $completion eq 'auto')) {
+ print "$job_id: $op-job finished\n";
+ delete $jobs->{$job_id};
next;
}
- die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
+ die "$job_id: '$op' has been cancelled\n" if !defined($job);
- my $busy = $running_mirror_jobs->{$job}->{busy};
- my $ready = $running_mirror_jobs->{$job}->{ready};
- if (my $total = $running_mirror_jobs->{$job}->{len}) {
- my $transferred = $running_mirror_jobs->{$job}->{offset} || 0;
+ my $busy = $job->{busy};
+ my $ready = $job->{ready};
+ if (my $total = $job->{len}) {
+ my $transferred = $job->{offset} || 0;
my $remaining = $total - $transferred;
my $percent = sprintf "%.2f", ($transferred * 100 / $total);
- print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+ my $duration = $ctime - $starttime;
+ my $total_h = render_bytes($total, 1);
+ my $transferred_h = render_bytes($transferred, 1);
+
+ my $status = sprintf(
+ "transferred $transferred_h of $total_h ($percent%%) in %s",
+ render_duration($duration),
+ );
+
+ if ($ready) {
+ if ($busy) {
+ $status .= ", still busy"; # shouldn't even happen? but mirror is weird
+ } else {
+ $status .= ", ready";
+ }
+ }
+ print "$job_id: $status\n" if !$jobs->{$job_id}->{ready};
+ $jobs->{$job_id}->{ready} = $ready;
}
- $readycounter++ if $running_mirror_jobs->{$job}->{ready};
+ $readycounter++ if $job->{ready};
}
last if scalar(keys %$jobs) == 0;
if ($readycounter == scalar(keys %$jobs)) {
- print "all mirroring jobs are ready \n";
- last if $completion eq 'skip'; #do the complete later
+ print "all '$op' jobs are ready\n";
+
+ # do the complete later (or has already been done)
+ last if $completion eq 'skip' || $completion eq 'auto';
if ($vmiddst && $vmiddst != $vmid) {
my $agent_running = $qga && qga_check_running($vmid);
last;
} else {
- foreach my $job (keys %$jobs) {
+ for my $job_id (sort keys %$jobs) {
# try to switch the disk if source and destination are on the same guest
- print "$job: Completing block job...\n";
+ print "$job_id: Completing block job_id...\n";
my $op;
if ($completion eq 'complete') {
} else {
die "invalid completion value: $completion\n";
}
- eval { mon_cmd($vmid, $op, device => $job) };
+ eval { mon_cmd($vmid, $op, device => $job_id) };
if ($@ =~ m/cannot be completed/) {
- print "$job: Block job cannot be completed, try again.\n";
+ print "$job_id: block job cannot be completed, trying again.\n";
$err_complete++;
}else {
- print "$job: Completed successfully.\n";
- $jobs->{$job}->{complete} = 1;
+ print "$job_id: Completed successfully.\n";
+ $jobs->{$job_id}->{complete} = 1;
}
}
}
if ($err) {
eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
- die "mirroring error: $err";
+ die "block job ($op) error: $err";
}
-
}
sub qemu_blockjobs_cancel {
$storeid = $storage if $storage;
my $dst_format = resolve_dst_disk_format($storecfg, $storeid, $volname, $format);
- my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
print "create full clone of drive $drivename ($drive->{file})\n";
my $name = undef;
+ my $size = undef;
if (drive_is_cloudinit($drive)) {
$name = "vm-$newvmid-cloudinit";
- $name .= ".$dst_format" if $dst_format ne 'raw';
+ my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+ if ($scfg->{path}) {
+ $name .= ".$dst_format";
+ }
$snapname = undef;
$size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE;
} elsif ($drivename eq 'efidisk0') {
$size = get_efivars_size($conf);
+ } else {
+ ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 10);
}
- $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
+ $newvolid = PVE::Storage::vdisk_alloc(
+ $storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024)
+ );
push @$newvollist, $newvolid;
PVE::Storage::activate_volumes($storecfg, [$newvolid]);
if (drive_is_cloudinit($drive)) {
+ # when cloning multiple disks (e.g. during clone_vm) it might be the last disk
+ # if this is the case, we have to complete any block-jobs still there from
+ # previous drive-mirrors
+ if (($completion eq 'complete') && (scalar(keys %$jobs) > 0)) {
+ qemu_drive_mirror_monitor($vmid, $newvmid, $jobs, $completion, $qga);
+ }
goto no_data_clone;
}
# that is given by the OVMF_VARS.fd
my $src_path = PVE::Storage::path($storecfg, $drive->{file});
my $dst_path = PVE::Storage::path($storecfg, $newvolid);
- run_command(['qemu-img', 'dd', '-n', '-O', $dst_format, "bs=1", "count=$size", "if=$src_path", "of=$dst_path"]);
+
+ # better for Ceph if block size is not too small, see bug #3324
+ my $bs = 1024*1024;
+
+ run_command(['qemu-img', 'dd', '-n', '-O', $dst_format, "bs=$bs", "osize=$size",
+ "if=$src_path", "of=$dst_path"]);
} else {
qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
}
if $drive->{iothread};
}
- qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $completion, $qga, $bwlimit);
+ qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs,
+ $completion, $qga, $bwlimit);
}
}
no_data_clone:
- my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
+ my ($size) = eval { PVE::Storage::volume_size_info($storecfg, $newvolid, 10) };
my $disk = $drive;
$disk->{format} = undef;
$disk->{file} = $newvolid;
- $disk->{size} = $size;
+ $disk->{size} = $size if defined($size);
return $disk;
}
$machine_type = $1;
$use_old_bios_files = 1;
} else {
- my $version = PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());
+ my $version = extract_version($machine_type, kvm_user_version());
# Note: kvm version < 2.4 use non-efi pxe files, and have problems when we
# load new efi bios files on migration. So this hack is required to allow
# live migration from qemu-2.2 to qemu-2.4, which is sometimes used when
}
my $controller = int($drive->{index} / $maxdev);
- my $controller_prefix = ($conf->{scsihw} && $conf->{scsihw} eq 'virtio-scsi-single') ? "virtioscsi" : "scsihw";
+ my $controller_prefix = ($conf->{scsihw} && $conf->{scsihw} eq 'virtio-scsi-single')
+ ? "virtioscsi"
+ : "scsihw";
return ($maxdev, $controller, $controller_prefix);
}
return $res;
}
+sub bootorder_from_legacy {
+ my ($conf, $bootcfg) = @_;
+
+ my $boot = $bootcfg->{legacy} || $boot_fmt->{legacy}->{default};
+ my $bootindex_hash = {};
+ my $i = 1;
+ foreach my $o (split(//, $boot)) {
+ $bootindex_hash->{$o} = $i*100;
+ $i++;
+ }
+
+ my $bootorder = {};
+
+ PVE::QemuConfig->foreach_volume($conf, sub {
+ my ($ds, $drive) = @_;
+
+ if (drive_is_cdrom ($drive, 1)) {
+ if ($bootindex_hash->{d}) {
+ $bootorder->{$ds} = $bootindex_hash->{d};
+ $bootindex_hash->{d} += 1;
+ }
+ } elsif ($bootindex_hash->{c}) {
+ $bootorder->{$ds} = $bootindex_hash->{c}
+ if $conf->{bootdisk} && $conf->{bootdisk} eq $ds;
+ $bootindex_hash->{c} += 1;
+ }
+ });
+
+ if ($bootindex_hash->{n}) {
+ for (my $i = 0; $i < $MAX_NETS; $i++) {
+ my $netname = "net$i";
+ next if !$conf->{$netname};
+ $bootorder->{$netname} = $bootindex_hash->{n};
+ $bootindex_hash->{n} += 1;
+ }
+ }
+
+ return $bootorder;
+}
+
+# Generate default device list for 'boot: order=' property. Matches legacy
+# default boot order, but with explicit device names. This is important, since
+# the fallback for when neither 'order' nor the old format is specified relies
+# on 'bootorder_from_legacy' above, and it would be confusing if this diverges.
+sub get_default_bootdevices {
+ my ($conf) = @_;
+
+ my @ret = ();
+
+ # harddisk
+ my $first = PVE::QemuServer::Drive::resolve_first_disk($conf, 0);
+ push @ret, $first if $first;
+
+ # cdrom
+ $first = PVE::QemuServer::Drive::resolve_first_disk($conf, 1);
+ push @ret, $first if $first;
+
+ # network
+ for (my $i = 0; $i < $MAX_NETS; $i++) {
+ my $netname = "net$i";
+ next if !$conf->{$netname};
+ push @ret, $netname;
+ last;
+ }
+
+ return \@ret;
+}
+
+sub device_bootorder {
+ my ($conf) = @_;
+
+ return bootorder_from_legacy($conf) if !defined($conf->{boot});
+
+ my $boot = parse_property_string($boot_fmt, $conf->{boot});
+
+ my $bootorder = {};
+ if (!defined($boot) || $boot->{legacy}) {
+ $bootorder = bootorder_from_legacy($conf, $boot);
+ } elsif ($boot->{order}) {
+ my $i = 100; # start at 100 to allow user to insert devices before us with -args
+ for my $dev (PVE::Tools::split_list($boot->{order})) {
+ $bootorder->{$dev} = $i++;
+ }
+ }
+
+ return $bootorder;
+}
+
+sub register_qmeventd_handle {
+ my ($vmid) = @_;
+
+ my $fh;
+ my $peer = "/var/run/qmeventd.sock";
+ my $count = 0;
+
+ for (;;) {
+ $count++;
+ $fh = IO::Socket::UNIX->new(Peer => $peer, Blocking => 0, Timeout => 1);
+ last if $fh;
+ if ($! != EINTR && $! != EAGAIN) {
+ die "unable to connect to qmeventd socket (vmid: $vmid) - $!\n";
+ }
+ if ($count > 4) {
+ die "unable to connect to qmeventd socket (vmid: $vmid) - timeout "
+ . "after $count retries\n";
+ }
+ usleep(25000);
+ }
+
+ # send handshake to mark VM as backing up
+ print $fh to_json({vzdump => {vmid => "$vmid"}});
+
+ # return handle to be closed later when inhibit is no longer required
+ return $fh;
+}
+
# bash completion helper
sub complete_backup_archives {
return $res;
}
+sub vm_is_paused {
+ my ($vmid) = @_;
+ my $qmpstatus = eval {
+ PVE::QemuConfig::assert_config_exists_on_node($vmid);
+ mon_cmd($vmid, "query-status");
+ };
+ warn "$@\n" if $@;
+ return $qmpstatus && $qmpstatus->{status} eq "paused";
+}
+
1;