]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
cleanup: drop superfluous condition in assignment
[qemu-server.git] / PVE / QemuServer.pm
index 9999b83e4dfe2209a4920f433fd730f7d5ceb1bc..bc26da2d08fd5e7cac3d7d708eecfdc71b020b66 100644 (file)
@@ -75,6 +75,13 @@ PVE::JSONSchema::register_standard_option('pve-snapshot-name', {
     maxLength => 40,
 });
 
+PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
+    type => 'string',
+    enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
+    description => "The drive's backing file's data format.",
+    optional => 1,
+});
+
 #no warnings 'redefine';
 
 sub cgroups_write {
@@ -140,7 +147,6 @@ my $cpu_fmt = {
        description => "Emulated CPU type.",
        type => 'string',
        enum => [ sort { "\L$a" cmp "\L$b" } keys %$cpu_vendor_list ],
-       format_description => 'cputype',
        default => 'kvm64',
        default_key => 1,
     },
@@ -267,7 +273,7 @@ my $confdesc = {
     ostype => {
        optional => 1,
        type => 'string',
-        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 l24 l26 solaris)],
+        enum => [qw(other wxp w2k w2k3 w2k8 wvista win7 win8 win10 l24 l26 solaris)],
        description => "Specify guest operating system.",
        verbose_description => <<EODESC,
 Specify guest operating system. This is used to enable special
@@ -453,7 +459,7 @@ EODESCR
     cdrom => {
        optional => 1,
        type => 'string', format => 'pve-qm-ide',
-       typetext => 'volume',
+       typetext => '<volume>',
        description => "This is an alias for option -ide2",
     },
     cpu => {
@@ -607,7 +613,6 @@ my $net_fmt = {
     model => {
        type => 'string',
        description => "Network Card Model. The 'virtio' model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use 'e1000'.",
-       format_description => 'model',
         enum => $nic_model_list,
         default_key => 1,
     },
@@ -734,13 +739,7 @@ my %drivedesc_base = (
        description => "The drive's cache mode",
        optional => 1,
     },
-    format => {
-       type => 'string',
-       format_description => 'image format',
-       enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
-       description => "The drive's backing file's data format.",
-       optional => 1,
-    },
+    format => get_standard_option('pve-qm-image-format'),
     size => {
        type => 'string',
        format => 'disk-size',
@@ -822,32 +821,42 @@ my %queues_fmt = (
 );
 
 my $add_throttle_desc = sub {
-    my ($key, $type, $what, $unit, $longunit) = @_;
-    $drivedesc_base{$key} = {
+    my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
+    my $d = {
        type => $type,
        format_description => $unit,
-       description => "Maximum $what speed in $longunit per second.",
+       description => "Maximum $what in $longunit.",
        optional => 1,
     };
+    $d->{minimum} = $minimum if defined($minimum);
+    $drivedesc_base{$key} = $d;
 };
 # throughput: (leaky bucket)
-$add_throttle_desc->('bps',     'integer', 'r/w speed',   'bps',  'bytes');
-$add_throttle_desc->('bps_rd',  'integer', 'read speed',  'bps',  'bytes');
-$add_throttle_desc->('bps_wr',  'integer', 'write speed', 'bps',  'bytes');
-$add_throttle_desc->('mbps',    'number',  'r/w speed',   'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd', 'number',  'read speed',  'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr', 'number',  'write speed', 'mbps', 'megabytes');
-$add_throttle_desc->('iops',    'integer', 'r/w I/O',     'iops', 'operations');
-$add_throttle_desc->('iops_rd', 'integer', 'read I/O',    'iops', 'operations');
-$add_throttle_desc->('iops_wr', 'integer', 'write I/O',   'iops', 'operations');
+$add_throttle_desc->('bps',     'integer', 'r/w speed',   'bps',  'bytes per second');
+$add_throttle_desc->('bps_rd',  'integer', 'read speed',  'bps',  'bytes per second');
+$add_throttle_desc->('bps_wr',  'integer', 'write speed', 'bps',  'bytes per second');
+$add_throttle_desc->('mbps',    'number',  'r/w speed',   'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd', 'number',  'read speed',  'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr', 'number',  'write speed', 'mbps', 'megabytes per second');
+$add_throttle_desc->('iops',    'integer', 'r/w I/O',     'iops', 'operations per second');
+$add_throttle_desc->('iops_rd', 'integer', 'read I/O',    'iops', 'operations per second');
+$add_throttle_desc->('iops_wr', 'integer', 'write I/O',   'iops', 'operations per second');
 
 # pools: (pool of IO before throttling starts taking effect)
-$add_throttle_desc->('mbps_max',    'number',  'unthrottled r/w pool',       'mbps', 'megabytes');
-$add_throttle_desc->('mbps_rd_max', 'number',  'unthrottled read pool',      'mbps', 'megabytes');
-$add_throttle_desc->('mbps_wr_max', 'number',  'unthrottled write pool',     'mbps', 'megabytes');
-$add_throttle_desc->('iops_max',    'integer', 'unthrottled r/w I/O pool',   'iops', 'operations');
-$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool',  'iops', 'operations');
-$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations');
+$add_throttle_desc->('mbps_max',    'number',  'unthrottled r/w pool',       'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_rd_max', 'number',  'unthrottled read pool',      'mbps', 'megabytes per second');
+$add_throttle_desc->('mbps_wr_max', 'number',  'unthrottled write pool',     'mbps', 'megabytes per second');
+$add_throttle_desc->('iops_max',    'integer', 'unthrottled r/w I/O pool',   'iops', 'operations per second');
+$add_throttle_desc->('iops_rd_max', 'integer', 'unthrottled read I/O pool',  'iops', 'operations per second');
+$add_throttle_desc->('iops_wr_max', 'integer', 'unthrottled write I/O pool', 'iops', 'operations per second');
+
+# burst lengths
+$add_throttle_desc->('bps_max_length',  'integer', 'length of I/O bursts',       'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_rd_length',   'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('bps_wr_length',   'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_max_length', 'integer', 'length of I/O bursts',       'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_rd_length',  'integer', 'length of read I/O bursts',  'seconds', 'seconds', 1);
+$add_throttle_desc->('iops_wr_length',  'integer', 'length of write I/O bursts', 'seconds', 'seconds', 1);
 
 my $ide_fmt = {
     %drivedesc_base,
@@ -915,13 +924,7 @@ my $efidisk_fmt = {
        format_description => 'volume',
        description => "The drive's backing volume.",
     },
-    format => {
-       type => 'string',
-       format_description => 'image format',
-       enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
-       description => "The drive's backing file's data format.",
-       optional => 1,
-    },
+    format => get_standard_option('pve-qm-image-format'),
     size => {
        type => 'string',
        format => 'disk-size',
@@ -996,6 +999,13 @@ EODESCR
        optional => 1,
        default => 1,
     },
+    romfile => {
+        type => 'string',
+        pattern => '[^,;]+',
+        format_description => 'string',
+        description => "Custom pci device rom filename (must be located in /usr/share/kvm/).",
+        optional => 1,
+    },
     pcie => {
        type => 'boolean',
         description =>  "Choose the PCI-express bus (needs the 'q35' machine model).",
@@ -1182,6 +1192,7 @@ sub os_list_description {
        wvista => 'Windows Vista',
        win7 => 'Windows 7',
        win8 => 'Windows 8/2012',
+       win10 => 'Windows 10/2016',
        l24 => 'Linux 2.4',
        l26 => 'Linux 2.6',
     };
@@ -1339,6 +1350,22 @@ sub parse_drive {
            $res->{"m$opt"} = sprintf("%.3f", $bps / (1024*1024.0));
        }
     }
+
+    # can't use the schema's 'requires' because of the mbps* => bps* "transforming aliases"
+    for my $requirement (
+       [bps_max_length => 'mbps_max'],
+       [bps_rd_max_length => 'mbps_rd_max'],
+       [bps_wr_max_length => 'mbps_wr_max'],
+       [iops_max_length => 'iops_max'],
+       [iops_rd_max_length => 'iops_rd_max'],
+       [iops_wr_max_length => 'iops_wr_max']) {
+       my ($option, $requires) = @$requirement;
+       if ($res->{$option} && !$res->{$requires}) {
+           warn "$option requires $requires\n";
+           ++$error;
+       }
+    }
+
     return undef if $error;
 
     return undef if $res->{mbps_rd} && $res->{mbps};
@@ -2062,7 +2089,11 @@ sub destroy_vm {
        my ($path, $owner) = PVE::Storage::path($storecfg, $volid);
        return if !$path || !$owner || ($owner != $vmid);
 
-       PVE::Storage::vdisk_free($storecfg, $volid);
+       eval {
+           PVE::Storage::vdisk_free($storecfg, $volid);
+       };
+       warn "Could not remove disk '$volid', check manually: $@" if $@;
+
     });
 
     if ($keep_empty_config) {
@@ -2684,7 +2715,7 @@ sub vmstatus {
        $qmpclient->queue_cmd($vmid, $statuscb, 'query-status');
     }
 
-    $qmpclient->queue_execute(undef, 1);
+    $qmpclient->queue_execute(undef, 2);
 
     foreach my $vmid (keys %$list) {
        next if $opt_vmid && ($vmid ne $opt_vmid);
@@ -2761,6 +2792,8 @@ sub config_to_command {
     my $kvmver = kvm_user_version();
     my $vernum = 0; # unknown
     my $ostype = $conf->{ostype};
+    my $winversion = windows_version($ostype);
+
     if ($kvmver =~ m/^(\d+)\.(\d+)$/) {
        $vernum = $1*1000000+$2*1000;
     } elsif ($kvmver =~ m/^(\d+)\.(\d+)\.(\d+)$/) {
@@ -2850,13 +2883,7 @@ sub config_to_command {
     $vga = 'qxl' if $qxlnum;
 
     if (!$vga) {
-       if ($conf->{ostype} && ($conf->{ostype} eq 'win8' ||
-                               $conf->{ostype} eq 'win7' ||
-                               $conf->{ostype} eq 'w2k8')) {
-           $vga = 'std';
-       } else {
-           $vga = 'cirrus';
-       }
+       $vga = $winversion >= 6 ? 'std' : 'cirrus';
     }
 
     # enable absolute mouse coordinates (needed by vnc)
@@ -2872,6 +2899,8 @@ sub config_to_command {
     push @$devices, '-device', print_tabletdevice_full($conf) if $tablet;
 
     my $kvm_off = 0;
+    my $gpu_passthrough;
+
     # host pci devices
     for (my $i = 0; $i < $MAX_HOSTPCI_DEVICES; $i++)  {
        my $d = parse_hostpci($conf->{"hostpci$i"});
@@ -2886,14 +2915,15 @@ sub config_to_command {
        }
 
        my $rombar = defined($d->{rombar}) && !$d->{rombar} ? ',rombar=0' : '';
+       my $romfile = $d->{romfile};
+
        my $xvga = '';
        if ($d->{'x-vga'}) {
            $xvga = ',x-vga=on';
            $kvm_off = 1;
            $vga = 'none';
-           if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8') {
-               push @$cpuFlags , 'hv_vendor_id=proxmox';
-           }
+           $gpu_passthrough = 1;
+
            if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
                $xvga = "";
            }
@@ -2913,6 +2943,7 @@ sub config_to_command {
            if($j == 0){
                $devicestr .= "$rombar$xvga";
                $devicestr .= ",multifunction=on" if $multifunction;
+               $devicestr .= ",romfile=/usr/share/kvm/$romfile" if $romfile;
            }
 
            push @$devices, '-device', $devicestr;
@@ -3011,41 +3042,18 @@ sub config_to_command {
     my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
     my $useLocaltime = $conf->{localtime};
 
-    if ($ostype) {
-       # other, wxp, w2k, w2k3, w2k8, wvista, win7, win8, l24, l26, solaris
-
-       if ($ostype =~ m/^w/) { # windows
-           $useLocaltime = 1 if !defined($conf->{localtime});
+    if ($winversion >= 5) { # windows
+       $useLocaltime = 1 if !defined($conf->{localtime});
 
-           # use time drift fix when acpi is enabled
-           if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
-               $tdf = 1 if !defined($conf->{tdf});
-           }
-       }
-
-       if ($ostype eq 'win7' || $ostype eq 'win8' || $ostype eq 'w2k8' ||
-           $ostype eq 'wvista') {
-           push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
-           push @$cmd, '-no-hpet';
-           if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
-               push @$cpuFlags , 'hv_spinlocks=0x1fff' if !$nokvm;
-               push @$cpuFlags , 'hv_vapic' if !$nokvm;
-               push @$cpuFlags , 'hv_time' if !$nokvm;
-
-               if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
-                   push @$cpuFlags , 'hv_reset' if !$nokvm;
-                   push @$cpuFlags , 'hv_vpindex' if !$nokvm;
-                   push @$cpuFlags , 'hv_runtime' if !$nokvm;
-               }
-
-           } else {
-               push @$cpuFlags , 'hv_spinlocks=0xffff' if !$nokvm;
-           }
+       # use time drift fix when acpi is enabled
+       if (!(defined($conf->{acpi}) && $conf->{acpi} == 0)) {
+           $tdf = 1 if !defined($conf->{tdf});
        }
+    }
 
-       if ($ostype eq 'win7' || $ostype eq 'win8') {
-           push @$cpuFlags , 'hv_relaxed' if !$nokvm;
-       }
+    if ($winversion >= 6) {
+       push @$globalFlags, 'kvm-pit.lost_tick_policy=discard';
+       push @$cmd, '-no-hpet';
     }
 
     push @$rtcFlags, 'driftfix=slew' if $tdf;
@@ -3089,6 +3097,8 @@ sub config_to_command {
        push @$cpuFlags , '+kvm_pv_eoi' if !$nokvm;
     }
 
+    add_hyperv_enlighments($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $conf->{bios}, $gpu_passthrough);
+
     push @$cpuFlags, 'enforce' if $cpu ne 'host' && !$nokvm;
 
     push @$cpuFlags, 'kvm=off' if $kvm_off;
@@ -3128,7 +3138,7 @@ sub config_to_command {
 
     if ($qxlnum) {
        if ($qxlnum > 1) {
-           if ($conf->{ostype} && $conf->{ostype} =~ m/^w/){
+           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";
@@ -3828,7 +3838,9 @@ sub qemu_cpu_hotplug {
 sub qemu_block_set_io_throttle {
     my ($vmid, $deviceid,
        $bps, $bps_rd, $bps_wr, $iops, $iops_rd, $iops_wr,
-       $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max) = @_;
+       $bps_max, $bps_rd_max, $bps_wr_max, $iops_max, $iops_rd_max, $iops_wr_max,
+       $bps_max_length, $bps_rd_max_length, $bps_wr_max_length,
+       $iops_max_length, $iops_rd_max_length, $iops_wr_max_length) = @_;
 
     return if !check_running($vmid) ;
 
@@ -3844,7 +3856,13 @@ sub qemu_block_set_io_throttle {
        bps_wr_max => int($bps_wr_max),
        iops_max => int($iops_max),
        iops_rd_max => int($iops_rd_max),
-       iops_wr_max => int($iops_wr_max)
+       iops_wr_max => int($iops_wr_max),
+       bps_max_length => int($bps_max_length),
+       bps_rd_max_length => int($bps_rd_max_length),
+       bps_wr_max_length => int($bps_wr_max_length),
+       iops_max_length => int($iops_max_length),
+       iops_rd_max_length => int($iops_rd_max_length),
+       iops_wr_max_length => int($iops_wr_max_length),
     );
 
 }
@@ -3987,11 +4005,11 @@ sub qemu_volume_snapshot_delete {
 
     my $running = check_running($vmid);
 
-    return if !PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
-
-    return if !$running;
-
-    vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+    if ($running && do_snapshots_with_qemu($storecfg, $volid)){
+       vm_mon_cmd($vmid, "delete-drive-snapshot", device => $deviceid, name => $snap);
+    } else {
+       PVE::Storage::volume_snapshot_delete($storecfg, $volid, $snap, $running);
+    }
 }
 
 sub set_migration_caps {
@@ -4366,7 +4384,7 @@ sub vmconfig_update_disk {
                    # update existing disk
 
                    # skip non hotpluggable value
-                   if (&$safe_num_ne($drive->{discard}, $old_drive->{discard}) ||
+                   if (&$safe_string_ne($drive->{discard}, $old_drive->{discard}) ||
                        &$safe_string_ne($drive->{iothread}, $old_drive->{iothread}) ||
                        &$safe_string_ne($drive->{queues}, $old_drive->{queues}) ||
                        &$safe_string_ne($drive->{cache}, $old_drive->{cache})) {
@@ -4385,7 +4403,13 @@ sub vmconfig_update_disk {
                        &$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) ||
                        &$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
                        &$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) ||
-                       &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max})) {
+                       &$safe_num_ne($drive->{iops_wr_max}, $old_drive->{iops_wr_max}) ||
+                       &$safe_num_ne($drive->{bps_max_length}, $old_drive->{bps_max_length}) ||
+                       &$safe_num_ne($drive->{bps_rd_max_length}, $old_drive->{bps_rd_max_length}) ||
+                       &$safe_num_ne($drive->{bps_wr_max_length}, $old_drive->{bps_wr_max_length}) ||
+                       &$safe_num_ne($drive->{iops_max_length}, $old_drive->{iops_max_length}) ||
+                       &$safe_num_ne($drive->{iops_rd_max_length}, $old_drive->{iops_rd_max_length}) ||
+                       &$safe_num_ne($drive->{iops_wr_max_length}, $old_drive->{iops_wr_max_length})) {
 
                        qemu_block_set_io_throttle($vmid,"drive-$opt",
                                                   ($drive->{mbps} || 0)*1024*1024,
@@ -4399,7 +4423,13 @@ sub vmconfig_update_disk {
                                                   ($drive->{mbps_wr_max} || 0)*1024*1024,
                                                   $drive->{iops_max} || 0,
                                                   $drive->{iops_rd_max} || 0,
-                                                  $drive->{iops_wr_max} || 0);
+                                                  $drive->{iops_wr_max} || 0,
+                                                  $drive->{bps_max_length} || 1,
+                                                  $drive->{bps_rd_max_length} || 1,
+                                                  $drive->{bps_wr_max_length} || 1,
+                                                  $drive->{iops_max_length} || 1,
+                                                  $drive->{iops_rd_max_length} || 1,
+                                                  $drive->{iops_wr_max_length} || 1);
 
                    }
 
@@ -4429,7 +4459,7 @@ sub vmconfig_update_disk {
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
-       $forcemachine, $spice_ticket, $migration_network, $migration_type) = @_;
+       $forcemachine, $spice_ticket, $migration_network, $migration_type, $targetstorage) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
@@ -4450,6 +4480,54 @@ sub vm_start {
        # set environment variable useful inside network script
        $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
 
+       my $local_volumes = {};
+
+       if ($targetstorage) {
+           foreach_drive($conf, sub {
+               my ($ds, $drive) = @_;
+
+               return if drive_is_cdrom($drive);
+
+               my $volid = $drive->{file};
+
+               return if !$volid;
+
+               my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
+
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               return if $scfg->{shared};
+               $local_volumes->{$ds} = [$volid, $storeid, $volname];
+           });
+
+           my $format = undef;
+
+           foreach my $opt (sort keys %$local_volumes) {
+
+               my ($volid, $storeid, $volname) = @{$local_volumes->{$opt}};
+               my $drive = parse_drive($opt, $conf->{$opt});
+
+               #if remote storage is specified, use default format
+               if ($targetstorage && $targetstorage ne "1") {
+                   $storeid = $targetstorage;
+                   my ($defFormat, $validFormats) = PVE::Storage::storage_default_format($storecfg, $storeid);
+                   $format = $defFormat;
+               } else {
+                   #else we use same format than original
+                   my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+                   $format = qemu_img_format($scfg, $volid);
+               }
+
+               my $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $vmid, $format, undef, ($drive->{size}/1024));
+               my $newdrive = $drive;
+               $newdrive->{format} = $format;
+               $newdrive->{file} = $newvolid;
+               my $drivestr = PVE::QemuServer::print_drive($vmid, $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 $migrate_port = 0;
@@ -4460,6 +4538,14 @@ sub vm_start {
                my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
                my $nodename = PVE::INotify::nodename();
 
+               if (!defined($migration_type)) {
+                   if (defined($datacenterconf->{migration}->{type})) {
+                       $migration_type = $datacenterconf->{migration}->{type};
+                   } else {
+                       $migration_type = 'secure';
+                   }
+               }
+
                if ($migration_type eq 'insecure') {
                    my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
                    if ($migrate_network_addr) {
@@ -4579,8 +4665,27 @@ sub vm_start {
            warn $@ if $@;
        }
 
-       if ($migratedfrom) {
+       #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 $pfamily = PVE::Tools::get_host_address_family($nodename);
+           $migrate_port = PVE::Tools::next_migrate_port($pfamily);
+
+           vm_mon_cmd_nocheck($vmid, "nbd-server-start", addr => { type => 'inet', data => { host => "${localip}", port => "${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 );
+               my $migrate_storage_uri = "nbd:${localip}:${migrate_port}:exportname=drive-$opt";
+               print "storage migration listens on $migrate_storage_uri volume:$volid\n";
+           }
+       }
+
+       if ($migratedfrom) {
            eval {
                set_migration_caps($vmid);
            };
@@ -4595,7 +4700,6 @@ 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};
@@ -5809,91 +5913,221 @@ sub qemu_img_format {
 }
 
 sub qemu_drive_mirror {
-    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized) = @_;
+    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
 
-    my $storecfg = PVE::Storage::config();
-    my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
+    $jobs = {} if !$jobs;
+
+    my $qemu_target;
+    my $format;
+    $jobs->{"drive-$drive"} = {};
+
+    if ($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+):exportname=(\S+)/) {
+       my $server = $1;
+       my $port = $2;
+       my $exportname = $3;
+
+       $format = "nbd";
+       my $unixsocket = "/run/qemu-server/$vmid.mirror-drive-$drive";
+       $qemu_target = "nbd+unix:///$exportname?socket=$unixsocket";
+       my $cmd = ['socat', '-T30', "UNIX-LISTEN:$unixsocket,fork", "TCP:$server:$2,connect-timeout=5"];
+
+       my $pid = fork();
+       if (!defined($pid)) {
+           die "forking socat tunnel failed\n";
+       } elsif ($pid == 0) {
+           exec(@$cmd);
+           warn "exec failed: $!\n";
+           POSIX::_exit(-1);
+       }
+       $jobs->{"drive-$drive"}->{pid} = $pid;
+
+       my $timeout = 0;
+       while (!-S $unixsocket) {
+           die "nbd connection helper timed out\n"
+               if $timeout++ > 5;
+           sleep 1;
+       }
+    } else {
+       my $storecfg = PVE::Storage::config();
+       my ($dst_storeid, $dst_volname) = PVE::Storage::parse_volume_id($dst_volid);
 
-    my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
+       my $dst_scfg = PVE::Storage::storage_config($storecfg, $dst_storeid);
 
-    my $format = qemu_img_format($dst_scfg, $dst_volname);
+       $format = qemu_img_format($dst_scfg, $dst_volname);
 
-    my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
+       my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
-    my $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+       $qemu_target = $is_zero_initialized ? "zeroinit:$dst_path" : $dst_path;
+    }
 
     my $opts = { timeout => 10, device => "drive-$drive", mode => "existing", sync => "full", target => $qemu_target };
     $opts->{format} = $format if $format;
 
-    print "drive mirror is starting (scanning bitmap) : this step can take some minutes/hours, depend of disk size and storage speed\n";
+    print "drive mirror is starting for drive-$drive\n";
 
-    my $finish_job = sub {
-       while (1) {
-           my $stats = vm_mon_cmd($vmid, "query-block-jobs");
-           my $stat = @$stats[0];
-           last if !$stat;
-           sleep 1;
-       }
-    };
+    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
+
+    if (my $err = $@) {
+       eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
+       die "mirroring error: $err";
+    }
+
+    qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
+}
+
+sub qemu_drive_mirror_monitor {
+    my ($vmid, $vmiddst, $jobs, $skipcomplete, $qga) = @_;
 
     eval {
-    vm_mon_cmd($vmid, "drive-mirror", %$opts);
+       my $err_complete = 0;
+
        while (1) {
+           die "storage migration timed out\n" if $err_complete > 300;
+
            my $stats = vm_mon_cmd($vmid, "query-block-jobs");
-           my $stat = @$stats[0];
-           die "mirroring job seem to have die. Maybe do you have bad sectors?" if !$stat;
-           die "error job is not mirroring" if $stat->{type} ne "mirror";
 
-           my $busy = $stat->{busy};
-           my $ready = $stat->{ready};
+           my $running_mirror_jobs = {};
+           foreach my $stat (@$stats) {
+               next if $stat->{type} ne 'mirror';
+               $running_mirror_jobs->{$stat->{device}} = $stat;
+           }
+
+           my $readycounter = 0;
 
-           if (my $total = $stat->{len}) {
-               my $transferred = $stat->{offset} || 0;
-               my $remaining = $total - $transferred;
-               my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+           foreach my $job (keys %$jobs) {
 
-               print "transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+               if(defined($jobs->{$job}->{complete}) && !defined($running_mirror_jobs->{$job})) {
+                   print "$job : finished\n";
+                   delete $jobs->{$job};
+                   next;
+               }
+
+               die "$job: mirroring has been cancelled\n" if !defined($running_mirror_jobs->{$job});
+
+               my $busy = $running_mirror_jobs->{$job}->{busy};
+               my $ready = $running_mirror_jobs->{$job}->{ready};
+               if (my $total = $running_mirror_jobs->{$job}->{len}) {
+                   my $transferred = $running_mirror_jobs->{$job}->{offset} || 0;
+                   my $remaining = $total - $transferred;
+                   my $percent = sprintf "%.2f", ($transferred * 100 / $total);
+
+                   print "$job: transferred: $transferred bytes remaining: $remaining bytes total: $total bytes progression: $percent % busy: $busy ready: $ready \n";
+               }
+
+               $readycounter++ if $running_mirror_jobs->{$job}->{ready} eq 'true';
            }
 
+           last if scalar(keys %$jobs) == 0;
 
-           if ($stat->{ready} eq 'true') {
+           if ($readycounter == scalar(keys %$jobs)) {
+               print "all mirroring jobs are ready \n";
+               last if $skipcomplete; #do the complete later
 
-               last if $vmiddst != $vmid;
+               if ($vmiddst && $vmiddst != $vmid) {
+                   if ($qga) {
+                       print "freeze filesystem\n";
+                       eval { PVE::QemuServer::vm_mon_cmd($vmid, "guest-fsfreeze-freeze"); };
+                   } else {
+                       print "suspend vm\n";
+                       eval { PVE::QemuServer::vm_suspend($vmid, 1); };
+                   }
+
+                   # if we clone a disk for a new target vm, we don't switch the disk
+                   PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs);
+
+                   if ($qga) {
+                       print "unfreeze filesystem\n";
+                       eval { PVE::QemuServer::vm_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
-               eval { vm_mon_cmd($vmid, "block-job-complete", device => "drive-$drive") };
-               if (!$@) {
-                   &$finish_job();
                    last;
+               } else {
+
+                   foreach my $job (keys %$jobs) {
+                       # 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) };
+                       if ($@ =~ m/cannot be completed/) {
+                           print "$job: Block job cannot be completed, try again.\n";
+                           $err_complete++;
+                       }else {
+                           print "$job: Completed successfully.\n";
+                           $jobs->{$job}->{complete} = 1;
+                           eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
+                       }
+                   }
                }
-               die $@ if $@ !~ m/cannot be completed/;
            }
            sleep 1;
        }
-
-
     };
     my $err = $@;
 
-    my $cancel_job = sub {
-       vm_mon_cmd($vmid, "block-job-cancel", device => "drive-$drive");
-       &$finish_job();
-    };
-
     if ($err) {
-       eval { &$cancel_job(); };
+       eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
        die "mirroring error: $err";
     }
 
-    if ($vmiddst != $vmid) {
-       # if we clone a disk for a new target vm, we don't switch the disk
-       &$cancel_job(); # so we call block-job-cancel
+}
+
+sub qemu_blockjobs_cancel {
+    my ($vmid, $jobs) = @_;
+
+    foreach my $job (keys %$jobs) {
+       print "$job: Cancelling block job\n";
+       eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
+       $jobs->{$job}->{cancel} = 1;
+    }
+
+    while (1) {
+       my $stats = vm_mon_cmd($vmid, "query-block-jobs");
+
+       my $running_jobs = {};
+       foreach my $stat (@$stats) {
+           $running_jobs->{$stat->{device}} = $stat;
+       }
+
+       foreach my $job (keys %$jobs) {
+
+           if (defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) {
+               print "$job: Done.\n";
+               eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
+               delete $jobs->{$job};
+           }
+       }
+
+       last if scalar(keys %$jobs) == 0;
+
+       sleep 1;
     }
 }
 
+sub qemu_blockjobs_finish_tunnel {
+   my ($vmid, $job, $cpid) = @_;
+
+   return if !$cpid;
+
+   for (my $i = 1; $i < 20; $i++) {
+       my $waitpid = waitpid($cpid, WNOHANG);
+       last if (defined($waitpid) && ($waitpid == $cpid));
+       if ($i == 10) {
+           kill(15, $cpid);
+       } elsif ($i >= 15) {
+           kill(9, $cpid);
+       }
+       sleep (1);
+    }
+    unlink "/run/qemu-server/$vmid.mirror-$job";
+}
+
 sub clone_disk {
     my ($storecfg, $vmid, $running, $drivename, $drive, $snapname,
-       $newvmid, $storage, $format, $full, $newvollist) = @_;
+       $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
 
     my $newvolid;
 
@@ -5902,6 +6136,7 @@ sub clone_disk {
        $newvolid = PVE::Storage::vdisk_clone($storecfg,  $drive->{file}, $newvmid, $snapname);
        push @$newvollist, $newvolid;
     } else {
+
        my ($storeid, $volname) = PVE::Storage::parse_volume_id($drive->{file});
        $storeid = $storage if $storage;
 
@@ -5934,7 +6169,7 @@ sub clone_disk {
                    if $drive->{iothread};
            }
 
-           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit);
+           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
        }
     }
 
@@ -6078,6 +6313,52 @@ sub scsihw_infos {
     return ($maxdev, $controller, $controller_prefix);
 }
 
+sub add_hyperv_enlighments {
+    my ($cpuFlags, $winversion, $machine_type, $kvmver, $nokvm, $bios, $gpu_passthrough) = @_;
+
+    return if $nokvm;
+    return if $winversion < 6;
+    return if $bios && $bios eq 'ovmf' && $winversion < 8;
+
+    push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+
+    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
+       push @$cpuFlags , 'hv_spinlocks=0x1fff';
+       push @$cpuFlags , 'hv_vapic';
+       push @$cpuFlags , 'hv_time';
+    } else {
+       push @$cpuFlags , 'hv_spinlocks=0xffff';
+    }
+
+    if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 6)) {
+       push @$cpuFlags , 'hv_reset';
+       push @$cpuFlags , 'hv_vpindex';
+       push @$cpuFlags , 'hv_runtime';
+    }
+
+    if ($winversion >= 7) {
+       push @$cpuFlags , 'hv_relaxed';
+    }
+}
+
+sub windows_version {
+    my ($ostype) = @_;
+
+    return 0 if !$ostype;
+
+    my $winversion = 0;
+
+    if($ostype eq 'wxp' || $ostype eq 'w2k3' || $ostype eq 'w2k') {
+        $winversion = 5;
+    } elsif($ostype eq 'w2k8' || $ostype eq 'wvista') {
+        $winversion = 6;
+    } elsif ($ostype =~ m/^win(\d+)$/) {
+        $winversion = $1;
+    }
+
+    return $winversion;
+}
+
 # bash completion helper
 
 sub complete_backup_archives {
@@ -6151,4 +6432,10 @@ sub complete_storage {
     return $res;
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 1;