Skip to content

Commit f06ac51

Browse files
author
Daan Hoogland
committed
merge forward 4.22 to main
2 parents da1c7ce + 7aba434 commit f06ac51

File tree

30 files changed

+2233
-775
lines changed

30 files changed

+2233
-775
lines changed

api/src/main/java/com/cloud/server/ManagementService.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
import com.cloud.capacity.Capacity;
7878
import com.cloud.dc.Pod;
7979
import com.cloud.dc.Vlan;
80+
import com.cloud.deploy.DeploymentPlan;
81+
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
8082
import com.cloud.exception.ConcurrentOperationException;
8183
import com.cloud.exception.ManagementServerException;
8284
import com.cloud.exception.ResourceUnavailableException;
@@ -97,6 +99,7 @@
9799
import com.cloud.vm.InstanceGroup;
98100
import com.cloud.vm.VirtualMachine;
99101
import com.cloud.vm.VirtualMachine.Type;
102+
import com.cloud.vm.VirtualMachineProfile;
100103

101104
/**
102105
* Hopefully this is temporary.
@@ -478,6 +481,19 @@ public interface ManagementService {
478481

479482
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(VirtualMachine vm, Long startIndex, Long pageSize, String keyword, List<VirtualMachine> vmList);
480483

484+
/**
485+
* Apply affinity group constraints and other exclusion rules for VM migration.
486+
* This is a helper method that can be used independently for per-iteration affinity checks in DRS.
487+
*
488+
* @param vm The virtual machine to migrate
489+
* @param vmProfile The VM profile
490+
* @param plan The deployment plan
491+
* @param vmList List of VMs with current/simulated placements for affinity processing
492+
* @return ExcludeList containing hosts to avoid
493+
*/
494+
ExcludeList applyAffinityConstraints(VirtualMachine vm, VirtualMachineProfile vmProfile,
495+
DeploymentPlan plan, List<VirtualMachine> vmList);
496+
481497
/**
482498
* List storage pools for live migrating of a volume. The API returns list of all pools in the cluster to which the
483499
* volume can be migrated. Current pool is not included in the list. In case of vSphere datastore cluster storage pools,

api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.cloud.exception.ResourceAllocationException;
3535
import com.cloud.exception.ResourceUnavailableException;
3636
import com.cloud.user.Account;
37+
import com.cloud.utils.StringUtils;
3738

3839
@APICommand(name = "createManagementNetworkIpRange",
3940
description = "Creates a Management network IP range.",
@@ -118,7 +119,7 @@ public Boolean isForSystemVms() {
118119
}
119120

120121
public String getVlan() {
121-
if (vlan == null || vlan.isEmpty()) {
122+
if (StringUtils.isBlank(vlan)) {
122123
vlan = "untagged";
123124
}
124125
return vlan;

api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.cloud.configuration.ConfigurationService;
2020
import com.cloud.network.Network;
2121
import com.cloud.utils.net.NetUtils;
22+
import com.cloud.utils.StringUtils;
2223

2324
import org.apache.cloudstack.api.APICommand;
2425
import org.apache.cloudstack.api.ApiConstants;
@@ -163,7 +164,7 @@ public Network.Provider getProvider() {
163164
}
164165

165166
public String getVlan() {
166-
if ((vlan == null || vlan.isEmpty()) && !ConfigurationService.IsIpRangeForProvider(getProvider())) {
167+
if (StringUtils.isBlank(vlan) && !ConfigurationService.IsIpRangeForProvider(getProvider())) {
167168
vlan = "untagged";
168169
}
169170
return vlan;

api/src/main/java/org/apache/cloudstack/cluster/ClusterDrsAlgorithm.java

Lines changed: 115 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
import com.cloud.host.Host;
2323
import com.cloud.offering.ServiceOffering;
2424
import com.cloud.org.Cluster;
25-
import com.cloud.utils.Pair;
2625
import com.cloud.utils.Ternary;
2726
import com.cloud.utils.component.Adapter;
2827
import com.cloud.vm.VirtualMachine;
28+
import org.apache.commons.collections.CollectionUtils;
2929
import org.apache.commons.math3.stat.descriptive.moment.Mean;
3030
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
3131

@@ -40,6 +40,9 @@
4040

4141
public interface ClusterDrsAlgorithm extends Adapter {
4242

43+
Mean MEAN_CALCULATOR = new Mean();
44+
StandardDeviation STDDEV_CALCULATOR = new StandardDeviation(false);
45+
4346
/**
4447
* Determines whether a DRS operation is needed for a given cluster and host-VM
4548
* mapping.
@@ -59,79 +62,121 @@ public interface ClusterDrsAlgorithm extends Adapter {
5962
boolean needsDrs(Cluster cluster, List<Ternary<Long, Long, Long>> cpuList,
6063
List<Ternary<Long, Long, Long>> memoryList) throws ConfigurationException;
6164

62-
6365
/**
64-
* Determines the metrics for a given virtual machine and destination host in a DRS cluster.
65-
*
66-
* @param clusterId
67-
* the ID of the cluster to check
68-
* @param vm
69-
* the virtual machine to check
70-
* @param serviceOffering
71-
* the service offering for the virtual machine
72-
* @param destHost
73-
* the destination host for the virtual machine
74-
* @param hostCpuMap
75-
* a map of host IDs to the Ternary of used, reserved and total CPU on each host
76-
* @param hostMemoryMap
77-
* a map of host IDs to the Ternary of used, reserved and total memory on each host
78-
* @param requiresStorageMotion
79-
* whether storage motion is required for the virtual machine
66+
* Calculates the metrics (improvement, cost, benefit) for migrating a VM to a destination host. Improvement is
67+
* calculated based on the change in cluster imbalance before and after the migration.
8068
*
69+
* @param cluster the cluster to check
70+
* @param vm the virtual machine to check
71+
* @param serviceOffering the service offering for the virtual machine
72+
* @param destHost the destination host for the virtual machine
73+
* @param hostCpuMap a map of host IDs to the Ternary of used, reserved and total CPU on each host
74+
* @param hostMemoryMap a map of host IDs to the Ternary of used, reserved and total memory on each host
75+
* @param requiresStorageMotion whether storage motion is required for the virtual machine
76+
* @param preImbalance the pre-calculated cluster imbalance before migration (null to calculate it)
77+
* @param baseMetricsArray pre-calculated array of all host metrics before migration
78+
* @param hostIdToIndexMap mapping from host ID to index in the metrics array
8179
* @return a ternary containing improvement, cost, benefit
8280
*/
8381
Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm, ServiceOffering serviceOffering,
8482
Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
8583
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
86-
Boolean requiresStorageMotion) throws ConfigurationException;
84+
Boolean requiresStorageMotion, Double preImbalance,
85+
double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap) throws ConfigurationException;
8786

8887
/**
89-
* Calculates the imbalance of the cluster after a virtual machine migration.
88+
* Calculates the cluster imbalance after migrating a VM to a destination host.
9089
*
91-
* @param serviceOffering
92-
* the service offering for the virtual machine
93-
* @param vm
94-
* the virtual machine being migrated
95-
* @param destHost
96-
* the destination host for the virtual machine
97-
* @param hostCpuMap
98-
* a map of host IDs to the Ternary of used, reserved and total CPU on each host
99-
* @param hostMemoryMap
100-
* a map of host IDs to the Ternary of used, reserved and total memory on each host
90+
* @param vm the virtual machine being migrated
91+
* @param destHost the destination host for the virtual machine
92+
* @param clusterId the cluster ID
93+
* @param vmMetric the VM's resource consumption metric
94+
* @param baseMetricsArray pre-calculated array of all host metrics before migration
95+
* @param hostIdToIndexMap mapping from host ID to index in the metrics array
96+
* @return the cluster imbalance after migration
97+
*/
98+
default Double getImbalancePostMigration(VirtualMachine vm,
99+
Host destHost, Long clusterId, long vmMetric, double[] baseMetricsArray,
100+
Map<Long, Integer> hostIdToIndexMap, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
101+
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) {
102+
// Create a copy of the base array and adjust only the two affected hosts
103+
double[] adjustedMetrics = new double[baseMetricsArray.length];
104+
System.arraycopy(baseMetricsArray, 0, adjustedMetrics, 0, baseMetricsArray.length);
105+
106+
long destHostId = destHost.getId();
107+
long vmHostId = vm.getHostId();
108+
109+
// Adjust source host (remove VM resources)
110+
Integer sourceIndex = hostIdToIndexMap.get(vmHostId);
111+
if (sourceIndex != null && sourceIndex < adjustedMetrics.length) {
112+
Map<Long, Ternary<Long, Long, Long>> sourceMetricsMap = getClusterDrsMetric(clusterId).equals("cpu") ? hostCpuMap : hostMemoryMap;
113+
Ternary<Long, Long, Long> sourceMetrics = sourceMetricsMap.get(vmHostId);
114+
if (sourceMetrics != null) {
115+
adjustedMetrics[sourceIndex] = getMetricValuePostMigration(clusterId, sourceMetrics, vmMetric, vmHostId, destHostId, vmHostId);
116+
}
117+
}
118+
119+
// Adjust destination host (add VM resources)
120+
Integer destIndex = hostIdToIndexMap.get(destHostId);
121+
if (destIndex != null && destIndex < adjustedMetrics.length) {
122+
Map<Long, Ternary<Long, Long, Long>> destMetricsMap = getClusterDrsMetric(clusterId).equals("cpu") ? hostCpuMap : hostMemoryMap;
123+
Ternary<Long, Long, Long> destMetrics = destMetricsMap.get(destHostId);
124+
if (destMetrics != null) {
125+
adjustedMetrics[destIndex] = getMetricValuePostMigration(clusterId, destMetrics, vmMetric, destHostId, destHostId, vmHostId);
126+
}
127+
}
128+
129+
return calculateImbalance(adjustedMetrics);
130+
}
131+
132+
/**
133+
* Calculate imbalance from an array of metric values.
134+
* Imbalance is defined as standard deviation divided by mean.
101135
*
102-
* @return a pair containing the CPU and memory imbalance of the cluster after the migration
136+
* Uses reusable stateless calculator objects to avoid object creation overhead.
137+
* @param values array of metric values
138+
* @return calculated imbalance
103139
*/
104-
default Double getImbalancePostMigration(ServiceOffering serviceOffering, VirtualMachine vm,
105-
Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
106-
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
107-
Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair = getHostMetricsMapAndType(destHost.getClusterId(), serviceOffering, hostCpuMap, hostMemoryMap);
108-
long vmMetric = pair.first();
109-
Map<Long, Ternary<Long, Long, Long>> hostMetricsMap = pair.second();
140+
private static double calculateImbalance(double[] values) {
141+
if (values == null || values.length == 0) {
142+
return 0.0;
143+
}
110144

111-
List<Double> list = new ArrayList<>();
112-
for (Long hostId : hostMetricsMap.keySet()) {
113-
list.add(getMetricValuePostMigration(destHost.getClusterId(), hostMetricsMap.get(hostId), vmMetric, hostId, destHost.getId(), vm.getHostId()));
145+
double mean = MEAN_CALCULATOR.evaluate(values);
146+
if (mean == 0.0) {
147+
return 0.0; // Avoid division by zero
114148
}
115-
return getImbalance(list);
149+
double stdDev = STDDEV_CALCULATOR.evaluate(values, mean);
150+
return stdDev / mean;
116151
}
117152

118-
private Pair<Long, Map<Long, Ternary<Long, Long, Long>>> getHostMetricsMapAndType(Long clusterId,
119-
ServiceOffering serviceOffering, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
120-
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
153+
/**
154+
* Helper method to get VM metric based on cluster configuration.
155+
*/
156+
static long getVmMetric(ServiceOffering serviceOffering, Long clusterId) throws ConfigurationException {
121157
String metric = getClusterDrsMetric(clusterId);
122-
Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair;
123158
switch (metric) {
124159
case "cpu":
125-
pair = new Pair<>((long) serviceOffering.getCpu() * serviceOffering.getSpeed(), hostCpuMap);
126-
break;
160+
return (long) serviceOffering.getCpu() * serviceOffering.getSpeed();
127161
case "memory":
128-
pair = new Pair<>(serviceOffering.getRamSize() * 1024L * 1024L, hostMemoryMap);
129-
break;
162+
return serviceOffering.getRamSize() * 1024L * 1024L;
130163
default:
131164
throw new ConfigurationException(
132165
String.format("Invalid metric: %s for cluster: %d", metric, clusterId));
133166
}
134-
return pair;
167+
}
168+
169+
/**
170+
* Helper method to calculate metrics from pre and post imbalance values.
171+
*/
172+
default Ternary<Double, Double, Double> calculateMetricsFromImbalances(Double preImbalance, Double postImbalance) {
173+
// This needs more research to determine the cost and benefit of a migration
174+
// TODO: Cost should be a factor of the VM size and the host capacity
175+
// TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
176+
final double improvement = preImbalance - postImbalance;
177+
final double cost = 0.0;
178+
final double benefit = 1.0;
179+
return new Ternary<>(improvement, cost, benefit);
135180
}
136181

137182
private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, Long, Long> metrics, long vmMetric,
@@ -151,9 +196,26 @@ private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, Long, L
151196
}
152197

153198
private static Double getImbalance(List<Double> metricList) {
154-
Double clusterMeanMetric = getClusterMeanMetric(metricList);
155-
Double clusterStandardDeviation = getClusterStandardDeviation(metricList, clusterMeanMetric);
156-
return clusterStandardDeviation / clusterMeanMetric;
199+
if (CollectionUtils.isEmpty(metricList)) {
200+
return 0.0;
201+
}
202+
// Convert List<Double> to double[] once, avoiding repeated conversions
203+
double[] values = new double[metricList.size()];
204+
int index = 0;
205+
for (Double value : metricList) {
206+
if (value != null) {
207+
values[index++] = value;
208+
}
209+
}
210+
211+
// Trim array if some values were null
212+
if (index < values.length) {
213+
double[] trimmed = new double[index];
214+
System.arraycopy(values, 0, trimmed, 0, index);
215+
values = trimmed;
216+
}
217+
218+
return calculateImbalance(values);
157219
}
158220

159221
static String getClusterDrsMetric(long clusterId) {
@@ -181,36 +243,6 @@ static Double getMetricValue(long clusterId, long used, long free, long total, F
181243
return null;
182244
}
183245

184-
/**
185-
* Mean is the average of a collection or set of metrics. In context of a DRS
186-
* cluster, the cluster metrics defined as the average metrics value for some
187-
* metric (such as CPU, memory etc.) for every resource such as host.
188-
* Cluster Mean Metric, mavg = (∑mi) / N, where mi is a measurable metric for a
189-
* resource ‘i’ in a cluster with total N number of resources.
190-
*/
191-
static Double getClusterMeanMetric(List<Double> metricList) {
192-
return new Mean().evaluate(metricList.stream().mapToDouble(i -> i).toArray());
193-
}
194-
195-
/**
196-
* Standard deviation is defined as the square root of the absolute squared sum
197-
* of difference of a metric from its mean for every resource divided by the
198-
* total number of resources. In context of the DRS, the cluster standard
199-
* deviation is the standard deviation based on a metric of resources in a
200-
* cluster such as for the allocation or utilisation CPU/memory metric of hosts
201-
* in a cluster.
202-
* Cluster Standard Deviation, σc = sqrt((∑∣mi−mavg∣^2) / N), where mavg is the
203-
* mean metric value and mi is a measurable metric for some resource ‘i’ in the
204-
* cluster with total N number of resources.
205-
*/
206-
static Double getClusterStandardDeviation(List<Double> metricList, Double mean) {
207-
if (mean != null) {
208-
return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray(), mean);
209-
} else {
210-
return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray());
211-
}
212-
}
213-
214246
static boolean getDrsMetricUseRatio(long clusterId) {
215247
return ClusterDrsMetricUseRatio.valueIn(clusterId);
216248
}

api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616
// under the License.
1717
package org.apache.cloudstack.config;
1818

19+
import com.cloud.exception.InvalidParameterValueException;
1920
import org.apache.cloudstack.framework.config.ConfigKey;
2021
import org.apache.cloudstack.framework.config.Configurable;
22+
import org.apache.commons.lang3.StringUtils;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
2125

2226
public class ApiServiceConfiguration implements Configurable {
27+
protected static Logger LOGGER = LogManager.getLogger(ApiServiceConfiguration.class);
2328
public static final ConfigKey<String> ManagementServerAddresses = new ConfigKey<>(String.class, "host", "Advanced", "localhost", "The ip address of management server. This can also accept comma separated addresses.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null);
2429
public static final ConfigKey<String> ApiServletPath = new ConfigKey<String>("Advanced", String.class, "endpoint.url", "http://localhost:8080/client/api",
2530
"API end point. Can be used by CS components/services deployed remotely, for sending CS API requests", true);
@@ -29,6 +34,20 @@ public class ApiServiceConfiguration implements Configurable {
2934
"true", "Are the source checks on API calls enabled (true) or not (false)? See api.allowed.source.cidr.list", true, ConfigKey.Scope.Global);
3035
public static final ConfigKey<String> ApiAllowedSourceCidrList = new ConfigKey<>(String.class, "api.allowed.source.cidr.list", "Advanced",
3136
"0.0.0.0/0,::/0", "Comma separated list of IPv4/IPv6 CIDRs from which API calls can be performed. Can be set on Global and Account levels.", true, ConfigKey.Scope.Account, null, null, null, null, null, ConfigKey.Kind.CSV, null);
37+
38+
39+
public static void validateEndpointUrl() {
40+
String csUrl = getApiServletPathValue();
41+
if (StringUtils.isBlank(csUrl) || StringUtils.containsAny(csUrl, "localhost", "127.0.0.1", "[::1]")) {
42+
LOGGER.error("Global setting [{}] cannot contain localhost or be blank. Current value: {}", ApiServletPath.key(), csUrl);
43+
throw new InvalidParameterValueException("Unable to complete this operation. Contact your cloud admin.");
44+
}
45+
}
46+
47+
public static String getApiServletPathValue() {
48+
return ApiServletPath.value();
49+
}
50+
3251
@Override
3352
public String getConfigComponentName() {
3453
return ApiServiceConfiguration.class.getSimpleName();

0 commit comments

Comments
 (0)