mirror of
https://github.com/apache/cloudstack.git
synced 2025-12-15 18:12:35 +01:00
Optimize drs plan generation (#12014)
This commit is contained in:
parent
ba52db9b3e
commit
4348386970
@ -71,6 +71,8 @@ import com.cloud.alert.Alert;
|
||||
import com.cloud.capacity.Capacity;
|
||||
import com.cloud.dc.Pod;
|
||||
import com.cloud.dc.Vlan;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.exception.ConcurrentOperationException;
|
||||
import com.cloud.exception.ManagementServerException;
|
||||
import com.cloud.exception.ResourceUnavailableException;
|
||||
@ -91,6 +93,7 @@ import com.cloud.utils.Ternary;
|
||||
import com.cloud.vm.InstanceGroup;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachine.Type;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
|
||||
/**
|
||||
* Hopefully this is temporary.
|
||||
@ -452,6 +455,19 @@ public interface ManagementService {
|
||||
|
||||
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(VirtualMachine vm, Long startIndex, Long pageSize, String keyword, List<VirtualMachine> vmList);
|
||||
|
||||
/**
|
||||
* Apply affinity group constraints and other exclusion rules for VM migration.
|
||||
* This is a helper method that can be used independently for per-iteration affinity checks in DRS.
|
||||
*
|
||||
* @param vm The virtual machine to migrate
|
||||
* @param vmProfile The VM profile
|
||||
* @param plan The deployment plan
|
||||
* @param vmList List of VMs with current/simulated placements for affinity processing
|
||||
* @return ExcludeList containing hosts to avoid
|
||||
*/
|
||||
ExcludeList applyAffinityConstraints(VirtualMachine vm, VirtualMachineProfile vmProfile,
|
||||
DeploymentPlan plan, List<VirtualMachine> vmList);
|
||||
|
||||
/**
|
||||
* List storage pools for live migrating of a volume. The API returns list of all pools in the cluster to which the
|
||||
* volume can be migrated. Current pool is not included in the list. In case of vSphere datastore cluster storage pools,
|
||||
|
||||
@ -22,10 +22,10 @@ package org.apache.cloudstack.cluster;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.offering.ServiceOffering;
|
||||
import com.cloud.org.Cluster;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.Ternary;
|
||||
import com.cloud.utils.component.Adapter;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.Mean;
|
||||
import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation;
|
||||
|
||||
@ -40,6 +40,9 @@ import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetricUs
|
||||
|
||||
public interface ClusterDrsAlgorithm extends Adapter {
|
||||
|
||||
Mean MEAN_CALCULATOR = new Mean();
|
||||
StandardDeviation STDDEV_CALCULATOR = new StandardDeviation(false);
|
||||
|
||||
/**
|
||||
* Determines whether a DRS operation is needed for a given cluster and host-VM
|
||||
* mapping.
|
||||
@ -59,79 +62,121 @@ public interface ClusterDrsAlgorithm extends Adapter {
|
||||
boolean needsDrs(Cluster cluster, List<Ternary<Long, Long, Long>> cpuList,
|
||||
List<Ternary<Long, Long, Long>> memoryList) throws ConfigurationException;
|
||||
|
||||
|
||||
/**
|
||||
* Determines the metrics for a given virtual machine and destination host in a DRS cluster.
|
||||
*
|
||||
* @param clusterId
|
||||
* the ID of the cluster to check
|
||||
* @param vm
|
||||
* the virtual machine to check
|
||||
* @param serviceOffering
|
||||
* the service offering for the virtual machine
|
||||
* @param destHost
|
||||
* the destination host for the virtual machine
|
||||
* @param hostCpuMap
|
||||
* a map of host IDs to the Ternary of used, reserved and total CPU on each host
|
||||
* @param hostMemoryMap
|
||||
* a map of host IDs to the Ternary of used, reserved and total memory on each host
|
||||
* @param requiresStorageMotion
|
||||
* whether storage motion is required for the virtual machine
|
||||
* Calculates the metrics (improvement, cost, benefit) for migrating a VM to a destination host. Improvement is
|
||||
* calculated based on the change in cluster imbalance before and after the migration.
|
||||
*
|
||||
* @param cluster the cluster to check
|
||||
* @param vm the virtual machine to check
|
||||
* @param serviceOffering the service offering for the virtual machine
|
||||
* @param destHost the destination host for the virtual machine
|
||||
* @param hostCpuMap a map of host IDs to the Ternary of used, reserved and total CPU on each host
|
||||
* @param hostMemoryMap a map of host IDs to the Ternary of used, reserved and total memory on each host
|
||||
* @param requiresStorageMotion whether storage motion is required for the virtual machine
|
||||
* @param preImbalance the pre-calculated cluster imbalance before migration (null to calculate it)
|
||||
* @param baseMetricsArray pre-calculated array of all host metrics before migration
|
||||
* @param hostIdToIndexMap mapping from host ID to index in the metrics array
|
||||
* @return a ternary containing improvement, cost, benefit
|
||||
*/
|
||||
Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm, ServiceOffering serviceOffering,
|
||||
Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
|
||||
Boolean requiresStorageMotion) throws ConfigurationException;
|
||||
Boolean requiresStorageMotion, Double preImbalance,
|
||||
double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap) throws ConfigurationException;
|
||||
|
||||
/**
|
||||
* Calculates the imbalance of the cluster after a virtual machine migration.
|
||||
* Calculates the cluster imbalance after migrating a VM to a destination host.
|
||||
*
|
||||
* @param serviceOffering
|
||||
* the service offering for the virtual machine
|
||||
* @param vm
|
||||
* the virtual machine being migrated
|
||||
* @param destHost
|
||||
* the destination host for the virtual machine
|
||||
* @param hostCpuMap
|
||||
* a map of host IDs to the Ternary of used, reserved and total CPU on each host
|
||||
* @param hostMemoryMap
|
||||
* a map of host IDs to the Ternary of used, reserved and total memory on each host
|
||||
*
|
||||
* @return a pair containing the CPU and memory imbalance of the cluster after the migration
|
||||
* @param vm the virtual machine being migrated
|
||||
* @param destHost the destination host for the virtual machine
|
||||
* @param clusterId the cluster ID
|
||||
* @param vmMetric the VM's resource consumption metric
|
||||
* @param baseMetricsArray pre-calculated array of all host metrics before migration
|
||||
* @param hostIdToIndexMap mapping from host ID to index in the metrics array
|
||||
* @return the cluster imbalance after migration
|
||||
*/
|
||||
default Double getImbalancePostMigration(ServiceOffering serviceOffering, VirtualMachine vm,
|
||||
Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
|
||||
Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair = getHostMetricsMapAndType(destHost.getClusterId(), serviceOffering, hostCpuMap, hostMemoryMap);
|
||||
long vmMetric = pair.first();
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMetricsMap = pair.second();
|
||||
default Double getImbalancePostMigration(VirtualMachine vm,
|
||||
Host destHost, Long clusterId, long vmMetric, double[] baseMetricsArray,
|
||||
Map<Long, Integer> hostIdToIndexMap, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) {
|
||||
// Create a copy of the base array and adjust only the two affected hosts
|
||||
double[] adjustedMetrics = new double[baseMetricsArray.length];
|
||||
System.arraycopy(baseMetricsArray, 0, adjustedMetrics, 0, baseMetricsArray.length);
|
||||
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (Long hostId : hostMetricsMap.keySet()) {
|
||||
list.add(getMetricValuePostMigration(destHost.getClusterId(), hostMetricsMap.get(hostId), vmMetric, hostId, destHost.getId(), vm.getHostId()));
|
||||
long destHostId = destHost.getId();
|
||||
long vmHostId = vm.getHostId();
|
||||
|
||||
// Adjust source host (remove VM resources)
|
||||
Integer sourceIndex = hostIdToIndexMap.get(vmHostId);
|
||||
if (sourceIndex != null && sourceIndex < adjustedMetrics.length) {
|
||||
Map<Long, Ternary<Long, Long, Long>> sourceMetricsMap = getClusterDrsMetric(clusterId).equals("cpu") ? hostCpuMap : hostMemoryMap;
|
||||
Ternary<Long, Long, Long> sourceMetrics = sourceMetricsMap.get(vmHostId);
|
||||
if (sourceMetrics != null) {
|
||||
adjustedMetrics[sourceIndex] = getMetricValuePostMigration(clusterId, sourceMetrics, vmMetric, vmHostId, destHostId, vmHostId);
|
||||
}
|
||||
}
|
||||
return getImbalance(list);
|
||||
|
||||
// Adjust destination host (add VM resources)
|
||||
Integer destIndex = hostIdToIndexMap.get(destHostId);
|
||||
if (destIndex != null && destIndex < adjustedMetrics.length) {
|
||||
Map<Long, Ternary<Long, Long, Long>> destMetricsMap = getClusterDrsMetric(clusterId).equals("cpu") ? hostCpuMap : hostMemoryMap;
|
||||
Ternary<Long, Long, Long> destMetrics = destMetricsMap.get(destHostId);
|
||||
if (destMetrics != null) {
|
||||
adjustedMetrics[destIndex] = getMetricValuePostMigration(clusterId, destMetrics, vmMetric, destHostId, destHostId, vmHostId);
|
||||
}
|
||||
}
|
||||
|
||||
return calculateImbalance(adjustedMetrics);
|
||||
}
|
||||
|
||||
private Pair<Long, Map<Long, Ternary<Long, Long, Long>>> getHostMetricsMapAndType(Long clusterId,
|
||||
ServiceOffering serviceOffering, Map<Long, Ternary<Long, Long, Long>> hostCpuMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryMap) throws ConfigurationException {
|
||||
/**
|
||||
* Calculate imbalance from an array of metric values.
|
||||
* Imbalance is defined as standard deviation divided by mean.
|
||||
*
|
||||
* Uses reusable stateless calculator objects to avoid object creation overhead.
|
||||
* @param values array of metric values
|
||||
* @return calculated imbalance
|
||||
*/
|
||||
private static double calculateImbalance(double[] values) {
|
||||
if (values == null || values.length == 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double mean = MEAN_CALCULATOR.evaluate(values);
|
||||
if (mean == 0.0) {
|
||||
return 0.0; // Avoid division by zero
|
||||
}
|
||||
double stdDev = STDDEV_CALCULATOR.evaluate(values, mean);
|
||||
return stdDev / mean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get VM metric based on cluster configuration.
|
||||
*/
|
||||
static long getVmMetric(ServiceOffering serviceOffering, Long clusterId) throws ConfigurationException {
|
||||
String metric = getClusterDrsMetric(clusterId);
|
||||
Pair<Long, Map<Long, Ternary<Long, Long, Long>>> pair;
|
||||
switch (metric) {
|
||||
case "cpu":
|
||||
pair = new Pair<>((long) serviceOffering.getCpu() * serviceOffering.getSpeed(), hostCpuMap);
|
||||
break;
|
||||
return (long) serviceOffering.getCpu() * serviceOffering.getSpeed();
|
||||
case "memory":
|
||||
pair = new Pair<>(serviceOffering.getRamSize() * 1024L * 1024L, hostMemoryMap);
|
||||
break;
|
||||
return serviceOffering.getRamSize() * 1024L * 1024L;
|
||||
default:
|
||||
throw new ConfigurationException(
|
||||
String.format("Invalid metric: %s for cluster: %d", metric, clusterId));
|
||||
}
|
||||
return pair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to calculate metrics from pre and post imbalance values.
|
||||
*/
|
||||
default Ternary<Double, Double, Double> calculateMetricsFromImbalances(Double preImbalance, Double postImbalance) {
|
||||
// This needs more research to determine the cost and benefit of a migration
|
||||
// TODO: Cost should be a factor of the VM size and the host capacity
|
||||
// TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
|
||||
final double improvement = preImbalance - postImbalance;
|
||||
final double cost = 0.0;
|
||||
final double benefit = 1.0;
|
||||
return new Ternary<>(improvement, cost, benefit);
|
||||
}
|
||||
|
||||
private Double getMetricValuePostMigration(Long clusterId, Ternary<Long, Long, Long> metrics, long vmMetric,
|
||||
@ -151,9 +196,26 @@ public interface ClusterDrsAlgorithm extends Adapter {
|
||||
}
|
||||
|
||||
private static Double getImbalance(List<Double> metricList) {
|
||||
Double clusterMeanMetric = getClusterMeanMetric(metricList);
|
||||
Double clusterStandardDeviation = getClusterStandardDeviation(metricList, clusterMeanMetric);
|
||||
return clusterStandardDeviation / clusterMeanMetric;
|
||||
if (CollectionUtils.isEmpty(metricList)) {
|
||||
return 0.0;
|
||||
}
|
||||
// Convert List<Double> to double[] once, avoiding repeated conversions
|
||||
double[] values = new double[metricList.size()];
|
||||
int index = 0;
|
||||
for (Double value : metricList) {
|
||||
if (value != null) {
|
||||
values[index++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim array if some values were null
|
||||
if (index < values.length) {
|
||||
double[] trimmed = new double[index];
|
||||
System.arraycopy(values, 0, trimmed, 0, index);
|
||||
values = trimmed;
|
||||
}
|
||||
|
||||
return calculateImbalance(values);
|
||||
}
|
||||
|
||||
static String getClusterDrsMetric(long clusterId) {
|
||||
@ -181,36 +243,6 @@ public interface ClusterDrsAlgorithm extends Adapter {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mean is the average of a collection or set of metrics. In context of a DRS
|
||||
* cluster, the cluster metrics defined as the average metrics value for some
|
||||
* metric (such as CPU, memory etc.) for every resource such as host.
|
||||
* Cluster Mean Metric, mavg = (∑mi) / N, where mi is a measurable metric for a
|
||||
* resource ‘i’ in a cluster with total N number of resources.
|
||||
*/
|
||||
static Double getClusterMeanMetric(List<Double> metricList) {
|
||||
return new Mean().evaluate(metricList.stream().mapToDouble(i -> i).toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard deviation is defined as the square root of the absolute squared sum
|
||||
* of difference of a metric from its mean for every resource divided by the
|
||||
* total number of resources. In context of the DRS, the cluster standard
|
||||
* deviation is the standard deviation based on a metric of resources in a
|
||||
* cluster such as for the allocation or utilisation CPU/memory metric of hosts
|
||||
* in a cluster.
|
||||
* Cluster Standard Deviation, σc = sqrt((∑∣mi−mavg∣^2) / N), where mavg is the
|
||||
* mean metric value and mi is a measurable metric for some resource ‘i’ in the
|
||||
* cluster with total N number of resources.
|
||||
*/
|
||||
static Double getClusterStandardDeviation(List<Double> metricList, Double mean) {
|
||||
if (mean != null) {
|
||||
return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray(), mean);
|
||||
} else {
|
||||
return new StandardDeviation(false).evaluate(metricList.stream().mapToDouble(i -> i).toArray());
|
||||
}
|
||||
}
|
||||
|
||||
static boolean getDrsMetricUseRatio(long clusterId) {
|
||||
return ClusterDrsMetricUseRatio.valueIn(clusterId);
|
||||
}
|
||||
|
||||
@ -68,23 +68,26 @@ public class Balanced extends AdapterBase implements ClusterDrsAlgorithm {
|
||||
return "balanced";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm,
|
||||
ServiceOffering serviceOffering, Host destHost,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
|
||||
Boolean requiresStorageMotion) throws ConfigurationException {
|
||||
Double preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), null);
|
||||
Double postImbalance = getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuMap, hostMemoryMap);
|
||||
Boolean requiresStorageMotion, Double preImbalance,
|
||||
double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap) throws ConfigurationException {
|
||||
// Use provided pre-imbalance if available, otherwise calculate it
|
||||
if (preImbalance == null) {
|
||||
preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()), new ArrayList<>(hostMemoryMap.values()), null);
|
||||
}
|
||||
|
||||
logger.debug("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost: {} destHost: {}",
|
||||
// Use optimized post-imbalance calculation that adjusts only affected hosts
|
||||
Double postImbalance = getImbalancePostMigration(vm, destHost,
|
||||
cluster.getId(), ClusterDrsAlgorithm.getVmMetric(serviceOffering, cluster.getId()),
|
||||
baseMetricsArray, hostIdToIndexMap, hostCpuMap, hostMemoryMap);
|
||||
|
||||
logger.trace("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost ID: {} destHost: {}",
|
||||
cluster, preImbalance, postImbalance, getName(), vm, vm.getHostId(), destHost);
|
||||
|
||||
// This needs more research to determine the cost and benefit of a migration
|
||||
// TODO: Cost should be a factor of the VM size and the host capacity
|
||||
// TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
|
||||
final double improvement = preImbalance - postImbalance;
|
||||
final double cost = 0.0;
|
||||
final double benefit = 1.0;
|
||||
return new Ternary<>(improvement, cost, benefit);
|
||||
return calculateMetricsFromImbalances(preImbalance, postImbalance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterDrsMetric;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterImbalance;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getMetricValue;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@ -119,6 +122,48 @@ public class BalancedTest {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prepare metrics data for getMetrics calls with optimized signature.
|
||||
* Calculates pre-imbalance and builds baseMetricsArray and hostIdToIndexMap.
|
||||
*
|
||||
* @return a Ternary containing preImbalance, baseMetricsArray, and hostIdToIndexMap
|
||||
*/
|
||||
private Ternary<Double, double[], Map<Long, Integer>> prepareMetricsData() throws ConfigurationException {
|
||||
// Calculate pre-imbalance
|
||||
Double preImbalance = getClusterImbalance(clusterId, new ArrayList<>(hostCpuFreeMap.values()),
|
||||
new ArrayList<>(hostMemoryFreeMap.values()), null);
|
||||
|
||||
// Build baseMetricsArray and hostIdToIndexMap
|
||||
String metricType = getClusterDrsMetric(clusterId);
|
||||
Map<Long, Ternary<Long, Long, Long>> baseMetricsMap = "cpu".equals(metricType) ? hostCpuFreeMap : hostMemoryFreeMap;
|
||||
double[] baseMetricsArray = new double[baseMetricsMap.size()];
|
||||
Map<Long, Integer> hostIdToIndexMap = new HashMap<>();
|
||||
|
||||
int index = 0;
|
||||
for (Map.Entry<Long, Ternary<Long, Long, Long>> entry : baseMetricsMap.entrySet()) {
|
||||
Long hostId = entry.getKey();
|
||||
Ternary<Long, Long, Long> metrics = entry.getValue();
|
||||
long used = metrics.first();
|
||||
long actualTotal = metrics.third() - metrics.second();
|
||||
long free = actualTotal - metrics.first();
|
||||
Double metricValue = getMetricValue(clusterId, used, free, actualTotal, null);
|
||||
if (metricValue != null) {
|
||||
baseMetricsArray[index] = metricValue;
|
||||
hostIdToIndexMap.put(hostId, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim array if some values were null
|
||||
if (index < baseMetricsArray.length) {
|
||||
double[] trimmed = new double[index];
|
||||
System.arraycopy(baseMetricsArray, 0, trimmed, 0, index);
|
||||
baseMetricsArray = trimmed;
|
||||
}
|
||||
|
||||
return new Ternary<>(preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>needsDrs tests</b>
|
||||
* <p>Scenarios to test for needsDrs
|
||||
@ -183,8 +228,14 @@ public class BalancedTest {
|
||||
@Test
|
||||
public void getMetricsWithCpu() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
|
||||
overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
|
||||
|
||||
Ternary<Double, double[], Map<Long, Integer>> metricsData = prepareMetricsData();
|
||||
Double preImbalance = metricsData.first();
|
||||
double[] baseMetricsArray = metricsData.second();
|
||||
Map<Long, Integer> hostIdToIndexMap = metricsData.third();
|
||||
|
||||
Ternary<Double, Double, Double> result = balanced.getMetrics(cluster, vm3, serviceOffering, destHost,
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false);
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false, preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
assertEquals(0.0, result.first(), 0.01);
|
||||
assertEquals(0.0, result.second(), 0.0);
|
||||
assertEquals(1.0, result.third(), 0.0);
|
||||
@ -197,8 +248,14 @@ public class BalancedTest {
|
||||
@Test
|
||||
public void getMetricsWithMemory() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
|
||||
overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
|
||||
|
||||
Ternary<Double, double[], Map<Long, Integer>> metricsData = prepareMetricsData();
|
||||
Double preImbalance = metricsData.first();
|
||||
double[] baseMetricsArray = metricsData.second();
|
||||
Map<Long, Integer> hostIdToIndexMap = metricsData.third();
|
||||
|
||||
Ternary<Double, Double, Double> result = balanced.getMetrics(cluster, vm3, serviceOffering, destHost,
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false);
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false, preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
assertEquals(0.4, result.first(), 0.01);
|
||||
assertEquals(0, result.second(), 0.0);
|
||||
assertEquals(1, result.third(), 0.0);
|
||||
|
||||
@ -75,20 +75,22 @@ public class Condensed extends AdapterBase implements ClusterDrsAlgorithm {
|
||||
public Ternary<Double, Double, Double> getMetrics(Cluster cluster, VirtualMachine vm,
|
||||
ServiceOffering serviceOffering, Host destHost,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap,
|
||||
Boolean requiresStorageMotion) throws ConfigurationException {
|
||||
Double preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()),
|
||||
new ArrayList<>(hostMemoryMap.values()), null);
|
||||
Double postImbalance = getImbalancePostMigration(serviceOffering, vm, destHost, hostCpuMap, hostMemoryMap);
|
||||
Boolean requiresStorageMotion, Double preImbalance,
|
||||
double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap) throws ConfigurationException {
|
||||
// Use provided pre-imbalance if available, otherwise calculate it
|
||||
if (preImbalance == null) {
|
||||
preImbalance = ClusterDrsAlgorithm.getClusterImbalance(cluster.getId(), new ArrayList<>(hostCpuMap.values()),
|
||||
new ArrayList<>(hostMemoryMap.values()), null);
|
||||
}
|
||||
|
||||
logger.debug("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost: {} destHost: {}",
|
||||
// Use optimized post-imbalance calculation that adjusts only affected hosts
|
||||
Double postImbalance = getImbalancePostMigration(vm, destHost,
|
||||
cluster.getId(), ClusterDrsAlgorithm.getVmMetric(serviceOffering, cluster.getId()),
|
||||
baseMetricsArray, hostIdToIndexMap, hostCpuMap, hostMemoryMap);
|
||||
|
||||
logger.trace("Cluster {} pre-imbalance: {} post-imbalance: {} Algorithm: {} VM: {} srcHost ID: {} destHost: {}",
|
||||
cluster, preImbalance, postImbalance, getName(), vm, vm.getHostId(), destHost);
|
||||
|
||||
// This needs more research to determine the cost and benefit of a migration
|
||||
// TODO: Cost should be a factor of the VM size and the host capacity
|
||||
// TODO: Benefit should be a factor of the VM size and the host capacity and the number of VMs on the host
|
||||
final double improvement = postImbalance - preImbalance;
|
||||
final double cost = 0;
|
||||
final double benefit = 1;
|
||||
return new Ternary<>(improvement, cost, benefit);
|
||||
return calculateMetricsFromImbalances(postImbalance, preImbalance);
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,6 +43,9 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterDrsMetric;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterImbalance;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getMetricValue;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsImbalanceThreshold;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMetric;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@ -121,6 +124,48 @@ public class CondensedTest {
|
||||
closeable.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to prepare metrics data for getMetrics calls with optimized signature.
|
||||
* Calculates pre-imbalance and builds baseMetricsArray and hostIdToIndexMap.
|
||||
*
|
||||
* @return a Ternary containing preImbalance, baseMetricsArray, and hostIdToIndexMap
|
||||
*/
|
||||
private Ternary<Double, double[], Map<Long, Integer>> prepareMetricsData() throws ConfigurationException {
|
||||
// Calculate pre-imbalance
|
||||
Double preImbalance = getClusterImbalance(clusterId, new ArrayList<>(hostCpuFreeMap.values()),
|
||||
new ArrayList<>(hostMemoryFreeMap.values()), null);
|
||||
|
||||
// Build baseMetricsArray and hostIdToIndexMap
|
||||
String metricType = getClusterDrsMetric(clusterId);
|
||||
Map<Long, Ternary<Long, Long, Long>> baseMetricsMap = "cpu".equals(metricType) ? hostCpuFreeMap : hostMemoryFreeMap;
|
||||
double[] baseMetricsArray = new double[baseMetricsMap.size()];
|
||||
Map<Long, Integer> hostIdToIndexMap = new HashMap<>();
|
||||
|
||||
int index = 0;
|
||||
for (Map.Entry<Long, Ternary<Long, Long, Long>> entry : baseMetricsMap.entrySet()) {
|
||||
Long hostId = entry.getKey();
|
||||
Ternary<Long, Long, Long> metrics = entry.getValue();
|
||||
long used = metrics.first();
|
||||
long actualTotal = metrics.third() - metrics.second();
|
||||
long free = actualTotal - metrics.first();
|
||||
Double metricValue = getMetricValue(clusterId, used, free, actualTotal, null);
|
||||
if (metricValue != null) {
|
||||
baseMetricsArray[index] = metricValue;
|
||||
hostIdToIndexMap.put(hostId, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim array if some values were null
|
||||
if (index < baseMetricsArray.length) {
|
||||
double[] trimmed = new double[index];
|
||||
System.arraycopy(baseMetricsArray, 0, trimmed, 0, index);
|
||||
baseMetricsArray = trimmed;
|
||||
}
|
||||
|
||||
return new Ternary<>(preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>needsDrs tests
|
||||
* <p>Scenarios to test for needsDrs
|
||||
@ -185,8 +230,14 @@ public class CondensedTest {
|
||||
@Test
|
||||
public void getMetricsWithCpu() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
|
||||
overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "cpu");
|
||||
|
||||
Ternary<Double, double[], Map<Long, Integer>> metricsData = prepareMetricsData();
|
||||
Double preImbalance = metricsData.first();
|
||||
double[] baseMetricsArray = metricsData.second();
|
||||
Map<Long, Integer> hostIdToIndexMap = metricsData.third();
|
||||
|
||||
Ternary<Double, Double, Double> result = condensed.getMetrics(cluster, vm3, serviceOffering, destHost,
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false);
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false, preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
assertEquals(0.0, result.first(), 0.0);
|
||||
assertEquals(0, result.second(), 0.0);
|
||||
assertEquals(1, result.third(), 0.0);
|
||||
@ -199,8 +250,14 @@ public class CondensedTest {
|
||||
@Test
|
||||
public void getMetricsWithMemory() throws NoSuchFieldException, IllegalAccessException, ConfigurationException {
|
||||
overrideDefaultConfigValue(ClusterDrsMetric, "_defaultValue", "memory");
|
||||
|
||||
Ternary<Double, double[], Map<Long, Integer>> metricsData = prepareMetricsData();
|
||||
Double preImbalance = metricsData.first();
|
||||
double[] baseMetricsArray = metricsData.second();
|
||||
Map<Long, Integer> hostIdToIndexMap = metricsData.third();
|
||||
|
||||
Ternary<Double, Double, Double> result = condensed.getMetrics(cluster, vm3, serviceOffering, destHost,
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false);
|
||||
hostCpuFreeMap, hostMemoryFreeMap, false, preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
assertEquals(-0.4, result.first(), 0.01);
|
||||
assertEquals(0, result.second(), 0.0);
|
||||
assertEquals(1, result.third(), 0.0);
|
||||
|
||||
@ -692,6 +692,7 @@ import com.cloud.dc.dao.HostPodDao;
|
||||
import com.cloud.dc.dao.PodVlanMapDao;
|
||||
import com.cloud.dc.dao.VlanDao;
|
||||
import com.cloud.deploy.DataCenterDeployment;
|
||||
import com.cloud.deploy.DeploymentPlan;
|
||||
import com.cloud.deploy.DeploymentPlanner;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.deploy.DeploymentPlanningManager;
|
||||
@ -1454,17 +1455,27 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(final VirtualMachine vm, final Long startIndex, final Long pageSize,
|
||||
final String keyword, List<VirtualMachine> vmList) {
|
||||
|
||||
validateVmForHostMigration(vm);
|
||||
/**
|
||||
* Get technically compatible hosts for VM migration (storage, hypervisor, UEFI filtering).
|
||||
* This determines which hosts are technically capable of hosting the VM based on
|
||||
* storage requirements, hypervisor capabilities, and UEFI requirements.
|
||||
*
|
||||
* @param vm The virtual machine to migrate
|
||||
* @param startIndex Starting index for pagination
|
||||
* @param pageSize Page size for pagination
|
||||
* @param keyword Keyword filter for host search
|
||||
* @return Ternary containing: (all hosts with count, filtered compatible hosts, storage motion requirements map)
|
||||
*/
|
||||
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> getTechnicallyCompatibleHosts(
|
||||
final VirtualMachine vm,
|
||||
final Long startIndex,
|
||||
final Long pageSize,
|
||||
final String keyword) {
|
||||
|
||||
// GPU check
|
||||
if (_serviceOfferingDetailsDao.findDetail(vm.getServiceOfferingId(), GPU.Keys.pciDevice.toString()) != null) {
|
||||
logger.info(" Live Migration of GPU enabled VM : " + vm.getInstanceName() + " is not supported");
|
||||
// Return empty list.
|
||||
return new Ternary<>(new Pair<>(new ArrayList<>(), 0),
|
||||
new ArrayList<>(), new HashMap<>());
|
||||
logger.info("Live Migration of GPU enabled VM : {} is not supported", vm);
|
||||
return new Ternary<>(new Pair<>(new ArrayList<>(), 0), new ArrayList<>(), new HashMap<>());
|
||||
}
|
||||
|
||||
final long srcHostId = vm.getHostId();
|
||||
@ -1478,6 +1489,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
ex.addProxyObject(vm.getUuid(), "vmId");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
String srcHostVersion = srcHost.getHypervisorVersion();
|
||||
if (HypervisorType.KVM.equals(srcHost.getHypervisorType()) && srcHostVersion == null) {
|
||||
srcHostVersion = "";
|
||||
@ -1513,7 +1525,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
List<HostVO> allHosts = null;
|
||||
List<HostVO> filteredHosts = null;
|
||||
final Map<Host, Boolean> requiresStorageMotion = new HashMap<>();
|
||||
DataCenterDeployment plan = null;
|
||||
|
||||
if (canMigrateWithStorage) {
|
||||
Long podId = !VirtualMachine.Type.User.equals(vm.getType()) ? srcHost.getPodId() : null;
|
||||
allHostsPair = searchForServers(startIndex, pageSize, null, hostType, null, srcHost.getDataCenterId(), podId, null, null, keyword,
|
||||
@ -1562,7 +1574,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
if (CollectionUtils.isEmpty(filteredHosts)) {
|
||||
return new Ternary<>(new Pair<>(allHosts, allHostsPair.second()), new ArrayList<>(), new HashMap<>());
|
||||
}
|
||||
plan = new DataCenterDeployment(srcHost.getDataCenterId(), podId, null, null, null, null);
|
||||
} else {
|
||||
final Long cluster = srcHost.getClusterId();
|
||||
if (logger.isDebugEnabled()) {
|
||||
@ -1571,22 +1582,38 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
allHostsPair = searchForServers(startIndex, pageSize, null, hostType, null, null, null, cluster, null, keyword, null, null, null,
|
||||
null, srcHost.getId());
|
||||
allHosts = allHostsPair.first();
|
||||
plan = new DataCenterDeployment(srcHost.getDataCenterId(), srcHost.getPodId(), srcHost.getClusterId(), null, null, null);
|
||||
filteredHosts = allHosts;
|
||||
}
|
||||
|
||||
final Pair<List<? extends Host>, Integer> otherHosts = new Pair<>(allHosts, allHostsPair.second());
|
||||
final Pair<List<? extends Host>, Integer> allHostsPairResult = new Pair<>(allHosts, allHostsPair.second());
|
||||
Pair<Boolean, List<HostVO>> uefiFilteredResult = filterUefiHostsForMigration(allHosts, filteredHosts, vm);
|
||||
if (!uefiFilteredResult.first()) {
|
||||
return new Ternary<>(otherHosts, new ArrayList<>(), new HashMap<>());
|
||||
return new Ternary<>(allHostsPairResult, new ArrayList<>(), new HashMap<>());
|
||||
}
|
||||
filteredHosts = uefiFilteredResult.second();
|
||||
|
||||
List<Host> suitableHosts = new ArrayList<>();
|
||||
return new Ternary<>(allHostsPairResult, filteredHosts, requiresStorageMotion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply affinity group constraints and other exclusion rules for VM migration.
|
||||
* This builds an ExcludeList based on affinity groups, DPDK requirements, and dedicated resources.
|
||||
*
|
||||
* @param vm The virtual machine to migrate
|
||||
* @param vmProfile The VM profile
|
||||
* @param plan The deployment plan
|
||||
* @param vmList List of VMs with current/simulated placements for affinity processing
|
||||
* @return ExcludeList containing hosts to avoid
|
||||
*/
|
||||
@Override
|
||||
public ExcludeList applyAffinityConstraints(VirtualMachine vm, VirtualMachineProfile vmProfile, DeploymentPlan plan, List<VirtualMachine> vmList) {
|
||||
final ExcludeList excludes = new ExcludeList();
|
||||
excludes.addHost(srcHostId);
|
||||
excludes.addHost(vm.getHostId());
|
||||
|
||||
if (dpdkHelper.isVMDpdkEnabled(vm.getId())) {
|
||||
excludeNonDPDKEnabledHosts(plan, excludes);
|
||||
if (plan instanceof DataCenterDeployment) {
|
||||
excludeNonDPDKEnabledHosts((DataCenterDeployment) plan, excludes);
|
||||
}
|
||||
}
|
||||
|
||||
// call affinitygroup chain
|
||||
@ -1599,13 +1626,37 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
}
|
||||
|
||||
if (vm.getType() == VirtualMachine.Type.User || vm.getType() == VirtualMachine.Type.DomainRouter) {
|
||||
final DataCenterVO dc = _dcDao.findById(srcHost.getDataCenterId());
|
||||
final DataCenterVO dc = _dcDao.findById(plan.getDataCenterId());
|
||||
_dpMgr.checkForNonDedicatedResources(vmProfile, dc, excludes);
|
||||
}
|
||||
|
||||
return excludes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hosts with available capacity using host allocators, and apply architecture filtering.
|
||||
*
|
||||
* @param vm The virtual machine (for architecture filtering)
|
||||
* @param vmProfile The VM profile
|
||||
* @param plan The deployment plan
|
||||
* @param compatibleHosts List of technically compatible hosts
|
||||
* @param excludes ExcludeList with hosts to avoid
|
||||
* @param srcHost Source host (for architecture filtering)
|
||||
* @return List of suitable hosts with capacity
|
||||
*/
|
||||
protected List<Host> getCapableSuitableHosts(
|
||||
final VirtualMachine vm,
|
||||
final VirtualMachineProfile vmProfile,
|
||||
final DataCenterDeployment plan,
|
||||
final List<? extends Host> compatibleHosts,
|
||||
final ExcludeList excludes,
|
||||
final Host srcHost) {
|
||||
|
||||
List<Host> suitableHosts = new ArrayList<>();
|
||||
|
||||
for (final HostAllocator allocator : hostAllocators) {
|
||||
if (CollectionUtils.isNotEmpty(filteredHosts)) {
|
||||
suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, filteredHosts, HostAllocator.RETURN_UPTO_ALL, false);
|
||||
if (CollectionUtils.isNotEmpty(compatibleHosts)) {
|
||||
suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, compatibleHosts, HostAllocator.RETURN_UPTO_ALL, false);
|
||||
} else {
|
||||
suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false);
|
||||
}
|
||||
@ -1631,6 +1682,43 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
}
|
||||
}
|
||||
|
||||
return suitableHosts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> listHostsForMigrationOfVM(final VirtualMachine vm, final Long startIndex, final Long pageSize,
|
||||
final String keyword, List<VirtualMachine> vmList) {
|
||||
|
||||
validateVmForHostMigration(vm);
|
||||
|
||||
// Get technically compatible hosts (storage, hypervisor, UEFI)
|
||||
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> compatibilityResult =
|
||||
getTechnicallyCompatibleHosts(vm, startIndex, pageSize, keyword);
|
||||
|
||||
Pair<List<? extends Host>, Integer> allHostsPair = compatibilityResult.first();
|
||||
List<? extends Host> filteredHosts = compatibilityResult.second();
|
||||
Map<Host, Boolean> requiresStorageMotion = compatibilityResult.third();
|
||||
|
||||
// If no compatible hosts, return early
|
||||
if (CollectionUtils.isEmpty(filteredHosts)) {
|
||||
final Pair<List<? extends Host>, Integer> otherHosts = new Pair<>(allHostsPair.first(), allHostsPair.second());
|
||||
return new Ternary<>(otherHosts, new ArrayList<>(), requiresStorageMotion);
|
||||
}
|
||||
|
||||
// Create deployment plan and VM profile
|
||||
final Host srcHost = _hostDao.findById(vm.getHostId());
|
||||
final DataCenterDeployment plan = new DataCenterDeployment(
|
||||
srcHost.getDataCenterId(), srcHost.getPodId(), srcHost.getClusterId(), null, null, null);
|
||||
final VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(
|
||||
vm, null, _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()), null, null);
|
||||
|
||||
// Apply affinity constraints
|
||||
final ExcludeList excludes = applyAffinityConstraints(vm, vmProfile, plan, vmList);
|
||||
|
||||
// Get hosts with capacity
|
||||
List<Host> suitableHosts = getCapableSuitableHosts(vm, vmProfile, plan, filteredHosts, excludes, srcHost);
|
||||
|
||||
final Pair<List<? extends Host>, Integer> otherHosts = new Pair<>(allHostsPair.first(), allHostsPair.second());
|
||||
return new Ternary<>(otherHosts, suitableHosts, requiresStorageMotion);
|
||||
}
|
||||
|
||||
@ -1923,7 +2011,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
|
||||
return suitablePools;
|
||||
}
|
||||
|
||||
private Pair<List<HostVO>, Integer> searchForServers(final Long startIndex, final Long pageSize, final Object name, final Object type,
|
||||
Pair<List<HostVO>, Integer> searchForServers(final Long startIndex, final Long pageSize, final Object name, final Object type,
|
||||
final Object state, final Object zone, final Object pod, final Object cluster, final Object id, final Object keyword,
|
||||
final Object resourceState, final Object haHosts, final Object hypervisorType, final Object hypervisorVersion, final Object... excludes) {
|
||||
final Filter searchFilter = new Filter(HostVO.class, "id", Boolean.TRUE, startIndex, pageSize);
|
||||
|
||||
@ -24,6 +24,8 @@ import com.cloud.api.query.dao.HostJoinDao;
|
||||
import com.cloud.api.query.vo.HostJoinVO;
|
||||
import com.cloud.dc.ClusterVO;
|
||||
import com.cloud.dc.dao.ClusterDao;
|
||||
import com.cloud.deploy.DataCenterDeployment;
|
||||
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.event.ActionEventUtils;
|
||||
import com.cloud.event.EventTypes;
|
||||
@ -51,6 +53,8 @@ import com.cloud.utils.db.TransactionCallback;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VirtualMachineProfile;
|
||||
import com.cloud.vm.VirtualMachineProfileImpl;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
@ -62,6 +66,8 @@ import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd;
|
||||
import org.apache.cloudstack.api.response.ClusterDrsPlanMigrationResponse;
|
||||
import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.affinity.AffinityGroupVMMapVO;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.apache.cloudstack.cluster.dao.ClusterDrsPlanDao;
|
||||
import org.apache.cloudstack.cluster.dao.ClusterDrsPlanMigrationDao;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
@ -71,6 +77,7 @@ import org.apache.cloudstack.framework.jobs.AsyncJobManager;
|
||||
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
|
||||
import org.apache.cloudstack.jobs.JobInfo;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.apache.commons.collections.MapUtils;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
@ -81,13 +88,18 @@ import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.cloud.org.Grouping.AllocationState.Disabled;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterImbalance;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getClusterDrsMetric;
|
||||
import static org.apache.cloudstack.cluster.ClusterDrsAlgorithm.getMetricValue;
|
||||
|
||||
public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsService, PluggableService {
|
||||
|
||||
@ -125,6 +137,9 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
@Inject
|
||||
ManagementServer managementServer;
|
||||
|
||||
@Inject
|
||||
AffinityGroupVMMapDao affinityGroupVMMapDao;
|
||||
|
||||
List<ClusterDrsAlgorithm> drsAlgorithms = new ArrayList<>();
|
||||
|
||||
Map<String, ClusterDrsAlgorithm> drsAlgorithmMap = new HashMap<>();
|
||||
@ -318,19 +333,14 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
* @throws ConfigurationException
|
||||
* If there is an error in the DRS configuration.
|
||||
*/
|
||||
List<Ternary<VirtualMachine, Host, Host>> getDrsPlan(Cluster cluster,
|
||||
int maxIterations) throws ConfigurationException {
|
||||
List<Ternary<VirtualMachine, Host, Host>> migrationPlan = new ArrayList<>();
|
||||
List<Ternary<VirtualMachine, Host, Host>> getDrsPlan(Cluster cluster, int maxIterations) throws ConfigurationException {
|
||||
|
||||
if (cluster.getAllocationState() == Disabled || maxIterations <= 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
ClusterDrsAlgorithm algorithm = getDrsAlgorithm(ClusterDrsAlgorithm.valueIn(cluster.getId()));
|
||||
List<HostVO> hostList = hostDao.findByClusterId(cluster.getId());
|
||||
List<VirtualMachine> vmList = new ArrayList<>(vmInstanceDao.listByClusterId(cluster.getId()));
|
||||
|
||||
int iteration = 0;
|
||||
|
||||
Map<Long, Host> hostMap = hostList.stream().collect(Collectors.toMap(HostVO::getId, host -> host));
|
||||
|
||||
Map<Long, List<VirtualMachine>> hostVmMap = getHostVmMap(hostList, vmList);
|
||||
@ -357,10 +367,39 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()));
|
||||
}
|
||||
|
||||
Pair<Map<Long, List<? extends Host>>, Map<Long, Map<Host, Boolean>>> hostCache = getCompatibleHostAndVmStorageMotionCache(vmList);
|
||||
Map<Long, List<? extends Host>> vmToCompatibleHostsCache = hostCache.first();
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = hostCache.second();
|
||||
|
||||
Set<Long> vmsWithAffinityGroups = getVmsWithAffinityGroups(vmList, vmToCompatibleHostsCache);
|
||||
|
||||
return getMigrationPlans(maxIterations, cluster, hostMap, vmList, vmsWithAffinityGroups, vmToCompatibleHostsCache,
|
||||
vmToStorageMotionCache, vmIdServiceOfferingMap, originalHostIdVmIdMap, hostVmMap, hostCpuMap, hostMemoryMap);
|
||||
}
|
||||
|
||||
private List<Ternary<VirtualMachine, Host, Host>> getMigrationPlans(
|
||||
long maxIterations, Cluster cluster, Map<Long, Host> hostMap, List<VirtualMachine> vmList,
|
||||
Set<Long> vmsWithAffinityGroups, Map<Long, List<? extends Host>> vmToCompatibleHostsCache,
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache, Map<Long, ServiceOffering> vmIdServiceOfferingMap,
|
||||
Map<Long, List<Long>> originalHostIdVmIdMap, Map<Long, List<VirtualMachine>> hostVmMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuMap, Map<Long, Ternary<Long, Long, Long>> hostMemoryMap
|
||||
) throws ConfigurationException {
|
||||
ClusterDrsAlgorithm algorithm = getDrsAlgorithm(ClusterDrsAlgorithm.valueIn(cluster.getId()));
|
||||
int iteration = 0;
|
||||
List<Ternary<VirtualMachine, Host, Host>> migrationPlan = new ArrayList<>();
|
||||
while (iteration < maxIterations && algorithm.needsDrs(cluster, new ArrayList<>(hostCpuMap.values()),
|
||||
new ArrayList<>(hostMemoryMap.values()))) {
|
||||
|
||||
logger.debug("Starting DRS iteration {} for cluster {}", iteration + 1, cluster);
|
||||
// Re-evaluate affinity constraints with current (simulated) VM placements
|
||||
Map<Long, ExcludeList> vmToExcludesMap = getVmToExcludesMap(vmList, hostMap, vmsWithAffinityGroups,
|
||||
vmToCompatibleHostsCache, vmIdServiceOfferingMap);
|
||||
|
||||
logger.debug("Completed affinity evaluation for DRS iteration {} for cluster {}", iteration + 1, cluster);
|
||||
|
||||
Pair<VirtualMachine, Host> bestMigration = getBestMigration(cluster, algorithm, vmList,
|
||||
vmIdServiceOfferingMap, hostCpuMap, hostMemoryMap);
|
||||
vmIdServiceOfferingMap, hostCpuMap, hostMemoryMap,
|
||||
vmToCompatibleHostsCache, vmToStorageMotionCache, vmToExcludesMap);
|
||||
VirtualMachine vm = bestMigration.first();
|
||||
Host destHost = bestMigration.second();
|
||||
if (destHost == null || vm == null || originalHostIdVmIdMap.get(destHost.getId()).contains(vm.getId())) {
|
||||
@ -372,8 +411,6 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
ServiceOffering serviceOffering = vmIdServiceOfferingMap.get(vm.getId());
|
||||
migrationPlan.add(new Ternary<>(vm, hostMap.get(vm.getHostId()), hostMap.get(destHost.getId())));
|
||||
|
||||
hostVmMap.get(vm.getHostId()).remove(vm);
|
||||
hostVmMap.get(destHost.getId()).add(vm);
|
||||
hostVmMap.get(vm.getHostId()).remove(vm);
|
||||
hostVmMap.get(destHost.getId()).add(vm);
|
||||
|
||||
@ -391,6 +428,106 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
return migrationPlan;
|
||||
}
|
||||
|
||||
private Map<Long, ExcludeList> getVmToExcludesMap(List<VirtualMachine> vmList, Map<Long, Host> hostMap,
|
||||
Set<Long> vmsWithAffinityGroups, Map<Long, List<? extends Host>> vmToCompatibleHostsCache,
|
||||
Map<Long, ServiceOffering> vmIdServiceOfferingMap) {
|
||||
Map<Long, ExcludeList> vmToExcludesMap = new HashMap<>();
|
||||
for (VirtualMachine vm : vmList) {
|
||||
if (vmToCompatibleHostsCache.containsKey(vm.getId())) {
|
||||
Host srcHost = hostMap.get(vm.getHostId());
|
||||
if (srcHost != null) {
|
||||
// Only call expensive applyAffinityConstraints for VMs with affinity groups
|
||||
// For VMs without affinity groups, create minimal ExcludeList (just source host)
|
||||
ExcludeList excludes;
|
||||
if (vmsWithAffinityGroups.contains(vm.getId())) {
|
||||
DataCenterDeployment plan = new DataCenterDeployment(
|
||||
srcHost.getDataCenterId(), srcHost.getPodId(), srcHost.getClusterId(),
|
||||
null, null, null);
|
||||
VirtualMachineProfile vmProfile = new VirtualMachineProfileImpl(vm, null,
|
||||
vmIdServiceOfferingMap.get(vm.getId()), null, null);
|
||||
|
||||
excludes = managementServer.applyAffinityConstraints(
|
||||
vm, vmProfile, plan, vmList);
|
||||
} else {
|
||||
// VM has no affinity groups - create minimal ExcludeList (just source host)
|
||||
excludes = new ExcludeList();
|
||||
excludes.addHost(vm.getHostId());
|
||||
}
|
||||
vmToExcludesMap.put(vm.getId(), excludes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return vmToExcludesMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pre-compute suitable hosts (once per eligible VM - never changes)
|
||||
* Use listHostsForMigrationOfVM to get hosts validated by getCapableSuitableHosts
|
||||
* This ensures DRS uses the same validation as "find host for migration" command
|
||||
*
|
||||
* @param vmList List of VMs to pre-compute suitable hosts for
|
||||
* @return Pair of VM to compatible hosts map and VM to storage motion requirement map
|
||||
*/
|
||||
private Pair<Map<Long, List<? extends Host>>, Map<Long, Map<Host, Boolean>>> getCompatibleHostAndVmStorageMotionCache(
|
||||
List<VirtualMachine> vmList
|
||||
) {
|
||||
Map<Long, List<? extends Host>> vmToCompatibleHostsCache = new HashMap<>();
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = new HashMap<>();
|
||||
|
||||
for (VirtualMachine vm : vmList) {
|
||||
// Skip ineligible VMs
|
||||
if (vm.getType().isUsedBySystem() ||
|
||||
vm.getState() != VirtualMachine.State.Running ||
|
||||
(MapUtils.isNotEmpty(vm.getDetails()) &&
|
||||
"true".equalsIgnoreCase(vm.getDetails().get(VmDetailConstants.SKIP_DRS)))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use listHostsForMigrationOfVM to get suitable hosts (validated by getCapableSuitableHosts)
|
||||
// This ensures the same validation as the "find host for migration" command
|
||||
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> hostsForMigration =
|
||||
managementServer.listHostsForMigrationOfVM(vm, 0L, 500L, null, vmList);
|
||||
|
||||
List<? extends Host> suitableHosts = hostsForMigration.second(); // Get suitable hosts (validated by HostAllocator)
|
||||
Map<Host, Boolean> requiresStorageMotion = hostsForMigration.third();
|
||||
|
||||
if (suitableHosts != null && !suitableHosts.isEmpty()) {
|
||||
vmToCompatibleHostsCache.put(vm.getId(), suitableHosts);
|
||||
vmToStorageMotionCache.put(vm.getId(), requiresStorageMotion);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Could not get suitable hosts for VM {}: {}", vm, e.getMessage());
|
||||
}
|
||||
}
|
||||
return new Pair<>(vmToCompatibleHostsCache, vmToStorageMotionCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fetch affinity group mappings for all eligible VMs (once, before iterations)
|
||||
* This allows us to skip expensive affinity processing for VMs without affinity groups
|
||||
*
|
||||
* @param vmList List of VMs to check for affinity groups
|
||||
* @param vmToCompatibleHostsCache Cached map of VM IDs to their compatible hosts
|
||||
* @return Set of VM IDs that have affinity groups
|
||||
*/
|
||||
private Set<Long> getVmsWithAffinityGroups(
|
||||
List<VirtualMachine> vmList, Map<Long, List<? extends Host>> vmToCompatibleHostsCache
|
||||
) {
|
||||
Set<Long> vmsWithAffinityGroups = new HashSet<>();
|
||||
for (VirtualMachine vm : vmList) {
|
||||
if (vmToCompatibleHostsCache.containsKey(vm.getId())) {
|
||||
// Check if VM has any affinity groups - if list is empty, VM has no affinity groups
|
||||
List<AffinityGroupVMMapVO> affinityGroupMappings = affinityGroupVMMapDao.listByInstanceId(vm.getId());
|
||||
if (CollectionUtils.isNotEmpty(affinityGroupMappings)) {
|
||||
vmsWithAffinityGroups.add(vm.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return vmsWithAffinityGroups;
|
||||
}
|
||||
|
||||
private ClusterDrsAlgorithm getDrsAlgorithm(String algoName) {
|
||||
if (drsAlgorithmMap.containsKey(algoName)) {
|
||||
return drsAlgorithmMap.get(algoName);
|
||||
@ -429,6 +566,12 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
* @param hostMemoryCapacityMap
|
||||
* a map of host IDs to their corresponding memory
|
||||
* capacity
|
||||
* @param vmToCompatibleHostsCache
|
||||
* cached map of VM IDs to their compatible hosts
|
||||
* @param vmToStorageMotionCache
|
||||
* cached map of VM IDs to storage motion requirements
|
||||
* @param vmToExcludesMap
|
||||
* map of VM IDs to their ExcludeList (affinity constraints)
|
||||
*
|
||||
* @return a pair of the virtual machine and host that represent the best
|
||||
* migration, or null if no migration is
|
||||
@ -438,33 +581,46 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
List<VirtualMachine> vmList,
|
||||
Map<Long, ServiceOffering> vmIdServiceOfferingMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap) throws ConfigurationException {
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap,
|
||||
Map<Long, List<? extends Host>> vmToCompatibleHostsCache,
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache,
|
||||
Map<Long, ExcludeList> vmToExcludesMap) throws ConfigurationException {
|
||||
// Pre-calculate cluster imbalance once per iteration (same for all VM-host combinations)
|
||||
Double preImbalance = getClusterImbalance(cluster.getId(),
|
||||
new ArrayList<>(hostCpuCapacityMap.values()),
|
||||
new ArrayList<>(hostMemoryCapacityMap.values()),
|
||||
null);
|
||||
|
||||
// Pre-calculate base metrics array once per iteration for optimized imbalance calculation
|
||||
String metricType = getClusterDrsMetric(cluster.getId());
|
||||
Map<Long, Ternary<Long, Long, Long>> baseMetricsMap = "cpu".equals(metricType) ? hostCpuCapacityMap : hostMemoryCapacityMap;
|
||||
Pair<double[], Map<Long, Integer>> baseMetricsAndIndexMap = getBaseMetricsArrayAndHostIdIndexMap(cluster, baseMetricsMap);
|
||||
double[] baseMetricsArray = baseMetricsAndIndexMap.first();
|
||||
Map<Long, Integer> hostIdToIndexMap = baseMetricsAndIndexMap.second();
|
||||
|
||||
double improvement = 0;
|
||||
Pair<VirtualMachine, Host> bestMigration = new Pair<>(null, null);
|
||||
|
||||
for (VirtualMachine vm : vmList) {
|
||||
if (vm.getType().isUsedBySystem() || vm.getState() != VirtualMachine.State.Running ||
|
||||
(MapUtils.isNotEmpty(vm.getDetails()) &&
|
||||
vm.getDetails().get(VmDetailConstants.SKIP_DRS).equalsIgnoreCase("true"))
|
||||
) {
|
||||
List<? extends Host> compatibleHosts = vmToCompatibleHostsCache.get(vm.getId());
|
||||
Map<Host, Boolean> requiresStorageMotion = vmToStorageMotionCache.get(vm.getId());
|
||||
ExcludeList excludes = vmToExcludesMap.get(vm.getId());
|
||||
|
||||
ServiceOffering serviceOffering = vmIdServiceOfferingMap.get(vm.getId());
|
||||
if (skipDrs(vm, compatibleHosts, serviceOffering)) {
|
||||
continue;
|
||||
}
|
||||
Ternary<Pair<List<? extends Host>, Integer>, List<? extends Host>, Map<Host, Boolean>> hostsForMigrationOfVM = managementServer
|
||||
.listHostsForMigrationOfVM(
|
||||
vm, 0L, 500L, null, vmList);
|
||||
List<? extends Host> compatibleDestinationHosts = hostsForMigrationOfVM.first().first();
|
||||
List<? extends Host> suitableDestinationHosts = hostsForMigrationOfVM.second();
|
||||
|
||||
Map<Host, Boolean> requiresStorageMotion = hostsForMigrationOfVM.third();
|
||||
long vmCpu = (long) serviceOffering.getCpu() * serviceOffering.getSpeed();
|
||||
long vmMemory = serviceOffering.getRamSize() * 1024L * 1024L;
|
||||
|
||||
for (Host destHost : compatibleDestinationHosts) {
|
||||
if (!suitableDestinationHosts.contains(destHost) || cluster.getId() != destHost.getClusterId()) {
|
||||
for (Host destHost : compatibleHosts) {
|
||||
Ternary<Double, Double, Double> metrics = getMetricsForMigration(cluster, algorithm, vm, vmCpu,
|
||||
vmMemory, serviceOffering, destHost, hostCpuCapacityMap, hostMemoryCapacityMap,
|
||||
requiresStorageMotion, preImbalance, baseMetricsArray, hostIdToIndexMap, excludes);
|
||||
if (metrics == null) {
|
||||
continue;
|
||||
}
|
||||
Ternary<Double, Double, Double> metrics = algorithm.getMetrics(cluster, vm,
|
||||
vmIdServiceOfferingMap.get(vm.getId()), destHost, hostCpuCapacityMap, hostMemoryCapacityMap,
|
||||
requiresStorageMotion.get(destHost));
|
||||
|
||||
Double currentImprovement = metrics.first();
|
||||
Double cost = metrics.second();
|
||||
Double benefit = metrics.third();
|
||||
@ -477,6 +633,86 @@ public class ClusterDrsServiceImpl extends ManagerBase implements ClusterDrsServ
|
||||
return bestMigration;
|
||||
}
|
||||
|
||||
private boolean skipDrs(VirtualMachine vm, List<? extends Host> compatibleHosts, ServiceOffering serviceOffering) {
|
||||
if (vm.getType().isUsedBySystem() || vm.getState() != VirtualMachine.State.Running) {
|
||||
return true;
|
||||
}
|
||||
if (MapUtils.isNotEmpty(vm.getDetails()) &&
|
||||
"true".equalsIgnoreCase(vm.getDetails().get(VmDetailConstants.SKIP_DRS))) {
|
||||
return true;
|
||||
}
|
||||
if (CollectionUtils.isEmpty(compatibleHosts)) {
|
||||
return true;
|
||||
}
|
||||
if (serviceOffering == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Pair<double[], Map<Long, Integer>> getBaseMetricsArrayAndHostIdIndexMap(
|
||||
Cluster cluster, Map<Long, Ternary<Long, Long, Long>> baseMetricsMap
|
||||
) {
|
||||
double[] baseMetricsArray = new double[baseMetricsMap.size()];
|
||||
Map<Long, Integer> hostIdToIndexMap = new HashMap<>();
|
||||
|
||||
int index = 0;
|
||||
for (Map.Entry<Long, Ternary<Long, Long, Long>> entry : baseMetricsMap.entrySet()) {
|
||||
Long hostId = entry.getKey();
|
||||
Ternary<Long, Long, Long> metrics = entry.getValue();
|
||||
long used = metrics.first();
|
||||
long actualTotal = metrics.third() - metrics.second();
|
||||
long free = actualTotal - metrics.first();
|
||||
Double metricValue = getMetricValue(cluster.getId(), used, free, actualTotal, null);
|
||||
if (metricValue != null) {
|
||||
baseMetricsArray[index] = metricValue;
|
||||
hostIdToIndexMap.put(hostId, index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim array if some values were null
|
||||
if (index < baseMetricsArray.length) {
|
||||
double[] trimmed = new double[index];
|
||||
System.arraycopy(baseMetricsArray, 0, trimmed, 0, index);
|
||||
baseMetricsArray = trimmed;
|
||||
}
|
||||
return new Pair<>(baseMetricsArray, hostIdToIndexMap);
|
||||
}
|
||||
|
||||
private Ternary<Double, Double, Double> getMetricsForMigration(
|
||||
Cluster cluster, ClusterDrsAlgorithm algorithm, VirtualMachine vm, long vmCpu, long vmMemory,
|
||||
ServiceOffering serviceOffering, Host destHost, Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap,
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap, Map<Host, Boolean> requiresStorageMotion,
|
||||
Double preImbalance, double[] baseMetricsArray, Map<Long, Integer> hostIdToIndexMap, ExcludeList excludes
|
||||
) throws ConfigurationException {
|
||||
if (cluster.getId() != destHost.getClusterId()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check affinity constraints
|
||||
if (excludes != null && excludes.shouldAvoid(destHost)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Quick capacity pre-filter: skip hosts that don't have enough capacity
|
||||
Ternary<Long, Long, Long> destHostCpu = hostCpuCapacityMap.get(destHost.getId());
|
||||
Ternary<Long, Long, Long> destHostMemory = hostMemoryCapacityMap.get(destHost.getId());
|
||||
if (destHostCpu == null || destHostMemory == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long destHostAvailableCpu = (destHostCpu.third() - destHostCpu.second()) - destHostCpu.first();
|
||||
long destHostAvailableMemory = (destHostMemory.third() - destHostMemory.second()) - destHostMemory.first();
|
||||
|
||||
if (destHostAvailableCpu < vmCpu || destHostAvailableMemory < vmMemory) {
|
||||
return null; // Skip hosts without sufficient capacity
|
||||
}
|
||||
|
||||
return algorithm.getMetrics(cluster, vm, serviceOffering, destHost, hostCpuCapacityMap, hostMemoryCapacityMap,
|
||||
requiresStorageMotion.getOrDefault(destHost, false), preImbalance, baseMetricsArray, hostIdToIndexMap);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Saves a DRS plan for a given cluster and returns the saved plan along with the list of migrations to be executed.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,9 @@ import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.vm.VMInstanceVO;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import com.cloud.vm.VmDetailConstants;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.apache.cloudstack.api.command.admin.cluster.GenerateClusterDrsPlanCmd;
|
||||
import org.apache.cloudstack.api.response.ClusterDrsPlanMigrationResponse;
|
||||
import org.apache.cloudstack.api.response.ClusterDrsPlanResponse;
|
||||
@ -116,6 +118,9 @@ public class ClusterDrsServiceImplTest {
|
||||
@Mock
|
||||
private VMInstanceDao vmInstanceDao;
|
||||
|
||||
@Mock
|
||||
private AffinityGroupVMMapDao affinityGroupVMMapDao;
|
||||
|
||||
@Spy
|
||||
@InjectMocks
|
||||
private ClusterDrsServiceImpl clusterDrsService = new ClusterDrsServiceImpl();
|
||||
@ -168,9 +173,14 @@ public class ClusterDrsServiceImplTest {
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
|
||||
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm2.getHostId()).thenReturn(2L);
|
||||
Mockito.when(vm2.getId()).thenReturn(2L);
|
||||
Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
@ -201,10 +211,11 @@ public class ClusterDrsServiceImplTest {
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(
|
||||
true, false);
|
||||
Mockito.when(
|
||||
clusterDrsService.getBestMigration(Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
|
||||
Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap())).thenReturn(
|
||||
new Pair<>(vm1, host2));
|
||||
|
||||
Mockito.doReturn(new Pair<>(vm1, host2)).when(clusterDrsService).getBestMigration(
|
||||
Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
|
||||
Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap(),
|
||||
Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap());
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(
|
||||
serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(host1.getId(), host2.getId())).thenReturn(List.of(hostJoin1, hostJoin2));
|
||||
@ -219,6 +230,445 @@ public class ClusterDrsServiceImplTest {
|
||||
assertEquals(1, iterations.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithDisabledCluster() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithZeroMaxIterations() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 0);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithNegativeMaxIterations() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, -1);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithSystemVMs() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO systemVm = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(systemVm.getId()).thenReturn(1L);
|
||||
Mockito.when(systemVm.getHostId()).thenReturn(1L);
|
||||
Mockito.when(systemVm.getType()).thenReturn(VirtualMachine.Type.SecondaryStorageVm);
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(systemVm);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithNonRunningVMs() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO stoppedVm = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(stoppedVm.getId()).thenReturn(1L);
|
||||
Mockito.when(stoppedVm.getHostId()).thenReturn(1L);
|
||||
Mockito.when(stoppedVm.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(stoppedVm.getState()).thenReturn(VirtualMachine.State.Stopped);
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(stoppedVm);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithSkipDrsFlag() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO skippedVm = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(skippedVm.getId()).thenReturn(1L);
|
||||
Mockito.when(skippedVm.getHostId()).thenReturn(1L);
|
||||
Mockito.when(skippedVm.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(skippedVm.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Map<String, String> details = new HashMap<>();
|
||||
details.put(VmDetailConstants.SKIP_DRS, "true");
|
||||
Mockito.when(skippedVm.getDetails()).thenReturn(details);
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(skippedVm);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithNoCompatibleHosts() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(vm1);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
Mockito.verify(managementServer, Mockito.times(1)).listHostsForMigrationOfVM(Mockito.eq(vm1), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithExceptionInCompatibilityCheck() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(vm1);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
// Exception should be caught and logged, not propagated
|
||||
Mockito.verify(managementServer, Mockito.times(1)).listHostsForMigrationOfVM(Mockito.eq(vm1), Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithNoBestMigration() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(vm1);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1));
|
||||
|
||||
HostVO compatibleHost = Mockito.mock(HostVO.class);
|
||||
|
||||
// Return null migration (no best migration found)
|
||||
Mockito.doReturn(new Pair<>(null, null)).when(clusterDrsService).getBestMigration(
|
||||
Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
|
||||
Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap(),
|
||||
Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap());
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithMultipleIterations() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
HostVO host2 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host2.getId()).thenReturn(2L);
|
||||
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
VMInstanceVO vm2 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm2.getId()).thenReturn(2L);
|
||||
Mockito.when(vm2.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm2.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm2.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm2.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
hostList.add(host2);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(vm1);
|
||||
vmList.add(vm2);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
HostJoinVO hostJoin2 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin2.getId()).thenReturn(2L);
|
||||
Mockito.when(hostJoin2.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin2.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin2.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin2.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin2.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin2.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin2.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
|
||||
Mockito.when(serviceOffering.getCpu()).thenReturn(1);
|
||||
Mockito.when(serviceOffering.getRamSize()).thenReturn(1024);
|
||||
Mockito.when(serviceOffering.getSpeed()).thenReturn(1000);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(
|
||||
true, true, false);
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1, hostJoin2));
|
||||
|
||||
HostVO compatibleHost = Mockito.mock(HostVO.class);
|
||||
|
||||
// Return migrations for first two iterations, then null
|
||||
Mockito.doReturn(new Pair<>(vm1, host2), new Pair<>(vm2, host2), new Pair<>(null, null))
|
||||
.when(clusterDrsService).getBestMigration(
|
||||
Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
|
||||
Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap(),
|
||||
Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap());
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(2, result.size());
|
||||
Mockito.verify(balancedAlgorithm, Mockito.times(3)).needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDrsPlanWithMigrationToOriginalHost() throws ConfigurationException {
|
||||
ClusterVO cluster = Mockito.mock(ClusterVO.class);
|
||||
Mockito.when(cluster.getId()).thenReturn(1L);
|
||||
Mockito.when(cluster.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
|
||||
HostVO host1 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host1.getId()).thenReturn(1L);
|
||||
|
||||
HostVO host2 = Mockito.mock(HostVO.class);
|
||||
Mockito.when(host2.getId()).thenReturn(2L);
|
||||
|
||||
VMInstanceVO vm1 = Mockito.mock(VMInstanceVO.class);
|
||||
Mockito.when(vm1.getId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getHostId()).thenReturn(1L);
|
||||
Mockito.when(vm1.getType()).thenReturn(VirtualMachine.Type.User);
|
||||
Mockito.when(vm1.getState()).thenReturn(VirtualMachine.State.Running);
|
||||
Mockito.when(vm1.getDetails()).thenReturn(Collections.emptyMap());
|
||||
|
||||
List<HostVO> hostList = new ArrayList<>();
|
||||
hostList.add(host1);
|
||||
hostList.add(host2);
|
||||
|
||||
List<VMInstanceVO> vmList = new ArrayList<>();
|
||||
vmList.add(vm1);
|
||||
|
||||
HostJoinVO hostJoin1 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin1.getId()).thenReturn(1L);
|
||||
Mockito.when(hostJoin1.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin1.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin1.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin1.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin1.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
HostJoinVO hostJoin2 = Mockito.mock(HostJoinVO.class);
|
||||
Mockito.when(hostJoin2.getId()).thenReturn(2L);
|
||||
Mockito.when(hostJoin2.getCpuUsedCapacity()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin2.getCpuReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin2.getCpus()).thenReturn(4);
|
||||
Mockito.when(hostJoin2.getSpeed()).thenReturn(1000L);
|
||||
Mockito.when(hostJoin2.getMemUsedCapacity()).thenReturn(1024L);
|
||||
Mockito.when(hostJoin2.getMemReservedCapacity()).thenReturn(0L);
|
||||
Mockito.when(hostJoin2.getTotalMemory()).thenReturn(8192L);
|
||||
|
||||
ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class);
|
||||
|
||||
Mockito.when(hostDao.findByClusterId(1L)).thenReturn(hostList);
|
||||
Mockito.when(vmInstanceDao.listByClusterId(1L)).thenReturn(vmList);
|
||||
Mockito.when(balancedAlgorithm.needsDrs(Mockito.any(), Mockito.anyList(), Mockito.anyList())).thenReturn(true);
|
||||
Mockito.when(serviceOfferingDao.findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong())).thenReturn(serviceOffering);
|
||||
Mockito.when(hostJoinDao.searchByIds(Mockito.any())).thenReturn(List.of(hostJoin1, hostJoin2));
|
||||
|
||||
HostVO compatibleHost = Mockito.mock(HostVO.class);
|
||||
|
||||
// Return migration to original host (host1) - should break the loop
|
||||
Mockito.doReturn(new Pair<>(vm1, host1)).when(clusterDrsService).getBestMigration(
|
||||
Mockito.any(Cluster.class), Mockito.any(ClusterDrsAlgorithm.class),
|
||||
Mockito.anyList(), Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap(),
|
||||
Mockito.anyMap(), Mockito.anyMap(), Mockito.anyMap());
|
||||
|
||||
List<Ternary<VirtualMachine, Host, Host>> result = clusterDrsService.getDrsPlan(cluster, 5);
|
||||
assertEquals(0, result.size());
|
||||
// Should break early when VM would migrate to original host
|
||||
}
|
||||
|
||||
@Test(expected = InvalidParameterValueException.class)
|
||||
public void testGenerateDrsPlanClusterNotFound() {
|
||||
Mockito.when(clusterDao.findById(1L)).thenReturn(null);
|
||||
@ -387,18 +837,41 @@ public class ClusterDrsServiceImplTest {
|
||||
vmIdServiceOfferingMap.put(vm.getId(), serviceOffering);
|
||||
}
|
||||
|
||||
Mockito.when(managementServer.listHostsForMigrationOfVM(vm1, 0L, 500L, null, vmList)).thenReturn(
|
||||
new Ternary<>(new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost, false)));
|
||||
Mockito.when(managementServer.listHostsForMigrationOfVM(vm2, 0L, 500L, null, vmList)).thenReturn(
|
||||
new Ternary<>(new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost, false)));
|
||||
Mockito.when(balancedAlgorithm.getMetrics(cluster, vm1, serviceOffering, destHost, new HashMap<>(),
|
||||
new HashMap<>(), false)).thenReturn(new Ternary<>(1.0, 0.5, 1.5));
|
||||
// Create caches for the new method signature
|
||||
Map<Long, List<? extends Host>> vmToCompatibleHostsCache = new HashMap<>();
|
||||
vmToCompatibleHostsCache.put(vm1.getId(), List.of(destHost));
|
||||
vmToCompatibleHostsCache.put(vm2.getId(), List.of(destHost));
|
||||
|
||||
Mockito.when(balancedAlgorithm.getMetrics(cluster, vm2, serviceOffering, destHost, new HashMap<>(),
|
||||
new HashMap<>(), false)).thenReturn(new Ternary<>(1.0, 2.5, 1.5));
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = new HashMap<>();
|
||||
vmToStorageMotionCache.put(vm1.getId(), Map.of(destHost, false));
|
||||
vmToStorageMotionCache.put(vm2.getId(), Map.of(destHost, false));
|
||||
|
||||
Pair<VirtualMachine, Host> bestMigration = clusterDrsService.getBestMigration(cluster, balancedAlgorithm,
|
||||
vmList, vmIdServiceOfferingMap, new HashMap<>(), new HashMap<>());
|
||||
Map<Long, com.cloud.deploy.DeploymentPlanner.ExcludeList> vmToExcludesMap = new HashMap<>();
|
||||
vmToExcludesMap.put(vm1.getId(), Mockito.mock(com.cloud.deploy.DeploymentPlanner.ExcludeList.class));
|
||||
vmToExcludesMap.put(vm2.getId(), Mockito.mock(com.cloud.deploy.DeploymentPlanner.ExcludeList.class));
|
||||
|
||||
// Create capacity maps with dummy data for getClusterImbalance (include both source and dest hosts)
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap = new HashMap<>();
|
||||
hostCpuCapacityMap.put(host.getId(), new Ternary<>(2000L, 0L, 3000L)); // Source host
|
||||
hostCpuCapacityMap.put(destHost.getId(), new Ternary<>(1000L, 0L, 2000L)); // Dest host
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap = new HashMap<>();
|
||||
hostMemoryCapacityMap.put(host.getId(), new Ternary<>(2L * 1024L * 1024L * 1024L, 0L, 3L * 1024L * 1024L * 1024L)); // Source host
|
||||
hostMemoryCapacityMap.put(destHost.getId(), new Ternary<>(1024L * 1024L * 1024L, 0L, 2L * 1024L * 1024L * 1024L)); // Dest host
|
||||
|
||||
// Mock getMetrics for the optimized 10-parameter version used by getBestMigration
|
||||
// Return better improvement for vm1, worse for vm2
|
||||
Mockito.doReturn(new Ternary<>(1.0, 0.5, 1.5)).when(balancedAlgorithm).getMetrics(
|
||||
Mockito.eq(cluster), Mockito.eq(vm1), Mockito.any(ServiceOffering.class),
|
||||
Mockito.eq(destHost), Mockito.eq(hostCpuCapacityMap), Mockito.eq(hostMemoryCapacityMap), Mockito.any(Boolean.class),
|
||||
Mockito.any(Double.class), Mockito.any(double[].class), Mockito.any(Map.class));
|
||||
Mockito.doReturn(new Ternary<>(0.5, 2.5, 1.5)).when(balancedAlgorithm).getMetrics(
|
||||
Mockito.eq(cluster), Mockito.eq(vm2), Mockito.any(ServiceOffering.class),
|
||||
Mockito.eq(destHost), Mockito.eq(hostCpuCapacityMap), Mockito.eq(hostMemoryCapacityMap), Mockito.any(Boolean.class),
|
||||
Mockito.any(Double.class), Mockito.any(double[].class), Mockito.any(Map.class));
|
||||
|
||||
Pair<VirtualMachine, Host> bestMigration = clusterDrsService.getBestMigration(cluster, balancedAlgorithm,
|
||||
vmList, vmIdServiceOfferingMap, hostCpuCapacityMap, hostMemoryCapacityMap,
|
||||
vmToCompatibleHostsCache, vmToStorageMotionCache, vmToExcludesMap);
|
||||
|
||||
assertEquals(destHost, bestMigration.second());
|
||||
assertEquals(vm1, bestMigration.first());
|
||||
@ -443,12 +916,28 @@ public class ClusterDrsServiceImplTest {
|
||||
vmIdServiceOfferingMap.put(vm.getId(), serviceOffering);
|
||||
}
|
||||
|
||||
Mockito.when(managementServer.listHostsForMigrationOfVM(vm1, 0L, 500L, null, vmList)).thenReturn(
|
||||
new Ternary<>(new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost, false)));
|
||||
Mockito.when(managementServer.listHostsForMigrationOfVM(vm2, 0L, 500L, null, vmList)).thenReturn(
|
||||
new Ternary<>(new Pair<>(List.of(destHost), 1), List.of(destHost), Map.of(destHost, false)));
|
||||
// Create caches for the new method signature
|
||||
Map<Long, List<? extends Host>> vmToCompatibleHostsCache = new HashMap<>();
|
||||
vmToCompatibleHostsCache.put(vm1.getId(), List.of(destHost));
|
||||
vmToCompatibleHostsCache.put(vm2.getId(), List.of(destHost));
|
||||
|
||||
Map<Long, Map<Host, Boolean>> vmToStorageMotionCache = new HashMap<>();
|
||||
vmToStorageMotionCache.put(vm1.getId(), Map.of(destHost, false));
|
||||
vmToStorageMotionCache.put(vm2.getId(), Map.of(destHost, false));
|
||||
|
||||
Map<Long, com.cloud.deploy.DeploymentPlanner.ExcludeList> vmToExcludesMap = new HashMap<>();
|
||||
vmToExcludesMap.put(vm1.getId(), Mockito.mock(com.cloud.deploy.DeploymentPlanner.ExcludeList.class));
|
||||
vmToExcludesMap.put(vm2.getId(), Mockito.mock(com.cloud.deploy.DeploymentPlanner.ExcludeList.class));
|
||||
|
||||
// Create capacity maps with dummy data for getClusterImbalance
|
||||
Map<Long, Ternary<Long, Long, Long>> hostCpuCapacityMap = new HashMap<>();
|
||||
hostCpuCapacityMap.put(destHost.getId(), new Ternary<>(1000L, 0L, 2000L));
|
||||
Map<Long, Ternary<Long, Long, Long>> hostMemoryCapacityMap = new HashMap<>();
|
||||
hostMemoryCapacityMap.put(destHost.getId(), new Ternary<>(1024L * 1024L * 1024L, 0L, 2L * 1024L * 1024L * 1024L));
|
||||
|
||||
Pair<VirtualMachine, Host> bestMigration = clusterDrsService.getBestMigration(cluster, balancedAlgorithm,
|
||||
vmList, vmIdServiceOfferingMap, new HashMap<>(), new HashMap<>());
|
||||
vmList, vmIdServiceOfferingMap, hostCpuCapacityMap, hostMemoryCapacityMap,
|
||||
vmToCompatibleHostsCache, vmToStorageMotionCache, vmToExcludesMap);
|
||||
|
||||
assertNull(bestMigration.second());
|
||||
assertNull(bestMigration.first());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user