package org.ovirt.engine.core.bll.scheduling.utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.ovirt.engine.core.bll.scheduling.SlaValidator;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.MigrationSupport;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmStatisticsDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Find a VM that can be migrated and a set of hosts that can potentially
* receive it while not violating the utilization rules.
*/
public class FindVmAndDestinations {
private static final Logger log = LoggerFactory.getLogger(FindVmAndDestinations.class);
private Cluster cluster;
private int highCpuUtilization;
private long requiredMemory;
public static class Result {
private List<VDS> destinationHosts;
private VM vmToMigrate;
public Result(VM vmToMigrate, List<VDS> destinationHosts) {
this.vmToMigrate = vmToMigrate;
this.destinationHosts = destinationHosts;
}
public List<VDS> getDestinationHosts() {
return destinationHosts;
}
public VM getVmToMigrate() {
return vmToMigrate;
}
}
public FindVmAndDestinations(Cluster cluster, int highCpuUtilization, long requiredMemory) {
this.cluster = cluster;
this.highCpuUtilization = highCpuUtilization;
this.requiredMemory = requiredMemory;
}
public Optional<Result> invoke(List<VDS> sourceHosts,
List<VDS> destinationHosts,
VmDao vmDao,
VmStatisticsDao vmStatisticsDao) {
// Iterate over source hosts until you find valid vm to migrate, hosts sorted by cpu usage
for (VDS sourceHost : sourceHosts){
// Get list of all migratable vms on host
List<VM> migratableVmsOnHost = getMigratableVmsRunningOnVds(vmDao, sourceHost.getId());
if (migratableVmsOnHost.isEmpty()) {
continue;
}
// Statistics are needed for sorting by cpu usage
for (VM vm : migratableVmsOnHost) {
vm.setStatisticsData(vmStatisticsDao.get(vm.getId()));
}
// Sort vms by cpu usage
Collections.sort(migratableVmsOnHost, VmCpuUsageComparator.INSTANCE);
for (VM vmToMigrate : migratableVmsOnHost){
// Check if vm not over utilize memory or CPU of destination hosts
List<VDS> validDestinationHosts = getValidHosts(
destinationHosts, cluster, vmToMigrate, highCpuUtilization, requiredMemory);
if (!validDestinationHosts.isEmpty()){
log.debug("Vm '{}' selected for migration", vmToMigrate.getName());
return Optional.of(new Result(vmToMigrate, validDestinationHosts));
}
}
}
return Optional.empty();
}
/**
* The predicted CPU the CPU that the VM will take considering
* how many cores it has and how many cores the host has.
* @return
* predicted vm cpu
*/
protected int getPredictedVmCpu(VM vm, VDS vds, boolean countThreadsAsCores) {
Integer effectiveCpuCores = SlaValidator.getEffectiveCpuCores(vds, countThreadsAsCores);
if (vm.getUsageCpuPercent() != null && effectiveCpuCores != null) {
return (vm.getUsageCpuPercent() * vm.getNumOfCpus())
/ effectiveCpuCores;
}
return 0;
}
/**
* Return all VMs that run on a host and can be migrated away.
*
* This method is to be considered private. It is protected to be available
* from unit tests.
*
* @param vmDao The data source to get the VM information
* @param hostId Id of a host the returned VMs run at
* @return list od VM that run on host and can be migrated
*/
protected List<VM> getMigratableVmsRunningOnVds(final VmDao vmDao, final Guid hostId) {
return vmDao.getAllRunningForVds(hostId).stream()
.filter(vm -> vm.getMigrationSupport() == MigrationSupport.MIGRATABLE)
.collect(Collectors.toList());
}
/**
* Pre-filter candidate hosts with regards to the balancing policy. The default
* implementation does not allow putting VM on a host that would become
* over-utilized to prevent bouncing.
*
* @param candidates candidate hosts
* @param cluster cluster reference
* @param vm vm to be migrated
* @param highCpuUtilization CPU over-utilization threshold in percents
* @param minimalFreeMemory Memory over-utilization threshold in MB
* @return list of hosts that satisfy the balancing contraints
*/
private List<VDS> getValidHosts(Collection<VDS> candidates, Cluster cluster, VM vm, int highCpuUtilization, long minimalFreeMemory) {
List<VDS> result = new ArrayList<>();
for (VDS vds: candidates) {
int predictedVmCpu = getPredictedVmCpu(vm, vds, cluster.getCountThreadsAsCores());
if (vds.getUsageCpuPercent() + predictedVmCpu <= highCpuUtilization
&& vds.getMaxSchedulingMemory() - vm.getMemSizeMb() > minimalFreeMemory) {
result.add(vds);
}
}
return result;
}
}