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::GuestHelpers qw(safe_string_ne safe_num_ne safe_boolean_ne);
use PVE::INotify;
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;
#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 => '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'],
+ enum => ['configdrive2', 'nocloud', 'opennebula'],
},
ciuser => {
optional => 1,
# 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;
return $dev if $check->("usb");
return $dev if $check->("hostpci");
- return undef if $noerr;
+ 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);
}
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;
}
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";
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";
}
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_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 $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');
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_watchdog {
my ($value) = @_;
- return undef if !$value;
+ return if !$value;
my $res = eval { parse_property_string($watchdog_fmt, $value) };
warn $@ if $@;
}
sub parse_guest_agent {
- my ($value) = @_;
+ my ($conf) = @_;
- return {} if !defined($value->{agent});
+ return {} if !defined($conf->{agent});
- my $res = eval { 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) = @_;
sub parse_rng {
my ($value) = @_;
- return undef if !$value;
+ return if !$value;
my $res = eval { parse_property_string($rng_fmt, $value) };
warn $@ if $@;
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);
+ 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";
}
}
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 = parse_property_string($audio_fmt, $audio);
my $audiodriver = $audioproperties->{driver} // 'spice';
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";
}
push @$cmd, '-name', $vmname;
+ push @$cmd, '-no-shutdown';
+
my $use_virtio = 0;
my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
push @$devices, '-device', $kbd if defined($kbd);
}
+ my $bootorder = device_bootorder($conf);
+
# 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);
+ $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);
+ $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;
}
}
- 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};
$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};
});
for (my $i = 0; $i < $MAX_NETS; $i++) {
- next if !$conf->{"net$i"};
- my $d = parse_net($conf->{"net$i"});
+ 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';
- 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");
+ 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);
+ $vmid, $conf, $d, $netname, $bridges, $use_old_bios_files, $arch, $machine_type);
push @$devices, '-device', $netdevicefull;
}
} 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;
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+)$/) {
}
-# 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) = @_;
sub set_migration_caps {
my ($vmid) = @_;
+ my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
+
my $cap_ref = [];
my $enabled_cap = {
"xbzrle" => 1,
"x-rdma-pin-all" => 0,
"zero-blocks" => 0,
- "compress" => 0
+ "compress" => 0,
+ "dirty-bitmaps" => $qemu_support->{'pbs-dirty-bitmap-migration'} ? 1 : 0,
};
my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
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 { 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 {
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');
+
+ 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} || $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};
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 {
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");
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(
};
my $restore_update_config_line = sub {
- my ($outfd, $cookie, $vmid, $map, $line, $unique) = @_;
+ my ($cookie, $vmid, $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 $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 ($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 = "$username\@$server:$datastore";
+ my $repo = PVE::PBSClient::get_repository($scfg);
# This is only used for `pbs-restore`!
my $password = PVE::Storage::PBSPlugin::pbs_get_password($scfg, $storeid);
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 = {};
$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,
+ $vmid,
+ $map,
+ $line,
+ $options->{unique},
+ );
}
$fh->close();
- $outfd->close();
};
my $err = $@;
rmtree $tmpdir;
if ($err) {
- unlink $tmpfn;
$restore_destroy_volumes->($storecfg, $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
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;
$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,
+ $vmid,
+ $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
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,
+ $vmid,
+ $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 {
$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);
}
- $size /= 1024;
- $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, $size);
+ $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",
+
+ # 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);
}
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;
}
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;
+}
+
# 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;