[pve-devel] [RFC qemu-server] qemu: allow partial fastplugging of property string options
Thomas Lamprecht
t.lamprecht at proxmox.com
Thu Jan 23 07:47:26 CET 2020
On 1/22/20 5:37 PM, Oguz Bektas wrote:
> this patch adds the partial_fast_plug function, which allows to partially
> fastplug an option with a property string.
>
> this is done by having a map $partial_fast_plug_option, the format is commented.
>
> other helper functions:
>
> * safe_boolean_ne (!$a != !$b)
> * typesafe_ne (combines safe_string_ne, safe_num_ne and safe_boolean_ne by
> taking $type as an argument)
>
> the qemu-guest-agent is our first use case for this (although i am sure
> there are more, this is more of a proof of concept. it should be trivial
> to add other things via the map), specifically the fstrim_cloned_disks
> option.
>
> Co-Authored-by: Stefan Reiter <s.reiter at proxmox.com>
> Signed-off-by: Oguz Bektas <o.bektas at proxmox.com>
> ---
>
> i added stefan as a co-author for this, to thank for his help debugging and
> testing it with me
no thanks for me providing the initial hunk of code doing (almost) all
of this ? ;P
>
> also please note that i'm sending this as RFC for review only, and it
> shouldn't be applied yet since i'm working on generalizing it a bit more
> for code reuse via pve-guest-common
As you only call that method in the agent case this is also not ready to
be taken in here. Some other issues below.
Testing changing the agent from a deactivated to a "change every setting" leads
to an error with your series:
> agent: hotplug problem - format error enabled: property is missing and it is not optional
That happens because $old is empty in that case but you try to parse it as format
nonetheless.
>
> PVE/QemuServer.pm | 78 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 78 insertions(+)
>
> diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
> index bcdadca..74cbba0 100644
> --- a/PVE/QemuServer.pm
> +++ b/PVE/QemuServer.pm
> @@ -5031,6 +5031,10 @@ sub vmconfig_hotplug_pending {
> }
> vmconfig_update_disk($storecfg, $conf, $hotplug_features->{disk},
> $vmid, $opt, $value, 1, $arch, $machine_type);
> + } elsif ($opt eq 'agent') {
> + # partially fastpluggable
useless comment
> + # skip if all options were fastpluggable
No! You do the contrary, you skip if *not* all options were fastpluggable!
You could *always* skip if you deleted $cond->{pending}->{$opt} in partial_fast_plug
if $no_changes there is false. If you want to do it like this and let the deletion
from pending happen here, OK, but get the comments right - else this is confusing
as hell.
> + die "skip\n" if partial_fast_plug($conf, $opt);
> } elsif ($opt =~ m/^memory$/) { #dimms
> die "skip\n" if !$hotplug_features->{memory};
> $value = PVE::QemuServer::Memory::qemu_memory_hotplug($vmid, $conf, $defaults, $opt, $value);
> @@ -5165,6 +5169,80 @@ my $safe_string_ne = sub {
> return $a ne $b;
> };
>
> +my $safe_boolean_ne = sub {
> + my ($a, $b) = @_;
> +
> + # we don't check if value is defined, since undefined
> + # is false (so it's a valid boolean)
The comment below is enough, above adds just noise - it's a common enough
pattern, IMO.
> + # negate both values to normalize and compare
> + return !$a != !$b;
> +};
> +
> +my $typesafe_ne = sub {
> + my ($a, $b, $type) = @_;
> +
> + return 0 if !defined($a) && !defined($b);
> + return 1 if !defined($a);
> + return 1 if !defined($b);
> +
> + if ($type eq 'string') {
> + $safe_string_ne->($a, $b);
1. The returns are still missing, as told off-list. missing those works by chance only
and is very prone for subtle breakage.
2. if you move this to guest-common do the check directly here, no point in calling (and
then also moving that) method for a single line. I.e., just do here:
return $a ne $b;
> + } elsif ($type eq 'number') {
> + $safe_num_ne->($a, $b);
return $a != $b;
> + } elsif ($type eq 'boolean') {
> + $safe_boolean_ne->($a, $b);
return !$a != !$b;
directly.
> + }
> +};
> +
> +my $partial_fast_plug_option =
> +# name
> +# -> fmt -> format variable
> +# -> properties -> fastpluggable options hash
move above variable def.
> +{
this belongs at the line of variable def
> + agent => {
> + fmt => $agent_fmt,
Maybe we could/should really get the format from the $confdesc and simplify this
to
my $partial_fast_plug_option = {
agent => {
fstrim_cloned_disks => 1
},
};
> + properties => {
> + fstrim_cloned_disks => 1
> + },
> + },
> +};
> +
> +sub partial_fast_plug {
> + my ($conf, $opt) = @_;
> +
> + my $format = $partial_fast_plug_option->{$opt}->{fmt};
> + my $properties = $partial_fast_plug_option->{$opt}->{properties};
if you would call this $fast_pluggable instead of this over general name
you could avoid that extra $is_fastpluggable variable below.
> +
> + my $old = PVE::JSONSchema::parse_property_string($format, $conf->{$opt});
my $old = {};
if (exists($conf->{$opt})) {
$old = PVE::JSONSchema::parse_property_string($format, $conf->{$opt});
}
Else you throw up if $conf->{$opt} was not set previously.
> + my $new = PVE::JSONSchema::parse_property_string($format, $conf->{pending}->{$opt});
above should be OK as the pending $opt has to be always set, else this mustn't
be called.
> +
> + my $changes_left = 0;
> +
> + # merge old and new opts to iterate
> + my $all_opts = dclone($old);
> + foreach my $opt1 (keys %$new) {
> + $all_opts->{$opt1} = $new->{$opt1} if !defined($all_opts->{$opt1});
> + }
Instead of above 5 lines you could just do:
my @all_keys = keys %{{ %$new, %$old }};
(you only need the keys, not the data)
> +
> + foreach my $opt2 (keys %$all_opts) {
you can reuse variable names from for(each) in different scopes, no need
to use $opt1, $opt2, ...
> + my $is_fastpluggable = $properties->{$opt2};
> + my $type = $format->{$opt2}->{type};
> + if ($typesafe_ne->($old->{$opt2}, $new->{$opt2}, $type)) {
> + if (defined($is_fastpluggable)) {
why defined check? We control that definition, so normal "boolean" check is enough,
with above suggested changes together just write:
if ($fast_pluggable->{$opt}) {
...
> + $old->{$opt2} = $new->{$opt2};
> + } else {
> + $changes_left = 1;
> + }
> + }
> + }
> +
> + $conf->{$opt} = PVE::JSONSchema::print_property_string($old, $format);
above needs to be guarded too, else this can throw up too:
if (keys %$old) {
$conf->{$opt} = PVE::JSONSchema::print_property_string($old, $format);
}
> +
> + return $changes_left;
> +}
> +
> +
> sub vmconfig_update_net {
> my ($storecfg, $conf, $hotplug, $vmid, $opt, $value, $arch, $machine_type) = @_;
>
>
More information about the pve-devel
mailing list