From: Thomas Skinner <thomas@atskinner.net>
To: Daniel Kral <d.kral@proxmox.com>
Cc: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Subject: Re: [pve-devel] [PATCH ha-manager 2/2] add api getter/setter for node maintenance mode
Date: Tue, 26 Aug 2025 11:00:55 -0500 [thread overview]
Message-ID: <CALn9RMdGbTp1H1cc_3LHhke-ui30J9Vqf9ro3DXYsKt5L04hnw@mail.gmail.com> (raw)
In-Reply-To: <DCC9BWHL0DVV.36JQME5F53EZV@proxmox.com>
On Tue, Aug 26, 2025 at 4:38 AM Daniel Kral <d.kral@proxmox.com> wrote:
>
> Thanks for working on this!
>
> I have added some comments inline. Please run `make tidy` on this patch
> (and other patches for that sake) to automatically reformat the code.
>
> See [0] for more information.
>
> [0] https://git.proxmox.com/?p=proxmox-perltidy.git;a=summary
I thought I did this on all of them, but I will do it again just in case.
>
> On Mon Aug 25, 2025 at 6:11 AM CEST, Thomas Skinner wrote:
> > Signed-off-by: Thomas Skinner <thomas@atskinner.net>
> > ---
> > debian/pve-ha-manager.install | 1 +
> > src/PVE/API2/HA/Makefile | 2 +-
> > src/PVE/API2/HA/Nodes.pm | 176 ++++++++++++++++++++++++++++++++++
> > 3 files changed, 178 insertions(+), 1 deletion(-)
> > create mode 100644 src/PVE/API2/HA/Nodes.pm
> >
> > diff --git a/debian/pve-ha-manager.install b/debian/pve-ha-manager.install
> > index 2e6b7d5..ec85037 100644
> > --- a/debian/pve-ha-manager.install
> > +++ b/debian/pve-ha-manager.install
> > @@ -15,6 +15,7 @@
> > /usr/share/man/man8/pve-ha-crm.8.gz
> > /usr/share/man/man8/pve-ha-lrm.8.gz
> > /usr/share/perl5/PVE/API2/HA/Groups.pm
> > +/usr/share/perl5/PVE/API2/HA/Nodes.pm
> > /usr/share/perl5/PVE/API2/HA/Resources.pm
> > /usr/share/perl5/PVE/API2/HA/Rules.pm
> > /usr/share/perl5/PVE/API2/HA/Status.pm
> > diff --git a/src/PVE/API2/HA/Makefile b/src/PVE/API2/HA/Makefile
> > index 86c1013..45d714b 100644
> > --- a/src/PVE/API2/HA/Makefile
> > +++ b/src/PVE/API2/HA/Makefile
> > @@ -1,4 +1,4 @@
> > -SOURCES=Resources.pm Groups.pm Rules.pm Status.pm
> > +SOURCES=Resources.pm Groups.pm Nodes.pm Rules.pm Status.pm
> >
> > .PHONY: install
> > install:
> > diff --git a/src/PVE/API2/HA/Nodes.pm b/src/PVE/API2/HA/Nodes.pm
> > new file mode 100644
> > index 0000000..2afc3a3
> > --- /dev/null
> > +++ b/src/PVE/API2/HA/Nodes.pm
> > @@ -0,0 +1,176 @@
> > +package PVE::API2::HA::Nodes::Nodeinfo;
>
> Why use two packages in this file here? AFAICT this seems like it is
> copy-pasted from PVE::API2::Nodes and could very well be a single
> package as that's much cleaner than having two packages in one file.
Yes, I did use that file as the source for this package. It could very
well be one package, especially if it was placed elsewhere in the API.
I was trying to mirror the behavior of the existing nodes dir in the
API.
> Also it seems like we do that seldomly:
>
> # rg -o -c -g "*.pm" "^package" . | awk -F: '{if ($2 > 1){print $1}}'
> ./pve-guest-common/src/PVE/ReplicationConfig.pm
> ./pve-buildpkg/PVE/BuildPKG/Tools.pm
> ./pve-storage/src/PVE/Storage/ESXiPlugin.pm
> ./pve-manager/PVE/API2/Nodes.pm
> ./pve-manager/PVE/CLI/pve_network_interface_pinning.pm
> ./pve-firewall/src/PVE/API2/Firewall/VM.pm
> ./pve-firewall/src/PVE/API2/Firewall/Rules.pm
> ./pve-firewall/src/PVE/API2/Firewall/IPSet.pm
> ./pve-firewall/src/PVE/API2/Firewall/Aliases.pm
>
> > +
> > +use strict;
> > +use warnings;
> > +
> > +use PVE::SafeSyslog;
> > +use PVE::Tools qw(extract_param);
> > +use PVE::Cluster qw(cfs_read_file cfs_write_file);
> > +use PVE::HA::Config;
> > +use HTTP::Status qw(:constants);
> > +use Storable qw(dclone);
> > +use PVE::JSONSchema qw(get_standard_option);
> > +use PVE::RPCEnvironment;
>
> are extract_param, cfs_{read,write}_file, dclone used?
>
> nit: either way, use statements should follow the dependencies style
> guide [1], i.e. something like the following:
>
>
> use HTTP::Status qw(:constants);
> use Storable qw(dclone);
>
> use PVE::Cluster qw(cfs_read_file cfs_write_file);
> use PVE::JSONSchema qw(get_standard_option);
> use PVE::RPCEnvironment;
> use PVE::SafeSyslog;
> use PVE::Tools qw(extract_param);
>
> use PVE::HA::Config;
>
>
> [1] https://pve.proxmox.com/wiki/Perl_Style_Guide#Dependencies
I will clean these up in a v2 patch.
> > +
> > +use PVE::RESTHandler;
> > +
> > +use base qw(PVE::RESTHandler);
> > +
> > +__PACKAGE__->register_method({
> > + name => 'index',
> > + path => '',
> > + method => 'GET',
> > + permissions => { user => 'all' },
> > + description => "Node index.",
> > + parameters => {
> > + additionalProperties => 0,
> > + properties => {
> > + node => get_standard_option('pve-node'),
> > + },
> > + },
> > + returns => {
> > + type => 'array',
> > + items => {
> > + type => "object",
> > + properties => {},
> > + },
> > + links => [{ rel => 'child', href => "{name}" }],
> > + },
> > + code => sub {
> > + my ($param) = @_;
> > +
> > + my $result = [
> > + { name => 'maintenance' },
> > + ];
> > +
> > + return $result;
> > + },
> > +});
>
> Hm, we do that differently around the API endpoints, but at least for
> the HA-related API endpoints we don't list the subdirectories, but
> output the information in the entity itself, e.g. for HA resources:
>
>
> + ha
> |
> +-+ resources - GET List HA resources.
> + - POST Create a new HA resource.
> |
> +-+ {sid} - GET Read resource configuration.
> + - PUT Update resource configuration.
> + - DELETE Delete resource configuration.
> |
> +-+ migrate - POST Request resource migration to another node.
> + relocate - POST Request resource relocation to another node.
>
>
> Of course, it doesn't make sense for a POST/DELETE API endpoint for
> nodes, but it could still be useful to make the
> GET /cluster/ha/nodes/{node} route informational, e.g., output the
> current maintenance state and possibly other information about nodes
> there.
>
> So something like this:
>
> + ha
> |
> +-+ nodes - GET List HA nodes.
> |
> +-+ {node} - GET Read HA node information.
>
> ...
>
> > +
> > +__PACKAGE__->register_method({
> > + name => 'maintenance_state',
> > + path => 'maintenance',
> > + method => 'GET',
> > + permissions => { user => 'all' },
> > + description => "Get the node maintenance state.",
> > + permissions => {
> > + check => ['perm', '/', [ 'Sys.Audit' ]],
> > + },
> > + parameters => {
> > + additionalProperties => 0,
> > + properties => {
> > + node => get_standard_option('pve-node'),
> > + },
> > + },
> > + returns => {
> > + type => "object",
> > + properties => {},
> > + },
> > + code => sub {
> > + my ($param) = @_;
> > +
> > + my $status = PVE::HA::Config::read_manager_status();
> > +
> > + my $data = {
> > + request => $status->{node_request}->{$param->{node}},
> > + status => $status->{node_status}->{$param->{node}},
> > + };
> > +
> > + return $data;
> > + },
> > +});
>
> I think the GET /cluster/ha/nodes/{node}/maintenance API endpoint should
> be integrated into the GET /cluster/ha/nodes/{node} instead as putting a
> node in/out of maintenance mode is only an action, but the state could
> be more than just the node being in maintenance mode.
>
> On second thought, is this API endpoint even used at all in the patch
> series?
The API isn't specifically used here, but we have found it useful in
our local branch to have it so that we can query the transition of the
state of a host directly. I figured someone else might find it useful
as well, so I left it in here. If it should be removed, I can remove
it in the v2 series.
> > +
> > +__PACKAGE__->register_method ({
> > + name => 'maintenance_set',
> > + protected => 1,
> > + path => 'maintenance',
> > + method => 'POST',
> > + description => "Change the node-maintenance request state.",
> > + permissions => {
> > + check => ['perm', '/', [ 'Sys.Console' ]],
> > + },
> > + parameters => {
> > + additionalProperties => 0,
> > + properties => {
> > + node => get_standard_option('pve-node'),
> > + disable => {
> > + description => "Requests disabling or enabling maintenance-mode.",
> > + type => 'boolean',
>
> Hm, I see that that part is from the CLI handler, but there `disable` is
> only used for two different subcommands `enable` and `disable`, so
> wouldn't it be neater to have two API endpoints for that instead? Like
> for example the API endpoints for VM/container start/stop/..., e.g.
>
> - POST enable_maintenance
> - POST disable_maintenance
For this, I was mirroring the existing underlying API calls as listed
below, but I agree that enable/disable would be more clear. I'll put
that in the v2 series.
> Either way, the CLI command should then use this API handler instead of
> the CLI handler to have the logic at one place only, i.e. in
> ha_manager.pm
>
> 'node-maintenance' => {
> enable => [__PACKAGE__, 'node-maintenance-set', ['node'], { disable => 0 }],
> disable => [__PACKAGE__, 'node-maintenance-set', ['node'], { disable => 1 }],
> },
>
> should then be changed to:
>
> 'node-maintenance' => {
> enable => ['PVE::API2::HA::Nodes', 'enable_maintenance', ['node']],
> disable => ['PVE::API2::HA::Nodes', 'disable_maintenance', ['node']],
> },
Will update this in the v2 series.
>
> > + },
> > + },
> > + },
> > + returns => { type => 'null' },
> > + code => sub {
> > + my ($param) = @_;
> > +
> > + PVE::Cluster::check_node_exists($param->{node});
> > +
> > + my $cmd = $param->{disable} ? 'disable-node-maintenance' : 'enable-node-maintenance';
> > + PVE::HA::Config::queue_crm_commands("$cmd $param->{node}");
> > +
> > + return undef;
> > + }
> > +});
> > +
> > +package PVE::API2::HA::Nodes;
> > +
> > +use strict;
> > +use warnings;
> > +
> > +use PVE::RESTHandler;
> > +use PVE::JSONSchema qw(get_standard_option);
> > +use PVE::RPCEnvironment;
> > +use PVE::Cluster;
> > +
> > +use base qw(PVE::RESTHandler);
> > +
> > +__PACKAGE__->register_method({
> > + subclass => "PVE::API2::HA::Nodes::Nodeinfo",
> > + path => '{node}',
> > +});
> > +
> > +__PACKAGE__->register_method({
> > + name => 'index',
> > + path => '',
> > + method => 'GET',
> > + permissions => { user => 'all' },
> > + description => "Cluster HA node index.",
>
> nit: for consistency's sake with the HA API endpoints, this could e.g.
> be "List HA nodes.".. even though they don't really belong to the HA..
>
> > + parameters => {
> > + additionalProperties => 0,
> > + properties => {},
> > + },
> > + returns => {
> > + type => 'array',
> > + items => {
> > + type => "object",
> > + properties => {
> > + node => get_standard_option('pve-node'),
> > + },
> > + },
> > + links => [{ rel => 'child', href => "{node}" }],
> > + },
> > + code => sub {
> > + my ($param) = @_;
> > +
> > + my $rpcenv = PVE::RPCEnvironment::get();
> > + my $authuser = $rpcenv->get_user();
> > +
> > + my $clinfo = PVE::Cluster::get_clinfo();
> > + my $res = [];
> > +
> > + my $nodelist = PVE::Cluster::get_nodelist();
> > + my $members = PVE::Cluster::get_members();
> > + my $rrd = PVE::Cluster::rrd_dump();
> > +
> > + foreach my $node (@$nodelist) {
> > + my $can_audit = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1);
>
> $can_audit is never used here and the check has $noerr set, so it
> doesn't do anything..
>
> The more I think about it, I'm somewhat inclined to register the whole
> subclass under /nodes/{node}/ha/... instead of /cluster/ha/nodes/... to
> not duplicate the information of a nodelist, which is provided by
> pve-cluster and there would be something wrong with the HA Manager
> anyway if those are out-of-sync and all the HA-related information can
> then live in a node-specific "ha" subdirectory.
>
> Then this API handler wouldn't be needed anymore at all.
If this is how Proxmox would like it, that makes it much easier IMO. I
wasn't sure which one to include it under, so I put it under the
cluster path since a cluster must be created to even have the HA
featureset. I'm good with either, I just need to know so that I know
what to update for a v2 series.
> > + my $entry = { node => $node };
> > +
> > + push @$res, $entry;
> > + }
> > +
> > + return $res;
> > + },
> > +});
> > +
> > +1;
>
>
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-08-26 16:02 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-25 4:11 [pve-devel] [PATCH SERIES docs/ha-manager/manager] close #6144: add ui button + api " Thomas Skinner
2025-08-25 4:11 ` [pve-devel] [PATCH ha-manager 1/2] add additional api field for lrm_mode in status check Thomas Skinner
2025-08-25 4:11 ` [pve-devel] [PATCH manager 1/2] add api path map for node HA endpoints Thomas Skinner
2025-08-26 9:38 ` Daniel Kral
2025-08-26 10:26 ` Thomas Lamprecht
2025-08-26 11:34 ` Daniel Kral
2025-08-25 4:11 ` [pve-devel] [PATCH docs 1/1] add docs for maintenance mode buttons in UI Thomas Skinner
2025-08-26 9:44 ` Daniel Kral
2025-08-26 16:05 ` Thomas Skinner
2025-08-27 7:50 ` Daniel Kral
2025-08-25 4:11 ` [pve-devel] [PATCH ha-manager 2/2] add api getter/setter for node maintenance mode Thomas Skinner
2025-08-26 9:38 ` Daniel Kral
2025-08-26 16:00 ` Thomas Skinner [this message]
2025-08-27 7:31 ` Thomas Lamprecht
2025-08-27 8:25 ` Daniel Kral
2025-08-27 9:20 ` Thomas Lamprecht
2025-08-29 20:13 ` Thomas Skinner
2025-09-01 8:52 ` Daniel Kral
2025-08-29 20:17 ` Thomas Skinner
2025-08-25 4:11 ` [pve-devel] [PATCH manager 2/2] add UI for node maintenance enable/disable Thomas Skinner
2025-08-26 10:00 ` Daniel Kral
2025-08-26 17:34 ` Thomas Skinner
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=CALn9RMdGbTp1H1cc_3LHhke-ui30J9Vqf9ro3DXYsKt5L04hnw@mail.gmail.com \
--to=thomas@atskinner.net \
--cc=d.kral@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