[pve-devel] [PATCH container 8/9] rework update_pct_config to write and apply pending changes

Fabian Grünbichler f.gruenbichler at proxmox.com
Wed Sep 11 09:40:27 CEST 2019


On September 5, 2019 4:11 pm, Oguz Bektas wrote:
> use vmconfig_hotplug_pending (when ct up) and vmconfig_apply_pending
> (when ct down) to apply or write pending changes.
> 
> Signed-off-by: Oguz Bektas <o.bektas at proxmox.com>
> ---
>  src/PVE/API2/LXC/Config.pm |  52 +-----
>  src/PVE/LXC/Config.pm      | 328 +++++++++++++++++--------------------
>  2 files changed, 147 insertions(+), 233 deletions(-)
> 
> diff --git a/src/PVE/API2/LXC/Config.pm b/src/PVE/API2/LXC/Config.pm
> index 405a079..a31ddd5 100644
> --- a/src/PVE/API2/LXC/Config.pm
> +++ b/src/PVE/API2/LXC/Config.pm
> @@ -156,58 +156,10 @@ __PACKAGE__->register_method({
>      code => sub {
>  	my ($param) = @_;
>  
> -	my $rpcenv = PVE::RPCEnvironment::get();
> -	my $authuser = $rpcenv->get_user();
> -
>  	my $node = extract_param($param, 'node');
>  	my $vmid = extract_param($param, 'vmid');
> -
>  	my $digest = extract_param($param, 'digest');
>  
> -	die "no options specified\n" if !scalar(keys %$param);
> -
> -	my $delete_str = extract_param($param, 'delete');
> -	my @delete = PVE::Tools::split_list($delete_str);
> -
> -	PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, {}, [@delete]);
> -
> -	foreach my $opt (@delete) {
> -	    raise_param_exc({ delete => "you can't use '-$opt' and -delete $opt' at the same time" })
> -		if defined($param->{$opt});
> -
> -	    if (!PVE::LXC::Config->option_exists($opt)) {
> -		raise_param_exc({ delete => "unknown option '$opt'" });
> -	    }
> -	}
> -
> -	PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $param, []);
> -
> -	my $storage_cfg = cfs_read_file("storage.cfg");
> -
> -	my $repl_conf = PVE::ReplicationConfig->new();
> -	my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
> -	if ($is_replicated) {
> -	    PVE::LXC::Config->foreach_mountpoint_full($param, 0, sub {
> -		my ($opt, $mountpoint) = @_;
> -		my $volid = $mountpoint->{volume};
> -		return if !$volid || !($mountpoint->{replicate}//1);
> -		if ($mountpoint->{type} eq 'volume') {
> -		    my ($storeid, $format);
> -		    if ($volid =~ $PVE::LXC::NEW_DISK_RE) {
> -			$storeid = $1;
> -			$format = $mountpoint->{format} || PVE::Storage::storage_default_format($storage_cfg, $storeid);
> -		    } else {
> -			($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
> -			$format = (PVE::Storage::parse_volname($storage_cfg, $volid))[6];
> -		    }
> -		    return if PVE::Storage::storage_can_replicate($storage_cfg, $storeid, $format);
> -		    my $scfg = PVE::Storage::storage_config($storage_cfg, $storeid);
> -		    return if $scfg->{shared};
> -		}
> -		die "cannot add non-replicatable volume to a replicated VM\n";
> -	    });
> -	}
> -
>  	my $code = sub {
>  
>  	    my $conf = PVE::LXC::Config->load_config($vmid);
> @@ -217,10 +169,8 @@ __PACKAGE__->register_method({
>  
>  	    my $running = PVE::LXC::check_running($vmid);
>  
> -	    PVE::LXC::Config->update_pct_config($vmid, $conf, $running, $param, \@delete);
> +	    PVE::LXC::Config->update_pct_config($vmid, $conf, $running, $param);
>  
> -	    PVE::LXC::Config->write_config($vmid, $conf);
> -	    PVE::LXC::update_lxc_config($vmid, $conf);

this call to update_lxc_config gets dropped without any comment - is it 
not longer needed? was the removal accidental?

>  	};
>  
>  	PVE::LXC::Config->lock_config($vmid, $code);
> diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm
> index 26c694f..6bec9b7 100644
> --- a/src/PVE/LXC/Config.pm
> +++ b/src/PVE/LXC/Config.pm
> @@ -1080,129 +1080,132 @@ sub write_pct_config {
>  }
>  
>  sub update_pct_config {
> -    my ($class, $vmid, $conf, $running, $param, $delete) = @_;
> +    my ($class, $vmid, $conf, $running, $param) = @_;
>  
> -    my @nohotplug;
> +    my $rpcenv = PVE::RPCEnvironment::get();

missing use statement for PVE::RPCEnvironment

> +    my $authuser = $rpcenv->get_user();
>  
> -    my $new_disks = 0;
> -    my @deleted_volumes;
> +    my $delete_str = extract_param($param, 'delete');
> +    my @delete = PVE::Tools::split_list($delete_str);
> +    my $revert_str = extract_param($param, 'revert');
> +    my $revert = {};

why this completely different handling of revert and delete?

> +    my $force = extract_param($param, 'force');
>  
> -    my $rootdir;
> -    if ($running) {
> -	my $pid = PVE::LXC::find_lxc_pid($vmid);
> -	$rootdir = "/proc/$pid/root";
> +    die "no options specified\n" if !$delete_str && !$revert_str && !scalar(keys %$param);

this also gets called for container creation/restore, which triggers 
this die in case of an in-place restore.

> +
> +    PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, {}, [@delete]);

what about reverting a pending change? e.g., admin changes something 
non-hotpluggable, non-privileged user reverts it?

> +
> +    foreach my $opt (PVE::Tools::split_list($revert_str)) {
> +	if (!PVE::LXC::Config->option_exists($opt)) {
> +	    raise_param_exc({ revert => "unknown option '$opt'" });
> +	}
> +
> +	raise_param_exc({ delete => "you can't use '-$opt' and " .
> +		"'-revert $opt' at the same time" })
> +	if defined($param->{$opt});

wrongly indented

also, style: two conditional raise_param_exc right next to eachother, 
one with regular, one with postfix if..
> +
> +	$revert->{$opt} = 1;
>      }
>  
> -    my $hotplug_error = sub {
> -	if ($running) {
> -	    push @nohotplug, @_;
> -	    return 1;
> -	} else {
> -	    return 0;
> +    foreach my $opt (keys %$revert) {
> +	if (defined($conf->{$opt})) {
> +	    $param->{$opt} = $conf->{$opt};

does this make sense? it should at best be a nop..

> +	} elsif (defined($conf->{pending}->{$opt})) {
> +	    push @delete, $opt;

why not simply 'delete $conf->{pending}->{$opt}' ? a pending change 
should by definition not yet have any effect..

>  	}
> -    };
> +    }
>  
> -    if (defined($delete)) {
> -	foreach my $opt (@$delete) {
> -	    if (!exists($conf->{$opt})) {
> -		# silently ignore
> -		next;
> -	    }
>  

superfluous blank line

> -	    if ($opt eq 'memory' || $opt eq 'rootfs') {
> -		die "unable to delete required option '$opt'\n";
> -	    } elsif ($opt eq 'hostname') {
> -		delete $conf->{$opt};
> -	    } elsif ($opt eq 'swap') {
> -		delete $conf->{$opt};
> -		PVE::LXC::write_cgroup_value("memory", $vmid,
> -					     "memory.memsw.limit_in_bytes", -1);
> -	    } elsif ($opt eq 'description' || $opt eq 'onboot' || $opt eq 'startup' || $opt eq 'hookscript') {
> -		delete $conf->{$opt};
> -	    } elsif ($opt eq 'nameserver' || $opt eq 'searchdomain' ||
> -		     $opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
> -		next if $hotplug_error->($opt);
> -		delete $conf->{$opt};
> -	    } elsif ($opt eq 'cores') {
> -		delete $conf->{$opt}; # rest is handled by pvestatd
> -	    } elsif ($opt eq 'cpulimit') {
> -		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
> -		delete $conf->{$opt};
> -	    } elsif ($opt eq 'cpuunits') {
> -		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $confdesc->{cpuunits}->{default});
> -		delete $conf->{$opt};
> -	    } elsif ($opt =~ m/^net(\d)$/) {
> -		delete $conf->{$opt};
> -		next if !$running;
> -		my $netid = $1;
> -		PVE::Network::veth_delete("veth${vmid}i$netid");
> -	    } elsif ($opt eq 'protection') {
> -		delete $conf->{$opt};
> -	    } elsif ($opt =~ m/^unused(\d+)$/) {
> -		next if $hotplug_error->($opt);
> -		PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
> -		push @deleted_volumes, $conf->{$opt};
> -		delete $conf->{$opt};
> -	    } elsif ($opt =~ m/^mp(\d+)$/) {
> -		next if $hotplug_error->($opt);
> -		PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
> -		my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
> -		delete $conf->{$opt};
> -		if ($mp->{type} eq 'volume') {
> -		    PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
> -		}
> -	    } elsif ($opt eq 'unprivileged') {
> -		die "unable to delete read-only option: '$opt'\n";
> -	    } elsif ($opt eq 'features') {
> -		next if $hotplug_error->($opt);
> -		delete $conf->{$opt};
> +    foreach my $opt (@delete) {
> +	raise_param_exc({ delete => "you can't use '-$opt' and -delete $opt' at the same time" })
> +	if defined($param->{$opt});

wrongly indented

this is a bit misleading, since @delete at this point potentially also 
contains -revert options

> +
> +	raise_param_exc({ delete => "you can't use '-delete $opt' and " .
> +		"'-revert $opt' at the same time" })
> +	if $revert->{$opt};

same as above, but this time it's not just misleading but will trigger 
an exception for any revert of a pending change where the key in 
question is absent from the currently active config..

$ cat /etc/pve/lxc/123123.conf

[PENDING]
net0: bridge=vmbr0,name=eth1

$ pct set 123123 -revert net0

400 Parameter verification failed.
delete: you can't use '-delete net0' and '-revert net0' at the same time
pct set <vmid> [OPTIONS]

> +
> +
> +	if (!PVE::LXC::Config->option_exists($opt)) {
> +	    raise_param_exc({ delete => "unknown option '$opt'" });
> +	}

style: the two preceding raise_param_exc had postfix, this one doesn't

> +    }
> +
> +    PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $param, []);

I am not convinced whether the code up to this point should not stay in 
the API endpoint, and update_pct_config keep a $delete and get a $revert 
hash as parameter..
> +
> +    my $storage_cfg = PVE::Storage::config();
> +
> +    my $repl_conf = PVE::ReplicationConfig->new();

missing use statement

> +    my $is_replicated = $repl_conf->check_for_existing_jobs($vmid, 1);
> +    if ($is_replicated) {
> +	PVE::LXC::Config->foreach_mountpoint_full($param, 0, sub {

this is now in PVE::LXC::Config, so should be $class-> (this is repeated 
quite often below for various methods)

> +	my ($opt, $mountpoint) = @_;
> +	my $volid = $mountpoint->{volume};
> +	return if !$volid || !($mountpoint->{replicate}//1);
> +	if ($mountpoint->{type} eq 'volume') {
> +	    my ($storeid, $format);
> +	    if ($volid =~ $PVE::LXC::NEW_DISK_RE) {
> +		$storeid = $1;
> +		$format = $mountpoint->{format} || PVE::Storage::storage_default_format($storage_cfg, $storeid);
>  	    } else {
> -		die "implement me (delete: $opt)"
> +		($storeid, undef) = PVE::Storage::parse_volume_id($volid, 1);
> +		$format = (PVE::Storage::parse_volname($storage_cfg, $volid))[6];
>  	    }
> -	    PVE::LXC::Config->write_config($vmid, $conf) if $running;
> +	    return if PVE::Storage::storage_can_replicate($storage_cfg, $storeid, $format);
> +	    my $scfg = PVE::Storage::storage_config($storage_cfg, $storeid);
> +	    return if $scfg->{shared};
>  	}
> +	die "cannot add non-replicatable volume to a replicated VM\n";
> +	});
>      }
>  
> -    # 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 $wanted_memory = PVE::Tools::extract_param($param, 'memory');
> -    my $wanted_swap =  PVE::Tools::extract_param($param, 'swap');
> -    if (defined($wanted_memory) || defined($wanted_swap)) {
> +    my $used_volids = {};
> +    my $new_disks = 0;
> +    my @deleted_volumes;
>  
> -	my $old_memory = ($conf->{memory} || 512);
> -	my $old_swap = ($conf->{swap} || 0);
> +    my $rootdir;
> +    if ($running) {
> +	my $pid = PVE::LXC::find_lxc_pid($vmid);
> +	$rootdir = "/proc/$pid/root";
> +    }

unused variable/code?

>  
> -	$wanted_memory //= $old_memory;
> -	$wanted_swap //= $old_swap;
> +    # write updates to pending section

I don't understand this comment? the code below does not only modify the 
pending section..

> +    my $modified = {}; # record modified options
>  
> -	my $total = $wanted_memory + $wanted_swap;
> -	if ($running) {
> -	    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));
> -	    }
> +    foreach my $opt (@delete) {
> +	$modified->{$opt} = 1;

move this below the if

> +	$conf = PVE::LXC::Config->load_config($vmid); # update/reload

should not be needed, this needs to be called while holding an flock 
anyway..

> +	if (!defined($conf->{$opt}) && !defined($conf->{pending}->{$opt})) {
> +	    warn "cannot delete '$opt' - not set in current configuration!\n";
> +	    $modified->{$opt} = 0;

then you can skip this line

> +	    next;
>  	}

the old code ignored deletion of absent options silently btw, is this 
change intended or just copied from qemu?

> -	$conf->{memory} = $wanted_memory;
> -	$conf->{swap} = $wanted_swap;
>  
> -	PVE::LXC::Config->write_config($vmid, $conf) if $running;
> +	if ($opt eq 'memory' || $opt eq 'rootfs') {
> +	    die "unable to delete required option '$opt'\n";

just moved, but memory is not actually a required option? the only 
required ones are 'arch', 'ostype' and 'rootfs'..

> +	} elsif ($opt =~ m/^unused(\d+)$/) {
> +	    PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
> +	    push @deleted_volumes, $conf->{$opt};
> +	    delete $conf->{$opt};
> +	    $class->write_config($vmid, $conf); # unused disks can be removed directly

there already is a write_config call after this if/elsif/else

> +	} elsif ($opt =~ m/^mp(\d+)$/) {
> +	    PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid drive '$opt'");
> +	    my $mp = PVE::LXC::Config->parse_ct_mountpoint($conf->{$opt});
> +	    PVE::LXC::Config->vmconfig_delete_pending_option($conf, $opt, $force);
> +	    if ($mp->{type} eq 'volume') {
> +		PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});

I'd only do this in apply_pending, and not here.. (see below for the 
non-deletion case with details)

> +		warn "--force is not yet implemented, will apply change after start" if $force; # FIXME
> +	    }
> +	} elsif ($opt eq 'unprivileged') {
> +	    die "unable to delete read-only option: '$opt'\n";
> +	} else {
> +	    PVE::LXC::Config->vmconfig_delete_pending_option($conf, $opt, $force);
> +	    PVE::LXC::Config->write_config($vmid, $conf);

there already is a write_config call after this if/elsif/else

> +	}
> +	PVE::LXC::Config->write_config($vmid, $conf);

this should be enough ;)

>      }
>  
>      my $storecfg = PVE::Storage::config();

we already have $storage_cfg a little bit above..

>  
> -    my $used_volids = {};
>      my $check_content_type = sub {
>  	my ($mp) = @_;
>  	my $sid = PVE::Storage::parse_volume_id($mp->{volume});
> @@ -1220,111 +1223,59 @@ sub update_pct_config {
>  	warn "Could not rescan volume size - $@\n" if $@;
>      };
>  
> -    foreach my $opt (keys %$param) {
> +
> +

this makes three blank lines now ;)

> +    foreach my $opt (keys %$param) { # add/change
> +	$modified->{$opt} = 1;
>  	my $value = $param->{$opt};
> -	my $check_protection_msg = "can't update CT $vmid drive '$opt'";
> -	if ($opt eq 'hostname' || $opt eq 'arch') {
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt eq 'onboot') {
> -	    $conf->{$opt} = $value ? 1 : 0;
> -	} elsif ($opt eq 'startup') {
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt eq 'tty' || $opt eq 'console' || $opt eq 'cmode') {
> -	    next if $hotplug_error->($opt);
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt eq 'nameserver') {
> -	    next if $hotplug_error->($opt);
> -	    my $list = PVE::LXC::verify_nameserver_list($value);
> -	    $conf->{$opt} = $list;
> -	} elsif ($opt eq 'searchdomain') {
> -	    next if $hotplug_error->($opt);
> -	    my $list = PVE::LXC::verify_searchdomain_list($value);
> -	    $conf->{$opt} = $list;
> -	} elsif ($opt eq 'cores') {
> -	    $conf->{$opt} = $value;# rest is handled by pvestatd
> -	} elsif ($opt eq 'cpulimit') {
> -	    if ($value == 0) {
> -		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", -1);
> -	    } else {
> -		PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.cfs_quota_us", int(100000*$value));
> -	    }
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt eq 'cpuunits') {
> -	    $conf->{$opt} = $value;
> -	    PVE::LXC::write_cgroup_value("cpu", $vmid, "cpu.shares", $value);
> -	} elsif ($opt eq 'description') {
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt =~ m/^net(\d+)$/) {
> -	    my $netid = $1;
> -	    my $net = PVE::LXC::Config->parse_lxc_network($value);
> -	    if (!$running) {
> -		$conf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
> -	    } else {
> -		PVE::LXC::update_net($vmid, $conf, $opt, $net, $netid, $rootdir);
> -	    }
> -	} elsif ($opt eq 'protection') {
> -	    $conf->{$opt} = $value ? 1 : 0;
> -	} elsif ($opt =~ m/^mp(\d+)$/) {
> -	    next if $hotplug_error->($opt);
> -	    PVE::LXC::Config->check_protection($conf, $check_protection_msg);
> +	if ($opt =~ m/^mp(\d+)$/) {
> +	    PVE::LXC::Config->check_protection($conf, "can't update CT $vmid drive '$opt'");
>  	    my $old = $conf->{$opt};
>  	    my $mp = PVE::LXC::Config->parse_ct_mountpoint($value);
>  	    if ($mp->{type} eq 'volume') {
> -		&$check_content_type($mp);
> +		$check_content_type->($mp);
>  		$used_volids->{$mp->{volume}} = 1;
> -		&$rescan_volume($mp);
> -		$conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
> +		$rescan_volume->($mp);

broken for the special storage:size syntax

> +		$conf->{pending}->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp);
>  	    } else {
> -		$conf->{$opt} = $value;
> -	    }
> -	    if (defined($old)) {
> -		my $mp = PVE::LXC::Config->parse_ct_mountpoint($old);
> -		if ($mp->{type} eq 'volume') {
> -		    PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
> -		}

we still need handling of adding the old volume as unused for regular 
mps as well - but maybe in apply_pending for now, until we actually 
hotplug the disk here? e.g., if I do

--------
$ pct start 66667

$ cat /etc/pve/lxc/66667.conf
arch: amd64
mp1: local-zfs:subvol-66667-disk-0,mp=/test,size=1G
ostype: debian
rootfs: fastzfs:subvol-66667-disk-0,acl=1

$ pct set 66667 -mp1 local-zfs:1,mp=/test,acl=1        ✔
Could not rescan volume size - unable to parse zfs volume name '1'

$ cat /etc/pve/lxc/66667.conf
arch: amd64
mp1: local-zfs:subvol-66667-disk-0,mp=/test,size=1G
ostype: debian
rootfs: fastzfs:subvol-66667-disk-0,acl=1

[PENDING]
mp1: local-zfs:subvol-66667-disk-1,mp=/test,acl=1,size=1G

--------

no unused added for the current/old mp1 volume, since it is still in 
use. but now, when I stop/start the container:

$ pct stop 66667; pct start 66667
$ cat /etc/pve/lxc/66667.conf
arch: amd64
mp1: local-zfs:subvol-66667-disk-1,mp=/test,acl=1,size=1G
ostype: debian
rootfs: fastzfs:subvol-66667-disk-0,acl=1

the old one is no longer referenced in the config, but still exists on 
the storage. regular users cannot delete this volume.

> +		$conf->{pending}->{$opt} = $param->{$opt};
>  	    }
>  	    $new_disks = 1;

wouldn't this be sufficient for volume type mountpoints?

>  	} elsif ($opt eq 'rootfs') {
> -	    next if $hotplug_error->($opt);
> -	    PVE::LXC::Config->check_protection($conf, $check_protection_msg);
> +	    PVE::LXC::Config->check_protection($conf, "can't update CT $vmid drive '$opt'");
>  	    my $old = $conf->{$opt};
>  	    my $mp = PVE::LXC::Config->parse_ct_rootfs($value);
>  	    if ($mp->{type} eq 'volume') {
> -		&$check_content_type($mp);
> +		$check_content_type->($mp);
>  		$used_volids->{$mp->{volume}} = 1;
> -		&$rescan_volume($mp);
> -		$conf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
> +		$rescan_volume->($mp);
> +		$conf->{pending}->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, 1);
>  	    } else {
> -		$conf->{$opt} = $value;
> +		$conf->{pending}->{$opt} = $value;
>  	    }
> +	    $new_disks = 1;

wouldn't this be sufficient for volume type mountpoints?

>  	    if (defined($old)) {
>  		my $mp = PVE::LXC::Config->parse_ct_rootfs($old);
>  		if ($mp->{type} eq 'volume') {
>  		    PVE::LXC::Config->add_unused_volume($conf, $mp->{volume});
>  		}
>  	    }
> -	    $new_disks = 1;
> -	} elsif ($opt eq 'unprivileged') {
> -	    die "unable to modify read-only option: '$opt'\n";
> -	} elsif ($opt eq 'ostype') {
> -	    next if $hotplug_error->($opt);
> -	    $conf->{$opt} = $value;
> -	} elsif ($opt eq 'features') {
> -	    next if $hotplug_error->($opt);
> -	    $conf->{$opt} = $value;
> +	    $conf->{pending}->{$opt} = $param->{$opt};
> +	} elsif ($opt =~ m/^unused(\d+)$/) {

missing FIXME?

>  	} elsif ($opt eq 'hookscript') {
>  	    PVE::GuestHelpers::check_hookscript($value);
> -	    $conf->{$opt} = $value;
> +	    $conf->{pending}->{$opt} = $param->{$opt};
> +	} elsif ($opt eq 'unprivileged') {
> +	    die "unable to modify read-only option: '$opt'\n";
>  	} else {
> -	    die "implement me: $opt";
> +	    $conf->{pending}->{$opt} = $param->{$opt};
>  	}
> -
> -	PVE::LXC::Config->write_config($vmid, $conf) if $running;
> +	$class->vmconfig_undelete_pending_option($conf, $opt);
> +	$class->write_config($vmid, $conf);
>      }
>  
>      # Apply deletions and creations of new volumes
>      if (@deleted_volumes) {
> -	my $storage_cfg = PVE::Storage::config();
>  	foreach my $volume (@deleted_volumes) {
>  	    next if $used_volids->{$volume}; # could have been re-added, too
>  	    # also check for references in snapshots
> @@ -1334,13 +1285,26 @@ sub update_pct_config {
>      }
>  
>      if ($new_disks) {
> -	my $storage_cfg = PVE::Storage::config();
> -	PVE::LXC::create_disks($storage_cfg, $vmid, $conf, $conf);
> +	PVE::LXC::create_disks($storage_cfg, $vmid, $conf->{pending}, $conf->{pending});

this is broken for unprivileged containers, both regular and with custom 
maps. $conf->{pending} does not contain the information, and thus disks 
are allocated with the wrong 'root' user..

> +	$class->write_config($vmid, $conf);
>      }
>  
> -    # This should be the last thing we do here
> -    if ($running && scalar(@nohotplug)) {
> -	die "unable to modify " . join(',', @nohotplug) . " while container is running\n";
> +
> +
> +    # remove pending changes when nothing changed

redundant comment

> +    $conf = PVE::LXC::Config->load_config($vmid); # update/reload

should not be needed, this needs to be called while holding an flock 
anyway..

> +    my $changes = PVE::LXC::Config->vmconfig_cleanup_pending($conf);
> +    PVE::LXC::Config->write_config($vmid, $conf) if $changes;
> +
> +    # apply pending changes

redundant comment

> +    $conf = PVE::LXC::Config->load_config($vmid); # update/reload

should not be needed, this needs to be called while holding an flock 
anyway..

> +
> +    if ($running) {
> +	my $errors = {};
> +	PVE::LXC::Config->vmconfig_hotplug_pending($vmid, $conf, $storecfg, $modified, $errors);
> +	raise_param_exc($errors) if %$errors;
> +    } else {
> +	PVE::LXC::Config->vmconfig_apply_pending($vmid, $conf, $storecfg);
>      }
>  }
>  
> -- 
> 2.20.1
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel at pve.proxmox.com
> https://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 
> 




More information about the pve-devel mailing list