]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
fix #3314: IPv6 requires type 'static6'
[qemu-server.git] / PVE / QemuServer.pm
index a498444b13f45bc6f849883f10746948e74ce1bd..f9379f64d286ee0bbe86de7bec3d93278f8102ed 100644 (file)
@@ -384,7 +384,7 @@ w2k8;; Microsoft Windows 2008
 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
@@ -2215,6 +2215,8 @@ sub parse_vm_config {
 
                $conf->{$key} = $value;
            }
+       } else {
+           warn "vm $vmid - unable to parse config: $line\n";
        }
     }
 
@@ -2555,6 +2557,16 @@ our $vmstatus_return_properties = {
        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;
@@ -2733,10 +2745,32 @@ sub vmstatus {
        $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');
@@ -2857,16 +2891,51 @@ my $default_machines = {
     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";
        }
@@ -4222,8 +4291,13 @@ sub qemu_block_resize {
     my $padding = (1024 - $size % 1024) % 1024;
     $size = $size + $padding;
 
-    mon_cmd($vmid, "block_resize", device => $deviceid, size => int($size));
-
+    mon_cmd(
+       $vmid,
+       "block_resize",
+       device => $deviceid,
+       size => int($size),
+       timeout => 60,
+    );
 }
 
 sub qemu_volume_snapshot {
@@ -4261,10 +4335,13 @@ sub qemu_volume_snapshot_delete {
 }
 
 sub set_migration_caps {
-    my ($vmid) = @_;
+    my ($vmid, $savevm) = @_;
 
     my $qemu_support = eval { mon_cmd($vmid, "query-proxmox-support") };
 
+    my $bitmap_prop = $savevm ? 'pbs-dirty-bitmap-savevm' : 'pbs-dirty-bitmap-migration';
+    my $dirty_bitmaps = $qemu_support->{$bitmap_prop} ? 1 : 0;
+
     my $cap_ref = [];
 
     my $enabled_cap = {
@@ -4273,7 +4350,7 @@ sub set_migration_caps {
        "x-rdma-pin-all" => 0,
        "zero-blocks" => 0,
        "compress" => 0,
-       "dirty-bitmaps" => $qemu_support->{'pbs-dirty-bitmap-migration'} ? 1 : 0,
+       "dirty-bitmaps" => $dirty_bitmaps,
     };
 
     my $supported_capabilities = mon_cmd($vmid, "query-migrate-capabilities");
@@ -5531,6 +5608,7 @@ sub vm_suspend {
        PVE::Storage::activate_volumes($storecfg, [$vmstate]);
 
        eval {
+           set_migration_caps($vmid, 1);
            mon_cmd($vmid, "savevm-start", statefile => $path);
            for(;;) {
                my $state = mon_cmd($vmid, "query-savevm");
@@ -5827,13 +5905,15 @@ my $restore_allocate_devices = sub {
 };
 
 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*$/)) {
@@ -5849,7 +5929,7 @@ my $restore_update_config_line = sub {
            };
            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) {
@@ -5857,20 +5937,20 @@ my $restore_update_config_line = sub {
        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;
@@ -5878,17 +5958,19 @@ my $restore_update_config_line = sub {
            # 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 {
@@ -6092,7 +6174,6 @@ sub restore_proxmox_backup_archive {
     mkpath $tmpdir;
 
     my $conffile = PVE::QemuConfig->config_file($vmid);
-    my $tmpfn = "$conffile.$$.tmp";
      # disable interrupts (always do cleanups)
     local $SIG{INT} =
        local $SIG{TERM} =
@@ -6102,6 +6183,7 @@ sub restore_proxmox_backup_archive {
     # 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 = {};
@@ -6204,15 +6286,18 @@ sub restore_proxmox_backup_archive {
 
        $fh->seek(0, 0) || die "seek failed - $!\n";
 
-       my $outfd = IO::File->new($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 = $@;
 
@@ -6221,13 +6306,11 @@ sub restore_proxmox_backup_archive {
     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
 
@@ -6302,11 +6385,11 @@ sub restore_vma_archive {
     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;
 
@@ -6326,11 +6409,13 @@ sub restore_vma_archive {
 
        my $virtdev_hash = $parse_backup_hints->($rpcenv, $user, $cfg, $fh, $devinfo, $opts);
 
-       foreach my $key (keys %storage_limits) {
-           my $limit = PVE::Storage::get_bandwidth_limit('restore', [$key], $bwlimit);
-           next if !$limit;
-           print STDERR "rate limit for storage $key: $limit KiB/s\n";
-           $storage_limits{$key} = $limit * 1024;
+       foreach my $info (values %{$virtdev_hash}) {
+           my $storeid = $info->{storeid};
+           next if defined($storage_limits{$storeid});
+
+           my $limit = PVE::Storage::get_bandwidth_limit('restore', [$storeid], $bwlimit) // 0;
+           print STDERR "rate limit for storage $storeid: $limit KiB/s\n" if $limit;
+           $storage_limits{$storeid} = $limit * 1024;
        }
 
        foreach my $devname (keys %$devinfo) {
@@ -6374,15 +6459,18 @@ sub restore_vma_archive {
 
        $fh->seek(0, 0) || die "seek failed - $!\n";
 
-       my $outfd = IO::File->new($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 {
@@ -6433,13 +6521,11 @@ sub restore_vma_archive {
     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
 
@@ -6484,7 +6570,7 @@ sub restore_tar_archive {
     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} =
@@ -6528,26 +6614,27 @@ sub restore_tar_archive {
 
        my $srcfd = IO::File->new($confsrc, "r") || die "unable to open file '$confsrc'\n";
 
-       my $outfd = IO::File->new($tmpfn, "w") || die "unable to write config for VM $vmid\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