]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
Fix 2070: vm_start: for a migrating VM, use current format of disk if possible
[qemu-server.git] / PVE / QemuServer.pm
index 2b68d81fe1d7b32cb44ccab37ad51dcdb415276a..59335c59217296e1e3ae0867a17766120323758d 100644 (file)
@@ -37,11 +37,11 @@ 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 dir_glob_foreach get_host_arch $IPV6RE);
+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::QMPClient;
 use PVE::QemuConfig;
-use PVE::QemuServer::Helpers qw(min_version);
+use PVE::QemuServer::Helpers qw(min_version config_aware_timeout);
 use PVE::QemuServer::Cloudinit;
 use PVE::QemuServer::Machine;
 use PVE::QemuServer::Memory;
@@ -3397,6 +3397,125 @@ sub get_command_for_arch($) {
     return $cmd;
 }
 
+# To use query_supported_cpu_flags and query_understood_cpu_flags to get flags
+# to use in a QEMU command line (-cpu element), first array_intersect the result
+# of query_supported_ with query_understood_. This is necessary because:
+#
+# a) query_understood_ returns flags the host cannot use and
+# b) query_supported_ (rather the QMP call) doesn't actually return CPU
+#    flags, but CPU settings - with most of them being flags. Those settings
+#    (and some flags, curiously) cannot be specified as a "-cpu" argument.
+#
+# query_supported_ needs to start up to 2 temporary VMs and is therefore rather
+# expensive. If you need the value returned from this, you can get it much
+# cheaper from pmxcfs using PVE::Cluster::get_node_kv('cpuflags-$accel') with
+# $accel being 'kvm' or 'tcg'.
+#
+# pvestatd calls this function on startup and whenever the QEMU/KVM version
+# changes, automatically populating pmxcfs.
+#
+# Returns: { kvm => [ flagX, flagY, ... ], tcg => [ flag1, flag2, ... ] }
+# since kvm and tcg machines support different flags
+#
+sub query_supported_cpu_flags {
+    my ($arch) = @_;
+
+    $arch //= get_host_arch();
+    my $default_machine = $default_machines->{$arch};
+
+    my $flags = {};
+
+    # FIXME: Once this is merged, the code below should work for ARM as well:
+    # https://lists.nongnu.org/archive/html/qemu-devel/2019-06/msg04947.html
+    die "QEMU/KVM cannot detect CPU flags on ARM (aarch64)\n" if
+       $arch eq "aarch64";
+
+    my $kvm_supported = defined(kvm_version());
+    my $qemu_cmd = get_command_for_arch($arch);
+    my $fakevmid = -1;
+    my $pidfile = PVE::QemuServer::Helpers::pidfile_name($fakevmid);
+
+    # Start a temporary (frozen) VM with vmid -1 to allow sending a QMP command
+    my $query_supported_run_qemu = sub {
+       my ($kvm) = @_;
+
+       my $flags = {};
+       my $cmd = [
+           $qemu_cmd,
+           '-machine', $default_machine,
+           '-display', 'none',
+           '-chardev', "socket,id=qmp,path=/var/run/qemu-server/$fakevmid.qmp,server,nowait",
+           '-mon', 'chardev=qmp,mode=control',
+           '-pidfile', $pidfile,
+           '-S', '-daemonize'
+       ];
+
+       if (!$kvm) {
+           push @$cmd, '-accel', 'tcg';
+       }
+
+       my $rc = run_command($cmd, noerr => 1, quiet => 0);
+       die "QEMU flag querying VM exited with code " . $rc if $rc;
+
+       eval {
+           my $cmd_result = mon_cmd(
+               $fakevmid,
+               'query-cpu-model-expansion',
+               type => 'full',
+               model => { name => 'host' }
+           );
+
+           my $props = $cmd_result->{model}->{props};
+           foreach my $prop (keys %$props) {
+               next if $props->{$prop} ne '1';
+               # QEMU returns some flags multiple times, with '_', '.' or '-'
+               # (e.g. lahf_lm and lahf-lm; sse4.2, sse4-2 and sse4_2; ...).
+               # We only keep those with underscores, to match /proc/cpuinfo
+               $prop =~ s/\.|-/_/g;
+               $flags->{$prop} = 1;
+           }
+       };
+       my $err = $@;
+
+       # force stop with 10 sec timeout and 'nocheck'
+       # always stop, even if QMP failed
+       vm_stop(undef, $fakevmid, 1, 1, 10, 0, 1);
+
+       die $err if $err;
+
+       return [ sort keys %$flags ];
+    };
+
+    # We need to query QEMU twice, since KVM and TCG have different supported flags
+    PVE::QemuConfig->lock_config($fakevmid, sub {
+       $flags->{tcg} = eval { $query_supported_run_qemu->(0) };
+       warn "warning: failed querying supported tcg flags: $@\n" if $@;
+
+       if ($kvm_supported) {
+           $flags->{kvm} = eval { $query_supported_run_qemu->(1) };
+           warn "warning: failed querying supported kvm flags: $@\n" if $@;
+       }
+    });
+
+    return $flags;
+}
+
+# Understood CPU flags are written to a file at 'pve-qemu' compile time
+my $understood_cpu_flag_dir = "/usr/share/kvm";
+sub query_understood_cpu_flags {
+    my $arch = get_host_arch();
+    my $filepath = "$understood_cpu_flag_dir/recognized-CPUID-flags-$arch";
+
+    die "Cannot query understood QEMU CPU flags for architecture: $arch (file not found)\n"
+       if ! -e $filepath;
+
+    my $raw = file_get_contents($filepath);
+    $raw =~ s/^\s+|\s+$//g;
+    my @flags = split(/\s+/, $raw);
+
+    return \@flags;
+}
+
 sub get_cpu_options {
     my ($conf, $arch, $kvm, $kvm_off, $machine_version, $winversion, $gpu_passthrough) = @_;
 
@@ -4977,48 +5096,53 @@ sub vmconfig_delete_or_detach_drive {
 
 
 sub vmconfig_apply_pending {
-    my ($vmid, $conf, $storecfg) = @_;
+    my ($vmid, $conf, $storecfg, $errors) = @_;
+
+    my $add_apply_error = sub {
+       my ($opt, $msg) = @_;
+       my $err_msg = "unable to apply pending change $opt : $msg";
+       $errors->{$opt} = $err_msg;
+       warn $err_msg;
+    };
 
     # cold plug
 
     my $pending_delete_hash = PVE::QemuConfig->parse_pending_delete($conf->{pending}->{delete});
     foreach my $opt (sort keys %$pending_delete_hash) {
-       die "internal error" if $opt =~ m/^unused/;
        my $force = $pending_delete_hash->{$opt}->{force};
-       $conf = PVE::QemuConfig->load_config($vmid); # update/reload
-       if (!defined($conf->{$opt})) {
-           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
-           PVE::QemuConfig->write_config($vmid, $conf);
-       } elsif (is_valid_drivename($opt)) {
-           vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
-           PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
-           delete $conf->{$opt};
-           PVE::QemuConfig->write_config($vmid, $conf);
+       eval {
+           if ($opt =~ m/^unused/) {
+               die "internal error";
+           } elsif (defined($conf->{$opt}) && is_valid_drivename($opt)) {
+               vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
+           }
+       };
+       if (my $err = $@) {
+           $add_apply_error->($opt, $err);
        } else {
            PVE::QemuConfig->remove_from_pending_delete($conf, $opt);
            delete $conf->{$opt};
-           PVE::QemuConfig->write_config($vmid, $conf);
        }
     }
 
-    $conf = PVE::QemuConfig->load_config($vmid); # update/reload
+    PVE::QemuConfig->cleanup_pending($conf);
 
     foreach my $opt (keys %{$conf->{pending}}) { # add/change
-       $conf = PVE::QemuConfig->load_config($vmid); # update/reload
-
-       if (defined($conf->{$opt}) && ($conf->{$opt} eq $conf->{pending}->{$opt})) {
-           # skip if nothing changed
-       } elsif (is_valid_drivename($opt)) {
-           vmconfig_register_unused_drive($storecfg, $vmid, $conf, parse_drive($opt, $conf->{$opt}))
-               if defined($conf->{$opt});
-           $conf->{$opt} = $conf->{pending}->{$opt};
+       next if $opt eq 'delete'; # just to be sure
+       eval {
+           if (defined($conf->{$opt}) && is_valid_drivename($opt)) {
+               vmconfig_register_unused_drive($storecfg, $vmid, $conf, parse_drive($opt, $conf->{$opt}))
+           }
+       };
+       if (my $err = $@) {
+           $add_apply_error->($opt, $err);
        } else {
-           $conf->{$opt} = $conf->{pending}->{$opt};
+           $conf->{$opt} = delete $conf->{pending}->{$opt};
        }
-
-       delete $conf->{pending}->{$opt};
-       PVE::QemuConfig->write_config($vmid, $conf);
     }
+
+    # write all changes at once to avoid unnecessary i/o
+    PVE::QemuConfig->write_config($vmid, $conf);
 }
 
 my $safe_num_ne = sub {
@@ -5196,7 +5320,7 @@ sub vmconfig_update_disk {
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
-       $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage) = @_;
+       $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage, $timeout) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
@@ -5252,13 +5376,16 @@ sub vm_start {
                my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}};
                my $drive = parse_drive($opt, $conf->{$opt});
 
-               #if remote storage is specified, use default format
+               # If a remote storage is specified and the format of the original
+               # volume is not available there, fall back to the default format.
+               # Otherwise use the same format as the original.
                if ($targetstorage && $targetstorage ne "1") {
                    $storeid = $targetstorage;
                    my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
-                   $format = $defFormat;
+                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+                   my $fileFormat = qemu_img_format($scfg, $volname);
+                   $format = (grep {$fileFormat eq $_} @{$validFormats}) ? $fileFormat : $defFormat;
                } else {
-                   #else we use same format than original
                    my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
                    $format = qemu_img_format($scfg, $volid);
                }
@@ -5399,7 +5526,7 @@ sub vm_start {
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
                                                  : $defaults->{cpuunits};
 
-       my $start_timeout = ($conf->{hugepages} || $is_suspended) ? 300 : 30;
+       my $start_timeout = $timeout // config_aware_timeout($conf, $is_suspended);
        my %run_params = (
            timeout => $statefile ? undef : $start_timeout,
            umask => 0077,