}
}
+sub vmconfig_cleanup_pending {
+ my ($conf) = @_;
+
+ # remove pending changes when nothing changed
+ my $changes;
+ foreach my $opt (keys %{$conf->{pending}}) {
+ if (defined($conf->{$opt}) && ($conf->{pending}->{$opt} eq $conf->{$opt})) {
+ $changes = 1;
+ delete $conf->{pending}->{$opt};
+ }
+ }
+
+ # remove delete if option is not set
+ my $pending_delete_hash = {};
+ foreach my $opt (PVE::Tools::split_list($conf->{pending}->{delete})) {
+ if (defined($conf->{$opt})) {
+ $pending_delete_hash->{$opt} = 1;
+ } else {
+ $changes = 1;
+ }
+ }
+
+ my @keylist = keys %$pending_delete_hash;
+ if (scalar(@keylist)) {
+ $conf->{pending}->{delete} = join(',', @keylist);
+ } else {
+ delete $conf->{pending}->{delete};
+ }
+
+ return $changes;
+}
+
my $valid_smbios1_options = {
manufacturer => '\S+',
product => '\S+',
my ($vmid) = @_;
my $res = vm_mon_cmd($vmid, 'query-pci');
-
my $devices = {};
foreach my $pcibus (@$res) {
foreach my $device (@{$pcibus->{devices}}) {
}
}
+ my $resmice = vm_mon_cmd($vmid, 'query-mice');
+ foreach my $mice (@$resmice) {
+ if ($mice->{name} eq 'QEMU HID Tablet') {
+ $devices->{tablet} = 1;
+ last;
+ }
+ }
+
return $devices;
}
my $q35 = machine_type_is_q35($conf);
- if ($deviceid eq 'tablet') {
- qemu_deviceadd($vmid, print_tabletdevice_full($conf));
- return 1;
- }
-
return 1 if !$conf->{hotplug};
my $devices_list = vm_devices_list($vmid);
return 1 if defined($devices_list->{$deviceid});
- qemu_bridgeadd($storecfg, $conf, $vmid, $deviceid); #add bridge if we need it for the device
+ if ($deviceid eq 'tablet') {
+ qemu_deviceadd($vmid, print_tabletdevice_full($conf));
+ return 1;
+ }
+
+ qemu_add_pci_bridge($storecfg, $conf, $vmid, $deviceid); # add PCI bridge if we need it for the device
if ($deviceid =~ m/^(virtio)(\d+)$/) {
return undef if !qemu_driveadd($storecfg, $vmid, $device);
return 1 if !check_running ($vmid);
- if ($deviceid eq 'tablet') {
- qemu_devicedel($vmid, $deviceid);
- return 1;
- }
-
return 1 if !$conf->{hotplug};
my $devices_list = vm_devices_list($vmid);
return 1 if !defined($devices_list->{$deviceid});
+ if ($deviceid eq 'tablet') {
+ qemu_devicedel($vmid, $deviceid);
+ return 1;
+ }
+
die "can't unplug bootdisk" if $conf->{bootdisk} && $conf->{bootdisk} eq $deviceid;
if ($deviceid =~ m/^(virtio)(\d+)$/) {
return 1;
}
-sub qemu_bridgeadd {
+sub qemu_add_pci_bridge {
my ($storecfg, $conf, $vmid, $device) = @_;
my $bridges = {};
- my $bridgeid = undef;
+
+ my $bridgeid;
+
print_pci_addr($device, $bridges);
while (my ($k, $v) = each %$bridges) {
$bridgeid = $k;
}
- return if !$bridgeid || $bridgeid < 1;
+ return if !defined($bridgeid) || $bridgeid < 1;
+
my $bridge = "pci.$bridgeid";
my $devices_list = vm_devices_list($vmid);
- if(!defined($devices_list->{$bridge})) {
+ if (!defined($devices_list->{$bridge})) {
return undef if !vm_deviceplug($storecfg, $conf, $vmid, $bridge);
}
+
return 1;
}
sub qemu_cpu_hotplug {
my ($vmid, $conf, $cores) = @_;
- die "new cores config is not defined" if !$cores;
- die "you can't add more cores than maxcpus"
- if $conf->{maxcpus} && ($cores > $conf->{maxcpus});
- return if !check_running($vmid);
+ my $sockets = $conf->{sockets} || 1;
+ die "cpu hotplug only works with one socket\n"
+ if $sockets > 1;
- my $currentcores = $conf->{cores} if $conf->{cores};
- die "current cores is not defined" if !$currentcores;
- die "maxcpus is not defined" if !$conf->{maxcpus};
- raise_param_exc({ 'cores' => "online cpu unplug is not yet possible" })
- if($cores < $currentcores);
+ die "maxcpus is not defined\n"
+ if !$conf->{maxcpus};
+
+ die "you can't add more cores than maxcpus\n"
+ if $cores > $conf->{maxcpus};
+
+ my $currentcores = $conf->{cores} || 1;
+ die "online cpu unplug is not yet possible\n"
+ if $cores < $currentcores;
my $currentrunningcores = vm_mon_cmd($vmid, "query-cpus");
- raise_param_exc({ 'cores' => "cores number if running vm is different than configuration" })
- if scalar (@{$currentrunningcores}) != $currentcores;
+ die "cores number if running vm is different than configuration\n"
+ if scalar(@{$currentrunningcores}) != $currentcores;
- for(my $i = $currentcores; $i < $cores; $i++) {
+ for (my $i = $currentcores; $i < $cores; $i++) {
vm_mon_cmd($vmid, "cpu-add", id => int($i));
}
}
vm_mon_cmd_nocheck($vmid, "migrate-set-capabilities", capabilities => $cap_ref);
}
+# hotplug changes in [PENDING]
+# $selection hash can be used to only apply specified options, for
+# example: { cores => 1 } (only apply changed 'cores')
+# $errors ref is used to return error messages
sub vmconfig_hotplug_pending {
- my ($vmid, $conf, $storecfg) = @_;
+ my ($vmid, $conf, $storecfg, $selection, $errors) = @_;
- my $defaults = PVE::QemuServer::load_defaults();
+ my $defaults = load_defaults();
# commit values which do not have any impact on running VM first
+ # Note: those option cannot raise errors, we we do not care about
+ # $selection and always apply them.
+
+ my $add_error = sub {
+ my ($opt, $msg) = @_;
+ $errors->{$opt} = "hotplug problem - $msg";
+ };
my $changes = 0;
foreach my $opt (keys %{$conf->{pending}}) { # add/change
$conf = load_config($vmid); # update/reload
}
- $changes = 0;
+ my $hotplug = defined($conf->{hotplug}) ? $conf->{hotplug} : $defaults->{hotplug};
- # allow manual ballooning if shares is set to zero
-
- if (defined($conf->{pending}->{balloon}) && defined($conf->{shares}) && ($conf->{shares} == 0)) {
- my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
- vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
- $conf->{balloon} = $conf->{pending}->{balloon};
- delete $conf->{pending}->{balloon};
- $changes = 1;
+ my @delete = PVE::Tools::split_list($conf->{pending}->{delete});
+ foreach my $opt (@delete) {
+ next if $selection && !$selection->{$opt};
+ eval {
+ if ($opt eq 'tablet') {
+ die "skip\n" if !$hotplug;
+ if ($defaults->{tablet}) {
+ vm_deviceplug($storecfg, $conf, $vmid, $opt);
+ } else {
+ vm_deviceunplug($vmid, $conf, $opt);
+ }
+ } elsif ($opt eq 'cores') {
+ die "skip\n" if !$hotplug;
+ qemu_cpu_hotplug($vmid, $conf, 1);
+ } else {
+ die "skip\n";
+ }
+ };
+ if (my $err = $@) {
+ &$add_error($opt, $err) if $err ne "skip\n";
+ } else {
+ # save new config if hotplug was successful
+ delete $conf->{$opt};
+ vmconfig_undelete_pending_option($conf, $opt);
+ update_config_nolock($vmid, $conf, 1);
+ $conf = load_config($vmid); # update/reload
+ }
}
- if ($changes) {
- update_config_nolock($vmid, $conf, 1);
- $conf = load_config($vmid); # update/reload
+ foreach my $opt (keys %{$conf->{pending}}) {
+ next if $selection && !$selection->{$opt};
+ my $value = $conf->{pending}->{$opt};
+ eval {
+ if ($opt eq 'tablet') {
+ die "skip\n" if !$hotplug;
+ if ($value == 1) {
+ vm_deviceplug($storecfg, $conf, $vmid, $opt);
+ } elsif ($value == 0) {
+ vm_deviceunplug($vmid, $conf, $opt);
+ }
+ } elsif ($opt eq 'cores') {
+ die "skip\n" if !$hotplug;
+ qemu_cpu_hotplug($vmid, $conf, $value);
+ } elsif ($opt eq 'balloon') {
+ die "skip\n" if !(defined($conf->{shares}) && ($conf->{shares} == 0));
+ # allow manual ballooning if shares is set to zero
+ my $balloon = $conf->{pending}->{balloon} || $conf->{memory} || $defaults->{memory};
+ vm_mon_cmd($vmid, "balloon", value => $balloon*1024*1024);
+ } else {
+ die "skip\n"; # skip non-hot-pluggable options
+ }
+ };
+ if (my $err = $@) {
+ &$add_error($opt, $err) if $err ne "skip\n";
+ } else {
+ # save new config if hotplug was successful
+ $conf->{$opt} = $value;
+ delete $conf->{pending}->{$opt};
+ update_config_nolock($vmid, $conf, 1);
+ $conf = load_config($vmid); # update/reload
+ }
}
-
- return if !$conf->{hotplug};
-
- # fixme: implement disk/network hotplug here
-
}
sub vmconfig_apply_pending {
- my ($vmid, $conf, $storecfg, $running) = @_;
-
- return vmconfig_hotplug_pending($vmid, $conf, $storecfg) if $running;
+ my ($vmid, $conf, $storecfg) = @_;
# cold plug
die "VM $vmid already running\n" if check_running($vmid, undef, $migratedfrom);
if (!$statefile && scalar(keys %{$conf->{pending}})) {
- vmconfig_apply_pending($vmid, $conf, $storecfg, 0);
+ vmconfig_apply_pending($vmid, $conf, $storecfg);
$conf = load_config($vmid); # update/reload
}
if ($migratedfrom) {
eval {
- PVE::QemuServer::set_migration_caps($vmid);
+ set_migration_caps($vmid);
};
warn $@ if $@;
if ($spice_port) {
print "spice listens on port $spice_port\n";
if ($spice_ticket) {
- PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
- PVE::QemuServer::vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
+ vm_mon_cmd_nocheck($vmid, "set_password", protocol => 'spice', password => $spice_ticket);
+ vm_mon_cmd_nocheck($vmid, "expire_password", protocol => 'spice', time => "+30");
}
}
my $storecfg = cfs_read_file('storage.cfg');
# destroy existing data - keep empty config
- my $vmcfgfn = PVE::QemuServer::config_file($vmid);
+ my $vmcfgfn = config_file($vmid);
destroy_vm($storecfg, $vmid, 1) if -f $vmcfgfn;
my $tocmd = "/usr/lib/qemu-server/qmextract";
my ($vmid) = @_;
my $cmd = { execute => 'query-machines', arguments => {} };
- my $res = PVE::QemuServer::vm_qmp_command($vmid, $cmd);
+ my $res = vm_qmp_command($vmid, $cmd);
my ($current, $default);
foreach my $e (@$res) {