This add a new section pending

[PENDING]

When we change an option, it's always write in pending section first,
then
   if the vm is not running,
   or if the vm is running and the option|device can be change online
we remove it from pending and update the main config

On delete,
  -disks as marked as unused in pending
  -options are marked as 'delete' in pending,

The pending changes are applied on vm_stop and vm_start.
(maybe could we add an api to manually apply pending changes when vm is stopped)

some examples:

delete option
-------------
[CONF]
ostype: l26

qm set -delete ostype

[CONF]
ostype: l26
[PENDING]
osdtype : delete

delete disk
-----------
[CONF]
virtio1 : local:100/vm-100-disk-1.raw,format=raw

qm set -delete virtio1

[CONF]
virtio1 : local:100/vm-100-disk-1.raw,format=raw
[PENDING]
unused0 : local:100/vm-100-disk-1.raw,format=raw

swap disk with unused disk
--------------------------
[CONF]
unused0 : local:100/vm-100-disk-2.raw,format=raw
virtio1 : local:100/vm-100-disk-1.raw,format=raw

qm set 100 -virtio1 local:100/vm-100-disk-2.raw

[CONF]
unused0 : local:100/vm-100-disk-2.raw,format=raw
virtio1 : local:100/vm-100-disk-1.raw,format=raw
[PENDING]
unused1 : local:100/vm-100-disk-1.raw,format=raw
virtio1 : local:100/vm-100-disk-2.raw,format=raw

update: swap disk with new disk
--------------------------
[CONF]
virtio1 : local:100/vm-100-disk-1.raw,format=raw

qm set 100 -virtio1 local:1

[CONF]
virtio1 : local:100/vm-100-disk-1.raw,format=raw
[PENDING]
unused1 : local:100/vm-100-disk-1.raw,format=raw
virtio1 : local:100/vm-100-disk-2.raw,format=raw

update option
---------------
[CONF]
ostype : l26

qm set -ostype win8
[CONF]
ostype : l26

[PENDING]
ostype: win8

update net
----------
[CONF]
net0: e1000=96:36:DB:21:74:47,bridge=vmbr0

qm set -net0 virtio=96:36:DB:21:74:47,bridge=vmbr0

[CONF]
net0: e1000=96:36:DB:21:74:47,bridge=vmbr0
[PENDING]
net0: virtio=96:36:DB:21:74:47,bridge=vmbr0

Signed-off-by: Alexandre Derumier <aderum...@odiso.com>
---
 PVE/API2/Qemu.pm  |  318 ++++++++++++++++++++++++++++++++++++++---------------
 PVE/QemuServer.pm |  164 +++++++++++++++++++++------
 pve-bridge        |    4 +
 3 files changed, 366 insertions(+), 120 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index a0fcd28..59c8957 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -102,7 +102,7 @@ my $check_storage_access_clone = sub {
 # Note: $pool is only needed when creating a VM, because pool permissions
 # are automatically inherited if VM already exists inside a pool.
 my $create_disks = sub {
-    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, 
$default_storage) = @_;
+    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $pool, $settings, 
$default_storage, $running) = @_;
 
     my $vollist = [];
 
@@ -167,7 +167,12 @@ my $create_disks = sub {
 
     # modify vm config if everything went well
     foreach my $ds (keys %$res) {
-       $conf->{$ds} = $res->{$ds};
+       if($conf->{pending}->{$ds}){
+           my $drive = PVE::QemuServer::parse_drive($ds, 
$conf->{pending}->{$ds});
+           my $volid = $drive->{file};
+           PVE::QemuServer::add_unused_volume($conf, $volid, $running);
+       }
+       $conf->{pending}->{$ds} = $res->{$ds};
     }
 
     return $vollist;
@@ -669,9 +674,27 @@ my $test_deallocate_drive = sub {
     return undef;
 };
 
+my $is_hotpluggable = sub {
+   my ($opt) = @_;
+
+    #online value change
+    my $hotplug_options = {
+       name => 1,
+       balloon => 1,
+       shares => 1,
+       hotplug => 1,
+       shares => 1,
+       onboot => 1
+    };
+
+    return 1 if $hotplug_options->{$opt};
+};
+
 my $delete_drive = sub {
     my ($conf, $storecfg, $vmid, $key, $drive, $force) = @_;
 
+    my $running = PVE::QemuServer::check_running($vmid);
+
     if (!PVE::QemuServer::drive_is_cdrom($drive)) {
        my $volid = $drive->{file};
 
@@ -686,15 +709,17 @@ my $delete_drive = sub {
                        if $used_paths->{$path};
 
                    PVE::Storage::vdisk_free($storecfg, $volid);
+                   delete $conf->{$key};
+
                };
                die $@ if $@;
            } else {
-               PVE::QemuServer::add_unused_volume($conf, $volid, $vmid);
+                   PVE::QemuServer::add_unused_volume($conf, $volid, $running);
            }
        }
     }
 
-    delete $conf->{$key};
+    delete $conf->{$key} if !$running;
 };
 
 my $vmconfig_delete_option = sub {
@@ -702,40 +727,70 @@ my $vmconfig_delete_option = sub {
 
     return if !defined($conf->{$opt});
 
+    my $running = PVE::QemuServer::check_running($vmid);
+
     my $isDisk = PVE::QemuServer::valid_drivename($opt)|| ($opt =~ m/^unused/);
+    my $drive = "";
 
-    if ($isDisk) {
+    if ($isDisk || ($opt =~ m/^unused/)) {
        $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
 
-       my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
+       $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
        if (my $sid = &$test_deallocate_drive($storecfg, $vmid, $opt, $drive, 
$force)) {
            $rpcenv->check($authuser, "/storage/$sid", 
['Datastore.AllocateSpace']);
        }
     }
 
-    my $unplugwarning = "";
-    if ($conf->{ostype} && $conf->{ostype} eq 'l26') {
-       $unplugwarning = "<br>verify that you have acpiphp && pci_hotplug 
modules loaded in your guest VM";
-    } elsif ($conf->{ostype} && $conf->{ostype} eq 'l24') {
-       $unplugwarning = "<br>kernel 2.4 don't support hotplug, please disable 
hotplug in options";
-    } elsif (!$conf->{ostype} || ($conf->{ostype} && $conf->{ostype} eq 
'other')) {
-       $unplugwarning = "<br>verify that your guest support acpi hotplug";
-    }
-
-    if ($opt eq 'tablet') {
-       PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, $opt);
-    } else {
-        die "error hot-unplug $opt $unplugwarning" if 
!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
-    }
-
+    #add to pending the device to delete
     if ($isDisk) {
-       my $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
        &$delete_drive($conf, $storecfg, $vmid, $opt, $drive, $force);
     } else {
+       $conf->{pending}->{$opt} = 'delete';
+    }
+    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+
+    #online value change
+    if(&$is_hotpluggable($opt)){
        delete $conf->{$opt};
+       delete $conf->{pending}->{$opt};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+       return;
+    }
+
+    #do the unplug
+    if($running){
+       if ($opt eq 'tablet') {
+           if($conf->{$opt} == 0 && !PVE::QemuServer::vm_deviceplug(undef, 
$conf, $vmid, $opt)){
+               warn "error hotplug tablet";
+               return;
+           }
+       } else {
+
+           if($conf->{hotplug} && $opt !~ m/^(ide|sata)(\d+)$/){
+        
+               if(!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt)){
+                   warn "error hot-unplug $opt";
+                   return;
+               }else{
+
+                   #if disk, delete unused pending disk
+                   if ($opt =~ m/^(virtio|scsi)(\d+)$/) {
+                       my $pendingconf = $conf->{pending};
+                       my $key = 
PVE::QemuServer::find_unused_volume($pendingconf, $drive->{file}, $storecfg);
+                       if($key){
+                               delete $pendingconf->{$key};
+                               PVE::QemuServer::add_unused_volume($conf, 
$drive->{file});
+                                PVE::QemuServer::update_config_nolock($vmid, 
$conf, 1);
+                       }
+                   }
+                   delete $conf->{$opt};
+                   delete $conf->{pending}->{$opt};
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+               }
+           }
+       }
     }
 
-    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
 };
 
 my $safe_num_ne = sub {
@@ -749,44 +804,45 @@ my $safe_num_ne = sub {
 };
 
 my $vmconfig_update_disk = sub {
-    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value, $force) = 
@_;
-
-    my $drive = PVE::QemuServer::parse_drive($opt, $value);
+    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $force) = @_;
 
-    if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
-       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.CDROM']);
-    } else {
-       $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk']);
-    }
+    my $running = PVE::QemuServer::check_running($vmid);
+    my $pendingvalue = $conf->{pending}->{$opt};
+    my $drive = PVE::QemuServer::parse_drive($opt, $pendingvalue);
 
     if ($conf->{$opt}) {
 
-       if (my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt}))  
{
+       if (my $old_drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt})) {
 
            my $media = $drive->{media} || 'disk';
            my $oldmedia = $old_drive->{media} || 'disk';
            die "unable to change media type\n" if $media ne $oldmedia;
+           if (!PVE::QemuServer::drive_is_cdrom($old_drive)){
+               #swap : unplug and put it in pending unusused
+               if ($drive->{file} ne $old_drive->{file}) {  # delete old disks
+                   &$vmconfig_delete_option($rpcenv, $authuser, $conf, 
$storecfg, $vmid, $opt, $force);
+                   $conf->{pending}->{$opt} = $pendingvalue;
+               } elsif ($running) { #update existing disk
+
+                   #non hotpluggable value
+                   if (&$safe_num_ne($drive->{discard}, $old_drive->{discard}) 
|| &$safe_num_ne($drive->{cache}, $old_drive->{cache})) {
+                       return; 
+                   }
 
-           if (!PVE::QemuServer::drive_is_cdrom($old_drive) &&
-               ($drive->{file} ne $old_drive->{file})) {  # delete old disks
-
-               &$vmconfig_delete_option($rpcenv, $authuser, $conf, $storecfg, 
$vmid, $opt, $force);
-               $conf = PVE::QemuServer::load_config($vmid); # update/reload
-           }
-
-            if(&$safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
-               &$safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) ||
-               &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) ||
-               &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
-               &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) ||
-               &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) ||
-               &$safe_num_ne($drive->{mbps_max}, $old_drive->{mbps_max}) ||
-               &$safe_num_ne($drive->{mbps_rd_max}, $old_drive->{mbps_rd_max}) 
||
-               &$safe_num_ne($drive->{mbps_wr_max}, $old_drive->{mbps_wr_max}) 
||
-               &$safe_num_ne($drive->{iops_max}, $old_drive->{iops_max}) ||
-               &$safe_num_ne($drive->{iops_rd_max}, $old_drive->{iops_rd_max}) 
||
-               &$safe_num_ne($drive->{iops_wr_max}, 
$old_drive->{iops_wr_max})) {
-               PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt",
+                   #apply throttle
+                   if(&$safe_num_ne($drive->{mbps}, $old_drive->{mbps}) ||
+                      &$safe_num_ne($drive->{mbps_rd}, $old_drive->{mbps_rd}) 
||
+                      &$safe_num_ne($drive->{mbps_wr}, $old_drive->{mbps_wr}) 
||
+                      &$safe_num_ne($drive->{iops}, $old_drive->{iops}) ||
+                      &$safe_num_ne($drive->{iops_rd}, $old_drive->{iops_rd}) 
||
+                      &$safe_num_ne($drive->{iops_wr}, $old_drive->{iops_wr}) 
||
+                      &$safe_num_ne($drive->{mbps_max}, 
$old_drive->{mbps_max}) ||
+                      &$safe_num_ne($drive->{mbps_rd_max}, 
$old_drive->{mbps_rd_max}) ||
+                      &$safe_num_ne($drive->{mbps_wr_max}, 
$old_drive->{mbps_wr_max}) ||
+                      &$safe_num_ne($drive->{iops_max}, 
$old_drive->{iops_max}) ||
+                      &$safe_num_ne($drive->{iops_rd_max}, 
$old_drive->{iops_rd_max}) ||
+                      &$safe_num_ne($drive->{iops_wr_max}, 
$old_drive->{iops_wr_max})) {
+                      
PVE::QemuServer::qemu_block_set_io_throttle($vmid,"drive-$opt",
                                                           ($drive->{mbps} || 
0)*1024*1024,
                                                           ($drive->{mbps_rd} 
|| 0)*1024*1024,
                                                           ($drive->{mbps_wr} 
|| 0)*1024*1024,
@@ -798,21 +854,21 @@ my $vmconfig_update_disk = sub {
                                                           
($drive->{mbps_wr_max} || 0)*1024*1024,
                                                           $drive->{iops_max} 
|| 0,
                                                           
$drive->{iops_rd_max} || 0,
-                                                          
$drive->{iops_wr_max} || 0)
-                  if !PVE::QemuServer::drive_is_cdrom($drive);
-            }
+                                                          
$drive->{iops_wr_max} || 0);
+
+                   }
+
+               }
+           }
+
        }
     }
 
-    &$create_disks($rpcenv, $authuser, $conf, $storecfg, $vmid, undef, {$opt 
=> $value});
     PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
 
-    $conf = PVE::QemuServer::load_config($vmid); # update/reload
-    $drive = PVE::QemuServer::parse_drive($opt, $conf->{$opt});
-
     if (PVE::QemuServer::drive_is_cdrom($drive)) { # cdrom
 
-       if (PVE::QemuServer::check_running($vmid)) {
+       if ($running) {
            if ($drive->{file} eq 'none') {
                PVE::QemuServer::vm_mon_cmd($vmid, "eject",force => 
JSON::true,device => "drive-$opt");
            } else {
@@ -821,51 +877,85 @@ my $vmconfig_update_disk = sub {
                PVE::QemuServer::vm_mon_cmd($vmid, "change",device => 
"drive-$opt",target => "$path") if $path;
            }
        }
+       $conf->{$opt} = $conf->{pending}->{$opt};
+       delete $conf->{pending}->{$opt};        
 
-    } else { # hotplug new disks
+    } elsif ($opt !~ m/^(ide|sata)(\d+)$/) { # hotplug new disks
+       if (!PVE::QemuServer::vm_deviceplug($storecfg, $conf, $vmid, $opt, 
$drive, $pendingvalue)){
+               warn "error hotplug $opt";
+               return;
+       }
 
-       die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, 
$conf, $vmid, $opt, $drive);
     }
+
+    
+    #if added disk was unused before, delete it from conf
+    my $key = PVE::QemuServer::find_unused_volume($conf, 
$drive->{file},$storecfg);
+    if($key){
+       delete $conf->{$key};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+    }
+
 };
 
 my $vmconfig_update_net = sub {
-    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt, $value) = @_;
+    my ($rpcenv, $authuser, $conf, $storecfg, $vmid, $opt) = @_;
+
+    if ($conf->{$opt}) {
+       my $running = PVE::QemuServer::check_running($vmid);
 
-    if ($conf->{$opt} && PVE::QemuServer::check_running($vmid)) {
        my $oldnet = PVE::QemuServer::parse_net($conf->{$opt});
-       my $newnet = PVE::QemuServer::parse_net($value);
+       my $newnet = PVE::QemuServer::parse_net($conf->{pending}->{$opt});
 
        if($oldnet->{model} ne $newnet->{model}){
            #if model change, we try to hot-unplug
-            die "error hot-unplug $opt for update" if 
!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
+           if($running && $conf->{hotplug}){
+               if(!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt)){
+                   warn "error hot-unplug $opt for update";
+                   return;
+               }
+           }
        }else{
 
            if($newnet->{bridge} && $oldnet->{bridge}){
                my $iface = "tap".$vmid."i".$1 if $opt =~ m/net(\d+)/;
 
-               if($newnet->{rate} ne $oldnet->{rate}){
+               if($newnet->{rate} && $oldnet->{rate} && $newnet->{rate} ne 
$oldnet->{rate}){
                    PVE::Network::tap_rate_limit($iface, $newnet->{rate});
                }
 
-               if(($newnet->{bridge} ne $oldnet->{bridge}) || ($newnet->{tag} 
ne $oldnet->{tag}) || ($newnet->{firewall} ne $oldnet->{firewall})){
+               if(($newnet->{bridge} && $oldnet->{bridge} &&$newnet->{bridge} 
ne $oldnet->{bridge}) || 
+                  ($newnet->{tag} && $oldnet->{tag} &&$newnet->{tag} ne 
$oldnet->{tag}) || 
+                  ($newnet->{firewall} && $oldnet->{firewall} && 
$newnet->{firewall} ne $oldnet->{firewall})){
                    PVE::Network::tap_unplug($iface);
                    PVE::Network::tap_plug($iface, $newnet->{bridge}, 
$newnet->{tag}, $newnet->{firewall});
                }
 
+               $conf->{$opt} = $conf->{pending}->{$opt};
+               delete $conf->{pending}->{$opt};
+               PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+               return;
+
            }else{
+
                #if bridge/nat mode change, we try to hot-unplug
-               die "error hot-unplug $opt for update" if 
!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
+               if($running && $conf->{hotplug}){
+                   if(!PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt)){
+                       warn "error hot-unplug $opt for update";
+                       return;
+                   }
+               }
+
            }
        }
 
     }
-    $conf->{$opt} = $value;
-    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
-    $conf = PVE::QemuServer::load_config($vmid); # update/reload
-
-    my $net = PVE::QemuServer::parse_net($conf->{$opt});
 
-    die "error hotplug $opt" if !PVE::QemuServer::vm_deviceplug($storecfg, 
$conf, $vmid, $opt, $net);
+    if($conf->{pending}->{$opt}){
+       my $net = PVE::QemuServer::parse_net($conf->{pending}->{$opt});
+       warn "error hotplug $opt" if(!PVE::QemuServer::vm_deviceplug($storecfg, 
$conf, $vmid, $opt, $net, $conf->{pending}->{$opt}));
+    }
+    
 };
 
 # POST/PUT {vmid}/config implementation
@@ -981,37 +1071,87 @@ my $update_vm_api  = sub {
 
            my $running = PVE::QemuServer::check_running($vmid);
 
+
+
+           #load values in pending and create disks
            foreach my $opt (keys %$param) { # add/change
 
-               $conf = PVE::QemuServer::load_config($vmid); # update/reload
+                   $conf = PVE::QemuServer::load_config($vmid);
 
-               next if $conf->{$opt} && ($param->{$opt} eq $conf->{$opt}); # 
skip if nothing changed
+                   if (PVE::QemuServer::valid_drivename($opt)) {
+                       my $drive = PVE::QemuServer::parse_drive($opt, 
$param->{$opt});
+
+                       if (PVE::QemuServer::drive_is_cdrom($drive)) { #cdrom
+                           $rpcenv->check_vm_perm($authuser, $vmid, undef, 
['VM.Config.CDROM']);
+                       } else {
+                           $rpcenv->check_vm_perm($authuser, $vmid, undef, 
['VM.Config.Disk']);
+                       }
+
+                       &$create_disks($rpcenv, $authuser, $conf, $storecfg, 
$vmid, undef, {$opt => $param->{$opt}}, undef, $running);
+                   }else{
+                       if($running){
+                            $conf->{pending}->{$opt} = $param->{$opt};
+                       }else{
+                            $conf->{$opt} = $param->{$opt};
+                       }
+                   }
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+           }
+           
+
+           #hotplug pending devices first
+           foreach my $opt (keys %$param){
+
+               next if !$conf->{pending}->{$opt};
+       
+               # delete pending if nothing changed
+               if ($conf->{$opt} && $param->{$opt} eq $conf->{$opt}){
+                   delete $conf->{pending}->{$opt};
+                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+                   next;
+               }
 
                if (PVE::QemuServer::valid_drivename($opt)) {
 
                    &$vmconfig_update_disk($rpcenv, $authuser, $conf, 
$storecfg, $vmid,
-                                          $opt, $param->{$opt}, $force);
+                                          $opt, $force);
 
                } elsif ($opt =~ m/^net(\d+)$/) { #nics
 
-                   &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, 
$vmid,
-                                         $opt, $param->{$opt});
+                   &$vmconfig_update_net($rpcenv, $authuser, $conf, $storecfg, 
$vmid, $opt);
 
-               } else {
+               } elsif ($opt eq 'tablet'){
 
-                   if($opt eq 'tablet' && $param->{$opt} == 1){
-                       PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, 
$opt);
-                   } elsif($opt eq 'tablet' && $param->{$opt} == 0){
+                   if($param->{$opt} == 1){
+                       PVE::QemuServer::vm_deviceplug(undef, $conf, $vmid, 
$opt, $param->{$opt});
+                   } elsif($param->{$opt} == 0){
                        PVE::QemuServer::vm_deviceunplug($vmid, $conf, $opt);
                    }
-               
-                   if($opt eq 'cores' && $conf->{maxcpus}){
-                       PVE::QemuServer::qemu_cpu_hotplug($vmid, $conf, 
$param->{$opt});
-                   }
 
+               } elsif($opt eq 'cores'){
+
+                   PVE::QemuServer::qemu_cpu_hotplug($vmid, $conf, 
$param->{$opt});
+               }
+
+               PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+           }
+
+           #then update pending options
+
+           foreach my $opt (keys %$param){
+
+               next if !$conf->{pending}->{$opt};
+
+               next if (PVE::QemuServer::valid_drivename($opt));
+               next if $opt =~ m/^net(\d+)$/;
+               next if $opt eq 'cores' || $opt eq 'tablet';
+
+               if(&$is_hotpluggable($opt)){
                    $conf->{$opt} = $param->{$opt};
-                   PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+                   delete $conf->{pending}->{$opt};
                }
+
+               PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
            }
 
            # allow manual ballooning if shares is set to zero
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 98264d1..f0f53a0 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -1370,25 +1370,43 @@ sub add_random_macs {
 }
 
 sub add_unused_volume {
-    my ($config, $volid) = @_;
+    my ($config, $volid, $running) = @_;
 
     my $key;
     for (my $ind = $MAX_UNUSED_DISKS - 1; $ind >= 0; $ind--) {
        my $test = "unused$ind";
        if (my $vid = $config->{$test}) {
            return if $vid eq $volid; # do not add duplicates
+       } elsif (my $vidpending = $config->{pending}->{$test}) {
+           return if $vidpending eq $volid; # do not add duplicates
        } else {
            $key = $test;
        }
     }
 
     die "To many unused volume - please delete them first.\n" if !$key;
-
-    $config->{$key} = $volid;
+    if($running){
+       $config->{pending}->{$key} = $volid;
+    }else{
+       $config->{$key} = $volid;
+    }
 
     return $key;
 }
 
+sub find_unused_volume {
+    my ($conf, $volid, $storecfg) = @_;
+
+    foreach my $key (keys %$conf) {
+        next if($key !~ m/^unused/);
+        my $confdrive = PVE::QemuServer::parse_drive($key, $conf->{$key});
+        if (!PVE::QemuServer::drive_is_cdrom($confdrive)) {
+            PVE::QemuServer::cleanup_drive_path($key, $storecfg, $confdrive);
+           return $key if $confdrive->{file} eq $volid;
+        }
+    }
+}
+
 my $valid_smbios1_options = {
     manufacturer => '\S+',
     product => '\S+',
@@ -1611,6 +1629,8 @@ sub check_type {
 
     die "unknown setting '$key'\n" if !$confdesc->{$key};
 
+    return $value if $value eq 'delete';
+
     my $type = $confdesc->{$key}->{type};
 
     if (!defined($value)) {
@@ -1782,6 +1802,7 @@ sub parse_vm_config {
     my $res = {
        digest => Digest::SHA::sha1_hex($raw),
        snapshots => {},
+       pending => {}
     };
 
     $filename =~ m|/qemu-server/(\d+)\.conf$|
@@ -1796,7 +1817,11 @@ sub parse_vm_config {
     foreach my $line (@lines) {
        next if $line =~ m/^\s*$/;
 
-       if ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
+       if ($line =~ m/^\[PENDING\]\s*$/i) {
+           $conf = $res->{pending} = {};
+           next;
+
+       }elsif ($line =~ m/^\[([a-z][a-z0-9_\-]+)\]\s*$/i) {
            my $snapname = $1;
            $conf->{description} = $descr if $descr;
            $descr = '';
@@ -1852,8 +1877,8 @@ sub parse_vm_config {
     return $res;
 }
 
-sub write_vm_config {
-    my ($filename, $conf) = @_;
+sub write_vm_config_cleanup {
+    my ($conf) = @_;
 
     delete $conf->{snapstate}; # just to be sure
 
@@ -1883,7 +1908,7 @@ sub write_vm_config {
 
        foreach my $key (keys %$cref) {
            next if $key eq 'digest' || $key eq 'description' || $key eq 
'snapshots' ||
-               $key eq 'snapstate';
+               $key eq 'snapstate' || $key eq 'pending';
            my $value = $cref->{$key};
            eval { $value = check_type($key, $value); };
            die "unable to parse value of '$key' - $@" if $@;
@@ -1910,6 +1935,14 @@ sub write_vm_config {
        }
     }
 
+}
+
+sub write_vm_config {
+    my ($filename, $conf) = @_;
+
+    write_vm_config_cleanup($conf);
+    write_vm_config_cleanup($conf->{pending});
+
     my $generate_raw_config = sub {
        my ($conf) = @_;
 
@@ -1922,18 +1955,25 @@ sub write_vm_config {
        }
 
        foreach my $key (sort keys %$conf) {
-           next if $key eq 'digest' || $key eq 'description' || $key eq 
'snapshots';
+           next if $key eq 'digest' || $key eq 'description' || $key eq 
'snapshots' || $key eq 'pending';
            $raw .= "$key: $conf->{$key}\n";
        }
        return $raw;
     };
 
     my $raw = &$generate_raw_config($conf);
+
+    if(keys %{$conf->{pending}}){
+           $raw .= "\n[PENDING]\n";
+           $raw .= &$generate_raw_config($conf->{pending});
+    }
+
     foreach my $snapname (sort keys %{$conf->{snapshots}}) {
        $raw .= "\n[$snapname]\n";
        $raw .= &$generate_raw_config($conf->{snapshots}->{$snapname});
     }
 
+
     return $raw;
 }
 
@@ -2903,14 +2943,27 @@ sub vm_devices_list {
 }
 
 sub vm_deviceplug {
-    my ($storecfg, $conf, $vmid, $deviceid, $device) = @_;
+    my ($storecfg, $conf, $vmid, $deviceid, $device, $optvalue) = @_;
 
-    return 1 if !check_running($vmid);
+    if (!check_running($vmid)){
+       if($conf->{pending}->{$deviceid}){
+           $conf->{$deviceid} = $optvalue;
+           delete $conf->{pending}->{$deviceid};
+           PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+       } 
+    }
 
     my $q35 = machine_type_is_q35($conf);
 
     if ($deviceid eq 'tablet') {
-       qemu_deviceadd($vmid, print_tabletdevice_full($conf));
+
+       eval { qemu_deviceadd($vmid, 
print_tabletdevice_full($conf->{pending}))};
+       #if tablet don't yet exist
+       if (!$@){
+           delete $conf->{tablet};
+       }
+       delete $conf->{pending}->{$deviceid};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
        return 1;
     }
 
@@ -2923,7 +2976,7 @@ sub vm_deviceplug {
 
     if ($deviceid =~ m/^(virtio)(\d+)$/) {
         return undef if !qemu_driveadd($storecfg, $vmid, $device);
-        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, 
$device);
+        my $devicefull = print_drivedevice_full($storecfg, $conf->{pending}, 
$vmid, $device);
         qemu_deviceadd($vmid, $devicefull);
         if(!qemu_deviceaddverify($vmid, $deviceid)) {
            qemu_drivedel($vmid, $deviceid);
@@ -2942,7 +2995,7 @@ sub vm_deviceplug {
     if ($deviceid =~ m/^(scsi)(\d+)$/) {
         return undef if !qemu_findorcreatescsihw($storecfg,$conf, $vmid, 
$device);
         return undef if !qemu_driveadd($storecfg, $vmid, $device);
-        my $devicefull = print_drivedevice_full($storecfg, $conf, $vmid, 
$device);
+        my $devicefull = print_drivedevice_full($storecfg, $conf->{pending}, 
$vmid, $device);
         if(!qemu_deviceadd($vmid, $devicefull)) {
            qemu_drivedel($vmid, $deviceid);
            return undef;
@@ -2950,8 +3003,8 @@ sub vm_deviceplug {
     }
 
     if ($deviceid =~ m/^(net)(\d+)$/) {
-        return undef if !qemu_netdevadd($vmid, $conf, $device, $deviceid);
-        my $netdevicefull = print_netdevice_full($vmid, $conf, $device, 
$deviceid);
+        return undef if !qemu_netdevadd($vmid, $conf->{pending}, $device, 
$deviceid);
+        my $netdevicefull = print_netdevice_full($vmid, $conf->{pending}, 
$device, $deviceid);
         qemu_deviceadd($vmid, $netdevicefull);
         if(!qemu_deviceaddverify($vmid, $deviceid)) {
            qemu_netdevdel($vmid, $deviceid);
@@ -2968,21 +3021,27 @@ sub vm_deviceplug {
        return undef if !qemu_deviceaddverify($vmid, $deviceid);
     }
 
+    #delete pending device after hotplug
+    if($conf->{pending}->{$deviceid}){
+       $conf->{$deviceid} = $optvalue;
+       delete $conf->{pending}->{$deviceid};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+    } 
+
     return 1;
 }
 
 sub vm_deviceunplug {
-    my ($vmid, $conf, $deviceid) = @_;
-
-    return 1 if !check_running ($vmid);
+    my ($vmid, $conf, $deviceid,$optvalue) = @_;
 
     if ($deviceid eq 'tablet') {
        qemu_devicedel($vmid, $deviceid);
+       $conf->{tablet} = 0;
+       delete $conf->{pending}->{tablet};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
        return 1;
     }
 
-    return 1 if !$conf->{hotplug};
-
     my $devices_list = vm_devices_list($vmid);
     return 1 if !defined($devices_list->{$deviceid});
 
@@ -3009,6 +3068,11 @@ sub vm_deviceunplug {
         return undef if !qemu_netdevdel($vmid, $deviceid);
     }
 
+    if($conf->{$deviceid}){
+       delete $conf->{$deviceid};
+       PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+    }
+
     return 1;
 }
 
@@ -3136,24 +3200,28 @@ 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 !$cores;
+    return if !$conf->{maxcpus};
     return if !check_running($vmid);
 
+    return if $cores > $conf->{maxcpus};
+
     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);
+    return if !$currentcores;
+
+    return if($cores < $currentcores); # unplug is not yet possible
 
     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;
+    return if scalar (@{$currentrunningcores}) != $currentcores;
 
     for(my $i = $currentcores; $i < $cores; $i++) {
        vm_mon_cmd($vmid, "cpu-add", id => int($i));
     }
+
+    $conf->{cores} = $cores;
+    delete $conf->{pending}->{cores};
+    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+
 }
 
 sub qemu_block_set_io_throttle {
@@ -3364,6 +3432,8 @@ sub vm_start {
        # set environment variable useful inside network script
        $ENV{PVE_MIGRATED_FROM} = $migratedfrom if $migratedfrom;
 
+        apply_pending_conf($conf, $vmid, $storecfg) if !$migratedfrom;
+
        my ($cmd, $vollist, $spice_port) = config_to_command($storecfg, $vmid, 
$conf, $defaults, $forcemachine);
 
        my $migrate_port = 0;
@@ -3557,7 +3627,7 @@ sub get_vm_volumes {
 }
 
 sub vm_stop_cleanup {
-    my ($storecfg, $vmid, $conf, $keepActive) = @_;
+    my ($storecfg, $vmid, $conf, $keepActive, $migratedfrom) = @_;
 
     eval {
        fairsched_rmnod($vmid); # try to destroy group
@@ -3572,6 +3642,8 @@ sub vm_stop_cleanup {
        }
     };
     warn $@ if $@; # avoid errors - just warn
+    apply_pending_conf($conf, $vmid, $storecfg) if !$migratedfrom;
+
 }
 
 # Note: use $nockeck to skip tests if VM configuration file exists.
@@ -3586,7 +3658,7 @@ sub vm_stop {
        my $pid = check_running($vmid, $nocheck, $migratedfrom);
        kill 15, $pid if $pid;
        my $conf = load_config($vmid, $migratedfrom);
-       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive);
+       vm_stop_cleanup($storecfg, $vmid, $conf, $keepActive, $migratedfrom);
        return;
     }
 
@@ -5301,4 +5373,34 @@ sub lspci {
     return $devices;
 }
 
+sub apply_pending_conf {
+    my ($conf, $vmid, $storecfg) = @_;
+
+    my $pendingconf = $conf->{pending};
+
+    #first unused pending disk
+    foreach my $pendingopt (keys %$pendingconf) {
+        if($pendingopt =~ m/^unused/) {
+           my $opt = PVE::QemuServer::find_unused_volume($conf, 
$conf->{pending}->{$pendingopt}, $storecfg);
+           delete $conf->{$opt} if $opt;
+           $conf->{$pendingopt} = $conf->{pending}->{$pendingopt};
+           delete $conf->{pending}->{$pendingopt};
+       }
+    }
+
+    foreach my $pendingopt (keys %$pendingconf) {
+
+       next if $pendingopt =~ m/^unused/;
+
+       if ($conf->{pending}->{$pendingopt} eq 'delete'){
+           delete $conf->{$pendingopt};
+       }else{
+           $conf->{$pendingopt} = $conf->{pending}->{$pendingopt};
+       }
+       delete $conf->{pending}->{$pendingopt};
+    }
+
+    PVE::QemuServer::update_config_nolock($vmid, $conf, 1);
+}
+
 1;
diff --git a/pve-bridge b/pve-bridge
index d6c5eb8..caee33b 100755
--- a/pve-bridge
+++ b/pve-bridge
@@ -20,6 +20,10 @@ my $migratedfrom = $ENV{PVE_MIGRATED_FROM};
 
 my $conf = PVE::QemuServer::load_config($vmid, $migratedfrom);
 
+if ($conf->{pending}->{$netid}){
+    $conf = $conf->{pending};
+}
+
 die "unable to get network config '$netid'\n"
     if !$conf->{$netid};
 
-- 
1.7.10.4

_______________________________________________
pve-devel mailing list
pve-devel@pve.proxmox.com
http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to