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

On Mon Aug 25, 2025 at 6:11 AM CEST, Thomas Skinner wrote:
> Signed-off-by: Thomas Skinner <tho...@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.

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

> +
> +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?

> +
> +__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

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']],
},


> +     },
> + },
> +    },
> +    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.

> +            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

Reply via email to