]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
ensure correct machine type gets saved on snapshot
[qemu-server.git] / PVE / QemuServer.pm
index f1ed6b6bc97ef6404a5f19645775a60700116b83..5eec8211d31a7d74f6665631c440bcffd20d35f9 100644 (file)
@@ -34,12 +34,14 @@ use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::USB qw(parse_usb_device);
 use PVE::QemuServer::Cloudinit;
+use PVE::Systemd;
 use Time::HiRes qw(gettimeofday);
 use File::Copy qw(copy);
 use URI::Escape;
 
-my $OVMF_CODE = '/usr/share/kvm/OVMF_CODE-pure-efi.fd';
-my $OVMF_VARS = '/usr/share/kvm/OVMF_VARS-pure-efi.fd';
+my $EDK2_FW_BASE = '/usr/share/pve-edk2-firmware/';
+my $OVMF_CODE = "$EDK2_FW_BASE/OVMF_CODE.fd";
+my $OVMF_VARS = "$EDK2_FW_BASE/OVMF_VARS.fd";
 
 my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
 
@@ -78,6 +80,14 @@ PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
     optional => 1,
 });
 
+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)?)',
+       maxLength => 40,
+       optional => 1,
+});
+
 #no warnings 'redefine';
 
 sub cgroups_write {
@@ -153,7 +163,7 @@ my $cpu_vendor_list = {
     max => 'default',
 };
 
-my $cpu_flag = qr/[+-](pcid|spec-ctrl)/;
+my $cpu_flag = qr/[+-](pcid|spec-ctrl|ibpb|ssbd|virt-ssbd|amd-ssbd|amd-no-ssb|pdpe1gb)/;
 
 my $cpu_fmt = {
     cputype => {
@@ -172,7 +182,7 @@ my $cpu_fmt = {
     flags => {
        description => "List of additional CPU flags separated by ';'."
                     . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
-                    . " Currently supported flags: 'pcid', 'spec-ctrl'.",
+                    . " Currently supported flags: 'pcid', 'spec-ctrl', 'ibpb', 'ssbd', 'virt-ssbd', 'amd-ssbd', 'amd-no-ssb', 'pdpe1gb'.",
        format_description => '+FLAG[;-FLAG...]',
        type => 'string',
        pattern => qr/$cpu_flag(;$cpu_flag)*/,
@@ -198,6 +208,21 @@ my $watchdog_fmt = {
 };
 PVE::JSONSchema::register_format('pve-qm-watchdog', $watchdog_fmt);
 
+my $agent_fmt = {
+    enabled => {
+       description => "Enable/disable Qemu GuestAgent.",
+       type => 'boolean',
+       default => 0,
+       default_key => 1,
+    },
+    fstrim_cloned_disks => {
+       description => "Run fstrim after cloning/moving a disk.",
+       type => 'boolean',
+       optional => 1,
+       default => 0
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -263,7 +288,7 @@ my $confdesc = {
     shares => {
         optional => 1,
         type => 'integer',
-        description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning",
+        description => "Amount of memory shares for auto-ballooning. The larger the number is, the more memory this VM gets. Number is relative to weights of all other running VMs. Using zero disables auto-ballooning. Auto-ballooning is done by pvestatd.",
        minimum => 0,
        maximum => 50000,
        default => 1000,
@@ -271,7 +296,7 @@ my $confdesc = {
     keyboard => {
        optional => 1,
        type => 'string',
-       description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.conf' configuration file.".
+       description => "Keybord layout for vnc server. Default is read from the '/etc/pve/datacenter.cfg' configuration file.".
                       "It should not be necessary to set it.",
        enum => PVE::Tools::kvmkeymaplist(),
        default => undef,
@@ -378,9 +403,9 @@ EODESC
     },
     agent => {
        optional => 1,
-       type => 'boolean',
-       description => "Enable/disable Qemu GuestAgent.",
-       default => 0,
+       description => "Enable/disable Qemu GuestAgent and its properties.",
+       type => 'string',
+       format => $agent_fmt,
     },
     kvm => {
        optional => 1,
@@ -511,13 +536,10 @@ EODESCR
        description => "Default storage for VM state volumes/files.",
        optional => 1,
     }),
-    machine => {
-       description => "Specific the Qemu machine type.",
-       type => 'string',
-       pattern => '(pc|pc(-i440fx)?-\d+\.\d+(\.pxe)?|q35|pc-q35-\d+\.\d+(\.pxe)?)',
-       maxLength => 40,
-       optional => 1,
-    },
+    runningmachine => get_standard_option('pve-qemu-machine', {
+       description => "Specifies the Qemu machine type of the running vm. This is used internally for snapshots.",
+    }),
+    machine => get_standard_option('pve-qemu-machine'),
     smbios1 => {
        description => "Specify SMBIOS type 1 fields.",
        type => 'string', format => 'pve-qm-smbios1',
@@ -1688,6 +1710,12 @@ sub print_drivedevice_full {
 
     $device .= ",bootindex=$drive->{bootindex}" if $drive->{bootindex};
 
+    if (my $serial = $drive->{serial}) {
+       $serial = URI::Escape::uri_unescape($serial);
+       $device .= ",serial=$serial";
+    }
+
+
     return $device;
 }
 
@@ -1760,11 +1788,6 @@ sub print_drive_full {
        }
     }
 
-    if (my $serial = $drive->{serial}) {
-       $serial = URI::Escape::uri_unescape($serial);
-       $opts .= ",serial=$serial";
-    }
-
     $opts .= ",format=$format" if $format && !$drive->{format};
 
     my $cache_direct = 0;
@@ -2208,6 +2231,19 @@ sub parse_watchdog {
     return $res;
 }
 
+sub parse_guest_agent {
+    my ($value) = @_;
+
+    return {} if !defined($value->{agent});
+
+    my $res = eval { PVE::JSONSchema::parse_property_string($agent_fmt, $value->{agent}) };
+    warn $@ if $@;
+
+    # if the agent is disabled ignore the other potentially set properties
+    return {} if !$res->{enabled};
+    return $res;
+}
+
 PVE::JSONSchema::register_format('pve-qm-usb-device', \&verify_usb_device);
 sub verify_usb_device {
     my ($value, $noerr) = @_;
@@ -2224,7 +2260,7 @@ sub json_config_properties {
     my $prop = shift;
 
     foreach my $opt (keys %$confdesc) {
-       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate';
+       next if $opt eq 'parent' || $opt eq 'snaptime' || $opt eq 'vmstate' || $opt eq 'runningmachine';
        $prop->{$opt} = $confdesc->{$opt};
     }
 
@@ -2761,6 +2797,53 @@ sub disksize {
     return $drive->{size};
 }
 
+our $vmstatus_return_properties = {
+    vmid => get_standard_option('pve-vmid'),
+    status => {
+       description => "Qemu process status.",
+       type => 'string',
+       enum => ['stopped', 'running'],
+    },
+    maxmem => {
+       description => "Maximum memory in bytes.",
+       type => 'integer',
+       optional => 1,
+       renderer => 'bytes',
+    },
+    maxdisk => {
+       description => "Root disk size in bytes.",
+       type => 'integer',
+       optional => 1,
+       renderer => 'bytes',
+    },
+    name => {
+       description => "VM name.",
+       type => 'string',
+       optional => 1,
+    },
+    qmpstatus => {
+       description => "Qemu QMP agent status.",
+       type => 'string',
+       optional => 1,
+    },
+    pid => {
+       description => "PID of running qemu process.",
+       type => 'integer',
+       optional => 1,
+    },
+    uptime => {
+       description => "Uptime.",
+       type => 'integer',
+       optional => 1,
+       renderer => 'duration',
+    },
+    cpus => {
+       description => "Maximum usable CPUs.",
+       type => 'number',
+       optional => 1,
+    },
+};
+
 my $last_proc_pid_stat;
 
 # get VM status information
@@ -2786,7 +2869,7 @@ sub vmstatus {
        my $cfspath = PVE::QemuConfig->cfs_config_path($vmid);
        my $conf = PVE::Cluster::cfs_read_file($cfspath) || {};
 
-       my $d = {};
+       my $d = { vmid => $vmid };
        $d->{pid} = $list->{$vmid}->{pid};
 
        # fixme: better status?
@@ -3392,7 +3475,7 @@ sub config_to_command {
     #push @$cmd, '-soundhw', 'es1370';
     #push @$cmd, '-soundhw', $soundhw if $soundhw;
 
-    if($conf->{agent}) {
+    if (parse_guest_agent($conf)->{enabled}) {
        my $qgasocket = qmp_socket($vmid, 1);
        my $pciaddr = print_pci_addr("qga0", $bridges);
        push @$devices, '-chardev', "socket,path=$qgasocket,server,nowait,id=qga0";
@@ -3407,7 +3490,7 @@ sub config_to_command {
            if ($winversion){
                for(my $i = 1; $i < $qxlnum; $i++){
                    my $pciaddr = print_pci_addr("vga$i", $bridges);
-                   push @$cmd, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
+                   push @$devices, '-device', "qxl,id=vga$i,ram_size=67108864,vram_size=33554432$pciaddr";
                }
            } else {
                # assume other OS works like Linux
@@ -3599,21 +3682,22 @@ sub vm_devices_list {
     my ($vmid) = @_;
 
     my $res = vm_mon_cmd($vmid, 'query-pci');
+    my $devices_to_check = [];
     my $devices = {};
     foreach my $pcibus (@$res) {
-       foreach my $device (@{$pcibus->{devices}}) {
-           next if !$device->{'qdev_id'};
-           if ($device->{'pci_bridge'}) {
-               $devices->{$device->{'qdev_id'}} = 1;
-               foreach my $bridge_device (@{$device->{'pci_bridge'}->{devices}}) {
-                   next if !$bridge_device->{'qdev_id'};
-                   $devices->{$bridge_device->{'qdev_id'}} = 1;
-                   $devices->{$device->{'qdev_id'}}++;
-               }
-           } else {
-               $devices->{$device->{'qdev_id'}} = 1;
-           }
+       push @$devices_to_check, @{$pcibus->{devices}},
+    }
+
+    while (@$devices_to_check) {
+       my $to_check = [];
+       for my $d (@$devices_to_check) {
+           $devices->{$d->{'qdev_id'}} = 1 if $d->{'qdev_id'};
+           next if !$d->{'pci_bridge'};
+
+           $devices->{$d->{'qdev_id'}} += scalar(@{$d->{'pci_bridge'}->{devices}});
+           push @$to_check, @{$d->{'pci_bridge'}->{devices}};
        }
+       $devices_to_check = $to_check;
     }
 
     my $resblock = vm_mon_cmd($vmid, 'query-block');
@@ -4193,6 +4277,16 @@ sub qemu_volume_snapshot_delete {
 
     my $running = check_running($vmid);
 
+    if($running) {
+
+       $running = undef;
+       my $conf = PVE::QemuConfig->load_config($vmid);
+       foreach_drive($conf, sub {
+           my ($ds, $drive) = @_;
+           $running = 1 if $drive->{file} eq $volid;
+       });
+    }
+
     if ($running && do_snapshots_with_qemu($storecfg, $volid)){
        vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
     } else {
@@ -4294,7 +4388,10 @@ sub vmconfig_hotplug_pending {
                qemu_cpu_hotplug($vmid, $conf, undef);
             } elsif ($opt eq 'balloon') {
                # enable balloon device is not hotpluggable
-               die "skip\n" if !defined($conf->{balloon}) || $conf->{balloon};
+               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);
            } elsif ($fast_plug_option->{$opt}) {
                # do nothing
            } elsif ($opt =~ m/^net(\d+)$/) {
@@ -4838,6 +4935,13 @@ sub vm_start {
        }
        $properties{timeout} = 10 if $statefile; # setting up the scope shoul be quick
 
+       my $run_qemu = sub {
+           PVE::Tools::run_fork sub {
+               PVE::Systemd::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
+               run_command($cmd, %run_params);
+           };
+       };
+
        if ($conf->{hugepages}) {
 
            my $code = sub {
@@ -4847,11 +4951,7 @@ sub vm_start {
                PVE::QemuServer::Memory::hugepages_mount();
                PVE::QemuServer::Memory::hugepages_allocate($hugepages_topology, $hugepages_host_topology);
 
-               eval  {
-                   PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
-                   run_command($cmd, %run_params);
-               };
-
+               eval { $run_qemu->() };
                if (my $err = $@) {
                    PVE::QemuServer::Memory::hugepages_reset($hugepages_host_topology);
                    die $err;
@@ -4862,10 +4962,7 @@ sub vm_start {
            eval { PVE::QemuServer::Memory::hugepages_update_locked($code); };
 
        } else {
-           eval  {
-               PVE::Tools::enter_systemd_scope($vmid, "Proxmox VE VM $vmid", %properties);
-               run_command($cmd, %run_params);
-           };
+           eval { $run_qemu->() };
        }
 
        if (my $err = $@) {
@@ -4916,10 +5013,8 @@ sub vm_start {
            }
 
        } else {
-           if (!$statefile && (!defined($conf->{balloon}) || $conf->{balloon})) {
-               vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
-                   if $conf->{balloon};
-           }
+           vm_mon_cmd_nocheck($vmid, "balloon", value => $conf->{balloon}*1024*1024)
+               if !$statefile && $conf->{balloon};
 
            foreach my $opt (keys %$conf) {
                next if $opt !~  m/^net\d+$/;
@@ -5090,7 +5185,7 @@ sub vm_stop {
 
        eval {
            if ($shutdown) {
-               if (defined($conf) && $conf->{agent}) {
+               if (defined($conf) && parse_guest_agent($conf)->{enabled}) {
                    vm_qmp_command($vmid, { execute => "guest-shutdown" }, $nocheck);
                } else {
                    vm_qmp_command($vmid, { execute => "system_powerdown" }, $nocheck);
@@ -5166,6 +5261,13 @@ sub vm_resume {
 
     PVE::QemuConfig->lock_config($vmid, sub {
 
+       my $res = vm_mon_cmd($vmid, 'query-status');
+       my $resume_cmd = 'cont';
+
+       if ($res->{status} && $res->{status} eq 'suspended') {
+           $resume_cmd = 'system_wakeup';
+       }
+
        if (!$nocheck) {
 
            my $conf = PVE::QemuConfig->load_config($vmid);
@@ -5173,10 +5275,10 @@ sub vm_resume {
            PVE::QemuConfig->check_lock($conf)
                if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
 
-           vm_mon_cmd($vmid, "cont");
+           vm_mon_cmd($vmid, $resume_cmd);
 
        } else {
-           vm_mon_cmd_nocheck($vmid, "cont");
+           vm_mon_cmd_nocheck($vmid, $resume_cmd);
        }
     });
 }
@@ -5526,6 +5628,7 @@ sub update_disksize {
     my ($vmid, $conf, $volid_hash) = @_;
 
     my $changes;
+    my $prefix = "VM $vmid:";
 
     # used and unused disks
     my $referenced = {};
@@ -5557,6 +5660,7 @@ sub update_disksize {
            if ($new ne $conf->{$opt}) {
                $changes = 1;
                $conf->{$opt} = $new;
+               print "$prefix update disk '$opt' information.\n";
            }
        }
     }
@@ -5567,6 +5671,7 @@ sub update_disksize {
        my $volid = $conf->{$opt};
        my $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;
            delete $conf->{$opt};
        }
@@ -5582,7 +5687,8 @@ sub update_disksize {
        next if !$path; # just to be sure
        next if $referencedpath->{$path};
        $changes = 1;
-       PVE::QemuConfig->add_unused_volume($conf, $volid);
+       my $key = PVE::QemuConfig->add_unused_volume($conf, $volid);
+       print "$prefix add unreferenced volume '$volid' as '$key' to config.\n";
        $referencedpath->{$path} = 1; # avoid to add more than once (aliases)
     }
 
@@ -5590,10 +5696,17 @@ sub update_disksize {
 }
 
 sub rescan {
-    my ($vmid, $nolock) = @_;
+    my ($vmid, $nolock, $dryrun) = @_;
 
     my $cfg = PVE::Storage::config();
 
+    # FIXME: Remove once our RBD plugin can handle CT and VM on a single storage
+    # see: https://pve.proxmox.com/pipermail/pve-devel/2018-July/032900.html
+    foreach my $stor (keys %{$cfg->{ids}}) {
+       delete($cfg->{ids}->{$stor}) if ! $cfg->{ids}->{$stor}->{content}->{images};
+    }
+
+    print "rescan volumes...\n";
     my $volid_hash = scan_volids($cfg, $vmid);
 
     my $updatefn =  sub {
@@ -5611,7 +5724,7 @@ sub rescan {
 
        my $changes = update_disksize($vmid, $conf, $vm_volids);
 
-       PVE::QemuConfig->write_config($vmid, $conf) if $changes;
+       PVE::QemuConfig->write_config($vmid, $conf) if $changes && !$dryrun;
     };
 
     if (defined($vmid)) {
@@ -5662,10 +5775,7 @@ sub restore_vma_archive {
            my $readlimit = PVE::Storage::get_bandwidth_limit('restore', [$sid], $bwlimit);
            if ($readlimit) {
                print STDERR "applying read rate limit: $readlimit\n";
-               my $cstream = ['cstream', '-t', $readlimit*1024];
-               if ($readfrom ne '-') {
-                   push @$cstream, '--', $readfrom;
-               }
+               my $cstream = ['cstream', '-t', $readlimit*1024, '--', $readfrom];
                $add_pipe->($cstream);
            }
        }
@@ -6094,11 +6204,11 @@ sub do_snapshots_with_qemu {
 }
 
 sub qga_check_running {
-    my ($vmid) = @_;
+    my ($vmid, $nowarn) = @_;
 
     eval { vm_mon_cmd($vmid, "guest-ping", timeout => 3); };
     if ($@) {
-       warn "Qemu Guest Agent is not running - $@";
+       warn "Qemu Guest Agent is not running - $@" if !$nowarn;
        return 0;
     }
     return 1;
@@ -6148,6 +6258,8 @@ sub qemu_img_convert {
        my $cmd = [];
        push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
        push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
+       push @$cmd, '-t', 'none' if $dst_scfg->{type} eq 'zfspool';
+       push @$cmd, '-T', 'none' if $src_scfg->{type} eq 'zfspool';
        push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
        if ($is_zero_initialized) {
            push @$cmd, "zeroinit:$dst_path";
@@ -6463,13 +6575,8 @@ sub qemu_machine_pxe {
 
     $machine =  PVE::QemuServer::get_current_qemu_machine($vmid) if !$machine;
 
-    foreach my $opt (keys %$conf) {
-       next if $opt !~ m/^net(\d+)$/;
-       my $net = PVE::QemuServer::parse_net($conf->{$opt});
-       next if !$net;
-       my $romfile = PVE::QemuServer::vm_mon_cmd_nocheck($vmid, 'qom-get', path => $opt, property => 'romfile');
-       return $machine.".pxe" if $romfile =~ m/pxe/;
-       last;
+    if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) {
+       $machine .= '.pxe';
     }
 
     return $machine;
@@ -6590,6 +6697,11 @@ sub add_hyperv_enlightenments {
 
     if ($winversion >= 7) {
        push @$cpuFlags , 'hv_relaxed';
+
+       if (qemu_machine_feature_enabled ($machine_type, $kvmver, 3, 0)) {
+           push @$cpuFlags , 'hv_synic';
+           push @$cpuFlags , 'hv_stimer';
+       }
     }
 }
 
@@ -6724,4 +6836,10 @@ sub complete_storage {
     return $res;
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 1;