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();
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,
},
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) = @_;
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";
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));
+ }
}
}
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);
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}";
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 {
+
+ 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) = @_;