]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
snapshot_list: add bash completion for vmid
[qemu-server.git] / PVE / QemuServer.pm
index af8a15c76ef4fb3bbd179fa9759ddb2f6ddd524b..728110fbaf7aa89f02f8947d0e132f07e4ed808d 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();
@@ -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) = @_;
 
@@ -1950,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;
        }
@@ -2103,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;
@@ -2115,11 +2174,7 @@ sub parse_vm_config {
                    }
                }
 
-               if ($key eq 'cdrom') {
-                   $conf->{ide2} = $value;
-               } else {
-                   $conf->{$key} = $value;
-               }
+               $conf->{$key} = $value;
            }
        }
     }
@@ -2745,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
+       }
     }
 
 
@@ -2880,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};
@@ -2893,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;
 
@@ -3136,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);
@@ -3651,6 +3753,8 @@ sub qemu_usb_hotplug {
 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};
@@ -3663,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));
+       }
     }
 }
 
@@ -4528,7 +4678,7 @@ sub vm_commandline {
 
     my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
 
-    return join(' ', @$cmd);
+    return PVE::Tools::cmd2string($cmd);
 }
 
 sub vm_reset {
@@ -4964,7 +5114,7 @@ sub restore_update_config_line {
        $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);
@@ -5266,7 +5416,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;
+                   }
                }
            });
 
@@ -5766,8 +5919,12 @@ sub clone_disk {
        if (!$running || $snapname) {
            qemu_img_convert($drive->{file}, $newvolid, $size, $snapname, $sparseinit);
        } else {
-           #qemu 2.6
-           die "drive-mirror is not working currently when iothread is enabled" if $drive->{iothread};
+
+           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);
        }
@@ -5800,6 +5957,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) = @_;