use UUID;
use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::DataCenterConfig;
use PVE::Exception qw(raise raise_param_exc);
use PVE::GuestHelpers;
use PVE::INotify;
use PVE::JSONSchema qw(get_standard_option);
use PVE::ProcFSTools;
use PVE::RPCEnvironment;
-use PVE::SafeSyslog;
use PVE::Storage;
use PVE::SysFSTools;
use PVE::Systemd;
use PVE::QMPClient;
use PVE::QemuConfig;
+use PVE::QemuServer::Helpers qw(min_version);
use PVE::QemuServer::Cloudinit;
+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::USB qw(parse_usb_device);
],
};
-my $qemu_snap_storage = { rbd => 1 };
-
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
my $QEMU_FORMAT_RE = qr/raw|cow|qcow|qcow2|qed|vmdk|cloop/;
PVE::JSONSchema::register_standard_option('pve-qemu-machine', {
description => "Specifies the Qemu machine type.",
type => 'string',
- pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\.pxe)?|virt(?:-\d+(\.\d+)+)?)',
+ pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)',
maxLength => 40,
optional => 1,
});
}
-my $nodename = PVE::INotify::nodename();
-
-mkdir "/etc/pve/nodes/$nodename";
-my $confdir = "/etc/pve/nodes/$nodename/qemu-server";
-mkdir $confdir;
-
-my $var_run_tmpdir = "/var/run/qemu-server";
-mkdir $var_run_tmpdir;
-
-my $lock_dir = "/var/lock/qemu-server";
-mkdir $lock_dir;
+my $nodename_cache;
+sub nodename {
+ $nodename_cache //= PVE::INotify::nodename();
+ return $nodename_cache;
+}
my $cpu_vendor_list = {
# Intel CPUs
optional => 1,
default => 0
},
+ type => {
+ description => "Select the agent type",
+ type => 'string',
+ default => 'virtio',
+ optional => 1,
+ enum => [qw(virtio isa)],
+ },
};
my $vga_fmt = {
win8;; Microsoft Windows 8/2012/2012r2
win10;; Microsoft Windows 10/2016
l24;; Linux 2.4 Kernel
-l26;; Linux 2.6/3.X Kernel
+l26;; Linux 2.6 - 5.X Kernel
solaris;; Solaris/OpenSolaris/OpenIndiania kernel
EODESC
},
description => "Configure additional enhancements for SPICE.",
optional => 1
},
+ tags => {
+ type => 'string', format => 'pve-tag-list',
+ description => 'Tags of the VM. This is only meta information.',
+ optional => 1,
+ },
};
my $cicustom_fmt = {
};
PVE::JSONSchema::register_standard_option("pve-qm-usb", $usbdesc);
-my $PCIRE = qr/[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
+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,
return defined($confdesc->{$key});
}
-sub nic_models {
- return $nic_model_list;
-}
-
-sub os_list_description {
-
- return {
- other => 'Other',
- wxp => 'Windows XP',
- w2k => 'Windows 2000',
- w2k3 =>, 'Windows 2003',
- w2k8 => 'Windows 2008',
- wvista => 'Windows Vista',
- win7 => 'Windows 7',
- win8 => 'Windows 8/2012',
- win10 => 'Windows 10/2016',
- l24 => 'Linux 2.4',
- l26 => 'Linux 2.6',
- };
-}
-
my $cdrom_path;
-
sub get_cdrom_path {
return $cdrom_path if $cdrom_path;
}
sub print_drive {
- my ($vmid, $drive) = @_;
+ my ($drive) = @_;
my $data = { %$drive };
delete $data->{$_} for qw(index interface);
return PVE::JSONSchema::print_property_string($data, $alldrive_fmt);
return $res;
}
-sub machine_type_is_q35 {
- my ($conf) = @_;
-
- return $conf->{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0;
-}
-
sub print_tabletdevice_full {
my ($conf, $arch) = @_;
- my $q35 = machine_type_is_q35($conf);
+ my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
# we use uhci for old VMs because tablet driver was buggy in older qemu
my $usbbus;
- if (machine_type_is_q35($conf) || $arch eq 'aarch64') {
+ if (PVE::QemuServer::Machine::machine_type_is_q35($conf) || $arch eq 'aarch64') {
$usbbus = 'ehci';
} else {
$usbbus = 'uhci';
}
# for compatibility only, we prefer scsi-hd (#2408, #2355, #2380)
+ my $version = PVE::QemuServer::Machine::extract_version($machine_type, kvm_user_version());
if ($path =~ m/^iscsi\:\/\// &&
- !qemu_machine_feature_enabled($machine_type, undef, 4, 1)) {
+ !min_version($version, 4, 1)) {
$devicetype = 'generic';
}
}
};
sub print_vga_device {
- my ($conf, $vga, $arch, $machine, $id, $qxlnum, $bridges) = @_;
+ my ($conf, $vga, $arch, $machine_version, $machine, $id, $qxlnum, $bridges) = @_;
my $type = $vga_map->{$vga->{type}};
if ($arch eq 'aarch64' && defined($type) && $type eq 'virtio-vga') {
$type = 'virtio-gpu';
}
my $vgamem_mb = $vga->{memory};
+
+ my $max_outputs = '';
if ($qxlnum) {
$type = $id ? 'qxl' : 'qxl-vga';
+
+ if (!$conf->{ostype} || $conf->{ostype} =~ m/^(?:l\d\d)|(?:other)$/) {
+ # set max outputs so linux can have up to 4 qxl displays with one device
+ if (min_version($machine_version, 4, 1)) {
+ $max_outputs = ",max_outputs=4";
+ }
+ }
}
+
die "no devicetype for $vga->{type}\n" if !$type;
my $memory = "";
$memory = ",ram_size=67108864,vram_size=33554432";
}
- my $q35 = machine_type_is_q35($conf);
+ 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}${pciaddr}";
+ return "$type,id=${vgaid}${memory}${max_outputs}${pciaddr}";
}
sub drive_is_cloudinit {
my @idlist = split(/;/, $res->{host});
delete $res->{host};
foreach my $id (@idlist) {
- if ($id =~ m/\./) { # full id 00:00.1
- push @{$res->{pciid}}, {
- id => $id,
- };
- } else { # partial id 00:00
- $res->{pciid} = PVE::SysFSTools::lspci($id);
- }
+ my $devs = PVE::SysFSTools::lspci($id);
+ die "no PCI device found for '$id'\n" if !scalar(@$devs);
+ push @{$res->{pciid}}, @$devs;
}
return $res;
}
}
sub destroy_vm {
- my ($storecfg, $vmid, $keep_empty_config, $skiplock) = @_;
+ my ($storecfg, $vmid, $skiplock, $replacement_conf) = @_;
my $conf = PVE::QemuConfig->load_config($vmid);
# check if any base image is still used by a linked clone
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
-
return if drive_is_cdrom($drive);
my $volid = $drive->{file};
-
return if !$volid || $volid =~ m|^/|;
die "base volume '$volid' is still in use by linked cloned\n"
# only remove disks owned by this VM
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
-
return if drive_is_cdrom($drive, 1);
my $volid = $drive->{file};
-
return if !$volid || $volid =~ m|^/|;
my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
return if !$path || !$owner || ($owner != $vmid);
- eval {
- PVE::Storage::vdisk_free($storecfg, $volid);
- };
+ eval { PVE::Storage::vdisk_free($storecfg, $volid) };
warn "Could not remove disk '$volid', check manually: $@" if $@;
-
});
# also remove unused disk
- eval {
- my $dl = PVE::Storage::vdisk_list($storecfg, undef, $vmid);
-
- eval {
- PVE::Storage::foreach_volid($dl, sub {
- my ($volid, $sid, $volname, $d) = @_;
- PVE::Storage::vdisk_free($storecfg, $volid);
- });
- };
+ 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 $@;
+ });
- };
- warn $@ if $@;
-
- if ($keep_empty_config) {
- PVE::QemuConfig->write_config($vmid, { memory => 128 });
+ if (defined $replacement_conf) {
+ PVE::QemuConfig->write_config($vmid, $replacement_conf);
} else {
PVE::QemuConfig->destroy_config($vmid);
}
my $v = parse_drive($key, $value);
if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) {
$v->{file} = $volid;
- $value = print_drive($vmid, $v);
+ $value = print_drive($v);
} else {
warn "vm $vmid - unable to parse value of '$key'\n";
next;
&$cleanup_config($conf->{pending}, 1);
foreach my $snapname (keys %{$conf->{snapshots}}) {
- die "internal error" if $snapname eq 'pending';
+ die "internal error: snapshot name '$snapname' is forbidden" if lc($snapname) eq 'pending';
&$cleanup_config($conf->{snapshots}->{$snapname}, undef, $snapname);
}
my $res = {};
return $res if !$vmlist || !$vmlist->{ids};
my $ids = $vmlist->{ids};
+ my $nodename = nodename();
foreach my $vmid (keys %$ids) {
my $d = $ids->{$vmid};
my $nodelist = PVE::Cluster::get_nodelist();
my $nodehash = { map { $_ => 1 } @$nodelist };
- my $nodename = PVE::INotify::nodename();
+ my $nodename = nodename();
foreach_drive($conf, sub {
my ($ds, $drive) = @_;
return $nodehash
}
-sub check_cmdline {
- my ($pidfile, $pid) = @_;
-
- my $fh = IO::File->new("/proc/$pid/cmdline", "r");
- if (defined($fh)) {
- my $line = <$fh>;
- $fh->close;
- return undef if !$line;
- my @param = split(/\0/, $line);
-
- my $cmd = $param[0];
- return if !$cmd || ($cmd !~ m|kvm$| && $cmd !~ m@(?:^|/)qemu-system-[^/]+$@);
-
- for (my $i = 0; $i < scalar (@param); $i++) {
- my $p = $param[$i];
- next if !$p;
- if (($p eq '-pidfile') || ($p eq '--pidfile')) {
- my $p = $param[$i+1];
- return 1 if $p && ($p eq $pidfile);
- return undef;
- }
- }
- }
- return undef;
-}
-
+# Compat only, use assert_config_exists_on_node and vm_running_locally where possible
sub check_running {
my ($vmid, $nocheck, $node) = @_;
- my $filename = PVE::QemuConfig->config_file($vmid, $node);
-
- die "unable to find configuration file for VM $vmid - no such machine\n"
- if !$nocheck && ! -f $filename;
-
- my $pidfile = pidfile_name($vmid);
-
- if (my $fd = IO::File->new("<$pidfile")) {
- my $st = stat($fd);
- my $line = <$fd>;
- close($fd);
-
- my $mtime = $st->mtime;
- if ($mtime > time()) {
- warn "file '$filename' modified in future\n";
- }
-
- if ($line =~ m/^(\d+)$/) {
- my $pid = $1;
- if (check_cmdline($pidfile, $pid)) {
- if (my $pinfo = PVE::ProcFSTools::check_process_running($pid)) {
- return $pid;
- }
- }
- }
- }
-
- return undef;
+ PVE::QemuConfig::assert_config_exists_on_node($vmid, $node) if !$nocheck;
+ return PVE::QemuServer::Helpers::vm_running_locally($vmid);
}
sub vzlist {
my $vzlist = config_list();
- my $fd = IO::Dir->new($var_run_tmpdir) || return $vzlist;
+ my $fd = IO::Dir->new($PVE::QemuServer::Helpers::var_run_tmpdir) || return $vzlist;
while (defined(my $de = $fd->read)) {
next if $de !~ m/^(\d+)\.pid$/;
description => "The current config lock, if any.",
type => 'string',
optional => 1,
- }
+ },
+ tags => {
+ description => "The current configured tags, if any",
+ type => 'string',
+ optional => 1,
+ },
};
my $last_proc_pid_stat;
$d->{serial} = 1 if conf_has_serial($conf);
$d->{lock} = $conf->{lock} if $conf->{lock};
+ $d->{tags} = $conf->{tags} if defined($conf->{tags});
$res->{$vmid} = $d;
}
return get_host_arch() eq $arch;
}
+sub get_vm_arch {
+ my ($conf) = @_;
+ return $conf->{arch} // get_host_arch();
+}
+
my $default_machines = {
x86_64 => 'pc',
aarch64 => 'virt',
};
-sub get_basic_machine_info {
- my ($conf, $forcemachine) = @_;
+sub get_vm_machine {
+ my ($conf, $forcemachine, $arch, $add_pve_version) = @_;
+
+ my $machine = $forcemachine || $conf->{machine};
- my $arch = $conf->{arch} // get_host_arch();
- my $machine = $forcemachine || $conf->{machine} || $default_machines->{$arch};
- return ($arch, $machine);
+ if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) {
+ $arch //= 'x86_64';
+ $machine ||= $default_machines->{$arch};
+ $machine .= "+pve$PVE::QemuServer::Machine::PVE_MACHINE_VERSION" if $add_pve_version;
+ }
+
+ return $machine;
}
sub get_ovmf_files($) {
}
sub get_cpu_options {
- my ($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $winversion, $gpu_passthrough) = @_;
+ my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
my $cpuFlags = [];
my $ostype = $conf->{ostype};
push @$cpuFlags , '+lahf_lm' if $cpu eq 'kvm64' && $arch eq 'x86_64';
- push @$cpuFlags , '-x2apic'
- if $conf->{ostype} && $conf->{ostype} eq 'solaris';
+ push @$cpuFlags , '-x2apic' if $ostype && $ostype eq 'solaris';
push @$cpuFlags, '+sep' if $cpu eq 'kvm64' || $cpu eq 'kvm32';
push @$cpuFlags, '-rdtscp' if $cpu =~ m/^Opteron/;
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3) && $arch eq 'x86_64') {
+ if (min_version($machine_version, 2, 3) && $arch eq 'x86_64') {
push @$cpuFlags , '+kvm_pv_unhalt' if $kvm;
push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
}
- add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
+ add_hyperv_enlightenments($cpuFlags, $winversion, $machine_version, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
my $ostype = $conf->{ostype};
my $winversion = windows_version($ostype);
my $kvm = $conf->{kvm};
+ my $nodename = nodename();
- my ($arch, $machine_type) = get_basic_machine_info($conf, $forcemachine);
+ my $arch = get_vm_arch($conf);
my $kvm_binary = get_command_for_arch($arch);
my $kvmver = kvm_user_version($kvm_binary);
+
+ 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);
$kvm //= 1 if is_native($arch);
+ $machine_version =~ m/(\d+)\.(\d+)/;
+ 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, $1, $2);
+
if ($kvm) {
die "KVM virtualisation configured, but not available. Either disable in VM configuration or enable in BIOS.\n"
if !defined kvm_version();
die "detected old qemu-kvm binary ($kvmver)\n" if $vernum < 15000;
- my $q35 = machine_type_is_q35($conf);
+ my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
my $hotplug_features = parse_hotplug_features(defined($conf->{hotplug}) ? $conf->{hotplug} : '1');
my $use_old_bios_files = undef;
($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
my $use_virtio = 0;
- my $qmpsocket = qmp_socket($vmid);
+ my $qmpsocket = PVE::QemuServer::Helpers::qmp_socket($vmid);
push @$cmd, '-chardev', "socket,id=qmp,path=$qmpsocket,server,nowait";
push @$cmd, '-mon', "chardev=qmp,mode=control";
- if (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 12)) {
+ if (min_version($machine_version, 2, 12)) {
push @$cmd, '-chardev', "socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5";
push @$cmd, '-mon', "chardev=qmp-event,mode=control";
}
- push @$cmd, '-pidfile' , pidfile_name($vmid);
+ push @$cmd, '-pidfile' , PVE::QemuServer::Helpers::pidfile_name($vmid);
push @$cmd, '-daemonize';
# load q35 config
if ($q35) {
# we use different pcie-port hardware for qemu >= 4.0 for passthrough
- if (qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0)) {
+ if (min_version($machine_version, 4, 0)) {
push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35-4.0.cfg';
} else {
push @$devices, '-readconfig', '/usr/share/qemu-server/pve-q35.cfg';
if (!$vga->{type}) {
if ($arch eq 'aarch64') {
$vga->{type} = 'virtio';
- } elsif (qemu_machine_feature_enabled($machine_type, $kvmver, 2, 9)) {
+ } elsif (min_version($machine_version, 2, 9)) {
$vga->{type} = (!$winversion || $winversion >= 6) ? 'std' : 'cirrus';
} else {
$vga->{type} = ($winversion >= 6) ? 'std' : 'cirrus';
if ($d->{mdev} && scalar(@$pcidevices) == 1) {
my $pci_id = $pcidevices->[0]->{id};
my $uuid = PVE::SysFSTools::generate_mdev_uuid($vmid, $i);
- $sysfspath = "/sys/bus/pci/devices/0000:$pci_id/$uuid";
+ $sysfspath = "/sys/bus/pci/devices/$pci_id/$uuid";
} elsif ($d->{mdev}) {
warn "ignoring mediated device '$id' with multifunction device\n";
}
# usb devices
my $usb_dev_features = {};
- $usb_dev_features->{spice_usb3} = 1 if qemu_machine_feature_enabled($machine_type, $kvmver, 4, 0);
+ $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);
push @$devices, @usbdevices if @usbdevices;
die "MAX $allowed_vcpus vcpus allowed per VM on this node\n"
if ($allowed_vcpus < $maxcpus);
- if($hotplug_features->{cpu} && qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 7)) {
+ if($hotplug_features->{cpu} && min_version($machine_version, 2, 7)) {
push @$cmd, '-smp', "1,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
for (my $i = 2; $i <= $vcpus; $i++) {
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_type, undef, $qxlnum, $bridges);
- my $socket = vnc_socket($vmid);
+ 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 {
push @$cmd, '-vga', 'none' if $vga->{type} eq 'none';
push @$rtcFlags, 'base=localtime';
}
- push @$cmd, get_cpu_options($conf, $arch, $kvm, $machine_type, $kvm_off, $kvmver, $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);
push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
- if (parse_guest_agent($conf)->{enabled}) {
- my $qgasocket = qmp_socket($vmid, 1);
- my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
+ my $guest_agent = parse_guest_agent($conf);
+
+ if ($guest_agent->{enabled}) {
+ my $qgasocket = PVE::QemuServer::Helpers::qmp_socket($vmid, 1);
push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
- push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
- push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
+
+ if (!$guest_agent->{type} || $guest_agent->{type} eq 'virtio') {
+ my $pciaddr = print_pci_addr("qga0", $bridges, $arch, $machine_type);
+ push @$devices, '-device', "virtio-serial,id=qga0$pciaddr";
+ push @$devices, '-device', 'virtserialport,chardev=qga0,name=org.qemu.guest_agent.0';
+ } elsif ($guest_agent->{type} eq 'isa') {
+ push @$devices, '-device', "isa-serial,chardev=qga0";
+ }
}
my $spice_port;
if ($qxlnum > 1) {
if ($winversion){
for(my $i = 1; $i < $qxlnum; $i++){
- push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_type, $i, $qxlnum, $bridges);
+ push @$devices, '-device', print_vga_device($conf, $vga, $arch, $machine_version, $machine_type, $i, $qxlnum, $bridges);
}
} else {
# assume other OS works like Linux
my $pciaddr = print_pci_addr("spice", $bridges, $arch, $machine_type);
- my $nodename = PVE::INotify::nodename();
my $pfamily = PVE::Tools::get_host_address_family($nodename);
my @nodeaddrs = PVE::Tools::getaddrinfo_all('localhost', family => $pfamily);
die "failed to get an ip address of type $pfamily for 'localhost'\n" if !@nodeaddrs;
if (!$q35) {
# add pci bridges
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+ if (min_version($machine_version, 2, 3)) {
$bridges->{1} = 1;
$bridges->{2} = 1;
}
my $statepath = PVE::Storage::path($storecfg, $vmstate);
push @$vollist, $vmstate;
push @$cmd, '-loadstate', $statepath;
+ print "activating and using '$vmstate' as vmstate\n";
}
# add custom args
return wantarray ? ($cmd, $vollist, $spice_port) : $cmd;
}
-sub vnc_socket {
- my ($vmid) = @_;
- return "${var_run_tmpdir}/$vmid.vnc";
-}
-
sub spice_port {
my ($vmid) = @_;
- my $res = vm_mon_cmd($vmid, 'query-spice');
+ my $res = mon_cmd($vmid, 'query-spice');
return $res->{'tls-port'} || $res->{'port'} || die "no spice port\n";
}
-sub qmp_socket {
- my ($vmid, $qga) = @_;
- my $sockettype = $qga ? 'qga' : 'qmp';
- return "${var_run_tmpdir}/$vmid.$sockettype";
-}
-
-sub pidfile_name {
- my ($vmid) = @_;
- return "${var_run_tmpdir}/$vmid.pid";
-}
-
sub vm_devices_list {
my ($vmid) = @_;
- my $res = vm_mon_cmd($vmid, 'query-pci');
+ my $res = mon_cmd($vmid, 'query-pci');
my $devices_to_check = [];
my $devices = {};
foreach my $pcibus (@$res) {
$devices_to_check = $to_check;
}
- my $resblock = vm_mon_cmd($vmid, 'query-block');
+ my $resblock = mon_cmd($vmid, 'query-block');
foreach my $block (@$resblock) {
if($block->{device} =~ m/^drive-(\S+)/){
$devices->{$1} = 1;
}
}
- my $resmice = vm_mon_cmd($vmid, 'query-mice');
+ my $resmice = mon_cmd($vmid, 'query-mice');
foreach my $mice (@$resmice) {
if ($mice->{name} eq 'QEMU HID Tablet') {
$devices->{tablet} = 1;
# for usb devices there is no query-usb
# but we can iterate over the entries in
# qom-list path=/machine/peripheral
- my $resperipheral = vm_mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');
+ my $resperipheral = mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');
foreach my $per (@$resperipheral) {
if ($per->{name} =~ m/^usb\d+$/) {
$devices->{$per->{name}} = 1;
sub vm_deviceplug {
my ($storecfg, $conf, $vmid, $deviceid, $device, $arch, $machine_type) = @_;
- my $q35 = machine_type_is_q35($conf);
+ my $q35 = PVE::QemuServer::Machine::machine_type_is_q35($conf);
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
- my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+ my $machine_type = PVE::QemuServer::Machine::qemu_machine_pxe($vmid, $conf);
my $use_old_bios_files = undef;
($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
$devicefull = "driver=".$devicefull;
my %options = split(/[=,]/, $devicefull);
- vm_mon_cmd($vmid, "device_add" , %options);
+ mon_cmd($vmid, "device_add" , %options);
}
sub qemu_devicedel {
my ($vmid, $deviceid) = @_;
- my $ret = vm_mon_cmd($vmid, "device_del", id => $deviceid);
+ my $ret = mon_cmd($vmid, "device_del", id => $deviceid);
}
sub qemu_iothread_add {
sub qemu_objectadd {
my($vmid, $objectid, $qomtype) = @_;
- vm_mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
+ mon_cmd($vmid, "object-add", id => $objectid, "qom-type" => $qomtype);
return 1;
}
sub qemu_objectdel {
my($vmid, $objectid) = @_;
- vm_mon_cmd($vmid, "object-del", id => $objectid);
+ mon_cmd($vmid, "object-del", id => $objectid);
return 1;
}
my $drive = print_drive_full($storecfg, $vmid, $device);
$drive =~ s/\\/\\\\/g;
- my $ret = vm_human_monitor_command($vmid, "drive_add auto \"$drive\"");
+ my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_add auto \"$drive\"");
# If the command succeeds qemu prints: "OK"
return 1 if $ret =~ m/OK/s;
sub qemu_drivedel {
my($vmid, $deviceid) = @_;
- my $ret = vm_human_monitor_command($vmid, "drive_del drive-$deviceid");
+ my $ret = PVE::QemuServer::Monitor::hmp_cmd($vmid, "drive_del drive-$deviceid");
$ret =~ s/^\s+//;
return 1 if $ret eq "";
sub qemu_set_link_status {
my ($vmid, $device, $up) = @_;
- vm_mon_cmd($vmid, "set_link", name => $device,
+ mon_cmd($vmid, "set_link", name => $device,
up => $up ? JSON::true : JSON::false);
}
my $netdev = print_netdev_full($vmid, $conf, $arch, $device, $deviceid, 1);
my %options = split(/[=,]/, $netdev);
- vm_mon_cmd($vmid, "netdev_add", %options);
+ mon_cmd($vmid, "netdev_add", %options);
return 1;
}
sub qemu_netdevdel {
my ($vmid, $deviceid) = @_;
- vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
+ mon_cmd($vmid, "netdev_del", id => $deviceid);
}
sub qemu_usb_hotplug {
sub qemu_cpu_hotplug {
my ($vmid, $conf, $vcpus) = @_;
- my $machine_type = PVE::QemuServer::get_current_qemu_machine($vmid);
+ my $machine_type = PVE::QemuServer::Machine::get_current_qemu_machine($vmid);
my $sockets = 1;
$sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
if ($vcpus < $currentvcpus) {
- if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+ if (PVE::QemuServer::Machine::machine_version($machine_type, 2, 7)) {
for (my $i = $currentvcpus; $i > $vcpus; $i--) {
qemu_devicedel($vmid, "cpu$i");
my $retry = 0;
my $currentrunningvcpus = undef;
while (1) {
- $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+ $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
last if scalar(@{$currentrunningvcpus}) == $i-1;
raise_param_exc({ vcpus => "error unplugging cpu$i" }) if $retry > 5;
$retry++;
return;
}
- my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+ my $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
die "vcpus in running vm does not match its configuration\n"
if scalar(@{$currentrunningvcpus}) != $currentvcpus;
- if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+ if (PVE::QemuServer::Machine::machine_version($machine_type, 2, 7)) {
for (my $i = $currentvcpus+1; $i <= $vcpus; $i++) {
my $cpustr = print_cpu_device($conf, $i);
my $retry = 0;
my $currentrunningvcpus = undef;
while (1) {
- $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+ $currentrunningvcpus = mon_cmd($vmid, "query-cpus");
last if scalar(@{$currentrunningvcpus}) == $i;
raise_param_exc({ vcpus => "error hotplugging cpu$i" }) if $retry > 10;
sleep 1;
} else {
for (my $i = $currentvcpus; $i < $vcpus; $i++) {
- vm_mon_cmd($vmid, "cpu-add", id => int($i));
+ mon_cmd($vmid, "cpu-add", id => int($i));
}
}
}
return if !check_running($vmid) ;
- vm_mon_cmd($vmid, "block_set_io_throttle", device => $deviceid,
+ mon_cmd($vmid, "block_set_io_throttle", device => $deviceid,
bps => int($bps),
bps_rd => int($bps_rd),
bps_wr => int($bps_wr),
return if !$running;
- vm_mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
+ mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
}
my $running = check_running($vmid);
if ($running && do_snapshots_with_qemu($storecfg, $volid)){
- vm_mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
+ mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
} else {
PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
}
}
if ($running && do_snapshots_with_qemu($storecfg, $volid)){
- vm_mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
+ mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
} else {
PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
}
"compress" => 0
};
- my $supported_capabilities = vm_mon_cmd_nocheck($vmid, "query-migrate-capabilities");
+ my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
for my $supported_capability (@$supported_capabilities) {
push @$cap_ref, {
};
}
- vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
+ mon_cmd($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
}
my $fast_plug_option = {
'protection' => 1,
'vmstatestorage' => 1,
'hookscript' => 1,
+ 'tags' => 1,
};
# hotplug changes in [PENDING]
my ($vmid, $conf, $storecfg, $selection, $errors) = @_;
my $defaults = load_defaults();
- my ($arch, $machine_type) = get_basic_machine_info($conf, undef);
+ my $arch = get_vm_arch($conf);
+ my $machine_type = get_vm_machine($conf, undef, $arch);
# commit values which do not have any impact on running VM first
# Note: those option cannot raise errors, we we do not care about
die "skip\n" if defined($conf->{balloon}) && $conf->{balloon} == 0;
# here we reset the ballooning value to memory
my $balloon = $conf->{memory} || $defaults->{memory};
- vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+ mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
} elsif ($fast_plug_option->{$opt}) {
# do nothing
} elsif ($opt =~ m/^net(\d+)$/) {
}
}
- my $apply_pending_cloudinit;
+ my ($apply_pending_cloudinit, $apply_pending_cloudinit_done);
$apply_pending_cloudinit = sub {
+ return if $apply_pending_cloudinit_done; # once is enough
+ $apply_pending_cloudinit_done = 1; # once is enough
+
my ($key, $value) = @_;
- $apply_pending_cloudinit = sub {}; # once is enough
my @cloudinit_opts = keys %$confdesc_cloudinit;
foreach my $opt (keys %{$conf->{pending}}) {
# allow manual ballooning if shares is set to zero
if ((defined($conf->{shares}) && ($conf->{shares} == 0))) {
my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
- vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+ mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
}
} elsif ($opt =~ m/^net(\d+)$/) {
# some changes can be done without hotplug
vmconfig_update_net($storecfg, $conf, $hotplug_features->{network},
$vmid, $opt, $value, $arch, $machine_type);
} elsif (is_valid_drivename($opt)) {
+ die "skip\n" if $opt eq 'efidisk0';
# some changes can be done without hotplug
my $drive = parse_drive($opt, $value);
if (drive_is_cloudinit($drive)) {
} else { # cdrom
if ($drive->{file} eq 'none') {
- vm_mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$opt");
+ mon_cmd($vmid, "eject",force => JSON::true,device => "drive-$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});
- vm_mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
- vm_mon_cmd($vmid, "change", device => "drive-$opt",target => "$path") if $path;
+ mon_cmd($vmid, "eject", force => JSON::true,device => "drive-$opt"); # force eject if locked
+ mon_cmd($vmid, "change", device => "drive-$opt",target => "$path") if $path;
}
return 1;
my $newdrive = $drive;
$newdrive->{format} = $format;
$newdrive->{file} = $newvolid;
- my $drivestr = PVE::QemuServer::print_drive($vmid, $newdrive);
+ my $drivestr = print_drive($newdrive);
$local_volumes->{$opt} = $drivestr;
#pass drive to conf for command line
$conf->{$opt} = $drivestr;
my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
+ my $migration_ip;
+ my $get_migration_ip = sub {
+ my ($cidr, $nodename) = @_;
+
+ return $migration_ip if defined($migration_ip);
+
+ if (!defined($cidr)) {
+ my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
+ $cidr = $dc_conf->{migration}->{network};
+ }
+
+ if (defined($cidr)) {
+ my $ips = PVE::Network::get_local_ip_from_cidr($cidr);
+
+ die "could not get IP: no address configured on local " .
+ "node for network '$cidr'\n" if scalar(@$ips) == 0;
+
+ die "could not get IP: multiple addresses configured on local " .
+ "node for network '$cidr'\n" if scalar(@$ips) > 1;
+
+ $migration_ip = @$ips[0];
+ }
+
+ $migration_ip = PVE::Cluster::remote_node_ip($nodename, 1)
+ if !defined($migration_ip);
+
+ return $migration_ip;
+ };
+
my $migrate_uri;
if ($statefile) {
if ($statefile eq 'tcp') {
my $localip = "localhost";
my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
- my $nodename = PVE::INotify::nodename();
+ my $nodename = nodename();
if (!defined($migration_type)) {
if (defined($datacenterconf->{migration}->{type})) {
}
if ($migration_type eq 'insecure') {
- my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
- if ($migrate_network_addr) {
- $localip = $migrate_network_addr;
- } else {
- $localip = PVE::Cluster::remote_node_ip($nodename, 1);
- }
-
+ $localip = $get_migration_ip->($migration_network, $nodename);
$localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
}
foreach my $pcidevice (@$pcidevices) {
my $pciid = $pcidevice->{id};
- my $info = PVE::SysFSTools::pci_device_info("0000:$pciid");
+ my $info = PVE::SysFSTools::pci_device_info("$pciid");
die "IOMMU not present\n" if !PVE::SysFSTools::check_iommu_support();
die "no pci device info for device '$pciid'\n" if !$info;
print "migration listens on $migrate_uri\n" if $migrate_uri;
if ($statefile && $statefile ne 'tcp' && $statefile ne 'unix') {
- eval { vm_mon_cmd_nocheck($vmid, "cont"); };
+ eval { mon_cmd($vmid, "cont"); };
warn $@ if $@;
}
#start nbd server for storage migration
if ($targetstorage) {
- my $nodename = PVE::INotify::nodename();
- my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
- my $localip = $migrate_network_addr ? $migrate_network_addr : PVE::Cluster::remote_node_ip($nodename, 1);
+ my $nodename = nodename();
+ my $localip = $get_migration_ip->($migration_network, $nodename);
my $pfamily = PVE::Tools::get_host_address_family($nodename);
my $storage_migrate_port = PVE::Tools::next_migrate_port($pfamily);
- vm_mon_cmd_nocheck($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);
foreach my $opt (sort keys %$local_volumes) {
my $volid = $local_volumes->{$opt};
- vm_mon_cmd_nocheck($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
+ mon_cmd($vmid, "nbd-server-add", device => "drive-$opt", writable => JSON::true );
my $migrate_storage_uri = "nbd:${localip}:${storage_migrate_port}:exportname=drive-$opt";
print "storage migration listens on $migrate_storage_uri volume:$volid\n";
}
if ($spice_port) {
print "spice listens on port $spice_port\n";
if ($spice_ticket) {
- vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
- vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
+ mon_cmd($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
+ mon_cmd($vmid, "expire_password", protocol => 'spice', time => "+30");
}
}
} else {
- vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+ mon_cmd($vmid, "balloon", value => $conf->{balloon}*1024*1024)
if !$statefile && $conf->{balloon};
foreach my $opt (keys %$conf) {
}
}
- vm_mon_cmd_nocheck($vmid, 'qom-set',
+ mon_cmd($vmid, 'qom-set',
path => "machine/peripheral/balloon0",
property => "guest-stats-polling-interval",
value => 2) if (!defined($conf->{balloon}) || $conf->{balloon});
- if ($is_suspended && (my $vmstate = $conf->{vmstate})) {
+ if ($is_suspended) {
print "Resumed VM, removing state\n";
+ if (my $vmstate = $conf->{vmstate}) {
+ PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+ PVE::Storage::vdisk_free($storecfg, $vmstate);
+ }
delete $conf->@{qw(lock vmstate runningmachine)};
- PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
- PVE::Storage::vdisk_free($storecfg, $vmstate);
PVE::QemuConfig->write_config($vmid, $conf);
}
});
}
-sub vm_mon_cmd {
- my ($vmid, $execute, %params) = @_;
-
- my $cmd = { execute => $execute, arguments => \%params };
- vm_qmp_command($vmid, $cmd);
-}
-
-sub vm_mon_cmd_nocheck {
- my ($vmid, $execute, %params) = @_;
-
- my $cmd = { execute => $execute, arguments => \%params };
- vm_qmp_command($vmid, $cmd, 1);
-}
-
-sub vm_qmp_command {
- my ($vmid, $cmd, $nocheck) = @_;
-
- my $res;
-
- my $timeout;
- if ($cmd->{arguments}) {
- $timeout = delete $cmd->{arguments}->{timeout};
- }
-
- eval {
- die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
- my $sname = qmp_socket($vmid);
- if (-e $sname) { # test if VM is reasonambe new and supports qmp/qga
- my $qmpclient = PVE::QMPClient->new();
-
- $res = $qmpclient->cmd($vmid, $cmd, $timeout);
- } else {
- die "unable to open monitor socket\n";
- }
- };
- if (my $err = $@) {
- syslog("err", "VM $vmid qmp command failed - $err");
- die $err;
- }
-
- return $res;
-}
-
-sub vm_human_monitor_command {
- my ($vmid, $cmdline) = @_;
-
- my $cmd = {
- execute => 'human-monitor-command',
- arguments => { 'command-line' => $cmdline},
- };
-
- return vm_qmp_command($vmid, $cmd);
-}
-
sub vm_commandline {
my ($storecfg, $vmid, $snapname) = @_;
my $conf = PVE::QemuConfig->load_config($vmid);
+ my $forcemachine;
if ($snapname) {
my $snapshot = $conf->{snapshots}->{$snapname};
die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+ # check for a 'runningmachine' in snapshot
+ $forcemachine = $snapshot->{runningmachine} if $snapshot->{runningmachine};
+
$snapshot->{digest} = $conf->{digest}; # keep file digest for API
$conf = $snapshot;
my $defaults = load_defaults();
- my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
+ my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
return PVE::Tools::cmd2string($cmd);
}
PVE::QemuConfig->check_lock($conf) if !$skiplock;
- vm_mon_cmd($vmid, "system_reset");
+ mon_cmd($vmid, "system_reset");
});
}
eval {
if ($shutdown) {
if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
- vm_qmp_command($vmid, {
- execute => "guest-shutdown",
- arguments => { timeout => $timeout }
- }, $nocheck);
+ mon_cmd($vmid, "guest-shutdown", timeout => $timeout);
} else {
- vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
+ mon_cmd($vmid, "system_powerdown");
}
} else {
- vm_qmp_command($vmid, { execute => "quit" }, $nocheck);
+ mon_cmd($vmid, "quit");
}
};
my $err = $@;
my ($vmid, $timeout) = @_;
PVE::QemuConfig->lock_config($vmid, sub {
+ eval {
- # only reboot if running, as qmeventd starts it again on a stop event
- return if !check_running($vmid);
+ # only reboot if running, as qmeventd starts it again on a stop event
+ return if !check_running($vmid);
- create_reboot_request($vmid);
+ create_reboot_request($vmid);
- my $storecfg = PVE::Storage::config();
- _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);
+ my $storecfg = PVE::Storage::config();
+ _do_vm_stop($storecfg, $vmid, undef, undef, $timeout, 1);
+ };
+ if (my $err = $@) {
+ # avoid that the next normal shutdown will be confused for a reboot
+ clear_reboot_request($vmid);
+ die $err;
+ }
});
}
$path = PVE::Storage::path($storecfg, $vmstate);
PVE::QemuConfig->write_config($vmid, $conf);
} else {
- vm_mon_cmd($vmid, "stop");
+ mon_cmd($vmid, "stop");
}
});
PVE::Storage::activate_volumes($storecfg, [$vmstate]);
eval {
- vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+ mon_cmd($vmid, "savevm-start", statefile => $path);
for(;;) {
- my $state = vm_mon_cmd_nocheck($vmid, "query-savevm");
+ my $state = mon_cmd($vmid, "query-savevm");
if (!$state->{status}) {
die "savevm not active\n";
} elsif ($state->{status} eq 'active') {
if ($err) {
# cleanup, but leave suspending lock, to indicate something went wrong
eval {
- vm_mon_cmd($vmid, "savevm-end");
+ mon_cmd($vmid, "savevm-end");
PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
PVE::Storage::vdisk_free($storecfg, $vmstate);
delete $conf->@{qw(vmstate runningmachine)};
die "lock changed unexpectedly\n"
if !PVE::QemuConfig->has_lock($conf, 'suspending');
- vm_qmp_command($vmid, { execute => "quit" });
+ mon_cmd($vmid, "quit");
$conf->{lock} = 'suspended';
PVE::QemuConfig->write_config($vmid, $conf);
});
my ($vmid, $skiplock, $nocheck) = @_;
PVE::QemuConfig->lock_config($vmid, sub {
- my $vm_mon_cmd = $nocheck ? \&vm_mon_cmd_nocheck : \&vm_mon_cmd;
- my $res = $vm_mon_cmd->($vmid, 'query-status');
+ my $res = mon_cmd($vmid, 'query-status');
my $resume_cmd = 'cont';
if ($res->{status} && $res->{status} eq 'suspended') {
if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
}
- $vm_mon_cmd->($vmid, $resume_cmd);
+ mon_cmd($vmid, $resume_cmd);
});
}
my $conf = PVE::QemuConfig->load_config($vmid);
# there is no qmp command, so we use the human monitor command
- my $res = vm_human_monitor_command($vmid, "sendkey $key");
+ my $res = PVE::QemuServer::Monitor::hmp_cmd($vmid, "sendkey $key");
die $res if $res ne '';
});
}
} elsif ($map->{$virtdev}) {
delete $di->{format}; # format can change on restore
$di->{file} = $map->{$virtdev};
- $value = print_drive($vmid, $di);
+ $value = print_drive($di);
print $outfd "$virtdev: $value\n";
} else {
print $outfd $line;
next if !$volid_hash->{$volid};
$drive->{size} = $volid_hash->{$volid}->{size};
- my $new = print_drive($vmid, $drive);
+ my $new = print_drive($drive);
if ($new ne $conf->{$opt}) {
$changes = 1;
$conf->{$opt} = $new;
my $storecfg = PVE::Storage::config();
- # Restoring a backup can replace an existing VM. In this case, we need to
- # remove existing data such as disks as they would pile up as unused disks
- # in the new VM otherwise.
- # keep_empty_config=1 to prevent races until overwriting it with the
- # restored config.
- # skiplock=1 because qmrestore has set the lock itself.
+ # avoid zombie disks when restoring over an existing VM -> cleanup first
+ # pass keep_empty_config=1 to keep the config (thus VMID) reserved for us
+ # skiplock=1 because qmrestore has set the 'create' lock itself already
my $vmcfgfn = PVE::QemuConfig->config_file($vmid);
- destroy_vm($storecfg, $vmid, 1, 1) if -f $vmcfgfn;
+ destroy_vm($storecfg, $vmid, 1, { lock => 'restore' }) if -f $vmcfgfn;
my $tocmd = "/usr/lib/qemu-server/qmextract";
$srcfd->close();
$outfd->close();
};
- my $err = $@;
-
- if ($err) {
-
+ if (my $err = $@) {
unlink $tmpfn;
-
tar_restore_cleanup($storecfg, "$tmpdir/qmrestore.stat") if !$opts->{info};
-
die $err;
}
}
}
+my $qemu_snap_storage = {
+ rbd => 1,
+};
sub do_snapshots_with_qemu {
my ($storecfg, $volid) = @_;
sub qga_check_running {
my ($vmid, $nowarn) = @_;
- eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); };
+ eval { mon_cmd($vmid, "guest-ping", timeout => 3); };
if ($@) {
warn "Qemu Guest Agent is not running - $@" if !$nowarn;
return 0;
my $voliddst = PVE::Storage::vdisk_create_base($storecfg, $volid);
$drive->{file} = $voliddst;
- $conf->{$ds} = print_drive($vmid, $drive);
+ $conf->{$ds} = print_drive($drive);
PVE::QemuConfig->write_config($vmid, $conf);
});
}
my $cachemode;
my $src_path;
my $src_is_iscsi = 0;
- my $src_format = 'raw';
+ my $src_format;
if ($src_storeid) {
PVE::Storage::activate_volumes($storecfg, [$src_volid], $snapname);
my $cmd = [];
push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
- push @$cmd, '-l', "snapshot.name=$snapname" if($snapname && $src_format eq "qcow2");
+ push @$cmd, '-l', "snapshot.name=$snapname"
+ if $snapname && $src_format && $src_format eq "qcow2";
push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
push @$cmd, '-T', $cachemode if defined($cachemode);
if ($src_is_iscsi) {
push @$cmd, '--image-opts';
$src_path = convert_iscsi_path($src_path);
- } else {
+ } elsif ($src_format) {
push @$cmd, '-f', $src_format;
}
}
# if a job already runs for this device we get an error, catch it for cleanup
- eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); };
+ eval { mon_cmd($vmid, "drive-mirror", %$opts); };
if (my $err = $@) {
eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
warn "$@\n" if $@;
while (1) {
die "storage migration timed out\n" if $err_complete > 300;
- my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+ my $stats = mon_cmd($vmid, "query-block-jobs");
my $running_mirror_jobs = {};
foreach my $stat (@$stats) {
my $agent_running = $qga && qga_check_running($vmid);
if ($agent_running) {
print "freeze filesystem\n";
- eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+ eval { mon_cmd($vmid, "guest-fsfreeze-freeze"); };
} else {
print "suspend vm\n";
eval { PVE::QemuServer::vm_suspend($vmid, 1); };
if ($agent_running) {
print "unfreeze filesystem\n";
- eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-thaw"); };
+ eval { mon_cmd($vmid, "guest-fsfreeze-thaw"); };
} else {
print "resume vm\n";
eval { PVE::QemuServer::vm_resume($vmid, 1, 1); };
# try to switch the disk if source and destination are on the same guest
print "$job: Completing block job...\n";
- eval { vm_mon_cmd($vmid, "block-job-complete", device => $job) };
+ eval { mon_cmd($vmid, "block-job-complete", device => $job) };
if ($@ =~ m/cannot be completed/) {
print "$job: Block job cannot be completed, try again.\n";
$err_complete++;
foreach my $job (keys %$jobs) {
print "$job: Cancelling block job\n";
- eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
+ eval { mon_cmd($vmid, "block-job-cancel", device => $job); };
$jobs->{$job}->{cancel} = 1;
}
while (1) {
- my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+ my $stats = mon_cmd($vmid, "query-block-jobs");
my $running_jobs = {};
foreach my $stat (@$stats) {
print "create full clone of drive $drivename ($drive->{file})\n";
my $name = undef;
+ if (drive_is_cloudinit($drive)) {
+ $name = "vm-$newvmid-cloudinit";
+ $name .= ".$dst_format" if $dst_format ne 'raw';
+ $snapname = undef;
+ $size = PVE::QemuServer::Cloudinit::CLOUDINIT_DISK_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)) {
+ goto no_data_clone;
+ }
+
my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
if (!$running || $snapname) {
# TODO: handle bwlimits
} else {
my $kvmver = get_running_qemu_version ($vmid);
- if (!qemu_machine_feature_enabled (undef, $kvmver, 2, 7)) {
+ if (!min_version($kvmver, 2, 7)) {
die "drive-mirror with iothread requires qemu version 2.7 or higher\n"
if $drive->{iothread};
}
}
}
+no_data_clone:
my ($size) = PVE::Storage::volume_size_info($storecfg, $newvolid, 3);
my $disk = $drive;
return $disk;
}
-# this only works if VM is running
-sub get_current_qemu_machine {
- my ($vmid) = @_;
-
- my $cmd = { execute => 'query-machines', arguments => {} };
- my $res = vm_qmp_command($vmid, $cmd);
-
- my ($current, $default);
- foreach my $e (@$res) {
- $default = $e->{name} if $e->{'is-default'};
- $current = $e->{name} if $e->{'is-current'};
- }
-
- # fallback to the default machine if current is not supported by qemu
- return $current || $default || 'pc';
-}
-
sub get_running_qemu_version {
my ($vmid) = @_;
- my $cmd = { execute => 'query-version', arguments => {} };
- my $res = vm_qmp_command($vmid, $cmd);
+ my $res = mon_cmd($vmid, "query-version");
return "$res->{qemu}->{major}.$res->{qemu}->{minor}";
}
-sub qemu_machine_feature_enabled {
- my ($machine, $kvmver, $version_major, $version_minor) = @_;
-
- my $current_major;
- my $current_minor;
-
- if ($machine && $machine =~ m/^((?:pc(-i440fx|-q35)?|virt)-(\d+)\.(\d+))/) {
-
- $current_major = $3;
- $current_minor = $4;
-
- } elsif ($kvmver =~ m/^(\d+)\.(\d+)/) {
-
- $current_major = $1;
- $current_minor = $2;
- }
-
- return 1 if version_cmp($current_major, $version_major, $current_minor, $version_minor) >= 0;
-}
-
-# gets in pairs the versions you want to compares, i.e.:
-# ($a-major, $b-major, $a-minor, $b-minor, $a-extra, $b-extra, ...)
-# returns 0 if same, -1 if $a is older than $b, +1 if $a is newer than $b
-sub version_cmp {
- my @versions = @_;
-
- my $size = scalar(@versions);
-
- return 0 if $size == 0;
- die "cannot compare odd count of versions" if $size & 1;
-
- for (my $i = 0; $i < $size; $i += 2) {
- my ($a, $b) = splice(@versions, 0, 2);
- $a //= 0;
- $b //= 0;
-
- return 1 if $a > $b;
- return -1 if $a < $b;
- }
- return 0;
-}
-
-# dies if a) VM not running or not exisiting b) Version query failed
-# So, any defined return value is valid, any invalid state can be caught by eval
-sub runs_at_least_qemu_version {
- my ($vmid, $major, $minor, $extra) = @_;
-
- my $v = vm_qmp_command($vmid, { execute => 'query-version' });
- die "could not query currently running version for VM $vmid\n" if !defined($v);
- $v = $v->{qemu};
-
- return version_cmp($v->{major}, $major, $v->{minor}, $minor, $v->{micro}, $extra) >= 0;
-}
-
-sub qemu_machine_pxe {
- my ($vmid, $conf) = @_;
-
- my $machine = PVE::QemuServer::get_current_qemu_machine($vmid);
-
- if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
- $machine .= '.pxe';
- }
-
- return $machine;
-}
-
sub qemu_use_old_bios_files {
my ($machine_type) = @_;
$machine_type = $1;
$use_old_bios_files = 1;
} else {
- my $kvmver = kvm_user_version();
+ my $version = PVE::QemuServer::Machine::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
# updrading from proxmox-ve-3.X to proxmox-ve 4.0
- $use_old_bios_files = !qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 4);
+ $use_old_bios_files = !min_version($version, 2, 4);
}
return ($use_old_bios_files, $machine_type);
PVE::Storage::activate_volumes($storecfg, [$volid]);
qemu_img_convert($ovmf_vars, $volid, $vars_size_b, undef, 0);
+ my ($size) = PVE::Storage::volume_size_info($storecfg, $volid, 3);
- return ($volid, $vars_size);
+ return ($volid, $size/1024);
}
sub vm_iothreads_list {
my ($vmid) = @_;
- my $res = vm_mon_cmd($vmid, 'query-iothreads');
+ my $res = mon_cmd($vmid, 'query-iothreads');
my $iothreads = {};
foreach my $iothread (@$res) {
}
sub add_hyperv_enlightenments {
- my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
+ my ($cpuFlags, $winversion, $machine_version, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
return if $winversion < 6;
return if $bios && $bios eq 'ovmf' && $winversion < 8;
push @$cpuFlags , "hv_vendor_id=$hv_vendor_id";
}
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+ if (min_version($machine_version, 2, 3)) {
push @$cpuFlags , 'hv_spinlocks=0x1fff';
push @$cpuFlags , 'hv_vapic';
push @$cpuFlags , 'hv_time';
push @$cpuFlags , 'hv_spinlocks=0xffff';
}
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
+ if (min_version($machine_version, 2, 6)) {
push @$cpuFlags , 'hv_reset';
push @$cpuFlags , 'hv_vpindex';
push @$cpuFlags , 'hv_runtime';
if ($winversion >= 7) {
push @$cpuFlags , 'hv_relaxed';
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 12)) {
+ if (min_version($machine_version, 2, 12)) {
push @$cpuFlags , 'hv_synic';
push @$cpuFlags , 'hv_stimer';
}
- if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 1)) {
+ if (min_version($machine_version, 3, 1)) {
push @$cpuFlags , 'hv_ipi';
}
}
sub nbd_stop {
my ($vmid) = @_;
- vm_mon_cmd($vmid, 'nbd-server-stop');
+ mon_cmd($vmid, 'nbd-server-stop');
}
sub create_reboot_request {
return $res;
}
+sub complete_migration_storage {
+ my ($cmd, $param, $current_value, $all_args) = @_;
+
+ my $targetnode = @$all_args[1];
+
+ my $cfg = PVE::Storage::config();
+ my $ids = $cfg->{ids};
+
+ my $res = [];
+ foreach my $sid (keys %$ids) {
+ next if !PVE::Storage::storage_check_enabled($cfg, $sid, $targetnode, 1);
+ next if !$ids->{$sid}->{content}->{images};
+ push @$res, $sid;
+ }
+
+ return $res;
+}
+
1;