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
next prev 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