]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
allow dedicated migration network, bug #1177
[qemu-server.git] / PVE / QemuServer.pm
index da60eee29f9c8dedc5d0b15be03a5958ceeeb280..9999b83e4dfe2209a4920f433fd730f7d5ceb1bc 100644 (file)
@@ -37,6 +37,10 @@ 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 $OVMF_IMG = '/usr/share/kvm/OVMF-pure-efi.fd';
+
 my $qemu_snap_storage = {rbd => 1, sheepdog => 1};
 
 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
@@ -213,7 +217,7 @@ my $confdesc = {
        verbose_description => "CPU weight for a VM. Argument is used in the kernel fair scheduler. The larger the number is, the more CPU time this VM gets. Number is relative to weights of all the other running VMs.\n\nNOTE: You can disable fair-scheduler configuration by setting this to 0.",
        minimum => 0,
        maximum => 500000,
-       default => 1000,
+       default => 1024,
     },
     memory => {
        optional => 1,
@@ -448,7 +452,7 @@ EODESCR
     },
     cdrom => {
        optional => 1,
-       type => 'string', format => 'pve-qm-drive',
+       type => 'string', format => 'pve-qm-ide',
        typetext => 'volume',
        description => "This is an alias for option -ide2",
     },
@@ -850,6 +854,7 @@ my $ide_fmt = {
     %rerror_fmt,
     %model_fmt,
 };
+PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
 my $idedesc = {
     optional => 1,
@@ -901,6 +906,39 @@ my $alldrive_fmt = {
     %queues_fmt,
 };
 
+my $efidisk_fmt = {
+    volume => { alias => 'file' },
+    file => {
+       type => 'string',
+       format => 'pve-volume-id-or-qm-path',
+       default_key => 1,
+       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,
+    },
+    size => {
+       type => 'string',
+       format => 'disk-size',
+       format_description => 'DiskSize',
+       description => "Disk size. This is purely informational and has no effect.",
+       optional => 1,
+    },
+};
+
+my $efidisk_desc = {
+    optional => 1,
+    type => 'string', format => $efidisk_fmt,
+    description => "Configure a Disk for storing EFI vars",
+};
+
+PVE::JSONSchema::register_standard_option("pve-qm-efidisk", $efidisk_desc);
+
 my $usb_fmt = {
     host => {
        default_key => 1,
@@ -1050,6 +1088,9 @@ for (my $i = 0; $i < $MAX_VIRTIO_DISKS; $i++)  {
     $confdesc->{"virtio$i"} = $virtiodesc;
 }
 
+$drivename_hash->{efidisk0} = 1;
+$confdesc->{efidisk0} = $efidisk_desc;
+
 for (my $i = 0; $i < $MAX_USB_DEVICES; $i++)  {
     $confdesc->{"usb$i"} = $usbdesc;
 }
@@ -1111,7 +1152,8 @@ sub valid_drive_names {
     return ((map { "ide$_" } (0 .. ($MAX_IDE_DISKS - 1))),
             (map { "scsi$_" } (0 .. ($MAX_SCSI_DISKS - 1))),
             (map { "virtio$_" } (0 .. ($MAX_VIRTIO_DISKS - 1))),
-            (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))));
+            (map { "sata$_" } (0 .. ($MAX_SATA_DISKS - 1))),
+            'efidisk0');
 }
 
 sub is_valid_drivename {
@@ -1633,6 +1675,28 @@ sub print_netdev_full {
     return $netdev;
 }
 
+
+sub print_cpu_device {
+    my ($conf, $id) = @_;
+
+    my $nokvm = defined($conf->{kvm}) && $conf->{kvm} == 0 ? 1 : 0;
+    my $cpu = $nokvm ? "qemu64" : "kvm64";
+    if (my $cputype = $conf->{cpu}) {
+       my $cpuconf = PVE::JSONSchema::parse_property_string($cpu_fmt, $cputype)
+           or die "Cannot parse cpu description: $cputype\n";
+       $cpu = $cpuconf->{cputype};
+    }
+
+    my $sockets = 1;
+    $sockets = $conf->{sockets} if  $conf->{sockets};
+    my $cores = $conf->{cores} || 1;
+
+    my $current_core = ($id - 1) % $cores;
+    my $current_socket = int(($id - $current_core)/$cores);
+
+    return "$cpu-x86_64-cpu,id=cpu$id,socket-id=$current_socket,core-id=$current_core,thread-id=0";
+}
+
 sub drive_is_cdrom {
     my ($drive) = @_;
 
@@ -1674,7 +1738,12 @@ sub parse_hostpci {
     delete $res->{host};
     foreach my $id (@idlist) {
        if ($id =~ /^$PCIRE$/) {
-           push @{$res->{pciid}}, { id => $1, function => ($2//'0') };
+           if (defined($2)) {
+               push @{$res->{pciid}}, { id => $1, function => $2 };
+           } else {
+               my $pcidevices = lspci($1);
+               $res->{pciid} = $pcidevices->{$1};
+           }
        } else {
            # should have been caught by parse_property_string already
            die "failed to parse PCI id: $id\n";
@@ -1692,7 +1761,10 @@ sub parse_net {
        warn $@;
        return undef;
     }
-    $res->{macaddr} = PVE::Tools::random_ether_addr() if !defined($res->{macaddr});
+    if (!defined($res->{macaddr})) {
+       my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
+       $res->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
+    }
     return $res;
 }
 
@@ -1942,12 +2014,6 @@ sub check_type {
         die "type check ('number') failed - got '$value'\n";
     } elsif ($type eq 'string') {
        if (my $fmt = $confdesc->{$key}->{format}) {
-           if ($fmt eq 'pve-qm-drive') {
-               # special case - we need to pass $key to parse_drive()
-               my $drive = parse_drive($key, $value);
-               return $value if $drive;
-               die "unable to parse drive options\n";
-           }
            PVE::JSONSchema::check_format($fmt, $value);
            return $value;
        }
@@ -2095,8 +2161,9 @@ sub parse_vm_config {
            if ($@) {
                warn "vm $vmid - unable to parse value of '$key' - $@";
            } else {
+               $key = 'ide2' if $key eq 'cdrom';
                my $fmt = $confdesc->{$key}->{format};
-               if ($fmt && $fmt eq 'pve-qm-drive') {
+               if ($fmt && $fmt =~ /^pve-qm-(?:ide|scsi|virtio|sata)$/) {
                    my $v = parse_drive($key, $value);
                    if (my $volid = filename_to_volume_id($vmid, $v->{file}, $v->{media})) {
                        $v->{file} = $volid;
@@ -2107,11 +2174,7 @@ sub parse_vm_config {
                    }
                }
 
-               if ($key eq 'cdrom') {
-                   $conf->{ide2} = $value;
-               } else {
-                   $conf->{$key} = $value;
-               }
+               $conf->{$key} = $value;
            }
        }
     }
@@ -2737,12 +2800,44 @@ sub config_to_command {
     }
 
     if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
-       my $ovmfvar = "OVMF_VARS-pure-efi.fd";
-       my $ovmfvar_src = "/usr/share/kvm/$ovmfvar";
-       my $ovmfvar_dst = "/tmp/$vmid-$ovmfvar";
-       PVE::Tools::file_copy($ovmfvar_src, $ovmfvar_dst, 256*1024);
-       push @$cmd, '-drive', "if=pflash,format=raw,readonly,file=/usr/share/kvm/OVMF-pure-efi.fd";
-       push @$cmd, '-drive', "if=pflash,format=raw,file=$ovmfvar_dst";
+       my $ovmfbase;
+
+       # prefer the OVMF_CODE variant
+       if (-f $OVMF_CODE) {
+           $ovmfbase = $OVMF_CODE;
+       } elsif (-f $OVMF_IMG) {
+           $ovmfbase = $OVMF_IMG;
+       }
+
+       die "no uefi base img found\n" if !$ovmfbase;
+       push @$cmd, '-drive', "if=pflash,unit=0,format=raw,readonly,file=$ovmfbase";
+
+       if (defined($conf->{efidisk0}) && ($ovmfbase eq $OVMF_CODE)) {
+           my $d = PVE::JSONSchema::parse_property_string($efidisk_fmt, $conf->{efidisk0});
+           my $format = $d->{format} // 'raw';
+           my $path;
+           my ($storeid, $volname) = PVE::Storage::parse_volume_id($d->{file}, 1);
+           if ($storeid) {
+               $path = PVE::Storage::path($storecfg, $d->{file});
+               my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
+               $format = qemu_img_format($scfg, $volname);
+           } else {
+               $path = $d->{file};
+               $format = "raw";
+           }
+           push @$cmd, '-drive', "if=pflash,unit=1,id=drive-efidisk0,format=$format,file=$path";
+       } elsif ($ovmfbase eq $OVMF_CODE) {
+           warn "using uefi without permanent efivars disk\n";
+           my $ovmfvar_dst = "/tmp/$vmid-ovmf.fd";
+           PVE::Tools::file_copy($OVMF_VARS, $ovmfvar_dst, 256*1024);
+           push @$cmd, '-drive', "if=pflash,unit=1,format=raw,file=$ovmfvar_dst";
+       } else {
+           # if the base img is not OVMF_CODE, we do not have to bother
+           # to create/use a vars image, since it will not be used anyway
+           # this can only happen if someone manually deletes the OVMF_CODE image
+           # or has an old pve-qemu-kvm version installed.
+           # both should not happen, but we ignore it here
+       }
     }
 
 
@@ -2872,8 +2967,18 @@ sub config_to_command {
     die "MAX $allowed_vcpus vcpus allowed per VM on this node\n"
        if ($allowed_vcpus < $maxcpus);
 
-    push @$cmd, '-smp', "$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+    if($hotplug_features->{cpu} && qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 7)) {
+
+       push @$cmd, '-smp', "1,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+        for (my $i = 2; $i <= $vcpus; $i++)  {
+           my $cpustr = print_cpu_device($conf,$i);
+           push @$cmd, '-device', $cpustr;
+       }
 
+    } else {
+
+       push @$cmd, '-smp', "$vcpus,sockets=$sockets,cores=$cores,maxcpus=$maxcpus";
+    }
     push @$cmd, '-nodefaults';
 
     my $bootorder = $conf->{boot} || $confdesc->{boot}->{default};
@@ -2885,7 +2990,7 @@ sub config_to_command {
        $i++;
     }
 
-    push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000";
+    push @$cmd, '-boot', "menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg";
 
     push @$cmd, '-no-acpi' if defined($conf->{acpi}) && $conf->{acpi} == 0;
 
@@ -3128,6 +3233,11 @@ sub config_to_command {
            $ahcicontroller->{$controller}=1;
         }
 
+       if ($drive->{interface} eq 'efidisk') {
+           # this will be added somewhere else
+           return;
+       }
+
        my $drive_cmd = print_drive_full($storecfg, $vmid, $drive);
        push @$devices, '-drive',$drive_cmd;
        push @$devices, '-device', print_drivedevice_full($storecfg, $conf, $vmid, $drive, $bridges);
@@ -3244,6 +3354,16 @@ sub vm_devices_list {
        }
     }
 
+    # for usb devices there is no query-usb
+    # but we can iterate over the entries in
+    # qom-list path=/machine/peripheral
+    my $resperipheral = vm_mon_cmd($vmid, 'qom-list', path => '/machine/peripheral');
+    foreach my $per (@$resperipheral) {
+       if ($per->{name} =~ m/^usb\d+$/) {
+           $devices->{$per->{name}} = 1;
+       }
+    }
+
     return $devices;
 }
 
@@ -3261,6 +3381,14 @@ sub vm_deviceplug {
 
        qemu_deviceadd($vmid, print_tabletdevice_full($conf));
 
+    } elsif ($deviceid =~ m/^usb(\d+)$/) {
+
+       die "usb hotplug currently not reliable\n";
+       # since we can't reliably hot unplug all added usb devices
+       # and usb passthrough disables live migration
+       # we disable usb hotplugging for now
+       qemu_deviceadd($vmid, PVE::QemuServer::USB::print_usbdevice_full($conf, $deviceid, $device));
+
     } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
        qemu_iothread_add($vmid, $deviceid, $device);
@@ -3356,6 +3484,15 @@ sub vm_deviceunplug {
 
        qemu_devicedel($vmid, $deviceid);
 
+    } elsif ($deviceid =~ m/^usb\d+$/) {
+
+       die "usb hotplug currently not reliable\n";
+       # when unplugging usb devices this way,
+       # there may be remaining usb controllers/hubs
+       # so we disable it for now
+       qemu_devicedel($vmid, $deviceid);
+       qemu_devicedelverify($vmid, $deviceid);
+
     } elsif ($deviceid =~ m/^(virtio)(\d+)$/) {
 
         qemu_devicedel($vmid, $deviceid);
@@ -3588,9 +3725,36 @@ sub qemu_netdevdel {
     vm_mon_cmd($vmid, "netdev_del", id => $deviceid);
 }
 
+sub qemu_usb_hotplug {
+    my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+
+    return if !$device;
+
+    # remove the old one first
+    vm_deviceunplug($vmid, $conf, $deviceid);
+
+    # check if xhci controller is necessary and available
+    if ($device->{usb3}) {
+
+       my $devicelist = vm_devices_list($vmid);
+
+       if (!$devicelist->{xhci}) {
+           my $pciaddr = print_pci_addr("xhci");
+           qemu_deviceadd($vmid, "nec-usb-xhci,id=xhci$pciaddr");
+       }
+    }
+    my $d = parse_usb_device($device->{host});
+    $d->{usb3} = $device->{usb3};
+
+    # add the new one
+    vm_deviceplug($storecfg, $conf, $vmid, $deviceid, $d);
+}
+
 sub qemu_cpu_hotplug {
     my ($vmid, $conf, $vcpus) = @_;
 
+    my $machine_type = PVE::QemuServer::get_current_qemu_machine($vmid);
+
     my $sockets = 1;
     $sockets = $conf->{smp} if $conf->{smp}; # old style - no longer iused
     $sockets = $conf->{sockets} if  $conf->{sockets};
@@ -3603,15 +3767,61 @@ sub qemu_cpu_hotplug {
        if $vcpus > $maxcpus;
 
     my $currentvcpus = $conf->{vcpus} || $maxcpus;
-    die "online cpu unplug is not yet possible\n"
-       if $vcpus < $currentvcpus;
+
+    if ($vcpus < $currentvcpus) {
+
+       if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+
+           for (my $i = $currentvcpus; $i > $vcpus; $i--) {
+               qemu_devicedel($vmid, "cpu$i");
+               my $retry = 0;
+               my $currentrunningvcpus = undef;
+               while (1) {
+                   $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+                   last if scalar(@{$currentrunningvcpus}) == $i-1;
+                   raise_param_exc({ vcpus => "error unplugging cpu$i" }) if $retry > 5;
+                   $retry++;
+                   sleep 1;
+               }
+               #update conf after each succesfull cpu unplug
+               $conf->{vcpus} = scalar(@{$currentrunningvcpus});
+               PVE::QemuConfig->write_config($vmid, $conf);
+           }
+       } else {
+           die "cpu hot-unplugging requires qemu version 2.7 or higher\n";
+       }
+
+       return;
+    }
 
     my $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
-    die "vcpus in running vm is different than configuration\n"
+    die "vcpus in running vm does not match its configuration\n"
        if scalar(@{$currentrunningvcpus}) != $currentvcpus;
 
-    for (my $i = $currentvcpus; $i < $vcpus; $i++) {
-       vm_mon_cmd($vmid, "cpu-add", id => int($i));
+    if (qemu_machine_feature_enabled ($machine_type, undef, 2, 7)) {
+
+       for (my $i = $currentvcpus+1; $i <= $vcpus; $i++) {
+           my $cpustr = print_cpu_device($conf, $i);
+           qemu_deviceadd($vmid, $cpustr);
+
+           my $retry = 0;
+           my $currentrunningvcpus = undef;
+           while (1) {
+               $currentrunningvcpus = vm_mon_cmd($vmid, "query-cpus");
+               last if scalar(@{$currentrunningvcpus}) == $i;
+               raise_param_exc({ vcpus => "error hotplugging cpu$i" }) if $retry > 10;
+               sleep 1;
+               $retry++;
+           }
+            #update conf after each succesfull cpu hotplug
+           $conf->{vcpus} = scalar(@{$currentrunningvcpus});
+           PVE::QemuConfig->write_config($vmid, $conf);
+       }
+    } else {
+
+       for (my $i = $currentvcpus; $i < $vcpus; $i++) {
+           vm_mon_cmd($vmid, "cpu-add", id => int($i));
+       }
     }
 }
 
@@ -3816,6 +4026,7 @@ my $fast_plug_option = {
     'shares' => 1,
     'startup' => 1,
     'description' => 1,
+    'protection' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -3865,6 +4076,12 @@ sub vmconfig_hotplug_pending {
                } else {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
+           } elsif ($opt =~ m/^usb\d+/) {
+               die "skip\n";
+               # since we cannot reliably hot unplug usb devices
+               # we are disabling it
+               die "skip\n" if !$hotplug_features->{usb} || $conf->{$opt} =~ m/spice/i;
+               vm_deviceunplug($vmid, $conf, $opt);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, undef);
@@ -3915,6 +4132,14 @@ sub vmconfig_hotplug_pending {
                } elsif ($value == 0) {
                    vm_deviceunplug($vmid, $conf, $opt);
                }
+           } elsif ($opt =~ m/^usb\d+$/) {
+               die "skip\n";
+               # since we cannot reliably hot unplug usb devices
+               # we are disabling it
+               die "skip\n" if !$hotplug_features->{usb} || $value =~ m/spice/i;
+               my $d = eval { PVE::JSONSchema::parse_property_string($usbdesc->{format}, $value) };
+               die "skip\n" if !$d;
+               qemu_usb_hotplug($storecfg, $conf, $vmid, $opt, $d);
            } elsif ($opt eq 'vcpus') {
                die "skip\n" if !$hotplug_features->{cpu};
                qemu_cpu_hotplug($vmid, $conf, $value);
@@ -4204,7 +4429,7 @@ sub vmconfig_update_disk {
 
 sub vm_start {
     my ($storecfg, $vmid, $statefile, $skiplock, $migratedfrom, $paused,
-       $forcemachine, $spice_ticket) = @_;
+       $forcemachine, $spice_ticket, $migration_network, $migration_type) = @_;
 
     PVE::QemuConfig->lock_config($vmid, sub {
        my $conf = PVE::QemuConfig->load_config($vmid, $migratedfrom);
@@ -4234,10 +4459,18 @@ sub vm_start {
                my $localip = "localhost";
                my $datacenterconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
                my $nodename = PVE::INotify::nodename();
-               if ($datacenterconf->{migration_unsecure}) {
+
+               if ($migration_type eq 'insecure') {
+                   my $migrate_network_addr = PVE::Cluster::get_local_migration_ip($migration_network);
+                   if ($migrate_network_addr) {
+                       $localip = $migrate_network_addr;
+                   } else {
                        $localip = PVE::Cluster::remote_node_ip($nodename, 1);
-                       $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
+                   }
+
+                   $localip = "[$localip]" if Net::IP::ip_is_ipv6($localip);
                }
+
                my $pfamily = PVE::Tools::get_host_address_family($nodename);
                $migrate_port = PVE::Tools::next_migrate_port($pfamily);
                $migrate_uri = "tcp:${localip}:${migrate_port}";
@@ -4453,7 +4686,7 @@ sub vm_commandline {
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
 
-    return join(' ', @$cmd);
+    return PVE::Tools::cmd2string($cmd);
 }
 
 sub vm_reset {
@@ -4866,12 +5099,13 @@ sub restore_update_config_line {
     return if $line =~ m/^parent:/;
     return if $line =~ m/^template:/; # restored VM is never a template
 
+    my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
     if (($line =~ m/^(vlan(\d+)):\s*(\S+)\s*$/)) {
        # try to convert old 1.X settings
        my ($id, $ind, $ethcfg) = ($1, $2, $3);
        foreach my $devconfig (PVE::Tools::split_list($ethcfg)) {
            my ($model, $macaddr) = split(/\=/, $devconfig);
-           $macaddr = PVE::Tools::random_ether_addr() if !$macaddr || $unique;
+           $macaddr = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if !$macaddr || $unique;
            my $net = {
                model => $model,
                bridge => "vmbr$ind",
@@ -4885,10 +5119,10 @@ sub restore_update_config_line {
     } elsif (($line =~ m/^(net\d+):\s*(\S+)\s*$/) && $unique) {
        my ($id, $netstr) = ($1, $2);
        my $net = parse_net($netstr);
-       $net->{macaddr} = PVE::Tools::random_ether_addr() if $net->{macaddr};
+       $net->{macaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}) if $net->{macaddr};
        $netstr = print_net($net);
        print $outfd "$id: $netstr\n";
-    } elsif ($line =~ m/^((ide|scsi|virtio|sata)\d+):\s*(\S+)\s*$/) {
+    } elsif ($line =~ m/^((ide|scsi|virtio|sata|efidisk)\d+):\s*(\S+)\s*$/) {
        my $virtdev = $1;
        my $value = $3;
        my $di = parse_drive($virtdev, $value);
@@ -5190,7 +5424,10 @@ sub restore_vma_archive {
                # Note: only delete disk we want to restore
                # other volumes will become unused
                if ($virtdev_hash->{$ds}) {
-                   PVE::Storage::vdisk_free($cfg, $volid);
+                   eval { PVE::Storage::vdisk_free($cfg, $volid); };
+                   if (my $err = $@) {
+                       warn $err;
+                   }
                }
            });
 
@@ -5534,7 +5771,7 @@ sub qemu_img_convert {
        my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
        my $cmd = [];
-       push @$cmd, '/usr/bin/qemu-img', 'convert', '-t', 'writeback', '-p', '-n';
+       push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
        push @$cmd, '-s', $snapname if($snapname && $src_format eq "qcow2");
        push @$cmd, '-f', $src_format, '-O', $dst_format, $src_path;
        if ($is_zero_initialized) {
@@ -5684,12 +5921,19 @@ sub clone_disk {
        $newvolid = PVE::Storage::vdisk_alloc($storecfg, $storeid, $newvmid, $format, undef, ($size/1024));
        push @$newvollist, $newvolid;
 
-       PVE::Storage::activate_volumes($storecfg, $newvollist);
+       PVE::Storage::activate_volumes($storecfg, [$newvolid]);
 
        my $sparseinit = PVE::Storage::volume_has_feature($storecfg, 'sparseinit', $newvolid);
        if (!$running || $snapname) {
            qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
        } else {
+
+           my $kvmver = get_running_qemu_version ($vmid);
+           if (!qemu_machine_feature_enabled (undef, $kvmver, 2, 7)) {
+               die "drive-mirror with iothread requires qemu version 2.7 or higher\n"
+                   if $drive->{iothread};
+           }
+
            qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit);
        }
     }
@@ -5721,6 +5965,13 @@ sub get_current_qemu_machine {
     return $current || $default || 'pc';
 }
 
+sub get_running_qemu_version {
+    my ($vmid) = @_;
+    my $cmd = { execute => 'query-version', arguments => {} };
+    my $res = vm_qmp_command($vmid, $cmd);
+    return "$res->{qemu}->{major}.$res->{qemu}->{minor}";
+}
+
 sub qemu_machine_feature_enabled {
     my ($machine, $kvmver, $version_major, $version_minor) = @_;