[pve-devel] [PATCH v2 pve-manager 7/7] add node rebalance api

Alexandre Derumier aderumier at odiso.com
Wed Oct 9 16:01:37 CEST 2019


This allow to rebalance a node, with defining max mem/cpu threshold.

we migrate vms until we are under threshold.

Signed-off-by: Alexandre Derumier <aderumier at odiso.com>
---
 PVE/API2/Nodes.pm | 130 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 127 insertions(+), 3 deletions(-)

diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm
index 25199249..35e138c7 100644
--- a/PVE/API2/Nodes.pm
+++ b/PVE/API2/Nodes.pm
@@ -2010,6 +2010,130 @@ __PACKAGE__->register_method ({
 
     }});
 
+__PACKAGE__->register_method ({
+    name => 'rebalance',
+    path => 'rebalance',
+    method => 'POST',
+    proxyto => 'node',
+    protected => 1,
+    permissions => {
+	check => ['perm', '/', [ 'VM.Migrate' ]],
+    },
+    description => "Rebalance VMs and Containers.",
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    node => get_standard_option('pve-node'),
+            mem_threshold => {
+                description => "target mem % threshold",
+                type => 'integer',
+                minimum => 1
+            },
+            cpu_threshold => {
+                description => "target mem % threshold",
+                type => 'integer',
+                minimum => 1
+            },
+	},
+    },
+    returns => {
+	type => 'string',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $rpcenv = PVE::RPCEnvironment::get();
+	my $authuser = $rpcenv->get_user();
+
+	my $nodename = $param->{node};
+	$nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
+
+	my $mem_threshold = $param->{mem_threshold};
+	my $cpu_threshold = $param->{cpu_threshold};
+
+	PVE::Cluster::check_cfs_quorum();
+
+	my $members = PVE::Cluster::get_members();
+	my $maxWorkers = 1;
+	my $storecfg = PVE::Storage::config();
+
+	my $code = sub {
+	    $rpcenv->{type} = 'priv'; # to start tasks in background
+
+	    my $vmlist = &$get_filtered_vmlist($nodename, undef, 0, 1);
+	    my $workers = {};
+
+	    my $rrd = PVE::Cluster::rrd_dump();
+
+	    my $node_stats = PVE::API2Tools::extract_node_stats($nodename, $members, $rrd);
+	    my $node_mem_pct = $node_stats->{mem} / $node_stats->{maxmem} * 100;
+	    my $node_cpu_pct = $node_stats->{cpu} / $node_stats->{maxcpu};
+
+	    return if ($node_mem_pct < $mem_threshold && $node_cpu_pct < $cpu_threshold);
+
+	    my $vmlist_stats = {};
+	    foreach my $vmid (sort keys %$vmlist) {
+                my $d = $vmlist->{$vmid};
+		my $vmconf = PVE::QemuConfig->load_config($vmid);
+		my $vm_stats = PVE::API2Tools::extract_vm_stats($vmid, $d, $rrd);
+		my $vm_cpu = $vm_stats->{cpu} * $vm_stats->{maxcpu};
+		my $vm_mem = $vm_stats->{mem};
+		$vmlist_stats->{$vmid}->{cpu} = $vm_cpu;
+		$vmlist_stats->{$vmid}->{mem} = $vm_mem;
+	    }
+
+	    my @vmlist_array;
+	    #order vmlist with bigger usage to reduce number of vm migration
+	    if ($node_mem_pct < $mem_threshold && $node_cpu_pct < $cpu_threshold) {
+		#order by cpu, then ram ?
+		@vmlist_array = sort { $a->{mem} <=> $b->{mem} || $a->{cpu} <=> $b->{cpu} } keys %$vmlist_stats;
+	    } elsif ($node_mem_pct < $mem_threshold) {
+		#order by mem usage first
+		@vmlist_array = sort { $a->{mem} <=> $b->{mem} } keys %$vmlist_stats;
+	    } elsif ($node_cpu_pct < $cpu_threshold) {
+		#order by cpu usage first
+		@vmlist_array = sort { $a->{cpu} <=> $b->{cpu} } keys %$vmlist_stats;
+	    }
+
+	    foreach my $vmid (@vmlist_array) {
+		my $d = $vmlist->{$vmid};
+		my $target = find_best_node_target($vmid, $d, $nodename, $storecfg, $mem_threshold/100, $cpu_threshold/100);
+		if(!$target) {
+		    warn "couldn't find a target for vmid $vmid\n";
+		    next;
+		}
+
+		my $pid;
+		eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
+		$target = $param->{target};
+		warn $@ if $@;
+		next if !$pid;
+
+		$workers->{$pid} = 1;
+		while (scalar(keys %$workers) >= $maxWorkers) {
+		    foreach my $p (keys %$workers) {
+			if (!PVE::ProcFSTools::check_process_running($p)) {
+			    delete $workers->{$p};
+			}
+		    }
+		    sleep(1);
+		}
+	    }
+	    while (scalar(keys %$workers)) {
+		foreach my $p (keys %$workers) {
+		    if (!PVE::ProcFSTools::check_process_running($p)) {
+			delete $workers->{$p};
+		    }
+		}
+		sleep(1);
+	    }
+	    return;
+	};
+
+	return $rpcenv->fork_worker('rebalance', undef, $authuser, $code);
+
+    }});
+
 __PACKAGE__->register_method ({
     name => 'get_etc_hosts',
     path => 'hosts',
@@ -2123,14 +2247,14 @@ sub dotprod {
 }
 
 sub find_best_node_target {
-    my($vmid, $d, $nodename, $storecfg) = @_;
+    my($vmid, $d, $nodename, $storecfg, $mem_threshold, $cpu_threshold) = @_;
 
     my $vmconf = PVE::QemuConfig->load_config($vmid);
     my $members = PVE::Cluster::get_members();
     my $rrd = PVE::Cluster::rrd_dump();
     my $nodelist = PVE::Cluster::get_nodelist();
-    my $mem_threshold = 0.8;
-    my $cpu_threshold = 0.8;
+    $mem_threshold = 0.8 if !$mem_threshold;
+    $cpu_threshold = 0.8 if !$cpu_threshold;
     my $vm_stats = PVE::API2Tools::extract_vm_stats($vmid, $d, $rrd);
     my $vm_cpu = $vm_stats->{cpu} * $vm_stats->{maxcpu};
     my $vm_mem = $vm_stats->{mem};
-- 
2.20.1




More information about the pve-devel mailing list