[pve-devel] [PATCH v2 container 18/18] add vmconfig_hotplug_pending and vmconfig_apply_pending

Oguz Bektas o.bektas at proxmox.com
Mon Sep 30 14:44:50 CEST 2019


vmconfig_hotplug_pending is responsible for checking if a key/value pair in the
pending section can be hotplugged, if yes; perform a generic replace,
or perform specific actions for hotplugging the special cases.

vmconfig_apply_pending is only supposed to be called when ct isn't live.

Signed-off-by: Oguz Bektas <o.bektas at proxmox.com>
---
 src/PVE/LXC.pm        |  14 +--
 src/PVE/LXC/Config.pm | 199 ++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 203 insertions(+), 10 deletions(-)

diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
index 65c41f5..f91e27d 100644
--- a/src/PVE/LXC.pm
+++ b/src/PVE/LXC.pm
@@ -1632,7 +1632,7 @@ sub alloc_disk {
 
 our $NEW_DISK_RE = qr/^([^:\s]+):(\d+(\.\d+)?)$/;
 sub create_disks {
-    my ($storecfg, $vmid, $settings, $conf) = @_;
+    my ($storecfg, $vmid, $settings, $conf, $pending) = @_;
 
     my $vollist = [];
 
@@ -1659,10 +1659,14 @@ sub create_disks {
 		push @$vollist, $volid;
 		$mountpoint->{volume} = $volid;
 		$mountpoint->{size} = $size_kb * 1024;
-		$conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+		if ($pending) {
+		    $conf->{pending}->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+		} else {
+		    $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+		}
 	    } else {
-                # use specified/existing volid/dir/device
-                $conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
+		# use specified/existing volid/dir/device
+		$conf->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
 	    }
 	});
 
@@ -1676,7 +1680,7 @@ sub create_disks {
     # free allocated images on error
     if (my $err = $@) {
 	destroy_disks($storecfg, $vollist);
-        die $err;
+	die $err;
     }
     return $vollist;
 }
diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
index 14c26bc..10dfc75 100644
--- a/src/PVE/LXC/Config.pm
+++ b/src/PVE/LXC/Config.pm
@@ -1177,6 +1177,194 @@ sub option_exists {
 }
 # END JSON config code
 
+my $LXC_FASTPLUG_OPTIONS= {
+    'description' => 1,
+    'onboot' => 1,
+    'startup' => 1,
+    'protection' => 1,
+    'hostname' => 1,
+    'hookscript' => 1,
+    'cores' => 1,
+    'tags' => 1,
+};
+
+sub vmconfig_hotplug_pending {
+    my ($class, $vmid, $conf, $storecfg, $selection, $errors) = @_;
+
+    my $pid = PVE::LXC::find_lxc_pid($vmid);
+    my $rootdir = "/proc/$pid/root";
+
+    my $add_error = sub {
+	my ($opt, $msg) = @_;
+	$errors->{$opt} = "hotplug problem - $msg";
+    };
+
+    my $changes;
+    foreach my $opt (keys %{$conf->{pending}}) { # add/change
+	next if $selection && !$selection->{$opt};
+	if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
+	    $conf->{$opt} = delete $conf->{pending}->{$opt};
+	    $changes = 1;
+	}
+    }
+
+    if ($changes) {
+	$class->write_config($vmid, $conf);
+    }
+
+    # There's no separate swap size to configure, there's memory and "total"
+    # memory (iow. memory+swap). This means we have to change them together.
+    my $hotplug_memory_done;
+    my $hotplug_memory = sub {
+	my ($wanted_memory, $wanted_swap) = @_;
+	my $old_memory = ($conf->{memory} || $confdesc->{memory}->{default});
+	my $old_swap = ($conf->{swap} || $confdesc->{swap}->{default});
+
+	$wanted_memory //= $old_memory;
+	$wanted_swap //= $old_swap;
+
+	my $total = $wanted_memory + $wanted_swap;
+	my $old_total = $old_memory + $old_swap;
+
+	if ($total > $old_total) {
+	    PVE::LXC::write_cgroup_value("memory", $vmid,
+					 "memory.memsw.limit_in_bytes",
+					 int($total*1024*1024));
+	    PVE::LXC::write_cgroup_value("memory", $vmid,
+					 "memory.limit_in_bytes",
+					 int($wanted_memory*1024*1024));
+	} else {
+	    PVE::LXC::write_cgroup_value("memory", $vmid,
+					 "memory.limit_in_bytes",
+					 int($wanted_memory*1024*1024));
+	    PVE::LXC::write_cgroup_value("memory", $vmid,
+					 "memory.memsw.limit_in_bytes",
+					 int($total*1024*1024));
+	}
+	$hotplug_memory_done = 1;
+    };
+
+    my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
+    # FIXME: $force deletion is not implemented for CTs
+    while (my ($opt, undef) = each %$pending_delete_hash) {
+	next if $selection && !$selection->{$opt};
+	eval {
+	    if ($LXC_FASTPLUG_OPTIONS->{$opt}) {
+		# pass
+	    } elsif ($opt =~ m/^unused(\d+)$/) {
+		PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
+		    if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
+	    } elsif ($opt eq 'swap') {
+		$hotplug_memory->(undef, 0);
+	    } elsif ($opt eq 'cpulimit') {
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_period_us", -1);
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
+	    } elsif ($opt eq 'cpuunits') {
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shared", $confdesc->{cpuunits}->{default});
+	    } elsif ($opt =~ m/^net(\d)$/) {
+		my $netid = $1;
+		PVE::Network::veth_delete("veth${vmid}i$netid");
+	    } else {
+		die "skip\n"; # skip non-hotpluggable opts
+	    }
+	};
+	if (my $err = $@) {
+	    $add_error->($opt, $err) if $err ne "skip\n";
+	} else {
+	    delete $conf->{$opt};
+	    $class->remove_from_pending_delete($conf, $opt);
+	    $class->write_config($vmid, $conf);
+	}
+    }
+
+    foreach my $opt (keys %{$conf->{pending}}) {
+	next if $opt eq 'delete'; # just to be sure
+	next if $selection && !$selection->{$opt};
+	my $value = $conf->{pending}->{$opt};
+	eval {
+	    if ($opt eq 'cpulimit') {
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_period_us", 100000);
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
+	    } elsif ($opt eq 'cpuunits') {
+		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
+	    } elsif ($opt =~ m/^net(\d+)$/) {
+		my $netid = $1;
+		my $net = $class->parse_lxc_network($value);
+		PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
+	    } elsif ($opt eq 'memory' || $opt eq 'swap') {
+		if (!$hotplug_memory_done) { # don't call twice if both opts are passed
+		    $hotplug_memory->($conf->{pending}->{memory}, $conf->{pending}->{swap});
+		}
+	    } else {
+		die "skip\n"; # skip non-hotpluggable
+	    }
+	};
+	if (my $err = $@) {
+	    $add_error->($opt, $err) if $err ne "skip\n";
+	    $conf = $class->load_config($vmid);
+	} else {
+	    $conf->{$opt} = $value;
+	    delete $conf->{pending}->{$opt};
+	    $class->write_config($vmid, $conf);
+	}
+    }
+}
+
+sub vmconfig_apply_pending {
+    my ($class, $vmid, $conf, $storecfg) = @_;
+
+    my $rescan_volume = sub {
+	my ($mp) = @_;
+	eval {
+	    $mp->{size} = PVE::Storage::volume_size_info($storecfg, $mp->{volume}, 5)
+		if !defined($mp->{size});
+	};
+	warn "Could not rescan volume size - $@\n" if $@;
+    };
+
+    my $pending_delete_hash = $class->parse_pending_delete($conf->{pending}->{delete});
+    # FIXME: $force deletion is not implemented for CTs
+    while (my ($opt, undef) = each %$pending_delete_hash) {
+	$class->cleanup_pending($conf);
+	if ($opt =~ m/^mp(\d+)$/) {
+	    my $mp = $class->parse_ct_mountpoint($conf->{$opt});
+	    if ($mp->{type} eq 'volume') {
+		$class->add_unused_volume($conf, $mp->{volume});
+	    }
+	} elsif ($opt =~ m/^unused(\d+)$/) {
+	    PVE::LXC::delete_mountpoint_volume($storecfg, $vmid, $conf->{$opt})
+		if !$class->is_volume_in_use($conf, $conf->{$opt}, 1, 1);
+	}
+	delete $conf->{$opt};
+	$class->remove_from_pending_delete($conf, $opt);
+    }
+
+    foreach my $opt (keys %{$conf->{pending}}) { # add/change
+	if ($opt =~ m/^mp(\d+)$/) {
+	    my $mp = $class->parse_ct_mountpoint($conf->{pending}->{$opt});
+	    my $old = $conf->{$opt};
+	    if ($mp->{type} eq 'volume') {
+		if ($mp->{volume} =~ $PVE::LXC::NEW_DISK_RE) {
+		    PVE::LXC::create_disks($storecfg, $vmid, { $opt => $conf->{pending}->{$opt} }, $conf, 1);
+		} else {
+		    $rescan_volume->($mp);
+		    $conf->{pending}->{$opt} = $class->print_ct_mountpoint($mp);
+		}
+	    }
+	    if (defined($old)) {
+		my $mp = $class->parse_ct_mountpoint($old);
+		if ($mp->{type} eq 'volume') {
+		    $class->add_unused_volume($conf, $mp->{volume});
+		}
+	    }
+	}
+	$class->cleanup_pending($conf);
+	$conf->{$opt} = delete $conf->{pending}->{$opt};
+    }
+
+    $class->write_config($vmid, $conf);
+}
+
 sub classify_mountpoint {
     my ($class, $vol) = @_;
     if ($vol =~ m!^/!) {
@@ -1186,7 +1374,7 @@ sub classify_mountpoint {
     return 'volume';
 }
 
-my $is_volume_in_use = sub {
+my $__is_volume_in_use = sub {
     my ($class, $config, $volid) = @_;
     my $used = 0;
 
@@ -1204,17 +1392,18 @@ sub is_volume_in_use_by_snapshots {
 
     if (my $snapshots = $config->{snapshots}) {
 	foreach my $snap (keys %$snapshots) {
-	    return 1 if $is_volume_in_use->($class, $snapshots->{$snap}, $volid);
+	    return 1 if $__is_volume_in_use->($class, $snapshots->{$snap}, $volid);
 	}
     }
 
     return 0;
-};
+}
 
 sub is_volume_in_use {
-    my ($class, $config, $volid, $include_snapshots) = @_;
-    return 1 if $is_volume_in_use->($class, $config, $volid);
+    my ($class, $config, $volid, $include_snapshots, $include_pending) = @_;
+    return 1 if $__is_volume_in_use->($class, $config, $volid);
     return 1 if $include_snapshots && $class->is_volume_in_use_by_snapshots($config, $volid);
+    return 1 if $include_pending && $__is_volume_in_use->($class, $config->{pending}, $volid);
     return 0;
 }
 
-- 
2.20.1




More information about the pve-devel mailing list