package org.ovirt.engine.core.bll.scheduling; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.ovirt.engine.core.bll.scheduling.pending.PendingOvercommitMemory; import org.ovirt.engine.core.bll.scheduling.pending.PendingResourceManager; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VDSStatus; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A helper class for the scheduling mechanism for checking the HA Reservation status of a Cluster */ public class HaReservationHandling { private static final Logger log = LoggerFactory.getLogger(HaReservationHandling.class); private final PendingResourceManager pendingResourceManager; public HaReservationHandling(PendingResourceManager pendingResourceManager) { this.pendingResourceManager = pendingResourceManager; } /** * @param cluster * - Cluster to check * @param failedHosts * - a list to return all the hosts that failed the check, must be initialized outside this method * @return true: Cluster is HaReservation safe. false: a failover in one of the Clusters Hosts could negatively * impacting performance. */ public boolean checkHaReservationStatusForCluster(Cluster cluster, List<VDS> failedHosts) { List<VDS> hosts = DbFacade.getInstance().getVdsDao().getAllForClusterWithStatus(cluster.getId(), VDSStatus.Up); // No hosts, return true if (hosts == null || hosts.isEmpty()) { return true; } // HA Reservation is not possible with less than 2 hosts if (hosts.size() < 2) { log.debug("Cluster '{}' failed HA reservation check because there is only one host in the cluster", cluster.getName()); failedHosts.addAll(hosts); return false; } // List of host id and cpu/ram free resources // for the outer Pair, first is host id second is a Pair of cpu and ram // for the inner Pair, first is cpu second is ram List<Pair<Guid, Pair<Integer, Integer>>> hostsUnutilizedResources = getUnutilizedResources(hosts); Map<Guid, List<VM>> hostToHaVmsMapping = mapHaVmToHostByCluster(cluster.getId()); for (VDS host : hosts) { if (hostToHaVmsMapping.get(host.getId()) != null) { boolean isHaSafe = findReplacementForHost(cluster, host, hostToHaVmsMapping.get(host.getId()), hostsUnutilizedResources); if (!isHaSafe) { failedHosts.add(host); } } } log.info("HA reservation status for cluster '{}' is '{}'", cluster.getName(), failedHosts.isEmpty() ? "OK" : "Failed"); return failedHosts.isEmpty(); } private boolean findReplacementForHost(Cluster cluster, VDS host, List<VM> vmList, List<Pair<Guid, Pair<Integer, Integer>>> hostsUnutilizedResources) { Map<Guid, Pair<Integer, Integer>> additionalHostsUtilizedResources = new HashMap<>(); for (VM vm : vmList) { int curVmMemSize = 0; if(vm.getUsageMemPercent() != null) { curVmMemSize = (int) Math.round(vm.getMemSizeMb() * (vm.getUsageMemPercent() / 100.0)); } // Make sure we reserve at least the guaranteed amount of memory or more // if the VM is using more than that. curVmMemSize = Math.max(curVmMemSize, vm.getMinAllocatedMem()); int curVmCpuPercent = 0; if (vm.getUsageCpuPercent() != null) { curVmCpuPercent = vm.getUsageCpuPercent() * vm.getNumOfCpus() / SlaValidator.getEffectiveCpuCores(host, cluster.getCountThreadsAsCores()); } log.debug("VM '{}'. CPU usage: {}%, RAM required: {}MB", vm.getName(), curVmCpuPercent, curVmMemSize); boolean foundForCurVm = false; for (Pair<Guid, Pair<Integer, Integer>> hostData : hostsUnutilizedResources) { // Make sure not to run on the same Host as the Host we are testing if (hostData.getFirst().equals(host.getId())) { continue; } // Check Memory and CPU if (hostData.getSecond() != null && hostData.getSecond().getSecond() != null && hostData.getSecond().getFirst() != null) { int memoryFree = hostData.getSecond().getSecond(); int cpuFree = hostData.getSecond().getFirst(); long additionalMemory = 0; int additionalCpu = 0; if (additionalHostsUtilizedResources.get(hostData.getFirst()) != null) { additionalCpu = additionalHostsUtilizedResources.get(hostData.getFirst()).getFirst(); additionalMemory = additionalHostsUtilizedResources.get(hostData.getFirst()).getSecond(); } if ((memoryFree - additionalMemory) >= curVmMemSize && (cpuFree - additionalCpu) >= curVmCpuPercent) { // Found a place for current vm, add the RAM and CPU size to additionalHostsUtilizedResources Pair<Integer, Integer> cpuRamPair = additionalHostsUtilizedResources.get(hostData.getFirst()); if (cpuRamPair != null) { cpuRamPair.setFirst(cpuRamPair.getFirst() + curVmCpuPercent); cpuRamPair.setSecond(cpuRamPair.getSecond() + curVmMemSize); } else { cpuRamPair = new Pair<>(curVmCpuPercent, curVmMemSize); additionalHostsUtilizedResources.put(hostData.getFirst(), cpuRamPair); } foundForCurVm = true; break; } } } if (!foundForCurVm) { log.info("Did not found a replacement host for VM '{}'", vm.getName()); return false; } } return true; } public static Map<Guid, List<VM>> mapVmToHost(List<VM> vms) { Map<Guid, List<VM>> hostToHaVmsMapping = new HashMap<>(); for (VM vm : vms) { if (!Guid.isNullOrEmpty(vm.getRunOnVds())) { if (!hostToHaVmsMapping.containsKey(vm.getRunOnVds())) { List<VM> vmsOfHost = new ArrayList<>(); vmsOfHost.add(vm); hostToHaVmsMapping.put(vm.getRunOnVds(), vmsOfHost); } else { hostToHaVmsMapping.get(vm.getRunOnVds()).add(vm); } } } return hostToHaVmsMapping; } private List<Pair<Guid, Pair<Integer, Integer>>> getUnutilizedResources(List<VDS> hosts) { List<Pair<Guid, Pair<Integer, Integer>>> hostsUnutilizedResources = new ArrayList<>(); for (VDS host : hosts) { Pair<Integer, Integer> innerUnutilizedCpuRamPair = new Pair<>(); int hostFreeCpu = 0; if (host.getUsageCpuPercent() != null) { hostFreeCpu = 100 - host.getUsageCpuPercent(); } innerUnutilizedCpuRamPair.setFirst(hostFreeCpu); // Get available memory for the Host, round down to int int hostFreeMem = (int) host.getMaxSchedulingMemory() - PendingOvercommitMemory.collectForHost(pendingResourceManager, host.getId()); innerUnutilizedCpuRamPair.setSecond(hostFreeMem); Pair<Guid, Pair<Integer, Integer>> outerUnutilizedCpuRamPair = new Pair<>(host.getId(), innerUnutilizedCpuRamPair); hostsUnutilizedResources.add(outerUnutilizedCpuRamPair); } return hostsUnutilizedResources; } public static Map<Guid, List<VM>> mapHaVmToHostByCluster(Guid clusterId) { List<VM> vms = DbFacade.getInstance().getVmDao().getAllForCluster(clusterId); if (vms == null || vms.isEmpty()) { log.debug("No VMs available for this cluster with id '{}'", clusterId); // return empty map return Collections.emptyMap(); } vms = vms.stream().filter(VM::isAutoStartup).collect(Collectors.toList()); return mapVmToHost(vms); } }