Skip to content

Commit 6caa33e

Browse files
Add 'balancedweighted' VM allocation algorithm (#14109)
* Add a DAO method to count VMs per host Counts VMs occupying each host in a zone, pod or cluster, optionally only those that changed state recently. One query for the whole scope rather than one per host. Signed-off-by: Brad House <bhouse@nexthop.ai> * Track a moving average of real host utilisation StatsCollector already polls CPU and memory utilisation for every host, but it keeps only the newest sample and nothing uses it for placement. - fold those samples into an exponentially weighted moving average - weight by elapsed time, so a missed poll decays correctly instead of over-weighting the previous value - report nothing usable until a host has been sampled, so callers can fall back to allocation figures Two settings: host.load.sample.interval and host.load.half.life. Signed-off-by: Brad House <bhouse@nexthop.ai> * Add 'balancedweighted' VM allocation algorithm Opt-in via vm.allocation.algorithm. Existing algorithms and the default are untouched. Allocated capacity alone is a poor ranking under heavy overprovisioning: it is measured against a total already multiplied by the overprovisioning factor, so a host under real strain still reports a low percentage and keeps being chosen. Anything allocation cannot see - VMs the scheduler has lost track of, guests using more than they asked for - is invisible. Ranks on a blend, lower is better: | term | source | |---------------------|-------------------------------------------| | CPU allocated | op_host_capacity, over the inflated total | | CPU utilisation | moving average of real usage | | memory allocated | op_host_capacity, over the inflated total | | memory utilisation | moving average of real usage | | VM count | VMs on the host | | recent starts | VMs started within the last few minutes | A dominant resource term is added on top so a host nearly out of any one resource does not rank well on a good average. Hosts measurably too busy are held back, unless that would leave nowhere to deploy. Selection is random among the best few rather than strictly ordered: capacity is only charged once a VM starts, so concurrent deployments all read the same figures and strict ordering makes them agree on one host. All weights and thresholds are settings, most cluster scoped. Signed-off-by: Brad House <bhouse@nexthop.ai> * Fix three defects found in review of the weighted scoring Allocated fraction was measured against the wrong total. op_host_capacity stores totals raw and overprovisioning is applied when they are read, so dividing by the stored total made the fraction reach 1 at the host's physical size. On a cluster overcommitted 10 times every host clamped to 1, killing both the allocation term and the dominant resource term - on exactly the clusters this algorithm is for. - apply the cluster ratio to the denominator - drop hosts missing a CPU or memory capacity row instead of scoring the missing resource as untouched, which made them rank first Utilisation thresholds could be bypassed. Held-back hosts were appended before the random spread was applied, so the spread could shuffle a busy host into the lead. With 1 healthy host and a spread of 3, two thirds of deployments picked an over-threshold host. - spread over healthy hosts only, before anything else is appended A host with no load samples was treated as idle. It was exempt from the thresholds and its dominant resource term fell back to allocation, so a host with broken stats outranked every measured host and collected the deployments. - rank hosts we cannot measure behind every host we can - when nothing can be measured, ranking falls back to allocation as before Signed-off-by: Brad House <bhouse@nexthop.ai> * Address the rest of the weighted scoring review Utilisation average - a host whose agent stops reporting kept vouching for itself forever: StatsCollector hands back the previous entry when a poll fails, and that unchanged reading was folded again every minute. Detect the repeat, and expire an average that stops being updated - sample on a scheduled executor catching Throwable, not a Timer, which dies permanently and silently on one escaping error - only collect when an algorithm that reads the figures is selected - read the half life once per sample rather than inside the map update - document what getCpuUtilization means per hypervisor: it is what this assumes on KVM, a reservation figure on VMware, and scaled by core count on XenServer Scoring - a negative weight would rank the most loaded host first; floor at zero and say so - zeroing all six terms no longer discards the dominant resource term - read weights once per ranking instead of once per host Queries - one query per ranking instead of two, returning both counts - count Stopping VMs, which still hold their host - correct the doc: every host in scope is returned, including empty ones Tests - cover rank() end to end, which is where the defects were: the capacity denominator, the thresholds, the spread and the ordering of measured against unmeasured hosts - the distribution simulation drew different random streams per arm, so the arms saw different workloads. Fix the workload up front and add an allocation-only-plus-spread control, which shows the scoring and not the spread is what evens out real load Signed-off-by: Brad House <bhouse@nexthop.ai> * Address review feedback on the weighted allocation algorithm vm_instance.update_time is a TIMESTAMP column, so there is no date that reliably means "never" - anything past 2038 is out of range. Counting with no cut-off used Long.MAX_VALUE, which is roughly year 292 million. The query now leaves the timestamp out entirely in that case rather than binding an impossible one. Also catch Exception rather than Throwable in the new query, and put the config key array one entry per line. Signed-off-by: Brad House <bhouse@nexthop.ai> * Hold back hosts with a backlog of VMs still starting The symptom this addresses is a burst of deployments all landing on one host and then sitting in Starting for minutes instead of seconds, while that host works through a queue of starts its storage and agent command path cannot absorb. Nothing in the algorithm saw that. Capacity is not charged until a VM reaches Starting, which is after the allocator has returned, and the utilisation average is a sample interval and a half life behind, so a burst is over before either notices. The recent-start term is the closest thing to a guard and it cannot do the job: it counts VMs whose state changed within a window, so a VM stuck in Starting for ten minutes stops counting after five - the signal decays fastest on the hosts that are worst off - and its contribution is capped at 0.111 of the score, so a host with 200 VMs stuck in Starting ranks the same as one with 50. Count the VMs actually in Starting instead, and use it as a gate rather than a weight. It does not expire while the condition persists, it is read fresh on every ranking rather than sampled, and a gate cannot be outvoted by the other terms. It costs nothing: the per-host VM count query already filters on vm.state, so it is one more aggregate in a query that was already being made. Held-back hosts are still never dropped, only moved down the list, so this cannot turn a deployment that would have succeeded into an InsufficientServerCapacity failure. Within the held-back group the order is by starting count, so if every host is backed up the least backed up one is tried first. Also fix a hole in the selection spread. It was applied to the healthy list only, so whenever no host was eligible that list was empty, the shuffle was a no-op, and the held-back hosts were handed out in strict score order - concurrent deployments agreeing on one host in exactly the conditions that caused the pile-up. The spread now applies to whichever tier leads, which also removes the special-cased branch it was hiding in. The "short of capacity" warning stays narrow: hosts merely being unmeasured, as they all are on a freshly started management server, is not a capacity problem and must not warn on every deployment. Signed-off-by: Brad House <bhouse@nexthop.ai> * Rank each candidate host once, however many tags it carries listAllUpAndEnabledNonHAHosts joins host_tags to filter out hosts whose tags are rules, and the join carries no DISTINCT, so a host comes back once per non-rule tag it holds. That path is taken whenever a VM has no host tag to filter the candidate list by, which in practice means system VMs: a virtual router deploying onto a twelve host cluster where three hosts carry a second tag is handed fifteen candidates. An allocator that walks the list and takes the first host that fits is indifferent to a repeat, which is why this has gone unnoticed. The weighted ranking is not. Scores are keyed by host, so a repeat changes no score, but the selection spread shuffles the leading few entries and the caller takes whichever ends up first - so a host holding two of the three slots draws two thirds of the deployments rather than one third. Collapse the repeats in the DAO, where the join creates them and every caller benefits, and again in the scorer, so ranking cannot be skewed by whatever a future caller hands it. Signed-off-by: Brad House <bhouse@nexthop.ai> * Let WeightedHostScorer implement HostScoringWeights The four shared weights are then inherited like the scorer's own keys instead of being qualified with the interface name. The scorer's own description suffix is renamed so it no longer hides the interface's. Signed-off-by: Brad House <bhouse@nexthop.ai> --------- Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent 7ccc48c commit 6caa33e

17 files changed

Lines changed: 2055 additions & 4 deletions

File tree

‎PendingReleaseNotes‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,31 @@ example.ver.1 > example.ver.2:
9191
no device-mapper multipath configuration is required. A new
9292
Storage.StoragePoolType.NVMeTCP + MultipathNVMeOFAdapterBase /
9393
NVMeTCPAdapter on the KVM side back the new pool type.
94+
95+
* New VM allocation algorithm 'balancedweighted' for vm.allocation.algorithm. Existing
96+
algorithms and the default are unchanged; the new one is opt-in.
97+
98+
Existing algorithms rank hosts on allocated capacity alone. Under a large overprovisioning
99+
factor that reads badly: allocation is measured against a total that has already been
100+
multiplied by the factor, so a host under real strain can still report a low percentage and
101+
keep attracting new VMs. Concurrent deployments make it worse, since they all read the same
102+
figures before any of them is accounted for.
103+
104+
'balancedweighted' ranks hosts on a blend of allocated CPU and memory, measured CPU and
105+
memory utilisation, VM count, and how many VMs started on the host recently. It holds back
106+
hosts that are measurably too busy, and chooses at random from among the best scoring hosts
107+
so that simultaneous deployments do not all pick the same one.
108+
109+
It also holds back hosts that already have host.weighted.starting.vms.threshold VMs sitting
110+
in the Starting state, so that a burst of deployments does not queue up behind itself on one
111+
host. This is the only signal that reacts within a single deployment: capacity is not charged
112+
until a VM reaches Starting and a utilisation average is a sample interval behind, so neither
113+
notices a burst until it is over. Set the threshold to 0 to disable it.
114+
115+
Held-back hosts are never excluded, only moved to the back of the list, so no threshold can
116+
turn a deployment that would have succeeded into an InsufficientServerCapacity failure.
117+
118+
Tuned with the host.weighted.* settings, most of which are cluster scoped. Measured
119+
utilisation comes from a moving average configured with host.load.sample.interval and
120+
host.load.half.life. Until a management server has collected samples, ranking falls back to
121+
allocation figures alone.

‎api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner {
6262
"vm.allocation.algorithm",
6363
"Advanced",
6464
"random",
65-
"Order in which hosts within a cluster will be considered for VM allocation. The value can be 'random', 'firstfit', 'userdispersing', or 'firstfitleastconsumed'.",
65+
"Order in which hosts within a cluster will be considered for VM allocation. The value can be 'random', "
66+
+ "'firstfit', 'userdispersing', 'firstfitleastconsumed', or 'balancedweighted'. 'balancedweighted' "
67+
+ "ranks hosts on a blend of allocated capacity, measured utilisation, VM count and how many VMs "
68+
+ "started recently, and is tuned with the host.weighted.* settings.",
6669
true,
6770
ConfigKey.Scope.Global, null, null, null, null, null,
6871
ConfigKey.Kind.Select,
69-
"random,firstfit,userdispersing,firstfitleastconsumed");
72+
"random,firstfit,userdispersing,firstfitleastconsumed,balancedweighted");
7073

7174
/**
7275
* This is called to determine list of possible clusters where a virtual

‎api/src/main/java/com/cloud/deploy/DeploymentPlanner.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public interface DeploymentPlanner extends Adapter {
7070
boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid);
7171

7272
public enum AllocationAlgorithm {
73-
random, firstfit, userdispersing, firstfitleastconsumed;
73+
random, firstfit, userdispersing, firstfitleastconsumed, balancedweighted;
7474
}
7575

7676
public enum PlannerResourceUsage {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.host;
18+
19+
import org.apache.cloudstack.framework.config.ConfigKey;
20+
21+
/**
22+
* How much each signal counts when ranking hosts by how loaded they are.
23+
*
24+
* Shared by initial placement and by rebalancing on purpose. If the two weighted these differently
25+
* they would disagree about which host is the better one, and rebalancing could move VMs off hosts
26+
* that placement had just chosen, only for placement to put them back.
27+
*
28+
* Weights are relative to each other; only their ratios matter, and zero disables a term. Terms that
29+
* only make sense for one of the two - how many VMs a host carries, how many started recently - stay
30+
* with whichever uses them.
31+
*/
32+
public interface HostScoringWeights {
33+
34+
String WEIGHT_DESCRIPTION_SUFFIX = " Relative weight, only meaningful compared with the other "
35+
+ "host.weighted.* weights. Zero disables the term. Used by both the 'balancedweighted' "
36+
+ "allocation algorithm and the 'weighted' DRS algorithm.";
37+
38+
ConfigKey<Double> CpuAllocatedWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
39+
Double.class, "host.weighted.cpu.allocated.weight", "1.0",
40+
"How much CPU allocated on a host counts against it." + WEIGHT_DESCRIPTION_SUFFIX,
41+
true, ConfigKey.Scope.Cluster);
42+
43+
ConfigKey<Double> CpuUsedWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
44+
Double.class, "host.weighted.cpu.used.weight", "2.0",
45+
"How much measured CPU utilisation counts against a host." + WEIGHT_DESCRIPTION_SUFFIX,
46+
true, ConfigKey.Scope.Cluster);
47+
48+
ConfigKey<Double> MemoryAllocatedWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
49+
Double.class, "host.weighted.memory.allocated.weight", "1.0",
50+
"How much memory allocated on a host counts against it." + WEIGHT_DESCRIPTION_SUFFIX,
51+
true, ConfigKey.Scope.Cluster);
52+
53+
ConfigKey<Double> MemoryUsedWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
54+
Double.class, "host.weighted.memory.used.weight", "2.0",
55+
"How much measured memory utilisation counts against a host." + WEIGHT_DESCRIPTION_SUFFIX,
56+
true, ConfigKey.Scope.Cluster);
57+
}

‎engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Date;
2525
import java.util.HashMap;
2626
import java.util.HashSet;
27+
import java.util.LinkedHashMap;
2728
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Objects;
@@ -935,7 +936,14 @@ public List<HostVO> listAllUpAndEnabledNonHAHosts(Type type, Long clusterId, Lon
935936
sc.setParameters("status", Status.Up);
936937
sc.setParameters("resourceState", ResourceState.Enabled);
937938

938-
return listBy(sc);
939+
// The tag join carries no DISTINCT, so a host comes back once per non-rule tag it holds.
940+
// Callers read this as a set of candidate hosts, so collapse the repeats here rather than
941+
// leave each of them to cope with the same host arriving more than once.
942+
Map<Long, HostVO> distinctHosts = new LinkedHashMap<>();
943+
for (HostVO host : listBy(sc)) {
944+
distinctHosts.putIfAbsent(host.getId(), host);
945+
}
946+
return new ArrayList<>(distinctHosts.values());
939947
}
940948

941949
@Override

‎engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import com.cloud.hypervisor.Hypervisor;
2727
import com.cloud.utils.Pair;
28+
import com.cloud.utils.Ternary;
2829
import com.cloud.utils.db.GenericDao;
2930
import com.cloud.utils.fsm.StateDao;
3031
import com.cloud.vm.VMInstanceVO;
@@ -131,6 +132,18 @@ public interface VMInstanceDao extends GenericDao<VMInstanceVO, Long>, StateDao<
131132

132133
List<Long> listHostIdsByVmCount(long dcId, Long podId, Long clusterId, long accountId);
133134

135+
/**
136+
* Counts the VMs occupying each host in a zone, pod or cluster, in one query.
137+
*
138+
* @param changedStateAfter
139+
* cut-off for the second count: VMs whose state last changed after this. Approximates
140+
* the VMs still working through their startup load, which neither allocation nor a
141+
* utilisation average has caught up with yet.
142+
* @return host id to {total VMs, VMs that changed state recently, VMs still in Starting}. Every
143+
* host in scope appears, including hosts with no VMs.
144+
*/
145+
Map<Long, Ternary<Long, Long, Long>> countVmsByHost(long dcId, Long podId, Long clusterId, Date changedStateAfter);
146+
134147
Long countRunningAndStartingByAccount(long accountId);
135148

136149
Long countByZoneAndState(long zoneId, State state);

‎engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.sql.PreparedStatement;
2020
import java.sql.ResultSet;
21+
import java.sql.Timestamp;
2122
import java.sql.SQLException;
2223
import java.util.ArrayList;
2324
import java.util.Collection;
@@ -45,6 +46,7 @@
4546
import com.cloud.tags.dao.ResourceTagDao;
4647
import com.cloud.utils.DateUtil;
4748
import com.cloud.utils.Pair;
49+
import com.cloud.utils.Ternary;
4850
import com.cloud.utils.db.Attribute;
4951
import com.cloud.utils.db.DB;
5052
import com.cloud.utils.db.Filter;
@@ -156,6 +158,21 @@ public class VMInstanceDaoImpl extends GenericDaoBase<VMInstanceVO, Long> implem
156158
private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2 =
157159
"GROUP BY gpu_card.name, vgpu_profile.name";
158160

161+
// %s is the "changed state recently" test, or a constant 0 when no cut-off is given. It is not
162+
// a bound parameter because there is no timestamp that reliably means "never" - update_time is
163+
// a TIMESTAMP column, so anything past 2038 is out of range.
164+
//
165+
// The third count is VMs still in Starting. Unlike the recent-state-change count it does not
166+
// expire on a clock: a VM that has been starting for ten minutes still counts, which is the
167+
// case that matters, since a host that is struggling is exactly the one whose VMs take longest
168+
// to leave Starting.
169+
private static final String COUNT_VMS_BY_HOST = "SELECT host.id, COUNT(vm.id), SUM(IF(%s, 1, 0)), " +
170+
"SUM(IF(vm.state = 'Starting', 1, 0)) " +
171+
"FROM `cloud`.`host` host LEFT JOIN `cloud`.`vm_instance` vm " +
172+
"ON vm.host_id = host.id AND vm.state IN ('Running', 'Starting', 'Stopping', 'Migrating') " +
173+
"AND vm.removed IS NULL WHERE host.type = 'Routing' AND host.removed IS NULL AND host.data_center_id = ? ";
174+
private static final String COUNT_VMS_BY_HOST_PART2 = " GROUP BY host.id ";
175+
159176
private static final String UPDATE_SYSTEM_VM_TEMPLATE_ID_FOR_HYPERVISOR = "UPDATE `cloud`.`vm_instance` SET vm_template_id = ? WHERE type <> 'User' AND hypervisor_type = ? AND removed is NULL";
160177

161178
private static final String COUNT_VMS_BY_ZONE_AND_STATE_AND_HOST_TAG = "SELECT COUNT(1) FROM vm_instance vi JOIN service_offering so ON vi.service_offering_id=so.id " +
@@ -801,6 +818,45 @@ public Pair<List<Long>, Map<Long, Double>> listPodIdsInZoneByVmCount(long dataCe
801818
}
802819
}
803820

821+
822+
@Override
823+
public Map<Long, Ternary<Long, Long, Long>> countVmsByHost(long dcId, Long podId, Long clusterId, Date changedStateAfter) {
824+
TransactionLegacy txn = TransactionLegacy.currentTxn();
825+
Map<Long, Ternary<Long, Long, Long>> result = new HashMap<>();
826+
String sql = String.format(COUNT_VMS_BY_HOST, changedStateAfter != null ? "vm.update_time > ?" : "0");
827+
if (podId != null) {
828+
sql = sql + " AND host.pod_id = ? ";
829+
}
830+
if (clusterId != null) {
831+
sql = sql + " AND host.cluster_id = ? ";
832+
}
833+
sql = sql + COUNT_VMS_BY_HOST_PART2;
834+
try {
835+
PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql);
836+
int index = 1;
837+
if (changedStateAfter != null) {
838+
pstmt.setTimestamp(index++, new Timestamp(changedStateAfter.getTime()));
839+
}
840+
pstmt.setLong(index++, dcId);
841+
if (podId != null) {
842+
pstmt.setLong(index++, podId);
843+
}
844+
if (clusterId != null) {
845+
pstmt.setLong(index++, clusterId);
846+
}
847+
ResultSet rs = pstmt.executeQuery();
848+
while (rs.next()) {
849+
// SUM over no rows is NULL, which getLong reports as 0 - correct for an empty host
850+
result.put(rs.getLong(1), new Ternary<>(rs.getLong(2), rs.getLong(3), rs.getLong(4)));
851+
}
852+
return result;
853+
} catch (SQLException e) {
854+
throw new CloudRuntimeException("DB Exception on: " + sql, e);
855+
} catch (Exception e) {
856+
throw new CloudRuntimeException("Caught: " + sql, e);
857+
}
858+
}
859+
804860
@Override
805861
public List<Long> listHostIdsByVmCount(long dcId, Long podId, Long clusterId, long accountId) {
806862
TransactionLegacy txn = TransactionLegacy.currentTxn();

‎engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939

4040
import com.cloud.cpu.CPU;
4141
import com.cloud.host.Host;
42+
import com.cloud.host.HostTagVO;
4243
import com.cloud.host.HostVO;
4344
import com.cloud.host.Status;
4445
import com.cloud.hypervisor.Hypervisor;
@@ -60,6 +61,32 @@ public class HostDaoImplTest {
6061
@Mock
6162
private SearchCriteria<HostVO> mockSearchCriteria;
6263

64+
@Test
65+
public void testListAllUpAndEnabledNonHAHostsCollapsesRepeatedHosts() {
66+
// the host_tags join carries no DISTINCT, so a host comes back once per non-rule tag it
67+
// holds. Callers read the result as a set of candidate hosts.
68+
SearchBuilder<HostTagVO> tagSearchBuilder = mock(SearchBuilder.class);
69+
when(tagSearchBuilder.entity()).thenReturn(mock(HostTagVO.class));
70+
HostTagsDao hostTagsDao = mock(HostTagsDao.class);
71+
when(hostTagsDao.createSearchBuilder()).thenReturn(tagSearchBuilder);
72+
hostDao._hostTagsDao = hostTagsDao;
73+
74+
when(mockSearchBuilder.entity()).thenReturn(mock(HostVO.class));
75+
when(mockSearchBuilder.create()).thenReturn(mockSearchCriteria);
76+
doReturn(mockSearchBuilder).when(hostDao).createSearchBuilder();
77+
78+
HostVO twoTags = mock(HostVO.class);
79+
when(twoTags.getId()).thenReturn(1L);
80+
HostVO oneTag = mock(HostVO.class);
81+
when(oneTag.getId()).thenReturn(2L);
82+
doReturn(Arrays.asList(twoTags, twoTags, oneTag)).when(hostDao).listBy(any(SearchCriteria.class));
83+
84+
List<HostVO> hosts = hostDao.listAllUpAndEnabledNonHAHosts(Host.Type.Routing, 3L, 2L, 1L, null);
85+
86+
assertEquals(2, hosts.size());
87+
assertEquals(Arrays.asList(1L, 2L), hosts.stream().map(HostVO::getId).collect(Collectors.toList()));
88+
}
89+
6390
@Test
6491
public void testCountUpAndEnabledHostsInZone() {
6592
long testZoneId = 100L;

‎server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package com.cloud.agent.manager.allocator.impl;
1818

19+
import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.balancedweighted;
1920
import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.firstfitleastconsumed;
2021
import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.random;
2122
import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.userdispersing;
@@ -95,6 +96,8 @@ public class FirstFitAllocator extends BaseAllocator {
9596
CapacityDao _capacityDao;
9697
@Inject
9798
VMInstanceDetailsDao vmInstanceDetailsDao;
99+
@Inject
100+
WeightedHostScorer weightedHostScorer;
98101

99102
boolean _checkHvm = true;
100103

@@ -209,6 +212,8 @@ protected List<Host> allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan
209212
hosts = reorderHostsByNumberOfVms(plan, hosts, account);
210213
} else if (firstfitleastconsumed.toString().equals(vmAllocationAlgorithm)) {
211214
hosts = reorderHostsByCapacity(plan, hosts);
215+
} else if (balancedweighted.toString().equals(vmAllocationAlgorithm)) {
216+
hosts = weightedHostScorer.rank(plan.getDataCenterId(), plan.getPodId(), plan.getClusterId(), hosts);
212217
}
213218

214219
logger.debug("FirstFitAllocator has {} hosts to check for allocation {}.", hosts.size(), hosts);
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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.agent.manager.allocator.impl;
18+
19+
/**
20+
* Smoothed view of what a host is actually doing, as opposed to what has been allocated on it.
21+
* Fractions are of the host's real capacity and ignore overprovisioning.
22+
*/
23+
public class HostLoad {
24+
25+
public static final HostLoad UNKNOWN = new HostLoad(0, 0, 0);
26+
27+
private final double cpuUtilisation;
28+
private final double memoryUtilisation;
29+
private final long samples;
30+
31+
public HostLoad(double cpuUtilisation, double memoryUtilisation, long samples) {
32+
this.cpuUtilisation = cpuUtilisation;
33+
this.memoryUtilisation = memoryUtilisation;
34+
this.samples = samples;
35+
}
36+
37+
public double getCpuUtilisation() {
38+
return cpuUtilisation;
39+
}
40+
41+
public double getMemoryUtilisation() {
42+
return memoryUtilisation;
43+
}
44+
45+
public long getSamples() {
46+
return samples;
47+
}
48+
49+
/**
50+
* False until enough has been observed to rank on. Callers fall back to allocation figures.
51+
*/
52+
public boolean isUsable() {
53+
return samples > 0;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return String.format("HostLoad[cpu=%.3f, memory=%.3f, samples=%d]", cpuUtilisation, memoryUtilisation, samples);
59+
}
60+
}

0 commit comments

Comments
 (0)