[pve-devel] [RFC v1 pve-storage 1/6] version: introduce PVE::Storage::Version

Wolfgang Bumiller w.bumiller at proxmox.com
Wed Feb 5 12:20:04 CET 2025


On Thu, Jan 30, 2025 at 03:51:19PM +0100, Max Carrara wrote:
> The purpose of this module is to separate the handling of the Storage
> API's version from the remaining code while also providing additional
> mechanisms to handle changes to the Storage API more gracefully.
> 
> The first such mechanism is the `pveStorageDeprecateAt` attribute,
> which, when added to a subroutine, marks the subroutine as deprecated
> at a specific version. Should the lowest supported API version
> increase beyond the version passed to the attribute, the module will
> fail to compile (during Perl's CHECK phase).
> 
> This provides a more robust mechanism instead of having to rely on
> "FIXME" comments or similar.
> 
> Unfortunately attributes can't conveniently be exported using the
> `Exporter` module or similar mechanisms, and they can neither be
> referenced via their fully qualified path. The attribute's name is
> therefore intentionally made long in order to not conflict with any
> other possible attributes in the UNIVERSAL namespace.
> 
> Signed-off-by: Max Carrara <m.carrara at proxmox.com>
> ---
>  src/PVE/Storage.pm         |  10 +--
>  src/PVE/Storage/Makefile   |   1 +
>  src/PVE/Storage/Version.pm | 180 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 185 insertions(+), 6 deletions(-)
>  create mode 100644 src/PVE/Storage/Version.pm
> 
> diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm
> index 3b4f041..df4d62f 100755
> --- a/src/PVE/Storage.pm
> +++ b/src/PVE/Storage.pm
> @@ -24,6 +24,8 @@ use PVE::RPCEnvironment;
>  use PVE::SSHInfo;
>  use PVE::RESTEnvironment qw(log_warn);
>  
> +use PVE::Storage::Version;
> +
>  use PVE::Storage::Plugin;
>  use PVE::Storage::DirPlugin;
>  use PVE::Storage::LVMPlugin;
> @@ -41,12 +43,8 @@ use PVE::Storage::PBSPlugin;
>  use PVE::Storage::BTRFSPlugin;
>  use PVE::Storage::ESXiPlugin;
>  
> -# Storage API version. Increment it on changes in storage API interface.
> -use constant APIVER => 10;
> -# 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 => 1;
> +use constant APIVER => PVE::Storage::Version::APIVER;
> +use constant APIAGE => PVE::Storage::Version::APIAGE;
>  
>  our $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs', 'btrfs'];
>  
> diff --git a/src/PVE/Storage/Makefile b/src/PVE/Storage/Makefile
> index ce3fd68..5315f69 100644
> --- a/src/PVE/Storage/Makefile
> +++ b/src/PVE/Storage/Makefile
> @@ -1,5 +1,6 @@
>  SOURCES= \
>  	Common.pm \
> +	Version.pm \
>  	Plugin.pm \
>  	DirPlugin.pm \
>  	LVMPlugin.pm \
> diff --git a/src/PVE/Storage/Version.pm b/src/PVE/Storage/Version.pm
> new file mode 100644
> index 0000000..a9216c9
> --- /dev/null
> +++ b/src/PVE/Storage/Version.pm
> @@ -0,0 +1,180 @@
> +=head1 NAME
> +
> +C<PVE::Storage::Version> - Storage API Version Management
> +
> +=head1 DESCRIPTION
> +
> +This module is concerned with tracking and managing the version of the Storage
> +API interface C<L<PVE::Storage::Plugin>>. Furthermore, this module also provides
> +a mechanism to I<deprecate> subroutines that are part of the Storage API via a
> +custom I<attribute> called C<L<< pveStorageDeprecateAt|/UNIVERSAL::pveStorageDeprecateAt() >>>.
> +
> +For more information on this mechanism, see L</"DEPRECATING SUBROUTINES"> below.
> +
> +B<NOTE:> Importing this module will define the C<L<< pveStorageDeprecateAt|/UNIVERSAL::pveStorageDeprecateAt() >>>
> +attribute inside the C<UNIVERSAL> (global) namespace. Ensure that no other
> +symbols have the same name as this attribute in order to avoid conflicts.
> +
> +=cut
> +
> +package PVE::Storage::Version;
> +
> +use strict;
> +use warnings;
> +
> +use v5.36;

^  activates signatures, use them.

> +
> +use Attribute::Handlers;
> +use Carp;
> +use Scalar::Util qw(reftype);
> +
> +# NOTE: This module must not import PVE::Storage or any of its submodules,
> +# either directly or transitively.
> +#
> +# Otherwise there's a risk of introducing nasty cyclical imports and/or causing
> +# obscure errors during compile time, which may be very hard to debug.
> +
> +=head1 CONSTANTS
> +
> +=head3 APIVER
> +
> +The version of the Storage API.
> +
> +This version must be incremented if changes to the C<L<PVE::Storage::Plugin>>
> +API interface are made.
> +
> +=head3 APIAGE
> +
> +The API age is the number of versions we're backward compatible with.
> +
> +This is similar to having C<current='APIVER'> and C<age='APIAGE'> in C<libtool>.
> +For more information, see L<here|https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html>.
> +
> +Generally, you'll want to increment this when incrementing C<L<< APIVER|/APIVER >>>.
> +Resetting C<APIAGE> to C<0> means that backward compatibility is broken and should
> +only be done when appropriate (e.g. during a release of a new major version of PVE).
> +
> +=cut
> +
> +use constant APIVER => 10;
> +use constant APIAGE => 1;
> +
> +my $MIN_VERSION = (APIVER - APIAGE);
> +
> +
> +=head1 DEPRECATING SUBROUTINES
> +
> +Upon importing this module, the C<L<< pveStorageDeprecateAt|/UNIVERSAL::pveStorageDeprecateAt >>>
> +attribute becomes available. This attribute ought to be used similar to how
> +prototypes are used in Perl and requires a C<version> and a C<message> key to
> +be passed:
> +
> +    sub some_sub : pveStorageDeprecateAt(version => 42, message => "...") {
> +	# [...]
> +    }
> +
> +Most likely you'll need more space for the C<message>, in which case the
> +attribute may simply span multiple lines:
> +
> +    sub some_sub : pveStorageDeprecateAt(
> +	version => 42,
> +	message => "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
> +	    . " Vestibulum ac placerat arcu. Nulla eget risus sed eros pretium"
> +	    . " tempus in sed purus. Mauris iaculis ligula justo, in facilisis nisl"
> +	    . " maximus at. Aliquam bibendum sapien ut turpis scelerisque, vel"
> +	    . " suscipit lorem varius. Mauris et erat posuere, gravida dui"
> +	    . " porttitor, faucibus mauris. Phasellus ultricies ante aliquet,"
> +	    . " luctus lectus pretium, lobortis arcu. Aliquam fringilla libero id"
> +	    . " diam venenatis, nec placerat felis viverra.",
> +    ) {
> +	# [...]
> +    }

One example with "..." as message should be good enough.

> +
> +As soon as the passed C<version> is below the I<lowest supported version>
> +(C<L<< APIVER|/APIVER >>> minus C<L<< APIAGE|/APIAGE >>>), the attribute raises
> +an exception during the C<CHECK> phase of the Perl interpreter and also displays
> +the passed C<message>. This happens before any runtime code is executed.
> +
> +Should the passed C<version> be equal to the lowest supported version, a warning
> +containing the passed C<message> is emitted whenever the subroutine is called
> +instead. This ensures that implementors of custom plugins are made aware of
> +subroutines being deprecated, should there be any in use.
> +
> +The C<L<< pveStorageDeprecateAt|/UNIVERSAL::pveStorageDeprecateAt >>> attribute
> +may only be used inside C<L<PVE::Storage>> or its submodules and throws an error
> +if used elsewhere.
> +
> +This mechanism's sole purpose is to enforce deprecation of subroutines during
> +"compile time", forcing the subroutine to be removed once the API version is
> +incremented too far or the API age is reset.
> +
> +=head1 ATTRIBUTES
> +
> +=head3 UNIVERSAL::pveStorageDeprecateAt
> +
> +This attribute marks subroutines as deprecated at a specific C<version> and will
> +emit an error with the given C<message> if the lowest supported Storage API
> +version exceeds the C<version> passed to the attribute.
> +
> +    sub some_sub : pveStorageDeprecateAt(version => 42, message => "...") {
> +	# [...]
> +    }
> +
> +    sub some_sub : pveStorageDeprecateAt(
> +	version => 42,
> +	message => "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
> +	    . " Vestibulum ac placerat arcu. Nulla eget risus sed eros pretium"
> +	    . " tempus in sed purus. Mauris iaculis ligula justo, in facilisis nisl"
> +	    . " maximus at. Aliquam bibendum sapien ut turpis scelerisque, vel"
> +	    . " suscipit lorem varius. Mauris et erat posuere, gravida dui"
> +	    . " porttitor, faucibus mauris. Phasellus ultricies ante aliquet,"
> +	    . " luctus lectus pretium, lobortis arcu. Aliquam fringilla libero id"
> +	    . " diam venenatis, nec placerat felis viverra.",
> +    ) {
> +	# [...]
> +    }
> +
> +Should the passed C<version> be equal to the currently lowest supported API
> +version, a warning containing the passed C<message> is emitted instead.
> +
> +=cut
> +
> +sub UNIVERSAL::pveStorageDeprecateAt : ATTR(CODE,CHECK) {
> +    my ($package, $symbol, $referent, $attr_name, $attr_data, $phase, $filename, $line) = @_;
> +
> +    confess "'$attr_name' attribute may only be used inside PVE::Storage and its submodules"
> +	if $package !~ m/^PVE::Storage/;

(Could end in `($|:)`, otherwise this allows `PVE::StorageNotReally`)

> +
> +    my $data = { $attr_data->@* };
> +
> +    my ($version, $message) = ($data->{version}, $data->{message});
> +    my $reftype = reftype($referent);
> +
> +    confess "Referent is not a reference"
> +	. " -- '$attr_name' attribute may only be used for subroutines"
> +	if !defined($reftype);

^ Style issues mentioned in my cover letter reply.

> +
> +    confess "Unexpected reftype '$reftype'"
> +	. " -- '$attr_name' attribute may only be used for subroutines"
> +	if !($reftype eq 'CODE' || $reftype eq 'ANON');
> +
> +    confess "Either version or message not defined"
> +	. " -- usage: $attr_name(version => 42, message => \"...\")"
> +	if !defined($version) || !defined($message);
> +
> +    confess "Storage API version '$version' not supported: $message"
> +	if $version < $MIN_VERSION;
> +
> +    no warnings 'redefine';
> +    *$symbol = sub {
> +	my $symbol_name = *{$symbol}{"NAME"};
> +	carp "Warning: subroutine '$symbol_name' is deprecated - $message"
> +	    if $version == $MIN_VERSION;
> +	$referent->(@_);
> +    };
> +
> +    return;
> +}
> +
> +
> +1;
> -- 
> 2.39.5




More information about the pve-devel mailing list