Skip to content

Commit b11258f

Browse files
author
Daan Hoogland
committed
load balance individual hosts when clusters are to big to distribute
1 parent a7a293e commit b11258f

2 files changed

Lines changed: 134 additions & 19 deletions

File tree

engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -88,37 +88,54 @@ public List<HostVO> getHostsToRebalance(ManagementServerHostVO ms, int avLoad) {
8888
hostToClusterMap = sortByClusterSize(hostToClusterMap);
8989

9090
int hostsToGive = allHosts.size() - avLoad;
91-
int hostsLeftToGive = hostsToGive;
92-
int hostsLeft = directHosts.size();
93-
List<HostVO> hostsToReturn = new ArrayList<HostVO>();
9491

9592
logger.debug("Management server {} can give away {} as it currently owns {} and the " +
9693
"average agent load in the system is {}; finalyzing list of hosts to give away...",
9794
ms, hostsToGive, allHosts.size(), avLoad);
95+
List<HostVO> hostsToReturn = selectHostsToGiveAway(hostToClusterMap, hostsToGive, directHosts.size());
96+
97+
logger.debug("Management server {} is ready to give away {} hosts", ms, hostsToReturn.size());
98+
return hostsToReturn;
99+
}
100+
101+
/**
102+
* Picks hosts to hand over from a management server that is over its average agent load.
103+
* Whole clusters are preferred (so a hypervisor cluster stays on a single management server
104+
* whenever possible), but when no combination of whole clusters can satisfy {@code hostsToGive}
105+
* (e.g. a single cluster holds all the hosts), a partial subset of the last cluster considered
106+
* is taken instead of giving away nothing, so rebalancing still makes progress.
107+
*/
108+
protected List<HostVO> selectHostsToGiveAway(Map<Long, List<HostVO>> hostToClusterMap, int hostsToGive, int totalDirectHosts) {
109+
int hostsLeftToGive = hostsToGive;
110+
int hostsLeft = totalDirectHosts;
111+
List<HostVO> hostsToReturn = new ArrayList<HostVO>();
112+
98113
for (Long cluster : hostToClusterMap.keySet()) {
99114
List<HostVO> hostsInCluster = hostToClusterMap.get(cluster);
100115
hostsLeft = hostsLeft - hostsInCluster.size();
101-
if (hostsToReturn.size() < hostsToGive) {
102-
logger.debug("Trying cluster id=" + cluster);
103-
104-
if (hostsInCluster.size() > hostsLeftToGive) {
105-
logger.debug("Skipping cluster id=" + cluster + " as it has more hosts than we need: " + hostsInCluster.size() + " vs " + hostsLeftToGive);
106-
if (hostsLeft >= hostsLeftToGive) {
107-
continue;
108-
} else {
109-
break;
110-
}
111-
} else {
112-
logger.debug("Taking all " + hostsInCluster.size() + " hosts: " + hostsInCluster + " from cluster id=" + cluster);
113-
hostsToReturn.addAll(hostsInCluster);
114-
hostsLeftToGive = hostsLeftToGive - hostsInCluster.size();
115-
}
116+
if (hostsToReturn.size() >= hostsToGive) {
117+
break;
118+
}
119+
120+
logger.debug("Trying cluster id=" + cluster);
121+
122+
if (hostsInCluster.size() <= hostsLeftToGive) {
123+
logger.debug("Taking all " + hostsInCluster.size() + " hosts: " + hostsInCluster + " from cluster id=" + cluster);
124+
hostsToReturn.addAll(hostsInCluster);
125+
hostsLeftToGive = hostsLeftToGive - hostsInCluster.size();
126+
} else if (hostsLeft >= hostsLeftToGive) {
127+
logger.debug("Skipping cluster id=" + cluster + " as it has more hosts than we need: " + hostsInCluster.size() + " vs " + hostsLeftToGive
128+
+ ", and remaining clusters can still satisfy the quota");
129+
continue;
116130
} else {
131+
logger.debug("No combination of whole clusters can satisfy the quota; taking a partial subset of " + hostsLeftToGive
132+
+ " hosts from cluster id=" + cluster + " instead of giving away nothing");
133+
hostsToReturn.addAll(hostsInCluster.subList(0, hostsLeftToGive));
134+
hostsLeftToGive = 0;
117135
break;
118136
}
119137
}
120138

121-
logger.debug("Management server {} is ready to give away {} hosts", ms, hostsToReturn.size());
122139
return hostsToReturn;
123140
}
124141

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package com.cloud.cluster.agentlb;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
22+
import java.util.ArrayList;
23+
import java.util.LinkedHashMap;
24+
import java.util.List;
25+
26+
import org.junit.Test;
27+
28+
import com.cloud.host.HostVO;
29+
30+
public class ClusterBasedAgentLoadBalancerPlannerTest {
31+
32+
private final ClusterBasedAgentLoadBalancerPlanner planner = new ClusterBasedAgentLoadBalancerPlanner();
33+
34+
private HostVO host(long clusterId) {
35+
HostVO host = new HostVO("guid-" + clusterId + "-" + System.nanoTime());
36+
host.setClusterId(clusterId);
37+
return host;
38+
}
39+
40+
private LinkedHashMap<Long, List<HostVO>> clusterOf(int... clusterSizes) {
41+
LinkedHashMap<Long, List<HostVO>> map = new LinkedHashMap<>();
42+
long clusterId = 1;
43+
for (int size : clusterSizes) {
44+
List<HostVO> hosts = new ArrayList<>();
45+
for (int i = 0; i < size; i++) {
46+
hosts.add(host(clusterId));
47+
}
48+
map.put(clusterId, hosts);
49+
clusterId++;
50+
}
51+
return map;
52+
}
53+
54+
@Test
55+
public void singleOversizedClusterGivesAwayPartialHosts() {
56+
LinkedHashMap<Long, List<HostVO>> hostToClusterMap = clusterOf(2);
57+
int totalDirectHosts = 2;
58+
int avLoad = 1;
59+
int hostsToGive = totalDirectHosts - avLoad;
60+
61+
List<HostVO> hostsToReturn = planner.selectHostsToGiveAway(hostToClusterMap, hostsToGive, totalDirectHosts);
62+
63+
assertEquals("a lone oversized cluster must still give away hosts to satisfy the quota", 1, hostsToReturn.size());
64+
}
65+
66+
@Test
67+
public void wholeClusterIsPreferredWhenItExactlyFitsTheQuota() {
68+
LinkedHashMap<Long, List<HostVO>> hostToClusterMap = clusterOf(2, 3);
69+
int totalDirectHosts = 5;
70+
int hostsToGive = 2;
71+
72+
List<HostVO> hostsToReturn = planner.selectHostsToGiveAway(hostToClusterMap, hostsToGive, totalDirectHosts);
73+
74+
assertEquals(2, hostsToReturn.size());
75+
assertTrue("should take the whole 2-host cluster rather than split the larger one", hostToClusterMap.get(1L).containsAll(hostsToReturn));
76+
}
77+
78+
@Test
79+
public void smallerClusterIsPreferredOverSplittingWhenBothCanSatisfyQuota() {
80+
LinkedHashMap<Long, List<HostVO>> hostToClusterMap = clusterOf(5, 2);
81+
int totalDirectHosts = 7;
82+
int hostsToGive = 2;
83+
84+
List<HostVO> hostsToReturn = planner.selectHostsToGiveAway(hostToClusterMap, hostsToGive, totalDirectHosts);
85+
86+
assertEquals(2, hostsToReturn.size());
87+
assertTrue("should skip the oversized cluster and take the smaller cluster whole", hostToClusterMap.get(2L).containsAll(hostsToReturn));
88+
}
89+
90+
@Test
91+
public void noHostsGivenAwayWhenAlreadyUnderThreshold() {
92+
LinkedHashMap<Long, List<HostVO>> hostToClusterMap = clusterOf(1);
93+
94+
List<HostVO> hostsToReturn = planner.selectHostsToGiveAway(hostToClusterMap, 0, 1);
95+
96+
assertTrue(hostsToReturn.isEmpty());
97+
}
98+
}

0 commit comments

Comments
 (0)