]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
qemu_img_convert : use "-l snapshot.name" instead -s for internal snapshot
[qemu-server.git] / PVE / QemuServer.pm
index ec91fadda8451405e2ec39b7d606abc377149a51..541d7b099ed3cdea1c7de82aae785d999ea4523c 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,
@@ -416,7 +441,7 @@ EODESC
            "displays you want, Linux guests can add displays them self. " .
            "You can also run without any graphic card, using a serial device" .
            " as terminal.",
-       enum => [qw(std cirrus vmware qxl serial0 serial1 serial2 serial3 qxl2 qxl3 qxl4)],
+       enum => [qw(cirrus qxl qxl2 qxl3 qxl4 serial0 serial1 serial2 serial3 std virtio vmware)],
     },
     watchdog => {
        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',
@@ -537,6 +559,43 @@ EODESCR
        description => "Select BIOS implementation.",
        default => 'seabios',
     },
+    vmgenid => {
+       type => 'string',
+       pattern => '(?:[a-fA-F0-9]{8}(?:-[a-fA-F0-9]{4}){3}-[a-fA-F0-9]{12}|[01])',
+       format_description => 'UUID',
+       description => "Set VM Generation ID. Use '1' to autogenerate on create or update, pass '0' to disable explicitly.",
+       verbose_description => "The VM generation ID (vmgenid) device exposes a".
+           " 128-bit integer value identifier to the guest OS. This allows to".
+           " notify the guest operating system when the virtual machine is".
+           " executed with a different configuration (e.g. snapshot execution".
+           " or creation from a template). The guest operating system notices".
+           " the change, and is then able to react as appropriate by marking".
+           " its copies of distributed databases as dirty, re-initializing its".
+           " random number generator, etc.\n".
+           "Note that auto-creation only works when done throug API/CLI create".
+           " or update methods, but not when manually editing the config file.",
+       default => "1 (autogenerated)",
+       optional => 1,
+    },
+};
+
+my $confdesc_cloudinit = {
+    citype => {
+       optional => 1,
+       type => 'string',
+       description => 'Specifies the cloud-init configuration format. The default depends on the configured operating system type (`ostype`. We use the `nocloud` format for Linux, and `configdrive2` for windows.',
+       enum => ['configdrive2', 'nocloud'],
+    },
+    ciuser => {
+       optional => 1,
+       type => 'string',
+       description => "cloud-init: User name to change ssh keys and password for instead of the image's configured default user.",
+    },
+    cipassword => {
+       optional => 1,
+       type => 'string',
+       description => 'cloud-init: Password to assign the user. Using this is generally not recommended. Use ssh keys instead. Also note that older cloud-init versions do not support hashed passwords.',
+    },
     searchdomain => {
        optional => 1,
        type => 'string',
@@ -551,14 +610,7 @@ EODESCR
        optional => 1,
        type => 'string',
        format => 'urlencoded',
-       description => "cloud-init : Setup public SSH keys (one key per line, " .
-                       "OpenSSH format).",
-    },
-    hostname => {
-       optional => 1,
-       description => "cloud-init: Hostname to use instead of the vm-name + search-domain.",
-       type => 'string', format => 'dns-name',
-       maxLength => 255,
+       description => "cloud-init: Setup public SSH keys (one key per line, OpenSSH format).",
     },
 };
 
@@ -772,7 +824,11 @@ PVE::JSONSchema::register_standard_option("pve-qm-ipconfig", $netdesc);
 
 for (my $i = 0; $i < $MAX_NETS; $i++)  {
     $confdesc->{"net$i"} = $netdesc;
-    $confdesc->{"ipconfig$i"} = $ipconfigdesc;
+    $confdesc_cloudinit->{"ipconfig$i"} = $ipconfigdesc;
+}
+
+foreach my $key (keys %$confdesc_cloudinit) {
+    $confdesc->{$key} = $confdesc_cloudinit->{$key};
 }
 
 PVE::JSONSchema::register_format('pve-volume-id-or-qm-path', \&verify_volume_id_or_qm_path);
@@ -1672,6 +1728,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;
 }
 
@@ -1744,11 +1806,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;
@@ -1880,10 +1937,15 @@ sub print_cpu_device {
     return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
 }
 
+sub drive_is_cloudinit {
+    my ($drive) = @_;
+    return $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
+}
+
 sub drive_is_cdrom {
     my ($drive, $exclude_cloudinit) = @_;
 
-    return 0 if $exclude_cloudinit && $drive->{file} =~ m@[:/]vm-\d+-cloudinit(?:\.$QEMU_FORMAT_RE)?$@;
+    return 0 if $exclude_cloudinit && drive_is_cloudinit($drive);
 
     return $drive && $drive->{media} && ($drive->{media} eq 'cdrom');
 
@@ -1950,7 +2012,6 @@ sub parse_net {
        my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
        $res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
     }
-    $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr});
     return $res;
 }
 
@@ -2057,7 +2118,10 @@ sub vmconfig_undelete_pending_option {
 sub vmconfig_register_unused_drive {
     my ($storecfg, $vmid, $conf, $drive) = @_;
 
-    if (!drive_is_cdrom($drive, 1)) {
+    if (drive_is_cloudinit($drive)) {
+       eval { PVE::Storage::vdisk_free($storecfg, $drive->{file}) };
+       warn $@ if $@;
+    } elsif (!drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
        if (vm_is_volid_owner($storecfg, $vmid, $volid)) {
            PVE::QemuConfig->add_unused_volume($conf, $volid, $vmid);
@@ -2185,6 +2249,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) = @_;
@@ -2201,13 +2278,19 @@ 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};
     }
 
     return $prop;
 }
 
+# return copy of $confdesc_cloudinit to generate documentation
+sub cloudinit_config_properties {
+
+    return dclone($confdesc_cloudinit);
+}
+
 sub check_type {
     my ($key, $value) = @_;
 
@@ -2543,9 +2626,6 @@ sub load_defaults {
        }
     }
 
-    my $conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
-    $res->{keyboard} = $conf->{keyboard} if $conf->{keyboard};
-
     return $res;
 }
 
@@ -2735,6 +2815,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
@@ -2760,7 +2887,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?
@@ -3063,6 +3190,10 @@ sub config_to_command {
 
     push @$cmd, '-id', $vmid;
 
+    my $vmname = $conf->{name} || "vm$vmid";
+
+    push @$cmd, '-name', $vmname;
+
     my $use_virtio = 0;
 
     my $qmpsocket = qmp_socket($vmid);
@@ -3078,6 +3209,10 @@ sub config_to_command {
        push @$cmd, '-smbios', "type=1,$conf->{smbios1}";
     }
 
+    if ($conf->{vmgenid}) {
+       push @$devices, '-device', 'vmgenid,guid='.$conf->{vmgenid};
+    }
+
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
        die "uefi base image not found\n" if ! -f $OVMF_CODE;
 
@@ -3219,9 +3354,6 @@ sub config_to_command {
        }
     }
 
-    my $vmname = $conf->{name} || "vm$vmid";
-
-    push @$cmd, '-name', $vmname;
 
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
@@ -3358,16 +3490,14 @@ sub config_to_command {
     
     push @$cmd, '-S' if $conf->{freeze};
 
-    # set keyboard layout
-    my $kb = $conf->{keyboard} || $defaults->{keyboard};
-    push @$cmd, '-k', $kb if $kb;
+    push @$cmd, '-k', $conf->{keyboard} if defined($conf->{keyboard});
 
     # enable sound
     #my $soundhw = $conf->{soundhw} || $defaults->{soundhw};
     #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";
@@ -3382,7 +3512,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
@@ -3574,21 +3704,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');
@@ -4138,81 +4269,6 @@ sub __read_avail {
     return $res;
 }
 
-# old code, only used to shutdown old VM after update
-sub vm_monitor_command {
-    my ($vmid, $cmdstr, $nocheck) = @_;
-
-    my $res;
-
-    eval {
-       die "VM $vmid not running\n" if !check_running($vmid, $nocheck);
-
-       my $sname = "${var_run_tmpdir}/$vmid.mon";
-
-       my $sock = IO::Socket::UNIX->new( Peer => $sname ) ||
-           die "unable to connect to VM $vmid socket - $!\n";
-
-       my $timeout = 3;
-
-       # hack: migrate sometime blocks the monitor (when migrate_downtime
-       # is set)
-       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
-           $timeout = 60*60; # 1 hour
-       }
-
-       # read banner;
-       my $data = __read_avail($sock, $timeout);
-
-       if ($data !~ m/^QEMU\s+(\S+)\s+monitor\s/) {
-           die "got unexpected qemu monitor banner\n";
-       }
-
-       my $sel = new IO::Select;
-       $sel->add($sock);
-
-       if (!scalar(my @ready = $sel->can_write($timeout))) {
-           die "monitor write error - timeout";
-       }
-
-       my $fullcmd = "$cmdstr\r";
-
-       # syslog('info', "VM $vmid monitor command: $cmdstr");
-
-       my $b;
-       if (!($b = $sock->syswrite($fullcmd)) || ($b != length($fullcmd))) {
-           die "monitor write error - $!";
-       }
-
-       return if ($cmdstr eq 'q') || ($cmdstr eq 'quit');
-
-       $timeout = 20;
-
-       if ($cmdstr =~ m/^(info\s+migrate|migrate\s)/) {
-           $timeout = 60*60; # 1 hour
-       } elsif ($cmdstr =~ m/^(eject|change)/) {
-           $timeout = 60; # note: cdrom mount command is slow
-       }
-       if ($res = __read_avail($sock, $timeout)) {
-
-           my @lines = split("\r?\n", $res);
-
-           shift @lines if $lines[0] !~ m/^unknown command/; # skip echo
-
-           $res = join("\n", @lines);
-           $res .= "\n";
-       }
-    };
-
-    my $err = $@;
-
-    if ($err) {
-       syslog("err", "VM $vmid monitor command failed - $err");
-       die $err;
-    }
-
-    return $res;
-}
-
 sub qemu_block_resize {
     my ($vmid, $deviceid, $storecfg, $volid, $size) = @_;
 
@@ -4232,7 +4288,7 @@ sub qemu_volume_snapshot {
     my $running = check_running($vmid);
 
     if ($running && do_snapshots_with_qemu($storecfg, $volid)){
-       vm_mon_cmd($vmid, "snapshot-drive", device => $deviceid, name => $snap);
+       vm_mon_cmd($vmid, 'blockdev-snapshot-internal-sync', device => $deviceid, name => $snap);
     } else {
        PVE::Storage::volume_snapshot($storecfg, $volid, $snap);
     }
@@ -4243,8 +4299,18 @@ 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);
+       vm_mon_cmd($vmid, 'blockdev-snapshot-delete-internal-sync', device => $deviceid, name => $snap);
     } else {
        PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
     }
@@ -4344,7 +4410,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+)$/) {
@@ -4376,6 +4445,22 @@ sub vmconfig_hotplug_pending {
        }
     }
 
+    my $apply_pending_cloudinit;
+    $apply_pending_cloudinit = sub {
+       my ($key, $value) = @_;
+       $apply_pending_cloudinit = sub {}; # once is enough
+
+       my @cloudinit_opts = keys %$confdesc_cloudinit;
+       foreach my $opt (keys %{$conf->{pending}}) {
+           next if !grep { $_ eq $opt } @cloudinit_opts;
+           $conf->{$opt} = delete $conf->{pending}->{$opt};
+       }
+
+       my $new_conf = { %$conf };
+       $new_conf->{$key} = $value;
+       PVE::QemuServer::Cloudinit::generate_cloudinitconfig($new_conf, $vmid);
+    };
+
     foreach my $opt (keys %{$conf->{pending}}) {
        next if $selection && !$selection->{$opt};
        my $value = $conf->{pending}->{$opt};
@@ -4417,6 +4502,10 @@ sub vmconfig_hotplug_pending {
                                    $vmid, $opt, $value);
            } elsif (is_valid_drivename($opt)) {
                # some changes can be done without hotplug
+               my $drive = parse_drive($opt, $value);
+               if (drive_is_cloudinit($drive)) {
+                   &$apply_pending_cloudinit($opt, $value);
+               }
                vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
                                     $vmid, $opt, $value, 1);
            } elsif ($opt =~ m/^memory$/) { #dimms
@@ -4679,6 +4768,9 @@ sub vmconfig_update_disk {
 
                if ($drive->{file} eq 'none') {
                    vm_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
@@ -4841,10 +4933,11 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       if (!check_running($vmid, 1) && -d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
-           my $cmd = [];
-           push @$cmd, '/bin/systemctl', 'stop', "$vmid.scope";
-           eval  { run_command($cmd); };
+       if (!check_running($vmid, 1)) {
+           eval {
+               run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
+                   outfunc => sub {}, errfunc => sub {});
+           };
        }
 
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
@@ -4864,6 +4957,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 {
@@ -4873,11 +4973,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;
@@ -4888,10 +4984,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 = $@) {
@@ -4942,10 +5035,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+$/;
@@ -4994,10 +5085,6 @@ sub vm_qmp_command {
            my $qmpclient = PVE::QMPClient->new();
 
            $res = $qmpclient->cmd($vmid, $cmd, $timeout);
-       } elsif (-e "${var_run_tmpdir}/$vmid.mon") {
-           die "can't execute complex command on old monitor - stop/start your vm to fix the problem\n"
-               if scalar(%{$cmd->{arguments}});
-           vm_monitor_command($vmid, $cmd->{execute}, $nocheck);
        } else {
            die "unable to open monitor socket\n";
        }
@@ -5120,7 +5207,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);
@@ -5196,6 +5283,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);
@@ -5203,10 +5297,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);
        }
     });
 }
@@ -5482,6 +5576,13 @@ sub restore_update_config_line {
        } else {
            print $outfd $line;
        }
+    } elsif (($line =~ m/^vmgenid: (.*)/)) {
+       my $vmgenid = $1;
+       if ($vmgenid ne '0') {
+           # always generate a new vmgenid if there was a valid one setup
+           $vmgenid = generate_uuid();
+       }
+       print $outfd "vmgenid: $vmgenid\n";
     } elsif (($line =~ m/^(smbios1: )(.*)/) && $unique) {
        my ($uuid, $uuid_str);
        UUID::generate($uuid);
@@ -5556,6 +5657,7 @@ sub update_disksize {
     my ($vmid, $conf, $volid_hash) = @_;
 
     my $changes;
+    my $prefix = "VM $vmid:";
 
     # used and unused disks
     my $referenced = {};
@@ -5587,6 +5689,7 @@ sub update_disksize {
            if ($new ne $conf->{$opt}) {
                $changes = 1;
                $conf->{$opt} = $new;
+               print "$prefix update disk '$opt' information.\n";
            }
        }
     }
@@ -5597,6 +5700,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};
        }
@@ -5612,7 +5716,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)
     }
 
@@ -5620,10 +5725,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 {
@@ -5641,7 +5753,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)) {
@@ -5665,21 +5777,49 @@ sub rescan {
 sub restore_vma_archive {
     my ($archive, $vmid, $user, $opts, $comp) = @_;
 
-    my $input = $archive eq '-' ? "<&STDIN" : undef;
     my $readfrom = $archive;
 
-    my $uncomp = '';
-    if ($comp) {
+    my $cfg = PVE::Storage::config();
+    my $commands = [];
+    my $bwlimit = $opts->{bwlimit};
+
+    my $dbg_cmdstring = '';
+    my $add_pipe = sub {
+       my ($cmd) = @_;
+       push @$commands, $cmd;
+       $dbg_cmdstring .= ' | ' if length($dbg_cmdstring);
+       $dbg_cmdstring .= PVE::Tools::cmd2string($cmd);
        $readfrom = '-';
-       my $qarchive = PVE::Tools::shellquote($archive);
+    };
+
+    my $input = undef;
+    if ($archive eq '-') {
+       $input = '<&STDIN';
+    } else {
+       # If we use a backup from a PVE defined storage we also consider that
+       # storage's rate limit:
+       my (undef, $volid) = PVE::Storage::path_to_volume_id($cfg, $archive);
+       if (defined($volid)) {
+           my ($sid, undef) = PVE::Storage::parse_volume_id($volid);
+           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, '--', $readfrom];
+               $add_pipe->($cstream);
+           }
+       }
+    }
+
+    if ($comp) {
+       my $cmd;
        if ($comp eq 'gzip') {
-           $uncomp = "zcat $qarchive|";
+           $cmd = ['zcat', $readfrom];
        } elsif ($comp eq 'lzop') {
-           $uncomp = "lzop -d -c $qarchive|";
+           $cmd = ['lzop', '-d', '-c', $readfrom];
        } else {
            die "unknown compression method '$comp'\n";
        }
-
+       $add_pipe->($cmd);
     }
 
     my $tmpdir = "/var/tmp/vzdumptmp$$";
@@ -5699,7 +5839,7 @@ sub restore_vma_archive {
        open($fifofh, '>', $mapfifo) || die $!;
     };
 
-    my $cmd = "${uncomp}vma extract -v -r $mapfifo $readfrom $tmpdir";
+    $add_pipe->(['vma', 'extract', '-v', '-r', $mapfifo, $readfrom, $tmpdir]);
 
     my $oldtimeout;
     my $timeout = 5;
@@ -5715,6 +5855,8 @@ sub restore_vma_archive {
     my $cfs_path = PVE::QemuConfig->cfs_config_path($vmid);
     my $oldconf = PVE::Cluster::cfs_read_file($cfs_path);
 
+    my %storage_limits;
+
     my $print_devmap = sub {
        my $virtdev_hash = {};
 
@@ -5753,17 +5895,24 @@ sub restore_vma_archive {
                    $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
                }
 
+               $storage_limits{$storeid} = $bwlimit;
+
                $virtdev_hash->{$virtdev} = $devinfo->{$devname};
            }
        }
 
+       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 $devname (keys %$devinfo) {
            die "found no device mapping information for device '$devname'\n"
                if !$devinfo->{$devname}->{virtdev};
        }
 
-       my $cfg = PVE::Storage::config();
-
        # create empty/temp config
        if ($oldconf) {
            PVE::Tools::file_set_contents($conffile, "memory: 128\n");
@@ -5806,14 +5955,20 @@ sub restore_vma_archive {
        foreach my $virtdev (sort keys %$virtdev_hash) {
            my $d = $virtdev_hash->{$virtdev};
            my $alloc_size = int(($d->{size} + 1024 - 1)/1024);
-           my $scfg = PVE::Storage::storage_config($cfg, $d->{storeid});
+           my $storeid = $d->{storeid};
+           my $scfg = PVE::Storage::storage_config($cfg, $storeid);
+
+           my $map_opts = '';
+           if (my $limit = $storage_limits{$storeid}) {
+               $map_opts .= "throttling.bps=$limit:throttling.group=$storeid:";
+           }
 
            # test if requested format is supported
-           my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $d->{storeid});
+           my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($cfg, $storeid);
            my $supported = grep { $_ eq $d->{format} } @$validFormats;
            $d->{format} = $defFormat if !$supported;
 
-           my $volid = PVE::Storage::vdisk_alloc($cfg, $d->{storeid}, $vmid,
+           my $volid = PVE::Storage::vdisk_alloc($cfg, $storeid, $vmid,
                                                  $d->{format}, undef, $alloc_size);
            print STDERR "new volume ID is '$volid'\n";
            $d->{volid} = $volid;
@@ -5826,7 +5981,7 @@ sub restore_vma_archive {
                $write_zeros = 0;
            }
 
-           print $fifofh "format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
+           print $fifofh "${map_opts}format=$d->{format}:${write_zeros}:$d->{devname}=$path\n";
 
            print "map '$d->{devname}' to '$path' (write zeros = ${write_zeros})\n";
            $map->{$virtdev} = $volid;
@@ -5879,8 +6034,8 @@ sub restore_vma_archive {
            }
        };
 
-       print "restore vma archive: $cmd\n";
-       run_command($cmd, input => $input, outfunc => $parser, afterfork => $openfifo);
+       print "restore vma archive: $dbg_cmdstring\n";
+       run_command($commands, input => $input, outfunc => $parser, afterfork => $openfifo);
     };
     my $err = $@;
 
@@ -5892,7 +6047,6 @@ sub restore_vma_archive {
        push @$vollist, $volid if $volid;
     }
 
-    my $cfg = PVE::Storage::config();
     PVE::Storage::deactivate_volumes($cfg, $vollist);
 
     unlink $mapfifo;
@@ -6079,11 +6233,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;
@@ -6132,7 +6286,9 @@ 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, '-l', "snapshot.name=$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";
@@ -6357,7 +6513,17 @@ sub clone_disk {
        my ($size) = PVE::Storage::volume_size_info($storecfg, $drive->{file}, 3);
 
        print "create full clone of drive $drivename ($drive->{file})\n";
-       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, undef, ($size/1024));
+       my $name = undef;
+       if (drive_is_cloudinit($drive)) {
+           $name = "vm-$newvmid-cloudinit";
+           # cloudinit only supports raw and qcow2 atm:
+           if ($dst_format eq 'qcow2') {
+               $name .= '.qcow2';
+           } elsif ($dst_format ne 'raw') {
+               die "clone: unhandled format for cloudinit image\n";
+           }
+       }
+       $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $dst_format, $name, ($size/1024));
        push @$newvollist, $newvolid;
 
        PVE::Storage::activate_volumes($storecfg, [$newvolid]);
@@ -6428,9 +6594,9 @@ sub qemu_machine_feature_enabled {
        $current_minor = $2;
     }
 
-    return 1 if $current_major >= $version_major && $current_minor >= $version_minor;
-
-
+    return 1 if $current_major > $version_major ||
+                ($current_major == $version_major &&
+                 $current_minor >= $version_minor);
 }
 
 sub qemu_machine_pxe {
@@ -6438,13 +6604,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;
@@ -6565,6 +6726,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';
+       }
     }
 }
 
@@ -6619,11 +6785,15 @@ sub resolve_first_disk {
     return $firstdisk;
 }
 
-sub generate_smbios1_uuid {
+sub generate_uuid {
     my ($uuid, $uuid_str);
     UUID::generate($uuid);
     UUID::unparse($uuid, $uuid_str);
-    return "uuid=$uuid_str";
+    return $uuid_str;
+}
+
+sub generate_smbios1_uuid {
+    return "uuid=".generate_uuid();
 }
 
 # bash completion helper
@@ -6699,4 +6869,10 @@ sub complete_storage {
     return $res;
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 1;