On Mon, Mar 31, 2025 at 05:13:10PM +0200, Fabian Grünbichler wrote:
> On March 26, 2025 3:20 pm, Max Carrara wrote:
> > This commit adds docstrings for the relevant PVE::SectionConfig
> > methods in the context of the storage plugin API.
> > 
> > Signed-off-by: Max Carrara <m.carr...@proxmox.com>
> > ---
> >  src/PVE/Storage/PluginBase.pm | 194 +++++++++++++++++++++++++++++++++-
> >  1 file changed, 192 insertions(+), 2 deletions(-)
> > 
> > diff --git a/src/PVE/Storage/PluginBase.pm b/src/PVE/Storage/PluginBase.pm
> > index 16977f3..5f7e6fd 100644
> > --- a/src/PVE/Storage/PluginBase.pm
> > +++ b/src/PVE/Storage/PluginBase.pm
> > @@ -88,7 +88,7 @@ package PVE::Storage::PluginBase;
> >  use strict;
> >  use warnings;
> >  
> > -use Carp qw(croak);
> > +use Carp qw(croak confess);
> >  
> >  use parent qw(PVE::SectionConfig);
> >  
> > @@ -100,27 +100,217 @@ use parent qw(PVE::SectionConfig);
> >  
> >  =cut
> >  
> > +=head3 $plugin->type()
> > +
> > +B<REQUIRED:> Must be implemented in every storage plugin.
> 
> I am not sure whether we should do
> 
> s/implemented in/implemented by
> 
> for this whole thing and SectionConfig, but I won't note it for every
> instance ;)
> 
> > +
> > +This should return a string with which the plugin can be uniquely 
> > identified.
> 
> a string which uniquely identifies the plugin.
> 
> > +Any string is acceptable, as long as it's descriptive and you're sure it 
> > won't
> > +conflict with another plugin. In the cases of built-in plugins, you will 
> > often
> > +find the filesystem name or something similar being used.
> 
> I'd replace that with something like:
> 
> Commonly, the name of the storage technology or filesystem supported by
> the plugin is used. If it is necessary to differentiate multiple
> plugins for the same storage technology/filesystem, add a suffix
> denoting the supported variant or feature set.

While in perl we seem to parse this via `/\S+/` (greedily), I'd argue we
should request to limit this to `[a-z\-]`...

Technically, `PVE::SectionConfig->parse_section_header()` would parse
this line:

    My:Plugin"with-a-BAD:name

as a header of type `My:Plugin"with-a-BAD` and section id `name`...

We may want to fix that...

> 
> > +
> > +See C<L<PVE::SectionConfig/type>> for more information.
> > +
> > +=cut
> > +
> >  sub type {
> >      croak "implement me in sub-class\n";
> >  }
> >  
> > +=head3 $plugin->properties()
> > +
> > +B<OPTIONAL:> May be implemented in a storage plugin.
> > +
> > +This method should be implemented if there are additional properties to be 
> > used
> > +by your plugin. Since properties are global and may be reused across 
> > plugins,
> 
> s/used/registered
> 
> IMHO that conveys better what the difference between properties and
> options are..
> 
> "global and may be reused across plugins" is kinda superfluous, maybe
> 
> "Since properties share a global namespace across all plugins"?
> 
> > +the names of properties must not collide with one another.
> 
> each property can only be registered once by a single plugin, but used
> as an option by many.
> 
> > +
> > +When implementing a third-party plugin, it is recommended to prefix 
> > properties
> > +with some kind of identifier, like so:
> 
> Third-party plugins should therefore prefix any properties they register
> with their plugin type, for example:
> 
> > +
> > +    sub properties {
> > +   return {
> > +       'example-storage-address' => {
> > +           description => 'Host address of the ExampleStorage to connect 
> > to.',
> > +           type => 'string',
> > +       },
> > +       'example-storage-pool' => {
> > +           description => 'Name of the ExampleStorage pool to use.',
> > +           type => 'string',
> > +       },
> > +       # [...]
> > +   };
> > +    }
> > +
> > +However, it is encouraged to reuse properties of inbuilt plugins whenever
> > +possible. There are a few provided properties that are regarded as 
> > I<sensitive>
> > +and will be treated differently in order to not expose them or write them 
> > as
> > +plain text into configuration files. One such property fit for external 
> > use is
> > +C<password>, which you can use to provide a password, API secret, or 
> > similar.
> 
> If possible, generic properties registered by built-in plugins should be
> reused.
> 
> (inbuilt is a very weird, very British word ;))
> 
> the rest of that is kinda misplaced here and should be its own section
> somewhere (it affects options just as much as properties). we should
> probably allow registering sensitive properties for plugins?
> 
> > +
> > +See C<L<PVE::SectionConfig/properties>> for more information.
> > +
> > +=cut
> > +
> >  sub properties {
> >      my ($class) = @_;
> >      return $class->SUPER::properties();
> >  }
> >  
> > +=head3 $plugin->options()
> > +
> > +B<REQUIRED:> Must be implemented in every storage plugin.
> > +
> > +This method returns a hash of the properties used by the plugin. Because
> > +properties are shared among plugins, it is recommended to reuse any 
> > existing
> > +ones of inbuilt plugins and only define custom properties via
> > +C<L<< properties()|/"$plugin->properties()" >>> if necessary.
> 
> Everything but the first sentence can be a reference to the properties
> part above..
> 
> > +
> > +The properties a plugin uses are then declared as follows:
> 
> or just, "For example:" ? ;)
> 
> > +
> > +    sub options {
> > +   return {
> > +       nodes => { optional => 1 },
> > +       content => { optional => 1 },
> > +       disable => { optional => 1 },

^ Currently these 3 need to actually be listed by the plugin, but we
probably want to enforce that they exist?

If the storage doesn't list the `disable` flag you, well, cannot disable
it...

> > +       'example-storage-pool' => { fixed => 1 },
> > +       'example-storage-address' => { fixed => 1 },
> > +       password => { optional => 1 },
> > +   };
> > +    }
> > +
> > +C<optional> properties are not required to be set. It is recommended to set
> 
> s/set/make or "declare"?
> 
> > +most properties optional by default, unless it I<really> is required to 
> > always
> > +exist.
> 
> s/it/they
> s/exist/be set
> 
> > +
> > +C<fixed> properties can only be set when creating a new storage via the 
> > plugin
> > +and cannot be changed afterwards.
> 
> s/creating a new storage/adding a new storage config entry/ ? a bit more
> precise to differentiate it from actually "creating a storage" as in
> formatting a disk/..
> 
> I am not sure what "via the plugin" is supposed to mean there, I think
> it can just be dropped.
> 
> > +
> > +See C<L<PVE::SectionConfig/options>> for more information.
> > +
> > +=cut
> > +
> >  sub options {
> >      my ($class) = @_;
> >      return $class->SUPER::options();
> >  }
> >  
> > +=head3 $plugin->plugindata()
> > +
> > +B<REQUIRED:> Must be implemented in every storage plugin.
> > +
> > +This method returns a hash that specifies additional capabilities of the 
> > storage
> > +plugin, such as what kinds of data may be stored on it or what VM disk 
> > formats
> 
> s/data/content or s/data/volumes
> 
> > +the storage supports. Additionally, defaults may also be set. This is done
> > +through the C<content> and C<format> keys.
> 
> but how about re-organizing it a bit?
> 
> This method returns a hash declaring which content types and image
> formats this plugin (or the underlying storage) supports.
> 
> and then describe each key?
> 
> > +
> > +The C<content> key is used to declare B<supported content types> and their
> > +defaults, while the C<format> key declares B<supported disk formats> and 
> > the
> > +default disk format of the storage:
> 
> "and their defaults" sounds weird.
> 
> The 'content' key stores a list containing two hashes. The first list
> element is a hash declaring which content types are supported by the
> plugin. The second list element is a hash declaring which content types
> are used by default if no explicit 'content' option is set on a plugin
> instance.
> 
> and maybe link somewhere else for a description of the content type
> values, so that there is a single place that various parts that need it
> can link to?
> 
> and then
> 
> The 'format' key stores a list containing two hashes. The first list
> element is a hash declaring which image formats are supported by the
> storage plugin. The second list element is the default image format that
> should be used for images on the storage if non is explicitly provided.
> 
> > +
> > +    sub plugindata {
> > +   return {
> > +       content => [
> > +           # possible content types
> > +           {
> > +               images => 1,   # disk images
> > +               rootdir => 1,  # container root directories

Side note...
`rootdir` is one we may need to rethink in the future.

The actual volume type for a container is still actually an "image".
(pve-container uses `vdisk_alloc` to get a `raw` file and then runs
`mkfs` on it or uses a *subvol* type (zfs, btrfs, or dirplugin with
size=0)

> > +               vztmpl => 1,   # container templates
> > +               iso => 1,      # iso images
> > +               backup => 1,   # vzdump backup files
> > +               snippets => 1, # snippets
> > +               import => 1,   # imports; see explanation below
> > +               none => 1,     # no content; see explanation below
> > +           },
> > +           # defaults if 'content' isn't explicitly set
> > +           {
> > +               images => 1,   # store disk images by default
> > +               rootdir => 1   # store containers by default
> > +               # possible to add more or have no defaults
> > +           }
> > +       ],
> > +       format => [
> > +           # possible disk formats
> > +           {
> > +               raw => 1,   # raw disk image
> > +               qcow2 => 1, # QEMU image format
> > +               vmdk => 1,  # VMware image format
> > +               subvol => 1 # subvolumes; see explanation below
> > +           },
> > +           "qcow2" # default if 'format' isn't explicitly set
> > +       ]
> > +       # [...]
> > +   };
> > +    }
> > +
> > +While the example above depicts a rather capable storage, the following
> > +shows a simpler storage that can only be used for VM disks:
> 
> raw-formatted VM disks
> 
> > +
> > +    sub plugindata {
> > +   return {
> > +       content => [
> > +           { images => 1 },
> > +       ],
> > +       format => [
> > +           { raw => 1 },
> > +           "raw",
> > +       ]
> > +   };
> > +    }
> > +
> > +Which content types and formats are supported depends on the underlying 
> > storage
> > +implementation.
> > +
> > +B<Regarding C<import>:> The C<import> content type is used internally to
> > +interface with virtual guests of foreign sources or formats. The 
> > corresponding
> > +functionality has not yet been published to the public parts of the storage
> > +API. Third-party plugins therefore should not declare this content type.
> 
> this should live somewhere where content types are described
> 
> > +
> > +B<Regarding C<none>:> The C<none> content type denotes the I<absence> of 
> > other
> > +types of content, i.e. this content type may only be set on a storage if no
> > +other content type is set. This is used internally for storages that 
> > support
> > +adding another storage "on top" of them; at the moment, this makes it 
> > possible
> > +to set up an LVM (thin) pool on top of an iSCSI LUN. The corresponding
> > +functionality has not yet been published to the public parts of the storage
> > +API. Third-party plugins therefore should not declare this content type.
> 
> same
> 
> > +
> > +B<Regarding C<subvol>:> The C<subvol> format is used internally to allow 
> > the
> > +root directories of containers to use ZFS subvolumes (also known as
> > +I<ZFS datasets>, not to be confused with I<ZVOLs>). Third-party plugins 
> > should
> > +not declare this format type.
> 
> this is incomplete, but same.

(btrfs subvolumes and size=0 dirs)

> 
> > +
> > +There is one more key, C<select_existing>, which is used internally for
> > +ISCSI-related GUI functionality. Third-party plugins should not declare 
> > this
> > +key.
> 
> this is okay here :)
> 
> > +
> > +=cut
> > +
> >  sub plugindata {
> >      my ($class) = @_;
> >      return $class->SUPER::plugindata();
> >  }
> >  
> > +=head3 $plugin->private()
> > +
> > +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.>
> > +
> > +Returns a hash containing the tracked plugin metadata, most notably the
> > +C<propertyList>, which contains all known properties of all plugins.
> > +
> > +C<L<PVE::Storage::Plugin>> uses this to predefine a lot of useful 
> > properties
> > +that are relevant for all plugins. Core functionality such as defining
> > +whether a storage is shared, which nodes may use it, whether a storage
> > +is enabled or not, etc. are implemented via these properties.
> 
> this is not true and sounds like it is very interesting to look at/mess
> with ;) in reality, it is part of SectionConfig machinery, so just say
> that and that it must not be overridden and be done with it?
> 
> if PluginBase becomes the real base plugin then it and some other
> helpers would need to move here anyway..
> 
> > +
> > +See C<L<PVE::SectionConfig/private>> for more information.
> > +
> > +=cut
> > +
> >  sub private {
> > -    croak "implement me in sub-class\n";
> > +    confess "private() is provided by PVE::Storage::Plugin and must not be 
> > overridden";
> >  }
> >  
> >  =head2 GENERAL
> > -- 
> > 2.39.5



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

Reply via email to