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();
},
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",
},
%rerror_fmt,
%model_fmt,
};
+PVE::JSONSchema::register_format("pve-qm-ide", $ide_fmt);
my $idedesc = {
optional => 1,
%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,
$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;
}
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 {
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) = @_;
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;
}
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;
}
}
- if ($key eq 'cdrom') {
- $conf->{ide2} = $value;
- } else {
- $conf->{$key} = $value;
- }
+ $conf->{$key} = $value;
}
}
}
}
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
+ }
}
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};
$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;
$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);
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};
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));
+ }
}
}
my $cmd = config_to_command($storecfg, $vmid, $conf, $defaults);
- return join(' ', @$cmd);
+ return PVE::Tools::cmd2string($cmd);
}
sub vm_reset {
$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);
# 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;
+ }
}
});
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);
}
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) = @_;