* [pve-devel] [RFC storage 1/2] close #6669: plugin api: introduce on_update_hook_full() method
2025-10-08 15:11 [pve-devel] [RFC storage 0/2] close #6669: plugin api: introduce on_update_hook_full() method Fiona Ebner
@ 2025-10-08 15:11 ` Fiona Ebner
2025-10-08 15:11 ` [pve-devel] [RFC storage 2/2] lvm plugin: disallow disabling 'snapshot-as-volume-chain' while a qcow2 image exists Fiona Ebner
1 sibling, 0 replies; 3+ messages in thread
From: Fiona Ebner @ 2025-10-08 15:11 UTC (permalink / raw)
To: pve-devel
The original on_update_hook() method is limited, because only the
updated properties and values are passed in. Introduce a new
on_update_hook_full() method which also receives the current storage
configuration and the list of which properties are to be deleted. This
allows detecting and reacting to all changes and knowing how values
changed.
Deletion of properties is deferred to after the on_update_hook(_full)
call. This makes it possible to pass the unmodified current storage
configuration to the method.
The default implementation of on_update_hook_full() just falls back to
the original on_update_hook().
Bump APIVER and APIAGE.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
ApiChangeLog | 13 +++++++++++++
src/PVE/API2/Storage/Config.pm | 18 +++++++++++++++---
src/PVE/Storage.pm | 4 ++--
src/PVE/Storage/Plugin.pm | 33 +++++++++++++++++++++++++++++++++
4 files changed, 63 insertions(+), 5 deletions(-)
diff --git a/ApiChangeLog b/ApiChangeLog
index d80bfb3..13e4339 100644
--- a/ApiChangeLog
+++ b/ApiChangeLog
@@ -6,6 +6,19 @@ without breaking anything unaware of it.)
Future changes should be documented in here.
+## Version 13:
+
+* Introduce `on_update_hook_full()` plugin method
+
+ The original `on_update_hook()` plugin method was limited, because only the updated properties and
+ values would be passed in. The new `on_update_hook_full()` plugin method also receives the current
+ storage configuration and the list of which properties are to be deleted. This allows detecting
+ and reacting to all changes and knowing how values changed. See also bug #6669 [0] for the initial
+ motiviation. If a plugin implements `on_update_hook_full()`, that method will be called rather
+ than the `on_update_hook()` method.
+
+ [0]: https://bugzilla.proxmox.com/show_bug.cgi?id=6669
+
## Version 12:
* Introduce `qemu_blockdev_options()` plugin method
diff --git a/src/PVE/API2/Storage/Config.pm b/src/PVE/API2/Storage/Config.pm
index 34f2d85..c10ccf8 100755
--- a/src/PVE/API2/Storage/Config.pm
+++ b/src/PVE/API2/Storage/Config.pm
@@ -360,6 +360,9 @@ __PACKAGE__->register_method({
my $plugin = PVE::Storage::Plugin->lookup($type);
my $opts = $plugin->check_config($storeid, $param, 0, 1);
+ # Do checks for deletion up-front, but defer actual deletion until after the
+ # on_update_hook(_full) call. This makes it possible to pass the unmodified current
+ # storage configuration to the method.
if ($delete) {
my $options = $plugin->private()->{options}->{$type};
foreach my $k (@$delete) {
@@ -368,12 +371,21 @@ __PACKAGE__->register_method({
die "unable to delete fixed option '$k'\n" if $d->{fixed};
die "cannot set and delete property '$k' at the same time!\n"
if defined($opts->{$k});
-
- delete $scfg->{$k};
}
}
- $returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
+ if ($plugin->can('api') && $plugin->api() < 13) {
+ $returned_config = $plugin->on_update_hook($storeid, $opts, %$sensitive);
+ } else {
+ $returned_config =
+ $plugin->on_update_hook_full($storeid, $scfg, $opts, $delete, $sensitive);
+ }
+
+ if ($delete) {
+ for my $k ($delete->@*) {
+ delete $scfg->{$k};
+ }
+ }
for my $k (keys %$opts) {
$scfg->{$k} = $opts->{$k};
diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm
index 1dde2b7..ca0bf0e 100755
--- a/src/PVE/Storage.pm
+++ b/src/PVE/Storage.pm
@@ -41,11 +41,11 @@ use PVE::Storage::BTRFSPlugin;
use PVE::Storage::ESXiPlugin;
# Storage API version. Increment it on changes in storage API interface.
-use constant APIVER => 12;
+use constant APIVER => 13;
# Age is the number of versions we're backward compatible with.
# This is like having 'current=APIVER' and age='APIAGE' in libtool,
# see https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
-use constant APIAGE => 3;
+use constant APIAGE => 4;
our $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs', 'btrfs'];
diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm
index 8acd214..9dc684d 100644
--- a/src/PVE/Storage/Plugin.pm
+++ b/src/PVE/Storage/Plugin.pm
@@ -672,6 +672,39 @@ sub on_update_hook {
return undef;
}
+=head3 on_update_hook_full
+
+ $returned_config = $plugin->on_update_hook_full($storeid, $scfg, $update, $delete, $sensitive);
+
+Most plugins use an empty C<return>, so that C<$returned_config> will be C<undef>. While a plugin
+can return auto-generated properties via C<$returned_config>, this is currently only used for the
+C<'encryption-key'> for the PBS plugin.
+
+Arguments:
+
+=over
+
+=item C<$storeid>: The storage ID.
+
+=item C<$scfg>: The current storage configuration.
+
+=item C<$update>: Hash reference with properties to be updated and their new values.
+
+=item C<$delete>: Array reference with properties to be deleted.
+
+=item C<$sensitive>: Hash reference with sensitive properties and their new values. Sensitive
+properties are declared via the plugin data method C<plugindata()>.
+
+=back
+
+=cut
+
+sub on_update_hook_full {
+ my ($class, $storeid, $scfg, $update, $delete, $sensitive) = @_;
+
+ return $class->on_update_hook($storeid, $update, $sensitive->%*);
+}
+
# called during deletion of storage (before the new storage config got written)
# and if the activate check on addition fails, to cleanup all storage traces
# which on_add_hook may have created.
--
2.47.3
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 3+ messages in thread
* [pve-devel] [RFC storage 2/2] lvm plugin: disallow disabling 'snapshot-as-volume-chain' while a qcow2 image exists
2025-10-08 15:11 [pve-devel] [RFC storage 0/2] close #6669: plugin api: introduce on_update_hook_full() method Fiona Ebner
2025-10-08 15:11 ` [pve-devel] [RFC storage 1/2] " Fiona Ebner
@ 2025-10-08 15:11 ` Fiona Ebner
1 sibling, 0 replies; 3+ messages in thread
From: Fiona Ebner @ 2025-10-08 15:11 UTC (permalink / raw)
To: pve-devel
There are multiple reasons to disallow disabling
'snapshot-as-volume-chain' while a qcow2 image exists:
1. The list of allowed formats depends on 'snapshot-as-volume-chain'.
2. Snapshot functionality is broken. This includes creating snapshots,
but also rollback, which removes the current volume and then fails.
3. There already is coupling between having qcow2 on LVM and having
'snapshot-as-volume-chain' enabled. For example, the
'discard-no-unref' option is required for qcow2 on LVM, but
qemu-server only checks for 'snapshot-as-volume-chain' to avoid
hard-coding LVM. Another one is that volume_qemu_snapshot_method()
returns 'mixed' when the format is qcow2 even when
'snapshot-as-volume-chain' is disabled. Hunting down these corner
cases just to make it easier to disable does not seem to be worth
it, considering there's already 1. and 2. as reasons too.
4. There might be other similar issues that have not surfaced yet,
because disabling the feature while qcow2 is present is essentially
untested and very uncommon.
For file-based storages, the 'snapshot-as-volume-chain' property is
already fixed, i.e. is determined upon storage creation.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/Storage/LVMPlugin.pm | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/src/PVE/Storage/LVMPlugin.pm b/src/PVE/Storage/LVMPlugin.pm
index 0416c9e..c884941 100644
--- a/src/PVE/Storage/LVMPlugin.pm
+++ b/src/PVE/Storage/LVMPlugin.pm
@@ -439,6 +439,25 @@ sub on_add_hook {
return;
}
+sub on_update_hook_full {
+ my ($class, $storeid, $scfg, $update, $delete, $sensitive) = @_;
+
+ if (
+ $scfg->{'snapshot-as-volume-chain'} # currently set
+ && ( # and won't be set after update, because:
+ (
+ defined($update->{'snapshot-as-volume-chain'})
+ && !$update->{'snapshot-as-volume-chain'}
+ ) # explicitly set to disabled
+ || grep { $_ eq 'snapshot-as-volume-chain' } $delete->@* # or deleted
+ )
+ ) {
+ my $images = $class->list_images($storeid, $scfg, undef, undef, undef);
+ die "$storeid - cannot disable 'snapshot-as-volume-chain' while a qcow2 image exists\n"
+ if grep { $_->{format} eq 'qcow2' } $images->@*;
+ }
+}
+
sub parse_volname {
my ($class, $volname) = @_;
--
2.47.3
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 3+ messages in thread