]> git.proxmox.com Git - qemu-server.git/blobdiff - PVE/QemuServer.pm
vmconfig_hotplug_pending: correctly skip values
[qemu-server.git] / PVE / QemuServer.pm
index f77da3b9bcab6a7d1acd9c9fff92cd2d4dda4b7a..f914db5484ca82878daf70a1d9706f2cead43b2e 100644 (file)
@@ -1486,6 +1486,38 @@ sub vmconfig_register_unused_drive {
     }
 }
 
+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+',
@@ -3095,7 +3127,6 @@ sub vm_devices_list {
     my ($vmid) = @_;
 
     my $res = vm_mon_cmd($vmid, 'query-pci');
-
     my $devices = {};
     foreach my $pcibus (@$res) {
        foreach my $device (@{$pcibus->{devices}}) {
@@ -3111,6 +3142,14 @@ sub vm_devices_list {
        }
     }
 
+    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;
 }
 
@@ -3121,17 +3160,17 @@ sub vm_deviceplug {
 
     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);
@@ -3188,16 +3227,16 @@ sub vm_deviceunplug {
 
     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+)$/) {
@@ -3308,23 +3347,27 @@ sub qemu_findorcreatescsihw {
     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;
 }
 
@@ -3348,22 +3391,25 @@ sub qemu_netdevdel {
 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));
     }
 }
@@ -3547,12 +3593,23 @@ sub set_migration_caps {
     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
@@ -3568,33 +3625,74 @@ sub vmconfig_hotplug_pending {
        $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
 
@@ -3650,7 +3748,7 @@ sub vm_start {
        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
        }
 
@@ -3721,15 +3819,15 @@ sub vm_start {
        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");
                }
            }
 
@@ -4796,7 +4894,7 @@ sub restore_tar_archive {
     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";
@@ -5612,7 +5710,7 @@ sub get_current_qemu_machine {
     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) {