use URI::Escape;
use UUID;
-use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file);
use PVE::DataCenterConfig;
use PVE::Exception qw(raise raise_param_exc);
use PVE::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
use PVE::INotify;
-use PVE::JSONSchema qw(get_standard_option);
+use PVE::JSONSchema qw(get_standard_option parse_property_string);
use PVE::ProcFSTools;
use PVE::RPCEnvironment;
use PVE::Storage;
use PVE::SysFSTools;
use PVE::Systemd;
-use PVE::Tools qw(run_command lock_file lock_file_full file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
+use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE);
use PVE::QMPClient;
use PVE::QemuConfig;
use PVE::QemuServer::Helpers qw(min_version config_aware_timeout);
use PVE::QemuServer::Cloudinit;
use PVE::QemuServer::CPUConfig qw(print_cpu_device get_cpu_options);
-use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive foreach_drive foreach_volid);
+use PVE::QemuServer::Drive qw(is_valid_drivename drive_is_cloudinit drive_is_cdrom parse_drive print_drive);
use PVE::QemuServer::Machine;
use PVE::QemuServer::Memory;
use PVE::QemuServer::Monitor qw(mon_cmd);
-use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port);
+use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr print_pcie_root_port parse_hostpci);
use PVE::QemuServer::USB qw(parse_usb_device);
my $have_sdn;
},
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.',
+ 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'],
},
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.
+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.
-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);
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 undef if $noerr;
+ die "invalid boot device '$dev'\n";
+}
+
+sub print_bootorder {
+ my ($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_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';
($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;
my $device = '';
my $maxdev = 0;
+ my $drive_id = "$drive->{interface}$drive->{index}";
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 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;
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;
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;
}
return undef 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;
}
return {} if !defined($value->{agent});
- my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $value->{agent}) };
+ my $res = eval { parse_property_string($agent_fmt, $value->{agent}) };
warn $@ if $@;
# if the agent is disabled ignore the other potentially set properties
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;
}
return undef 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;
}
if ($conf->{template}) {
# check if any base image is still used by a linked clone
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
}
# only remove disks owned by this VM
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive, 1);
}
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;
sub check_storage_availability {
my ($storecfg, $conf, $node) = @_;
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
my $volid = $drive->{file};
my $nodehash = { map { $_ => 1 } @$nodelist };
my $nodename = nodename();
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
my $volid = $drive->{file};
my $nodelist = PVE::Cluster::get_nodelist();
my $nodehash = { map { $_ => {} } @$nodelist };
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
my $volid = $drive->{file};
my $audio = $conf->{"audio$id"};
return undef 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 {
};
}
+sub audio_devs {
+ my ($audio, $audiopciaddr, $machine_version) = @_;
+
+ my $devs = [];
+
+ my $id = $audio->{dev_id};
+ my $audiodev = "";
+ if (min_version($machine_version, 4, 2)) {
+ $audiodev = ",audiodev=$audio->{backend_id}";
+ }
+
+ if ($audio->{dev} eq 'AC97') {
+ push @$devs, '-device', "AC97,id=${id}${audiopciaddr}$audiodev";
+ } elsif ($audio->{dev} =~ /intel\-hda$/) {
+ push @$devs, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
+ push @$devs, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0$audiodev";
+ push @$devs, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1$audiodev";
+ } else {
+ die "unkown audio device '$audio->{dev}', implement me!";
+ }
+
+ push @$devs, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+
+ return $devs;
+}
+
sub vga_conf_has_spice {
my ($vga) = @_;
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+)/;
my ($machine_major, $machine_minor) = ($1, $2);
- die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type', please upgrade node '$nodename'\n"
- if !PVE::QemuServer::min_version($kvmver, $machine_major, $machine_minor);
- if (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) {
+ if ($kvmver =~ m/^\d+\.\d+\.(\d+)/ && $1 >= 90) {
+ warn "warning: Installed QEMU version ($kvmver) is a release candidate, ignoring version checks\n";
+ } elsif (!min_version($kvmver, $machine_major, $machine_minor)) {
+ die "Installed QEMU version '$kvmver' is too old to run machine type '$machine_type',"
+ ." please upgrade node '$nodename'\n"
+ } elsif (!PVE::QemuServer::Machine::can_run_pve_machine_version($machine_version, $kvmver)) {
my $max_pve_version = PVE::QemuServer::Machine::get_pve_version($machine_version);
- die "Installed qemu-server (max feature level for $machine_major.$machine_minor is pve$max_pve_version)"
- . " is too old to run machine type '$machine_type', please upgrade node '$nodename'\n";
+ 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);
}
}
- my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch);
if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
- die "uefi base image not found\n" if ! -f $ovmf_code;
+ my ($ovmf_code, $ovmf_vars) = get_ovmf_files($arch);
+ die "uefi base image '$ovmf_code' not found\n" if ! -f $ovmf_code;
- my $path;
- my $format;
+ my ($path, $format);
if (my $efidisk = $conf->{efidisk0}) {
my $d = parse_drive('efidisk0', $efidisk);
my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
}
# 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 $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++;
+ my $bootorder = {};
+ my $boot = parse_property_string($boot_fmt, $conf->{boot}) if $conf->{boot};
+ if (!defined($boot) || $boot->{legacy}) {
+ $bootorder = bootorder_from_legacy($conf, $boot);
+ } elsif ($boot->{order}) {
+ # start at 100 to allow user to insert devices before us with -args
+ my $i = 100;
+ for my $dev (PVE::Tools::split_list($boot->{order})) {
+ $bootorder->{$dev} = $i++;
}
}
+ # host pci device passthrough
+ my ($kvm_off, $gpu_passthrough, $legacy_igd) = PVE::QemuServer::PCI::print_hostpci_devices(
+ $vmid, $conf, $devices, $winversion, $q35, $bridges, $arch, $machine_type, $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"}) {
}
}
- if (my $audio = conf_has_audio($conf)) {
-
+ if (min_version($machine_version, 4, 0) && (my $audio = conf_has_audio($conf))) {
my $audiopciaddr = print_pci_addr("audio0", $bridges, $arch, $machine_type);
-
- my $id = $audio->{dev_id};
- if ($audio->{dev} eq 'AC97') {
- push @$devices, '-device', "AC97,id=${id}${audiopciaddr}";
- } elsif ($audio->{dev} =~ /intel\-hda$/) {
- push @$devices, '-device', "$audio->{dev},id=${id}${audiopciaddr}";
- push @$devices, '-device', "hda-micro,id=${id}-codec0,bus=${id}.0,cad=0";
- push @$devices, '-device', "hda-duplex,id=${id}-codec1,bus=${id}.0,cad=1";
- } else {
- die "unkown audio device '$audio->{dev}', implement me!";
- }
-
- push @$devices, '-audiodev', "$audio->{backend},id=$audio->{backend_id}";
+ my $audio_devs = audio_devs($audio, $audiopciaddr, $machine_version);
+ push @$devices, @$audio_devs;
}
my $sockets = 1;
}
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 {
# time drift fix
my $tdf = defined($conf->{tdf}) ? $conf->{tdf} : $defaults->{tdf};
-
my $useLocaltime = $conf->{localtime};
if ($winversion >= 5) { # windows
push @$rtcFlags, 'driftfix=slew' if $tdf;
- if (($conf->{startdate}) && ($conf->{startdate} ne 'now')) {
+ if ($conf->{startdate} && $conf->{startdate} ne 'now') {
push @$rtcFlags, "base=$conf->{startdate}";
} elsif ($useLocaltime) {
push @$rtcFlags, 'base=localtime';
if ($forcecpu) {
push @$cmd, '-cpu', $forcecpu;
} else {
- push @$cmd, get_cpu_options($conf, $arch, $kvm, $kvm_off,
- $machine_version, $winversion, $gpu_passthrough);
+ push @$cmd, get_cpu_options($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough);
}
PVE::QemuServer::Memory::config($conf, $vmid, $sockets, $cores, $defaults, $hotplug_features, $cmd);
my $rng = parse_rng($conf->{rng0}) if $conf->{rng0};
if ($rng && &$version_guard(4, 1, 2)) {
+ check_rng_source($rng->{source});
+
my $max_bytes = $rng->{max_bytes} // $rng_fmt->{max_bytes}->{default};
my $period = $rng->{period} // $rng_fmt->{period}->{default};
-
my $limiter_str = "";
if ($max_bytes) {
$limiter_str = ",max-bytes=$max_bytes,period=$period";
}
- # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
- die "cannot create VirtIO RNG device: source file '$rng->{source}' doesn't exist\n"
- if ! -e $rng->{source};
-
my $rng_addr = print_pci_addr("rng0", $bridges, $arch, $machine_type);
-
push @$devices, '-object', "rng-random,filename=$rng->{source},id=rng0";
push @$devices, '-device', "virtio-rng-pci,rng=rng0$limiter_str$rng_addr";
}
if ($qxlnum) {
if ($qxlnum > 1) {
if ($winversion){
- for(my $i = 1; $i < $qxlnum; $i++){
- push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges);
+ for (my $i = 1; $i < $qxlnum; $i++){
+ 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";
}
push @$devices, '-iscsi', "initiator-name=$initiator";
}
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
if (PVE::Storage::parse_volume_id($drive->{file}, 1)) {
$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'){
+ if ($drive->{interface} eq 'virtio'){
push @$cmd, '-object', "iothread,id=iothread-$ds" if $drive->{iothread};
}
- if ($drive->{interface} eq 'scsi') {
+ if ($drive->{interface} eq 'scsi') {
my ($maxdev, $controller, $controller_prefix) = scsihw_infos($conf, $drive);
$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};
- $ahcicontroller->{$controller}=1;
+ my $controller = int($drive->{index} / $PVE::QemuServer::Drive::MAX_SATA_DISKS);
+ $pciaddr = print_pci_addr("ahci$controller", $bridges, $arch, $machine_type);
+ push @$devices, '-device', "ahci,id=ahci$controller,multifunction=on$pciaddr"
+ if !$ahcicontroller->{$controller};
+ $ahcicontroller->{$controller}=1;
}
my $drive_cmd = print_drive_commandline_full($storecfg, $vmid, $drive);
+ $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';
+ $d->{bootindex} = $bootorder->{$netname} if $bootorder->{$netname};
- if ($bootindex_hash->{n}) {
- $d->{bootindex} = $bootindex_hash->{n};
- $bootindex_hash->{n} += 1;
- }
+ my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, $netname);
+ push @$devices, '-netdev', $netdevfull;
- my $netdevfull = print_netdev_full($vmid, $conf, $arch, $d, "net$i");
- push @$devices, '-netdev', $netdevfull;
+ my $netdevicefull = print_netdevice_full(
+ $vmid, $conf, $d, $netname, $bridges, $use_old_bios_files, $arch, $machine_type);
- my $netdevicefull = print_netdevice_full($vmid, $conf, $d, "net$i", $bridges, $use_old_bios_files, $arch, $machine_type);
- push @$devices, '-device', $netdevicefull;
+ 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
push @$machineFlags, "type=${machine_type_min}";
push @$cmd, @$devices;
- push @$cmd, '-rtc', join(',', @$rtcFlags)
- if scalar(@$rtcFlags);
- push @$cmd, '-machine', join(',', @$machineFlags)
- if scalar(@$machineFlags);
- push @$cmd, '-global', join(',', @$globalFlags)
- if scalar(@$globalFlags);
+ push @$cmd, '-rtc', join(',', @$rtcFlags) if scalar(@$rtcFlags);
+ push @$cmd, '-machine', join(',', @$machineFlags) if scalar(@$machineFlags);
+ push @$cmd, '-global', join(',', @$globalFlags) if scalar(@$globalFlags);
if (my $vmstate = $conf->{vmstate}) {
my $statepath = PVE::Storage::path($storecfg, $vmstate);
return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
+sub check_rng_source {
+ my ($source) = @_;
+
+ # mostly relevant for /dev/hwrng, but doesn't hurt to check others too
+ die "cannot create VirtIO RNG device: source file '$source' doesn't exist\n"
+ if ! -e $source;
+
+ my $rng_current = '/sys/devices/virtual/misc/hw_random/rng_current';
+ if ($source eq '/dev/hwrng' && file_read_firstline($rng_current) eq 'none') {
+ # Needs to abort, otherwise QEMU crashes on first rng access. Note that rng_current cannot
+ # be changed to 'none' manually, so once the VM is past this point, it'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";
+ }
+}
+
sub spice_port {
my ($vmid) = @_;
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') {
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') {
my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
my %options = split(/[=,]/, $netdev);
+ if (defined(my $vhost = $options{vhost})) {
+ $options{vhost} = JSON::boolean(PVE::JSONSchema::parse_boolean($vhost));
+ }
+
+ if (defined(my $queues = $options{queues})) {
+ $options{queues} = $queues + 0;
+ }
+
mon_cmd($vmid, "netdev_add", %options);
return 1;
}
$running = undef;
my $conf = PVE::QemuConfig->load_config($vmid);
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
$running = 1 if $drive->{file} eq $volid;
});
mon_cmd($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
}
+sub foreach_volid {
+ my ($conf, $func, @param) = @_;
+
+ my $volhash = {};
+
+ my $test_volid = sub {
+ my ($key, $drive, $snapname) = @_;
+
+ my $volid = $drive->{file};
+ return if !$volid;
+
+ $volhash->{$volid}->{cdrom} //= 1;
+ $volhash->{$volid}->{cdrom} = 0 if !drive_is_cdrom($drive);
+
+ my $replicate = $drive->{replicate} // 1;
+ $volhash->{$volid}->{replicate} //= 0;
+ $volhash->{$volid}->{replicate} = 1 if $replicate;
+
+ $volhash->{$volid}->{shared} //= 0;
+ $volhash->{$volid}->{shared} = 1 if $drive->{shared};
+
+ $volhash->{$volid}->{referenced_in_config} //= 0;
+ $volhash->{$volid}->{referenced_in_config} = 1 if !defined($snapname);
+
+ $volhash->{$volid}->{referenced_in_snapshot}->{$snapname} = 1
+ if defined($snapname);
+
+ my $size = $drive->{size};
+ $volhash->{$volid}->{size} //= $size if $size;
+
+ $volhash->{$volid}->{is_vmstate} //= 0;
+ $volhash->{$volid}->{is_vmstate} = 1 if $key eq 'vmstate';
+
+ $volhash->{$volid}->{is_unused} //= 0;
+ $volhash->{$volid}->{is_unused} = 1 if $key =~ /^unused\d+$/;
+ };
+
+ my $include_opts = {
+ extra_keys => ['vmstate'],
+ include_unused => 1,
+ };
+
+ PVE::QemuConfig->foreach_volume_full($conf, $include_opts, $test_volid);
+ foreach my $snapname (keys %{$conf->{snapshots}}) {
+ my $snap = $conf->{snapshots}->{$snapname};
+ PVE::QemuConfig->foreach_volume_full($snap, $include_opts, $test_volid, $snapname);
+ }
+
+ foreach my $volid (keys %$volhash) {
+ &$func($volid, $volhash->{$volid}, @param);
+ }
+}
+
my $fast_plug_option = {
'lock' => 1,
'name' => 1,
# 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) };
+ 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') {
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;
}
}
my ($storecfg, $conf, $replicated_volumes) = @_;
my $local_volumes = {};
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
$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);
- $params->{resume} = PVE::QemuConfig->has_lock($conf, 'suspended');
+ my $has_suspended_lock = PVE::QemuConfig->has_lock($conf, 'suspended');
PVE::QemuConfig->check_lock($conf)
- if !($params->{skiplock} || $params->{resume});
+ if !($params->{skiplock} || $has_suspended_lock);
+
+ $params->{resume} = $has_suspended_lock || defined($conf->{vmstate});
die "VM $vmid already running\n" if check_running($vmid, undef, $migrate_opts->{migratedfrom});
# 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
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);
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};
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
return;
}
} else {
- if ($force) {
+ if (!check_running($vmid, $nocheck)) {
+ warn "Unexpected: VM shutdown command failed, but VM not running anymore..\n";
+ return;
+ }
+ if ($force) {
warn "VM quit/powerdown failed - terminating now with SIGTERM\n";
kill 15, $pid;
} else {
}
- $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 {
sub restore_file_archive {
my ($archive, $vmid, $user, $opts) = @_;
- my $format = $opts->{format};
- my $comp;
-
- if ($archive =~ m/\.tgz$/ || $archive =~ m/\.tar\.gz$/) {
- $format = 'tar' if !$format;
- $comp = 'gzip';
- } elsif ($archive =~ m/\.tar$/) {
- $format = 'tar' if !$format;
- } elsif ($archive =~ m/.tar.lzo$/) {
- $format = 'tar' if !$format;
- $comp = 'lzop';
- } elsif ($archive =~ m/\.vma$/) {
- $format = 'vma' if !$format;
- } elsif ($archive =~ m/\.vma\.gz$/) {
- $format = 'vma' if !$format;
- $comp = 'gzip';
- } elsif ($archive =~ m/\.vma\.lzo$/) {
- $format = 'vma' if !$format;
- $comp = 'lzop';
- } else {
- $format = 'vma' if !$format; # default
- }
+ return restore_vma_archive($archive, $vmid, $user, $opts)
+ if $archive eq '-';
+
+ my $info = PVE::Storage::archive_info($archive);
+ my $format = $opts->{format} // $info->{format};
+ my $comp = $info->{compression};
# try to detect archive format
if ($format eq 'tar') {
my $restore_cleanup_oldconf = sub {
my ($storecfg, $vmid, $oldconf, $virtdev_hash) = @_;
- foreach_drive($oldconf, sub {
+ PVE::QemuConfig->foreach_volume($oldconf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive, 1);
my $drive = parse_drive($virtdev, $2);
if (drive_is_cloudinit($drive)) {
my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
+ $storeid = $options->{storage} if defined ($options->{storage});
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
my $format = qemu_img_format($scfg, $volname); # has 'raw' fallback
$virtdev_hash->{$virtdev} = {
format => $format,
- storeid => $options->{storage} // $storeid,
+ storeid => $storeid,
size => PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_SIZE,
is_cloudinit => 1,
};
$name .= ".$d->{format}" if $d->{format} ne 'raw';
}
- 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;
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 $datastore = $scfg->{datastore};
my $username = $scfg->{username} // 'root@pam';
my $fingerprint = $scfg->{fingerprint};
+ my $keyfile = PVE::Storage::PBSPlugin::pbs_encryption_key_file_name($storecfg, $storeid);
my $repo = "$username\@$server:$datastore";
+
+ # This is only used for `pbs-restore`!
my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
local $ENV{PBS_PASSWORD} = $password;
local $ENV{PBS_FINGERPRINT} = $fingerprint if defined($fingerprint);
}
}
- my $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);
my $path = PVE::Storage::path($storecfg, $volid);
+ # This is the ONLY user of the PBS_ env vars set on top of this function!
my $pbs_restore_cmd = [
'/usr/bin/pbs-restore',
'--repository', $repo,
'--verbose',
];
+ push @$pbs_restore_cmd, '--format', $d->{format} if $d->{format};
+ push @$pbs_restore_cmd, '--keyfile', $keyfile if -e $keyfile;
+
if (PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $volid)) {
push @$pbs_restore_cmd, '--skip-zero';
}
}
if ($comp) {
- my $cmd;
- if ($comp eq 'gzip') {
- $cmd = ['zcat', $readfrom];
- } elsif ($comp eq 'lzop') {
- $cmd = ['lzop', '-d', '-c', $readfrom];
- } else {
- die "unknown compression method '$comp'\n";
- }
+ my $info = PVE::Storage::decompressor_info('vma', $comp);
+ my $cmd = $info->{decompressor};
+ push @$cmd, $readfrom;
$add_pipe->($cmd);
}
# 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) {
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';
}
my $sidhash = {};
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
my $storecfg = PVE::Storage::config();
- foreach_drive($conf, sub {
+ PVE::QemuConfig->foreach_volume($conf, sub {
my ($ds, $drive) = @_;
return if drive_is_cdrom($drive);
} elsif ($drivename eq 'efidisk0') {
$size = get_efivars_size($conf);
}
- $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
+ $size /= 1024;
+ $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, $size);
push @$newvollist, $newvolid;
PVE::Storage::activate_volumes($storecfg, [$newvolid]);
# 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"]);
+ run_command(['qemu-img', 'dd', '-n', '-O', $dst_format, "bs=1", "count=$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);
}
}
$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;
+}
+
# bash completion helper
sub complete_backup_archives {
my $res = [];
foreach my $id (keys %$data) {
foreach my $item (@{$data->{$id}}) {
- next if $item->{format} !~ m/^vma\.(gz|lzo)$/;
+ next if $item->{format} !~ m/^vma\.(${\PVE::Storage::Plugin::COMPRESSOR_RE})$/;
push @$res, $item->{volid} if defined($item->{volid});
}
}