On Wed, Apr 02, 2025 at 06:31:54PM +0200, Max Carrara wrote:
> On Mon Mar 31, 2025 at 5:12 PM CEST, Fabian Grünbichler wrote:
> > On March 26, 2025 3:20 pm, Max Carrara wrote:
> > > Add docstrings for the following methods:
> > > - check_connection
> > > - activate_storage
> > > - deactivate_storage
> > > - status
> > > - cluster_lock_storage

^ Remove or document as deprecated for public APIs.

> > > - parse_volname
> > > - get_subdir

^ Remove or document as deprecated for public APIs.

> > > - filesystem_path

^ Remove or document as deprecated for public APIs.

> > > - path
> > > - find_free_diskname

^ Remove or document as deprecated for public APIs.

While this has been mentioned in the replies for `get_subdir` and
`find_free_diskname`, this is also true for `filesystem_path`.
`filesystem_path` is actually a "DirPlugin API".
Remember, not all storages *have* a file system path...

> > > 
> > > Signed-off-by: Max Carrara <m.carr...@proxmox.com>
> > > ---
> > >  src/PVE/Storage/PluginBase.pm | 255 ++++++++++++++++++++++++++++++++++
> > >  1 file changed, 255 insertions(+)
> > > 
> > > diff --git a/src/PVE/Storage/PluginBase.pm b/src/PVE/Storage/PluginBase.pm
> > > index 5f7e6fd..8a61dc3 100644
> > > --- a/src/PVE/Storage/PluginBase.pm
> > > +++ b/src/PVE/Storage/PluginBase.pm
> > > @@ -317,53 +317,308 @@ sub private {
> > >  
> > >  =cut
> > >  
> > > +=head3 $plugin->check_connection($storeid, \%scfg)
> > > +
> > > +B<OPTIONAL:> May be implemented in a storage plugin.
> > > +
> > > +Performs a connection check.
> > > +
> > > +This method is useful for plugins that require some kind of network 
> > > connection
> > > +or similar and is called before C<L<< 
> > > activate_storage()|/"$plugin->activate_storage($storeid, \%scfg, 
> > > \%cache)" >>>.
> 
> Upfront note: Unless I otherwise comment something, I agree with you.
> Just sparing myself from writing and you from reading too many ACKs :P
> 
> >
> > This method can be implemented by network-based storages. It will be
> > called before storage activation attempts. Non-network storages should
> > not implement it.
> >
> > > +
> > > +Returns C<1> by default and C<0> if the storage isn't online / reachable.
> >
> > by default doesn't make any sense, even though I know what you mean.
> >
> > > +
> > > +C<die>s if an error occurs while performing the connection check.
> > > +
> > > +Note that this method's purpose is to simply verify that the storage is
> > > +I<reachable>, and not necessarily that authentication etc. succeeds.
> > > +
> > > +For example: If the storage is mainly accessed via TCP, you can try to 
> > > simply
> > > +open a TCP connection to see if it's online:
> > > +
> > > +    # In custom module:
> > > +
> > > +    use PVE::Network;
> > > +    use parent qw(PVE::Storage::Plugin)
> > > +
> > > +    # [...]
> > > +
> > > +    sub check_connection {
> > > + my ($class, $storeid, $scfg) = @_;
> > > +
> > > + my $port = $scfg->{port} || 5432;
> > > + return PVE::Network::tcp_ping($scfg->{server}, $port, 5);
> > > +    }
> > > +
> > > +=cut
> > > +
> > >  sub check_connection {
> > >      my ($class, $storeid, $scfg) = @_;
> > >  
> > >      return 1;
> > >  }
> > >  
> > > +=head3 $plugin->activate_storage($storeid, \%scfg, \%cache)
> > > +
> > > +=head3 $plugin->activate_storage(...)
> > > +
> > > +B<REQUIRED:> Must be implemented in every storage plugin.
> > > +
> > > +Activates the storage, making it ready for further use.
> > > +
> > > +In essence, this method performs the steps necessary so that the storage 
> > > can be
> > > +used by remaining parts of the system.
> >
> > this is superfluous (all of that information is already contained in the
> > previous sentence).
> >
> > > +
> > > +In the case of file-based storages, this usually entails creating the 
> > > directory
> > > +of the mountpoint, mounting the storage and then creating the 
> > > directories for
> > > +the different content types that the storage has enabled. See
> > > +C<L<PVE::Storage::NFSPlugin>> and C<L<PVE::Storage::CIFSPlugin>> for 
> > > examples
> > > +in that regard.
> > > +
> > > +Other types of storages would use this method for establishing a 
> > > connection to
> > > +the storage and authenticating with it or similar. See 
> > > C<L<PVE::Storage::ISCSIPlugin>>
> > > +for an example.
> > > +
> >
> > I am not sure this examples provide much benefit in this verbosity..
> > maybe just a single sentence like
> >
> > Common examples of activation might include mounting filesystems,
> > preparing directory trees or establishing sessions with network
> > storages.
> >
> > > +If the storage doesn't need to be activated in some way, this method can 
> > > be a
> > > +no-op.
> > > +
> > > +C<die>s in case of errors.
> >
> > *Should* die?
> >
> > > +
> > > +This method may reuse L<< cached information via C<\%cache>|/"CACHING 
> > > EXPENSIVE OPERATIONS" >>.
> > > +
> > > +=cut
> > > +
> > >  sub activate_storage {
> > >      my ($class, $storeid, $scfg, $cache) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->deactivate_storage($storeid, \%scfg, \%cache)
> > > +
> > > +=head3 $plugin->deactivate_storage(...)
> > > +
> > > +B<OPTIONAL:> May be implemented in a storage plugin.
> > > +
> > > +Deactivates the storage. Counterpart to C<L<< 
> > > activate_storage()|/"$plugin->activate_storage(...)" >>>.
> > > +
> > > +This method is used to make the storage unavailable to the rest of the 
> > > system,
> > > +which usually entails unmounting the storage, closing an established
> > > +connection, or similar.
> > > +
> > > +Does nothing by default.
> > > +
> > > +C<die>s in case of errors.
> >
> > *Should*
> >
> > > +
> > > +This method may reuse L<< cached information via C<\%cache>|/"CACHING 
> > > EXPENSIVE OPERATIONS" >>.
> > > +
> > > +B<NOTE:> This method is currently not used by our API due to historical
> > > +reasons.

(I mean, some storages "know" when things are in-use, so we could
probably start using this for storages which opt-in... but meh...
People *are* going to continue complaining about this, though  ;-) )

> > > +
> > > +=cut
> > > +
> > >  sub deactivate_storage {
> > >      my ($class, $storeid, $scfg, $cache) = @_;
> > >  
> > >      return;
> > >  }
> > >  
> > > +=head3 $plugin->status($storeid, \%scfg, \%cache)
> > > +
> > > +=head3 $plugin->status(...)
> > > +
> > > +B<REQUIRED:> Must be implemented in every storage plugin.
> > > +
> > > +Returns a list of scalars in the following form:
> > > +
> > > +    my ($total, $available, $used, $active) = $plugin->status($storeid, 
> > > $scfg, $cache)
> > > +
> > > +This list contains the C<$total>, C<$available> and C<$used> storage 
> > > capacity,
> > > +respectively, as well as whether the storage is C<$active> or not.
> >
> > might make sense to include the information which unit the returned
> > numbers should be in?
> >
> > > +
> > > +This method may reuse L<< cached information via C<\%cache>|/"CACHING 
> > > EXPENSIVE OPERATIONS" >>.
> > > +
> > > +=cut
> > > +
> > >  sub status {
> > >      my ($class, $storeid, $scfg, $cache) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->cluster_lock_storage($storeid, $shared, $timeout, 
> > > \&func, @param)
> > > +
> > > +=head3 $plugin->cluster_lock_storage(...)
> > > +
> > > +B<WARNING:> This method is provided by C<L<PVE::Storage::Plugin>> and
> > > +must be used as-is. It is merely documented here for informal purposes
> > > +and B<must not be overridden.>
> >
> > see my comment on the first patch.. this is not actually part of the
> > plugin interface, it is a helper that is implemented by
> > PVE::Storage::Plugin for historical reasons and should be moved
> > somewhere else and then removed from PVE::Storage::Plugin when we do the
> > next break of that inheritance hierarchy..
> >
> > > +
> > > +Locks the storage with the given C<$storeid> for the entire host and 
> > > runs the
> > > +given C<\&func> with the given C<@param>s, unlocking the storage once 
> > > finished.
> > > +If the storage is C<$shared>, it is instead locked on the entire 
> > > cluster. If
> > > +the storage is already locked, wait for at most the number of seconds 
> > > given by
> > > +C<$timeout>.
> >
> > Attempts to acquire a lock on C<$storeid>, waiting at most C<$timeout>
> > seconds before giving up. If successful, runs C<\&func> with the given
> > C<@param>s while holding the lock. If C<$shared> is set, the lock
> > operation will be cluster-wide (with a timeout for the execution of
> > C<\&func> of 60 seconds).
> >
> > This method must be used to guard against concurrent modifications of
> > the storage by multiple tasks running at the same time, in particular
> > for actions such as:
> > - allocating new volumes (including clones) or snapshots
> > - deleting existing volumes or snapshots
> > - renaming existing volumes or snapshots
> >
> > > +
> > > +This method is used to synchronize access to the given storage, 
> > > preventing
> > > +simultaneous modification from multiple workers. This is necessary for
> > > +operations that modify data on the storage directly, like cloning and
> > > +allocating images.
> >
> >
> > > +
> > > +=cut
> > > +
> > >  sub cluster_lock_storage {
> > >      my ($class, $storeid, $shared, $timeout, $func, @param) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->parse_volname($volname)
> > > +
> > > +B<REQUIRED:> Must be implemented in every storage plugin.
> > > +
> > > +Parses C<$volname>, returning a list representing the parts encoded in
> > > +the volume name:
> >
> > s/parts/volume properties/ ?
> >
> > should we incorporate the outcome of this discussion:
> >
> > https://lore.proxmox.com/pve-devel/qdnb3vypbucbcf7ch2hsjbeo3hqb5bh4whoinl5xglggpt7b7t@igryfncdupdp/
> 
> Hmm, I'd say so, yeah. The original message isn't on lore apparently, so
> I'll see if I can dig this up in my mailbox later.
> 
> >
> > ?
> >
> > > +
> > > +    my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format)
> > > + = $plugin->parse_volname($volname);
> > > +
> > > +Not all parts need to be included in the list. Those marked as 
> > > I<optional>
> > > +in the list below may be set to C<undef> if not applicable.
> > > +
> > > +This method may C<die> in case of errors.
> >
> > s/may/should/ ?
> >
> > > +
> > > +=over
> > > +
> > > +=item * C<< $vtype >>
> > > +
> > > +The content type ("volume type") of the volume, e.g. C<"images">, 
> > > C<"iso">,
> > > +etc.
> >
> > content type != volume type! the two are related obviously, but the
> > values are subtly different for images for historic reasons.
> 
> Oh, right. Thanks for pointing this out! I'll see if I can note down
> those differences somewhere.
> 
> >
> > > +
> > > +See C<L<< plugindata()|/"$plugin->plugindata()" >>> for all content 
> > > types.
> > > +
> > > +=item * C<< $name >>
> > > +
> > > +The display name of the volume. This is usually what the underlying 
> > > storage
> > > +itself uses to address the volume.
> >
> > Ideally, this maps directly to some entity on the underlying storage.
> >
> > > +
> > > +For example, disks for virtual machines that are stored on LVM thin 
> > > pools are
> > > +named C<vm-100-disk-0>, C<vm-1337-disk-1>, etc. That would be the 
> > > C<$name> in
> > > +this case.
> >
> > This is rather imprecise, maybe just say
> >
> > For example, a guest volume named C<vm-100-disk-0> stored on an LVM thin
> > pool will refer to a thin LV of the same name.
> >
> > > +
> > > +=item * C<< $vmid >> (optional)
> > > +
> > > +The ID of the guest that owns the volume.
> >
> > should maybe note that this must be set for `images` or `backup`, and
> > must not be set for the other vtypes?
> >
> > > +
> > > +=item * C<< $basename >> (optional)
> > > +
> > > +The C<$name> of the volume this volume is based on. Whether this part
> > > +is returned or not depends on the plugin and underlying storage.
> >
> > The C<$name> of this volume's base volume, if it is a linked clone.
> >
> > ?
> >
> > > +Only applies to disk images.
> > > +
> > > +For example, on ZFS, if the VM is a linked clone, C<$basename> refers
> > > +to the C<$name> of the original disk volume that the parsed disk volume
> > > +corresponds to.
> >
> > it is only used for that, and has this semantic across all storages,
> > right?
> 
> AFAIR yes. But now you made me check again soon. :P
> 
> >
> > > +
> > > +=item * C<< $basevmid >> (optional)
> > > +
> > > +Equivalent to C<$basename>, except that C<$basevmid> refers to the
> > > +C<$vmid> of the original disk volume instead.
> >
> > s/original/base
> >
> > > +
> > > +=item * C<< $isBase >> (optional)
> > > +
> > > +Whether the volume is a base disk image.
> > > +
> > > +=item * C<< $format >>
> > > +
> > > +The format of the volume. If the volume is a VM disk (C<$vtype> is
> > > +C<"images">), this should be whatever format the disk is in. For most
> > > +other content types C<"raw"> should be used.
> >
> > "images" is currently also for container volumes.. import volumes also
> > have a format..
> 
> Yeah, Wolfgang also told me this last week a little after I sent in this
> series. Will correct the docs.
> 
> >
> > > +
> > > +See C<L<< plugindata()|/"$plugin->plugindata()" >>> for all formats.
> > > +
> > > +=back
> > > +
> > > +=cut
> > > +
> > >  sub parse_volname {
> > >      my ($class, $volname) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->get_subdir(\%scfg, $vtype)
> >
> > I am not sure about this one long-term.. while the interface is generic
> > enough, it is only used for the helpers in PVE::Storage which are in
> > turn used for
> > - uploading/download things (templates, iso files, ..)
> > - creating backup archives (or rather, getting the path to the dump dir
> >   to create them)
> >
> > > +
> > > +B<SITUATIONAL:> This method must be implemented for file-based storages.
> >
> > s/file-based/dir-based
> >
> > ?
> >
> > > +
> > > +Returns the path to the sub-directory associated with the given content 
> > > type
> > > +(C<$vtype>). See C<L<< plugindata()|/"$plugin->plugindata()" >>> for all
> > > +content types.
> >
> > again, this is vtype not content type..
> >
> > > +
> > > +The default directory layout is predefined and must not be altered:
> > > +
> > > +    my $vtype_subdirs = {
> > > + images   => 'images',
> > > + rootdir  => 'private',
> > > + iso      => 'template/iso',
> > > + vztmpl   => 'template/cache',
> > > + backup   => 'dump',
> > > + snippets => 'snippets',
> > > + import   => 'import',
> > > +    };
> > > +
> > > +See also: L<Storage: 
> > > Directory|"https://pve.proxmox.com/wiki/Storage:_Directory";>
> > > +
> > > +=cut
> > > +
> > >  sub get_subdir {
> > >      my ($class, $scfg, $vtype) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->filesystem_path(\%scfg, $volname [, $snapname])
> > > +
> > > +=head3 $plugin->filesystem_path(...)
> > > +
> > > +B<SITUATIONAL:> This method must be implemented for file-based storages.
> >
> > file/dir
> >
> > > +
> > > +Returns the absolute path on the filesystem for the given volume or 
> > > snapshot.
> > > +In list context, returns path, guest ID and content type:
> >
> > content/volume
> >
> > but - this is not actually part of the plugin API. it just looks like it
> > because Plugin's implementation of path (which is the actual API) calls
> > it, and thus plugins derived from that base implement or call it as
> > well.
> >
> > the public interface if you want a file or blockdev path for a given
> > volid is PVE::Storage::abs_filesystem_path, which internally also calls
> > the plugin's `path` method, not the non-public `filesystem_path`.
> >
> > > +
> > > +    my $path = $plugin->filesystem_path($scfg, $volname, $snapname)
> > > +    my ($path, $vmid, $vtype) = $plugin->filesystem_path($scfg, 
> > > $volname, $snapname)
> > > +
> > > +=cut
> > > +
> > >  sub filesystem_path {
> > >      my ($class, $scfg, $volname, $snapname) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->path(\%scfg, $volname, $storeid [, $snapname])
> > > +
> > > +B<REQUIRED:> Must be implemented in every storage plugin.

I think we should make this one optional soon.
Currently we have some places where we blindly call this, but not all
storages have a path.

> > > +
> > > +Returns a unique path that points to the given volume or snapshot 
> > > depending
> > > +on the underlying storage implementation. For file-based storages, this
> > > +method should return the same as C<L<< 
> > > filesystem_path()|/"$plugin->filesystem_path(...)" >>>.
> >
> > this is inverted (see above). it should also note that if there is an
> > actual path corresponding to a volume, it should return that, but that
> > it can also return something else that Qemu understands in lieu of a
> > real path.

Particularly with the backup-provider API we may have storages with
neither paths nor URLs for qemu...

An example where we currently needlessly require this method is when
*deleting* a volume where the API endpoint calls `path()` to get the
volume type, which may as well use `parse_volname()` instead, then
deleting works without this method implemented.

> >
> > if multiple paths exist, it should return the most specific/unique one
> > to avoid collisions between multiple plugin instances of the same
> > storage type.
> >
> > > +
> > > +=cut
> > > +
> > >  sub path {
> > >      my ($class, $scfg, $volname, $storeid, $snapname) = @_;
> > >      croak "implement me in sub-class\n";
> > >  }
> > >  
> > > +=head3 $plugin->find_free_diskname($storeid, \%scfg, $vmid [, $fmt, 
> > > $add_fmt_suffix])
> > > +
> > > +=head3 $plugin->find_free_diskname(...)
> > > +
> > > +B<REQUIRED:> Must be implemented in every storage plugin.
> > > +
> > > +Finds and returns the next available disk name, that is, the volume name
> > > +(C<$volname>) for a new disk image. Optionally, C<$fmt> specifies the
> > > +format of the disk image and C<$add_fmt_suffix> denotes whether C<$fmt>
> > > +should be added as suffix to the resulting name.
> >
> > similarly, this is not actually part of the plugin API, it is an
> > implementation detail of the existing plugin hierarchy. it is currently
> > called by Plugin's clone, allocate and rename functionality, so if you
> > don't implement/override it you need to also override those (which you
> > likely need to do in any case..).
> >
> > these two are actually a good example of what I meant with the messiness
> > of Plugin.pm leaking if we continue deriving *all* plugins from it,
> > including new external ones..
> 
> ... Yeah, this just reaffirms my stance that a proper investigative look
> and documentation for all of this *really* necessary. Even after this
> digital archaeology tour we've apparently missed a lot of details, or in
> the case of the two methods above, included details that aren't actually
> part of the plugin API.
> 
> Thanks for going through all of this, by the way -- it's really highly
> appreciated!


_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to