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