]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
drive mirror: cleanup exception handling code a bit
[qemu-server.git] / PVE / QemuServer.pm
index 1ccdccf9410eca194366b0d2ca7be7ca83aa3078..fd854c4e1a984897b79f7e062773ead17bb9b97a 100644 (file)
@@ -2,6 +2,7 @@ package PVE::QemuServer;
 
 use strict;
 use warnings;
+
 use POSIX;
 use IO::Handle;
 use IO::Select;
@@ -30,6 +31,7 @@ use PVE::ProcFSTools;
 use PVE::QemuConfig;
 use PVE::QMPClient;
 use PVE::RPCEnvironment;
+use PVE::GuestHelpers;
 use PVE::QemuServer::PCI qw(print_pci_addr print_pcie_addr);
 use PVE::QemuServer::Memory;
 use PVE::QemuServer::USB qw(parse_usb_device);
@@ -76,12 +78,6 @@ PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
     optional => 1,
 });
 
-PVE::JSONSchema::register_standard_option('pve-snapshot-name', {
-    description => "The name of the snapshot.",
-    type => 'string', format => 'pve-configid',
-    maxLength => 40,
-});
-
 PVE::JSONSchema::register_standard_option('pve-qm-image-format', {
     type => 'string',
     enum => [qw(raw cow qcow qed qcow2 vmdk cloop)],
@@ -186,6 +182,13 @@ my $cpu_fmt = {
        optional => 1,
        default => 0
     },
+    'hv-vendor-id' => {
+       type => 'string',
+       pattern => qr/[a-zA-Z0-9]{1,12}/,
+       format_description => 'vendor-id',
+       description => 'The Hyper-V vendor ID. Some drivers or programs inside Windows guests need a specific ID.',
+       optional => 1,
+    },
     flags => {
        description => "List of additional CPU flags separated by ';'."
                     . " Use '+FLAG' to enable, '-FLAG' to disable a flag."
@@ -248,6 +251,21 @@ my $vga_fmt = {
     },
 };
 
+my $ivshmem_fmt = {
+    size => {
+       type => 'integer',
+       minimum => 1,
+       description => "The size of the file in MB.",
+    },
+    name => {
+       type => 'string',
+       pattern => '[a-zA-Z0-9\-]+',
+       optional => 1,
+       format_description => 'string',
+       description => "The name of the file. Will be prefixed with 'pve-shm-'. Default is the VMID. Will be deleted when the VM is stopped.",
+    },
+};
+
 my $confdesc = {
     onboot => {
        optional => 1,
@@ -277,7 +295,7 @@ my $confdesc = {
        optional => 1,
        type => 'string',
        description => "Lock/unlock the VM.",
-       enum => [qw(backup clone create migrate rollback snapshot snapshot-delete)],
+       enum => [qw(backup clone create migrate rollback snapshot snapshot-delete suspending suspended)],
     },
     cpulimit => {
        optional => 1,
@@ -607,7 +625,44 @@ EODESCR
        default => "1 (autogenerated)",
        optional => 1,
     },
+    hookscript => {
+       type => 'string',
+       format => 'pve-volume-id',
+       optional => 1,
+       description => "Script that will be executed during various steps in the vms lifetime.",
+    },
+    ivshmem => {
+       type => 'string',
+       format => $ivshmem_fmt,
+       description => "Inter-VM shared memory. Useful for direct communication between VMs, or to the host.",
+       optional => 1,
+    }
+};
+
+my $cicustom_fmt = {
+    meta => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all meta data passed to the VM via cloud-init. This is provider specific meaning configdrive2 and nocloud differ.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
+    network => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all network data passed to the VM via cloud-init.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
+    user => {
+       type => 'string',
+       optional => 1,
+       description => 'Specify a custom file containing all user data passed to the VM via cloud-init.',
+       format => 'pve-volume-id',
+       format_description => 'volume',
+    },
 };
+PVE::JSONSchema::register_format('pve-qm-cicustom', $cicustom_fmt);
 
 my $confdesc_cloudinit = {
     citype => {
@@ -626,6 +681,12 @@ my $confdesc_cloudinit = {
        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.',
     },
+    cicustom => {
+       optional => 1,
+       type => 'string',
+       description => 'cloud-init: Specify custom files to replace the automatically generated ones at start.',
+       format => 'pve-qm-cicustom',
+    },
     searchdomain => {
        optional => 1,
        type => 'string',
@@ -736,13 +797,9 @@ The DHCP server assign addresses to the guest starting from 10.0.2.15.
 __EOD__
 
 my $net_fmt = {
-    macaddr => {
-       type => 'string',
-       pattern => qr/[0-9a-f]{2}(?::[0-9a-f]{2}){5}/i,
+    macaddr  => get_standard_option('mac-addr', {
        description => "MAC address. That address must be unique withing your network. This is automatically generated if not specified.",
-       format_description => "XX:XX:XX:XX:XX:XX",
-       optional => 1,
-    },
+    }),
     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'.",
@@ -1038,6 +1095,16 @@ my %ssd_fmt = (
     },
 );
 
+my %wwn_fmt = (
+    wwn => {
+       type => 'string',
+       pattern => qr/^(0x)[0-9a-fA-F]{16}/,
+       format_description => 'wwn',
+       description => "The drive's worldwide name, encoded as 16 bytes hex string, prefixed by '0x'.",
+       optional => 1,
+    },
+);
+
 my $add_throttle_desc = sub {
     my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
     my $d = {
@@ -1086,6 +1153,7 @@ my $ide_fmt = {
     %drivedesc_base,
     %model_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
 
@@ -1102,6 +1170,7 @@ my $scsi_fmt = {
     %queues_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 my $scsidesc = {
     optional => 1,
@@ -1113,6 +1182,7 @@ PVE::JSONSchema::register_standard_option("pve-qm-scsi", $scsidesc);
 my $sata_fmt = {
     %drivedesc_base,
     %ssd_fmt,
+    %wwn_fmt,
 };
 my $satadesc = {
     optional => 1,
@@ -1139,6 +1209,7 @@ my $alldrive_fmt = {
     %queues_fmt,
     %scsiblock_fmt,
     %ssd_fmt,
+    %wwn_fmt,
 };
 
 my $efidisk_fmt = {
@@ -1770,6 +1841,7 @@ sub print_drivedevice_full {
        if ($drive->{ssd} && ($devicetype eq 'block' || $devicetype eq 'hd')) {
            $device .= ",rotation_rate=1";
        }
+       $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
 
     } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') {
        my $maxdev = ($drive->{interface} eq 'sata') ? $MAX_SATA_DISKS : 2;
@@ -1794,6 +1866,7 @@ sub print_drivedevice_full {
                $device .= ",rotation_rate=1";
            }
        }
+       $device .= ",wwn=$drive->{wwn}" if $drive->{wwn};
     } elsif ($drive->{interface} eq 'usb') {
        die "implement me";
        #  -device ide-drive,bus=ide.1,unit=0,drive=drive-ide0-1-0,id=ide0-1-0
@@ -2778,6 +2851,8 @@ sub check_local_resources {
     $loc_res = 1 if $conf->{hostusb}; # old syntax
     $loc_res = 1 if $conf->{hostpci}; # old syntax
 
+    $loc_res = 1 if $conf->{ivshmem};
+
     foreach my $k (keys %$conf) {
        next if $k =~ m/^usb/ && ($conf->{$k} eq 'spice');
        # sockets are safe: they will recreated be on the target side post-migrate
@@ -2985,6 +3060,11 @@ our $vmstatus_return_properties = {
        type => 'number',
        optional => 1,
     },
+    lock => {
+       description => "The current config lock, if any.",
+       type => 'string',
+       optional => 1,
+    }
 };
 
 my $last_proc_pid_stat;
@@ -3055,6 +3135,7 @@ sub vmstatus {
         $d->{template} = PVE::QemuConfig->is_template($conf);
 
        $d->{serial} = 1 if conf_has_serial($conf);
+       $d->{lock} = $conf->{lock} if $conf->{lock};
 
        $res->{$vmid} = $d;
     }
@@ -3331,11 +3412,13 @@ sub get_cpu_options {
     if ($arch eq 'aarch64') {
        $cpu = 'cortex-a57';
     }
+    my $hv_vendor_id;
     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};
        $kvm_off = 1 if $cpuconf->{hidden};
+       $hv_vendor_id = $cpuconf->{'hv-vendor-id'};
 
        if (defined(my $flags = $cpuconf->{flags})) {
            push @$cpuFlags, split(";", $flags);
@@ -3357,7 +3440,7 @@ sub get_cpu_options {
        push @$cpuFlags , '+kvm_pv_eoi' if $kvm;
     }
 
-    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough) if $kvm;
+    add_hyperv_enlightenments($cpuFlags, $winversion, $machine_type, $kvmver, $conf->{bios}, $gpu_passthrough, $hv_vendor_id) if $kvm;
 
     push @$cpuFlags, 'enforce' if $cpu ne 'host' && $kvm && $arch eq 'x86_64';
 
@@ -3524,7 +3607,7 @@ sub config_to_command {
        next if !$d;
 
        my $pcie = $d->{pcie};
-       if($pcie){
+       if ($pcie) {
            die "q35 machine model is not enabled" if !$q35;
            # win7 wants to have the pcie devices directly on the pcie bus
            # instead of in the root port
@@ -3533,7 +3616,7 @@ sub config_to_command {
            } else {
                $pciaddr = print_pcie_addr("hostpci$i");
            }
-       }else{
+       } else {
            $pciaddr = print_pci_addr("hostpci$i", $bridges, $arch, $machine_type);
        }
 
@@ -3544,7 +3627,7 @@ sub config_to_command {
        if ($d->{'x-vga'}) {
            $xvga = ',x-vga=on';
            $kvm_off = 1;
-           $vga->{type} = 'none';
+           $vga->{type} = 'none' if !defined($conf->{vga});
            $gpu_passthrough = 1;
 
            if ($conf->{bios} && $conf->{bios} eq 'ovmf') {
@@ -3876,6 +3959,23 @@ sub config_to_command {
          push @$devices, '-device', $netdevicefull;
     }
 
+    if ($conf->{ivshmem}) {
+       my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+
+       my $bus;
+       if ($q35) {
+           $bus = print_pcie_addr("ivshmem");
+       } else {
+           $bus = print_pci_addr("ivshmem", $bridges, $arch, $machine_type);
+       }
+
+       my $ivshmem_name = $ivshmem->{name} // $vmid;
+       my $path = '/dev/shm/pve-shm-' . $ivshmem_name;
+
+       push @$devices, '-device', "ivshmem-plain,memdev=ivshmem$bus,";
+       push @$devices, '-object', "memory-backend-file,id=ivshmem,share=on,mem-path=$path,size=$ivshmem->{size}M";
+    }
+
     if (!$q35) {
        # add pci bridges
         if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
@@ -3899,6 +3999,12 @@ sub config_to_command {
     push @$cmd, '-global', join(',', @$globalFlags)
        if scalar(@$globalFlags);
 
+    if (my $vmstate = $conf->{vmstate}) {
+       my $statepath = PVE::Storage::path($storecfg, $vmstate);
+       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+       push @$cmd, '-loadstate', $statepath;
+    }
+
     # add custom args
     if ($conf->{args}) {
        my $aa = PVE::Tools::split_args($conf->{args});
@@ -4060,20 +4166,23 @@ sub vm_deviceplug {
 
     } elsif ($deviceid =~ m/^(net)(\d+)$/) {
 
-        return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
+       return undef if !qemu_netdevadd($vmid, $conf, $arch, $device, $deviceid);
 
-        my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf); 
-        my $use_old_bios_files = undef;
-        ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
+       my $machine_type = PVE::QemuServer::qemu_machine_pxe($vmid, $conf);
+       my $use_old_bios_files = undef;
+       ($use_old_bios_files, $machine_type) = qemu_use_old_bios_files($machine_type);
 
-        my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
-        qemu_deviceadd($vmid, $netdevicefull);
-        eval { qemu_deviceaddverify($vmid, $deviceid); };
+       my $netdevicefull = print_netdevice_full($vmid, $conf, $device, $deviceid, undef, $use_old_bios_files, $arch, $machine_type);
+       qemu_deviceadd($vmid, $netdevicefull);
+       eval {
+           qemu_deviceaddverify($vmid, $deviceid);
+           qemu_set_link_status($vmid, $deviceid, !$device->{link_down});
+       };
        if (my $err = $@) {
            eval { qemu_netdevdel($vmid, $deviceid); };
            warn $@ if $@;
            die $err;
-        }
+       }
 
     } elsif (!$q35 && $deviceid =~ m/^(pci\.)(\d+)$/) {
 
@@ -4172,7 +4281,11 @@ sub qemu_iothread_add {
 sub qemu_iothread_del {
     my($conf, $vmid, $deviceid) = @_;
 
-    my $device = parse_drive($deviceid, $conf->{$deviceid});
+    my $confid = $deviceid;
+    if ($deviceid =~ m/^(?:virtioscsi|scsihw)(\d+)$/) {
+       $confid = 'scsi' . $1;
+    }
+    my $device = parse_drive($confid, $conf->{$confid});
     if ($device->{iothread}) {
        my $iothreads = vm_iothreads_list($vmid);
        qemu_objectdel($vmid, "iothread-$deviceid") if $iothreads->{"iothread-$deviceid"};
@@ -4587,6 +4700,7 @@ my $fast_plug_option = {
     'description' => 1,
     'protection' => 1,
     'vmstatestorage' => 1,
+    'hookscript' => 1,
 };
 
 # hotplug changes in [PENDING]
@@ -5041,7 +5155,10 @@ sub vm_start {
 
        die "you can't start a vm if it's a template\n" if PVE::QemuConfig->is_template($conf);
 
-       PVE::QemuConfig->check_lock($conf) if !$skiplock;
+       my $is_suspended = PVE::QemuConfig->has_lock($conf, 'suspended');
+
+       PVE::QemuConfig->check_lock($conf)
+           if !($skiplock || $is_suspended);
 
        die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
 
@@ -5105,6 +5222,14 @@ sub vm_start {
            }
        }
 
+       PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-start', 1);
+
+       if ($is_suspended) {
+           # enforce machine type on suspended vm to ensure HW compatibility
+           $forcemachine = $conf->{runningmachine};
+           print "Resuming suspended VM\n";
+       }
+
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, $conf, $defaults, $forcemachine);
 
        my $migrate_port = 0;
@@ -5185,7 +5310,7 @@ sub vm_start {
 
        PVE::Storage::activate_volumes($storecfg, $vollist);
 
-       if (!check_running($vmid, 1)) {
+       if (-d "/sys/fs/cgroup/systemd/qemu.slice/$vmid.scope") {
            eval {
                run_command(['/bin/systemctl', 'stop', "$vmid.scope"],
                    outfunc => sub {}, errfunc => sub {});
@@ -5195,7 +5320,7 @@ sub vm_start {
        my $cpuunits = defined($conf->{cpuunits}) ? $conf->{cpuunits}
                                                  : $defaults->{cpuunits};
 
-       my $start_timeout = $conf->{hugepages} ? 300 : 30;
+       my $start_timeout = ($conf->{hugepages} || $is_suspended) ? 300 : 30;
        my %run_params = (timeout => $statefile ? undef : $start_timeout, umask => 0077);
 
        my %properties = (
@@ -5302,6 +5427,15 @@ sub vm_start {
                    property => "guest-stats-polling-interval",
                    value => 2) if (!defined($conf->{balloon}) || $conf->{balloon});
 
+       if ($is_suspended && (my $vmstate = $conf->{vmstate})) {
+           print "Resumed VM, removing state\n";
+           delete $conf->@{qw(lock vmstate runningmachine)};
+           PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+           PVE::Storage::vdisk_free($storecfg, $vmstate);
+           PVE::QemuConfig->write_config($vmid, $conf);
+       }
+
+       PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'post-start');
     });
 }
 
@@ -5363,10 +5497,19 @@ sub vm_human_monitor_command {
 }
 
 sub vm_commandline {
-    my ($storecfg, $vmid) = @_;
+    my ($storecfg, $vmid, $snapname) = @_;
 
     my $conf = PVE::QemuConfig->load_config($vmid);
 
+    if ($snapname) {
+       my $snapshot = $conf->{snapshots}->{$snapname};
+       die "snapshot '$snapname' does not exist\n" if !defined($snapshot);
+
+       $snapshot->{digest} = $conf->{digest}; # keep file digest for API
+
+       $conf = $snapshot;
+    }
+
     my $defaults = load_defaults();
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
@@ -5419,6 +5562,15 @@ sub vm_stop_cleanup {
            unlink "/var/run/qemu-server/${vmid}.$ext";
        }
 
+       if ($conf->{ivshmem}) {
+           my $ivshmem = PVE::JSONSchema::parse_property_string($ivshmem_fmt, $conf->{ivshmem});
+           # just delete it for now, VMs which have this already open do not
+           # are affected, but new VMs will get a separated one. If this
+           # becomes an issue we either add some sort of ref-counting or just
+           # add a "don't delete on stop" flag to the ivshmem format.
+           unlink '/dev/shm/pve-shm-' . ($ivshmem->{name} // $vmid);
+       }
+
        foreach my $key (keys %$conf) {
            next if $key !~ m/^hostpci(\d+)$/;
            my $hostpciindex = $1;
@@ -5465,6 +5617,7 @@ sub vm_stop {
                my $opts = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
                $timeout = $opts->{down} if $opts->{down};
            }
+           PVE::GuestHelpers::exec_hookscript($conf, $vmid, 'pre-stop');
        }
 
        $timeout = 60 if !defined($timeout);
@@ -5529,17 +5682,84 @@ sub vm_stop {
 }
 
 sub vm_suspend {
-    my ($vmid, $skiplock) = @_;
+    my ($vmid, $skiplock, $includestate, $statestorage) = @_;
+
+    my $conf;
+    my $path;
+    my $storecfg;
+    my $vmstate;
 
     PVE::QemuConfig->lock_config($vmid, sub {
 
-       my $conf = PVE::QemuConfig->load_config($vmid);
+       $conf = PVE::QemuConfig->load_config($vmid);
 
+       my $is_backing_up = PVE::QemuConfig->has_lock($conf, 'backup');
        PVE::QemuConfig->check_lock($conf)
-           if !($skiplock || PVE::QemuConfig->has_lock($conf, 'backup'));
+           if !($skiplock || $is_backing_up);
+
+       die "cannot suspend to disk during backup\n"
+           if $is_backing_up && $includestate;
 
-       vm_mon_cmd($vmid, "stop");
+       if ($includestate) {
+           $conf->{lock} = 'suspending';
+           my $date = strftime("%Y-%m-%d", localtime(time()));
+           $storecfg = PVE::Storage::config();
+           $vmstate = PVE::QemuConfig->__snapshot_save_vmstate($vmid, $conf, "suspend-$date", $storecfg, $statestorage, 1);
+           $path = PVE::Storage::path($storecfg, $vmstate);
+           PVE::QemuConfig->write_config($vmid, $conf);
+       } else {
+           vm_mon_cmd($vmid, "stop");
+       }
     });
+
+    if ($includestate) {
+       # save vm state
+       PVE::Storage::activate_volumes($storecfg, [$vmstate]);
+
+       eval {
+           vm_mon_cmd($vmid, "savevm-start", statefile => $path);
+           for(;;) {
+               my $state = vm_mon_cmd_nocheck($vmid, "query-savevm");
+               if (!$state->{status}) {
+                   die "savevm not active\n";
+               } elsif ($state->{status} eq 'active') {
+                   sleep(1);
+                   next;
+               } elsif ($state->{status} eq 'completed') {
+                   print "State saved, quitting\n";
+                   last;
+               } elsif ($state->{status} eq 'failed' && $state->{error}) {
+                   die "query-savevm failed with error '$state->{error}'\n"
+               } else {
+                   die "query-savevm returned status '$state->{status}'\n";
+               }
+           }
+       };
+       my $err = $@;
+
+       PVE::QemuConfig->lock_config($vmid, sub {
+           $conf = PVE::QemuConfig->load_config($vmid);
+           if ($err) {
+               # cleanup, but leave suspending lock, to indicate something went wrong
+               eval {
+                   vm_mon_cmd($vmid, "savevm-end");
+                   PVE::Storage::deactivate_volumes($storecfg, [$vmstate]);
+                   PVE::Storage::vdisk_free($storecfg, $vmstate);
+                   delete $conf->@{qw(vmstate runningmachine)};
+                   PVE::QemuConfig->write_config($vmid, $conf);
+               };
+               warn $@ if $@;
+               die $err;
+           }
+
+           die "lock changed unexpectedly\n"
+               if !PVE::QemuConfig->has_lock($conf, 'suspending');
+
+           vm_qmp_command($vmid, { execute => "quit" });
+           $conf->{lock} = 'suspended';
+           PVE::QemuConfig->write_config($vmid, $conf);
+       });
+    }
 }
 
 sub vm_resume {
@@ -6411,6 +6631,23 @@ sub template_create {
     });
 }
 
+sub convert_iscsi_path {
+    my ($path) = @_;
+
+    if ($path =~ m|^iscsi://([^/]+)/([^/]+)/(.+)$|) {
+       my $portal = $1;
+       my $target = $2;
+       my $lun = $3;
+
+       my $initiator_name = get_initiator_name();
+
+       return "file.driver=iscsi,file.transport=tcp,file.initiator-name=$initiator_name,".
+              "file.portal=$portal,file.target=$target,file.lun=$lun,driver=raw";
+    }
+
+    die "cannot convert iscsi path '$path', unkown format\n";
+}
+
 sub qemu_img_convert {
     my ($src_volid, $dst_volid, $size, $snapname, $is_zero_initialized) = @_;
 
@@ -6431,13 +6668,32 @@ sub qemu_img_convert {
        my $src_path = PVE::Storage::path($storecfg, $src_volid, $snapname);
        my $dst_path = PVE::Storage::path($storecfg, $dst_volid);
 
+       my $src_is_iscsi = ($src_path =~ m|^iscsi://|);
+       my $dst_is_iscsi = ($dst_path =~ m|^iscsi://|);
+
        my $cmd = [];
        push @$cmd, '/usr/bin/qemu-img', 'convert', '-p', '-n';
        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) {
+
+       if ($src_is_iscsi) {
+           push @$cmd, '--image-opts';
+           $src_path = convert_iscsi_path($src_path);
+       } else {
+           push @$cmd, '-f', $src_format;
+       }
+
+       if ($dst_is_iscsi) {
+           push @$cmd, '--target-image-opts';
+           $dst_path = convert_iscsi_path($dst_path);
+       } else {
+           push @$cmd, '-O', $dst_format;
+       }
+
+       push @$cmd, $src_path;
+
+       if (!$dst_is_iscsi && $is_zero_initialized) {
            push @$cmd, "zeroinit:$dst_path";
        } else {
            push @$cmd, $dst_path;
@@ -6501,11 +6757,12 @@ sub qemu_drive_mirror {
 
     print "drive mirror is starting for drive-$drive\n";
 
-    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); }; #if a job already run for this device,it's throw an error
-
+    # if a job already runs for this device we get an error, catch it for cleanup
+    eval { vm_mon_cmd($vmid, "drive-mirror", %$opts); };
     if (my $err = $@) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
-       die "mirroring error: $err";
+       warn "$@\n" if $@;
+       die "mirroring error: $err\n";
     }
 
     qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
@@ -6833,12 +7090,15 @@ sub scsihw_infos {
 }
 
 sub add_hyperv_enlightenments {
-    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough) = @_;
+    my ($cpuFlags, $winversion, $machine_type, $kvmver, $bios, $gpu_passthrough, $hv_vendor_id) = @_;
 
     return if $winversion < 6;
     return if $bios && $bios eq 'ovmf' && $winversion < 8;
 
-    push @$cpuFlags , 'hv_vendor_id=proxmox' if $gpu_passthrough;
+    if ($gpu_passthrough || defined($hv_vendor_id)) {
+       $hv_vendor_id //= 'proxmox';
+       push @$cpuFlags , "hv_vendor_id=$hv_vendor_id";
+    }
 
     if (qemu_machine_feature_enabled ($machine_type, $kvmver, 2, 3)) {
        push @$cpuFlags , 'hv_spinlocks=0x1fff';