]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
don't compare booleans
[qemu-server.git] / PVE / QemuServer.pm
index 3b849644ca0150ce575245886e71e3dedf42145e..6865d60f321f7b2d69c178049d95a3ceb12fd1c9 100644 (file)
@@ -56,12 +56,6 @@ cfs_register_file('/qemu-server/',
                  \&parse_vm_config,
                  \&write_vm_config);
 
-PVE::JSONSchema::register_standard_option('skiplock', {
-    description => "Ignore locks - only root is allowed to use this option.",
-    type => 'boolean',
-    optional => 1,
-});
-
 PVE::JSONSchema::register_standard_option('pve-qm-stateuri', {
     description => "Some command save/restore state from this location.",
     type => 'string',
@@ -124,6 +118,7 @@ my $cpu_vendor_list = {
     'Haswell-noTSX' => 'GenuineIntel',
     Broadwell => 'GenuineIntel',
     'Broadwell-noTSX' => 'GenuineIntel',
+    'Skylake-Client' => 'GenuineIntel',
     
     # AMD CPUs
     athlon => 'AuthenticAMD',
@@ -820,6 +815,15 @@ my %queues_fmt = (
     }
 );
 
+my %scsiblock_fmt = (
+    scsiblock => {
+       type => 'boolean',
+       description => "whether to use scsi-block for full passthrough of host block device\n\nWARNING: can lead to I/O errors in combination with low memory or high memory fragmentation on host",
+       optional => 1,
+       default => 0,
+    },
+);
+
 my $add_throttle_desc = sub {
     my ($key, $type, $what, $unit, $longunit, $minimum) = @_;
     my $d = {
@@ -876,6 +880,7 @@ my $scsi_fmt = {
     %drivedesc_base,
     %iothread_fmt,
     %queues_fmt,
+    %scsiblock_fmt,
 };
 my $scsidesc = {
     optional => 1,
@@ -913,6 +918,7 @@ my $alldrive_fmt = {
     %iothread_fmt,
     %model_fmt,
     %queues_fmt,
+    %scsiblock_fmt,
 };
 
 my $efidisk_fmt = {
@@ -999,6 +1005,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).",
@@ -1486,7 +1499,7 @@ sub print_drivedevice_full {
            if ($drive->{file} =~ m|^/|) {
                $path = $drive->{file};
                if (my $info = path_is_scsi($path)) {
-                   if ($info->{type} == 0) {
+                   if ($info->{type} == 0 && $drive->{scsiblock}) {
                        $devicetype = 'block';
                    } elsif ($info->{type} == 1) { # tape
                        $devicetype = 'generic';
@@ -2908,6 +2921,8 @@ 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';
@@ -2934,6 +2949,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;
@@ -3146,7 +3162,7 @@ sub config_to_command {
        my $pfamily = PVE::Tools::get_host_address_family($nodename);
        $spice_port = PVE::Tools::next_spice_port($pfamily);
 
-       push @$devices, '-spice', "tls-port=${spice_port},addr=localhost,tls-ciphers=DES-CBC3-SHA,seamless-migration=on";
+       push @$devices, '-spice', "tls-port=${spice_port},addr=localhost,tls-ciphers=HIGH,seamless-migration=on";
 
        push @$devices, '-device', "virtio-serial,id=spice$pciaddr";
        push @$devices, '-chardev', "spicevmc,id=vdagent,name=vdagent";
@@ -3970,7 +3986,7 @@ sub qemu_block_resize {
 
     my $running = check_running($vmid);
 
-    return if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
+    $size = 0 if !PVE::Storage::volume_resize($storecfg, $volid, $size, $running);
 
     return if !$running;
 
@@ -4449,7 +4465,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);
@@ -4470,6 +4486,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;
@@ -4607,8 +4671,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);
            };
@@ -4623,7 +4706,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};
@@ -5837,21 +5919,41 @@ sub qemu_img_format {
 }
 
 sub qemu_drive_mirror {
-    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete) = @_;
+    my ($vmid, $drive, $dst_volid, $vmiddst, $is_zero_initialized, $jobs, $skipcomplete, $qga) = @_;
 
     $jobs = {} if !$jobs;
 
     my $qemu_target;
     my $format;
+    $jobs->{"drive-$drive"} = {};
 
-    if($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+)/) {
-       $qemu_target = $dst_volid;
+    if ($dst_volid =~ /^nbd:(localhost|[\d\.]+|\[[\d\.:a-fA-F]+\]):(\d+):exportname=(\S+)/) {
        my $server = $1;
        my $port = $2;
+       my $exportname = $3;
+
        $format = "nbd";
-       die "can't connect remote nbd server $server:$port" if !PVE::Network::tcp_ping($server, $port, 2);
+       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);
 
@@ -5870,21 +5972,19 @@ 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 (my $err = $@) {
        eval { PVE::QemuServer::qemu_blockjobs_cancel($vmid, $jobs) };
        die "mirroring error: $err";
     }
 
-    $jobs->{"drive-$drive"} = {};
-
-    qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete);
+    qemu_drive_mirror_monitor ($vmid, $vmiddst, $jobs, $skipcomplete, $qga);
 }
 
 sub qemu_drive_mirror_monitor {
-    my ($vmid, $vmiddst, $jobs, $skipcomplete) = @_;
+    my ($vmid, $vmiddst, $jobs, $skipcomplete, $qga) = @_;
 
     eval {
-
        my $err_complete = 0;
 
        while (1) {
@@ -5908,7 +6008,7 @@ sub qemu_drive_mirror_monitor {
                    next;
                }
 
-               die "$job: mirroring has been cancelled. Maybe do you have bad sectors?" if !defined($running_mirror_jobs->{$job});
+               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};
@@ -5920,7 +6020,7 @@ sub qemu_drive_mirror_monitor {
                    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';
+               $readycounter++ if $running_mirror_jobs->{$job}->{ready};
            }
 
            last if scalar(keys %$jobs) == 0;
@@ -5930,22 +6030,40 @@ sub qemu_drive_mirror_monitor {
                last if $skipcomplete; #do the complete later
 
                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); };
+                   }
+
                    last;
                } else {
 
                    foreach my $job (keys %$jobs) {
                        # try to switch the disk if source and destination are on the same guest
-                       print "$job : Try to complete block job\n";
+                       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 complete. Try again \n";
+                           print "$job: Block job cannot be completed, try again.\n";
                            $err_complete++;
                        }else {
-                           print "$job : complete ok : flushing pending writes\n";
+                           print "$job: Completed successfully.\n";
                            $jobs->{$job}->{complete} = 1;
+                           eval { qemu_blockjobs_finish_tunnel($vmid, $job, $jobs->{$job}->{pid}) } ;
                        }
                    }
                }
@@ -5966,7 +6084,7 @@ sub qemu_blockjobs_cancel {
     my ($vmid, $jobs) = @_;
 
     foreach my $job (keys %$jobs) {
-       print "$job: try to cancel block job\n";
+       print "$job: Cancelling block job\n";
        eval { vm_mon_cmd($vmid, "block-job-cancel", device => $job); };
        $jobs->{$job}->{cancel} = 1;
     }
@@ -5981,8 +6099,9 @@ sub qemu_blockjobs_cancel {
 
        foreach my $job (keys %$jobs) {
 
-           if(defined($jobs->{$job}->{cancel}) && !defined($running_jobs->{$job})) {
-               print "$job : finished\n";
+           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};
            }
        }
@@ -5993,9 +6112,28 @@ sub qemu_blockjobs_cancel {
     }
 }
 
+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, $jobs, $skipcomplete) = @_;
+       $newvmid, $storage, $format, $full, $newvollist, $jobs, $skipcomplete, $qga) = @_;
 
     my $newvolid;
 
@@ -6037,7 +6175,7 @@ sub clone_disk {
                    if $drive->{iothread};
            }
 
-           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete);
+           qemu_drive_mirror($vmid, $drivename, $newvolid, $newvmid, $sparseinit, $jobs, $skipcomplete, $qga);
        }
     }
 
@@ -6167,7 +6305,7 @@ sub scsihw_infos {
 
     my $maxdev = 0;
 
-    if ($conf->{scsihw} && ($conf->{scsihw} =~ m/^lsi/)) {
+    if (!$conf->{scsihw} || ($conf->{scsihw} =~ m/^lsi/)) {
         $maxdev = 7;
     } elsif ($conf->{scsihw} && ($conf->{scsihw} eq 'virtio-scsi-single')) {
         $maxdev = 1;
@@ -6300,4 +6438,10 @@ sub complete_storage {
     return $res;
 }
 
+sub nbd_stop {
+    my ($vmid) = @_;
+
+    vm_mon_cmd($vmid, 'nbd-server-stop');
+}
+
 1;