public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Jakob Klocker <j.klocker@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Jakob Klocker <j.klocker@proxmox.com>
Subject: [PATCH pve-manager 6/9] cluster: backend: include storages with unloadable plugins
Date: Fri, 31 Jul 2026 17:36:14 +0200	[thread overview]
Message-ID: <20260731153617.358265-7-j.klocker@proxmox.com> (raw)
In-Reply-To: <20260731153617.358265-1-j.klocker@proxmox.com>

Storages whose plugins failed to load (broken third party plugins,
unknown plugins) were previously dropped from the cluster resource list,
leaving them invisible in the tree.

Switch to `config(1)` so storages with unregistered types are still
returned and emit data stored in the cached failed-plugin metadata.

Add `pluginstate` and `plugin-url` to storage resource entries (for
both, loaded and failed plugins) so the GUI can show plugin state and
link to the plugin's documentation.

Signed-off-by: Jakob Klocker <j.klocker@proxmox.com>
---
 PVE/API2/Cluster.pm | 51 +++++++++++++++++++++++++++++++++++++++++++--
 PVE/API2Tools.pm    |  6 ++++++
 2 files changed, 55 insertions(+), 2 deletions(-)

diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm
index 4e5efbfd..432ee64b 100644
--- a/PVE/API2/Cluster.pm
+++ b/PVE/API2/Cluster.pm
@@ -435,6 +435,18 @@ __PACKAGE__->register_method({
                     format => 'pve-storage-content-list',
                     optional => 1,
                 },
+                'pluginstate' => {
+                    description => "State of the storage plugin (for type 'storage').",
+                    type => 'string',
+                    enum => ['up-to-date', 'outdated', 'load-error'],
+                    optional => 1,
+                },
+                'plugin-url' => {
+                    description =>
+                        "URL to the plugin's homepage or documentation (for type 'storage').",
+                    type => 'string',
+                    optional => 1,
+                },
                 shared => {
                     description => "Determines whether the storage is shared",
                     type => 'boolean',
@@ -640,14 +652,49 @@ __PACKAGE__->register_method({
 
         if (!$param->{type} || $param->{type} eq 'storage') {
 
-            my $cfg = PVE::Storage::config();
+            my $cfg = PVE::Storage::config(1);
             my @sids = PVE::Storage::storage_ids($cfg);
 
+            my $failed = PVE::Storage::failed_third_party_plugins();
+            my $failed_by_type = {};
+            for my $modname (keys %$failed) {
+                my $f = $failed->{$modname};
+                $failed_by_type->{ $f->{type} } = $f if defined $f->{type};
+            }
+
             foreach my $storeid (@sids) {
                 next if !$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Audit'], 1);
 
                 my $scfg = PVE::Storage::storage_config($cfg, $storeid);
-                # we create a entry for each node
+
+                # if no plugin is registered for this type (failed to load or unknown),
+                # emit a minimal entry from cached metadata and skip the stats path
+                my $plugin = eval { PVE::Storage::Plugin->lookup($scfg->{type}) };
+                if (!$plugin) {
+                    my $failed_plugin = $failed_by_type->{ $scfg->{type} };
+                    my $content = $scfg->{content} // '';
+                    foreach my $node (@$nodelist) {
+                        next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
+                        push @$res,
+                            {
+                                id => "storage/$node/$storeid",
+                                storage => $storeid,
+                                node => $node,
+                                type => 'storage',
+                                plugintype => $scfg->{type},
+                                status => 'unknown',
+                                shared => $scfg->{shared} || 0,
+                                content => $content,
+                                pluginstate => 'load-error',
+                                'plugin-url' => $failed_plugin
+                                ? $failed_plugin->{'plugin-url'}
+                                : undef,
+                            };
+                    }
+                    next;
+                }
+
+                # we create an entry for each node
                 foreach my $node (@$nodelist) {
                     next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
 
diff --git a/PVE/API2Tools.pm b/PVE/API2Tools.pm
index bca21aa5..247b9c7a 100644
--- a/PVE/API2Tools.pm
+++ b/PVE/API2Tools.pm
@@ -15,6 +15,7 @@ use PVE::Exception qw(raise_param_exc);
 use PVE::INotify;
 use PVE::RPCEnvironment;
 use PVE::SafeSyslog;
+use PVE::Storage;
 use PVE::Storage::Plugin;
 use PVE::Tools;
 
@@ -122,6 +123,9 @@ sub extract_storage_stats {
     my ($storeid, $scfg, $node, $rrd) = @_;
 
     my $content = PVE::Storage::Plugin::content_hash_to_string($scfg->{content});
+    my $plugin_url =
+        eval { PVE::Storage::Plugin->lookup($scfg->{type})->plugindata()->{'plugin-url'} };
+    my $pluginstate = PVE::Storage::get_plugin_state($scfg->{type});
 
     my $entry = {
         id => "storage/$node/$storeid",
@@ -132,6 +136,8 @@ sub extract_storage_stats {
         status => 'unknown',
         shared => $scfg->{shared} || 0,
         content => $content,
+        pluginstate => $pluginstate,
+        'plugin-url' => $plugin_url,
     };
 
     my $key = get_rrd_key($rrd, "storage", "${node}/${storeid}");
-- 
2.47.3




  parent reply	other threads:[~2026-07-31 15:37 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 15:36 [PATCH manager/storage 0/9] storage: plugins: display failed and outdated storage plugins in the GUI Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-storage 1/9] storage: drop unused variable in storage_info Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-storage 2/9] storage: add `plugin-url` to built-in plugins Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-storage 3/9] storage: add `unknown` flag to parse_config Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-storage 4/9] storage: plugins: cache and classify custom storage plugins Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-storage 5/9] storage: expose plugin metadata in storage status Jakob Klocker
2026-07-31 15:36 ` Jakob Klocker [this message]
2026-07-31 15:36 ` [PATCH pve-manager 7/9] www: storage: show plugin state/external/url in StatusView Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-manager 8/9] www: storage: distinguish failed/outdated storage plugins in resource tree Jakob Klocker
2026-07-31 15:36 ` [PATCH pve-manager 9/9] www: storage: display custom summary for failed plugins Jakob Klocker

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260731153617.358265-7-j.klocker@proxmox.com \
    --to=j.klocker@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal