[pve-devel] [PATCH] Implement pending change
Alexandre Derumier
aderumier at odiso.com
Wed Oct 22 10:03:51 CEST 2014
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 <aderumier at 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
More information about the pve-devel
mailing list