[RFC pve-manager 1/1] add ha node maintenance mode to the UI and API

Tiago Sousa joao.sousa at eurotux.com
Mon Jun 2 18:10:52 CEST 2025


Signed-off-by: Tiago Sousa <joao.sousa at eurotux.com>
---
 PVE/API2/Nodes.pm            | 45 ++++++++++++++++++++++++++++++++++++
 www/manager6/Utils.js        |  1 +
 www/manager6/node/CmdMenu.js | 36 +++++++++++++++++++++++++++--
 3 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm
index 791d2dec..f2365e59 100644
--- a/PVE/API2/Nodes.pm
+++ b/PVE/API2/Nodes.pm
@@ -21,6 +21,7 @@ use PVE::DataCenterConfig;
 use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
 use PVE::Firewall;
 use PVE::HA::Config;
+use PVE::HA::Usage;
 use PVE::HA::Env::PVE2;
 use PVE::INotify;
 use PVE::JSONSchema qw(get_standard_option);
@@ -300,6 +301,7 @@ __PACKAGE__->register_method ({
 	    { name => 'vncshell' },
 	    { name => 'vzdump' },
 	    { name => 'wakeonlan' },
+	    { name => 'node-maintenance-set' },
 	];
 
 	push @$result, { name => 'sdn' } if $have_sdn;
@@ -802,6 +804,49 @@ __PACKAGE__->register_method({
 	return $wol_config->{mac};
     }});
 
+__PACKAGE__->register_method({
+    name => 'node-maintenance-set',
+    path => 'node-maintenance-set',
+    method => 'POST',
+    permissions => {
+	check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
+    },
+    protected => 1,
+    description => "Set node maintenance mode (enable or disable)",
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+        node => get_standard_option('pve-node'),
+        disable => {
+        description => "Requests disabling or enabling maintenance-mode.",
+        type => 'boolean',
+        },
+	},
+    },
+    returns => {
+	type => 'string',
+	format => 'string',
+	description => '',
+    },
+    code => sub {
+	my ($param) = @_;
+	my $node = $param->{node};
+	my $rpcenv = PVE::RPCEnvironment::get();
+	my $authuser = $rpcenv->get_user();
+
+	PVE::Cluster::check_node_exists($node);
+	my $state = $param->{disable} ? 'disable' : 'enable';
+    my $hacmd = sub {
+    my $upid = shift;
+    print "Requesting to $state HA node maintenance for node $node\n";
+    my $cmd = ['ha-manager', 'crm-command', 'node-maintenance', $state, $node];
+    PVE::Tools::run_command($cmd);
+    return;
+    };
+
+	return $rpcenv->fork_worker('hamaintenance', undef, $authuser, $hacmd);
+    }});
+
 __PACKAGE__->register_method({
     name => 'rrd',
     path => 'rrd',
diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js
index 1f6778cd..48dac090 100644
--- a/www/manager6/Utils.js
+++ b/www/manager6/Utils.js
@@ -2034,6 +2034,7 @@ Ext.define('PVE.Utils', {
 	    hamigrate: ['HA', gettext('Migrate')],
 	    hashutdown: ['HA', gettext('Shutdown')],
 	    hastart: ['HA', gettext('Start')],
+	    hamaintenance: ['HA', gettext('Node Maintenance')],
 	    hastop: ['HA', gettext('Stop')],
 	    imgcopy: ['', gettext('Copy data')],
 	    imgdel: ['', gettext('Erase data')],
diff --git a/www/manager6/node/CmdMenu.js b/www/manager6/node/CmdMenu.js
index 7bdfebc5..0a8dc008 100644
--- a/www/manager6/node/CmdMenu.js
+++ b/www/manager6/node/CmdMenu.js
@@ -94,6 +94,34 @@ Ext.define('PVE.node.CmdMenu', {
 		PVE.Utils.openDefaultConsoleWindow(true, 'shell', undefined, nodename, undefined);
 	    },
 	},
+	{
+	    text: gettext('Enter Maintenance Mode'),
+	    itemId: 'entermaintenance',
+	    iconCls: 'fa fa-fw fa-building',
+	    handler: function() {
+		let nodename = this.up('menu').nodename;
+		Proxmox.Utils.API2Request({
+		    url: `/nodes/${nodename}/node-maintenance-set`,
+            params: { disable: 0 },
+		    method: 'POST',
+		    failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+		});
+	    },
+	},
+	{
+	    text: gettext('Exit Maintenance Mode'),
+	    itemId: 'exitmaintenance',
+	    iconCls: 'fa fa-fw fa-building',
+	    handler: function() {
+		let nodename = this.up('menu').nodename;
+		Proxmox.Utils.API2Request({
+		    url: `/nodes/${nodename}/node-maintenance-set`,
+            params: { disable: 1 },
+		    method: 'POST',
+		    failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+		});
+	    },
+	},
 	{ xtype: 'menuseparator' },
 	{
 	    text: gettext('Wake-on-LAN'),
@@ -150,11 +178,15 @@ Ext.define('PVE.node.CmdMenu', {
 	}
 	if (!caps.nodes['Sys.Console']) {
 	    me.getComponent('shell').setDisabled(true);
-	}
+    }
+    if (me.pveSelNode.data.hastate === 'maintenance') {
+	    me.getComponent('entermaintenance').setVisible(false);
+    } else {
+	    me.getComponent('exitmaintenance').setVisible(false);
+    }
 	if (me.pveSelNode.data.running) {
 	    me.getComponent('wakeonlan').setDisabled(true);
 	}
-
 	if (PVE.Utils.isStandaloneNode()) {
 	    me.getComponent('bulkmigrate').setVisible(false);
 	}
-- 
2.39.5




More information about the pve-devel mailing list