package org.ovirt.engine.core.bll.scheduling.policyunits; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.ovirt.engine.core.bll.scheduling.PolicyUnitImpl; import org.ovirt.engine.core.bll.scheduling.SchedulingUnit; import org.ovirt.engine.core.bll.scheduling.pending.PendingResourceManager; import org.ovirt.engine.core.bll.scheduling.pending.PendingVM; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.VM; import org.ovirt.engine.core.common.errors.EngineMessage; import org.ovirt.engine.core.common.scheduling.AffinityGroup; import org.ovirt.engine.core.common.scheduling.PerHostMessages; import org.ovirt.engine.core.common.scheduling.PolicyUnit; import org.ovirt.engine.core.common.scheduling.PolicyUnitType; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dao.VdsStaticDao; import org.ovirt.engine.core.dao.VmDao; import org.ovirt.engine.core.dao.scheduling.AffinityGroupDao; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SchedulingUnit( guid = "84e6ddee-ab0d-42dd-82f0-c297779db566", name = "VmAffinityGroups", description = "Enables Affinity Groups hard enforcement for VMs; VMs in group are required to run either on" + " the same hypervisor host (positive) or on independent hypervisor hosts (negative)", type = PolicyUnitType.FILTER ) public class VmAffinityFilterPolicyUnit extends PolicyUnitImpl { private static final Logger log = LoggerFactory.getLogger(VmAffinityFilterPolicyUnit.class); public VmAffinityFilterPolicyUnit(PolicyUnit policyUnit, PendingResourceManager pendingResourceManager) { super(policyUnit, pendingResourceManager); } @Override public List<VDS> filter(Cluster cluster, List<VDS> hosts, VM vm, Map<String, String> parameters, PerHostMessages messages) { return getAcceptableHosts(true, hosts, vm, messages, getPendingResourceManager()); } public static List<VDS> getAcceptableHosts(boolean enforcing, List<VDS> hosts, VM vm, PerHostMessages messages, PendingResourceManager pendingResourceManager) { List<AffinityGroup> affinityGroups = getAffinityGroupDao().getAllAffinityGroupsByVmId(vm.getId()); // no affinity groups found for VM return all hosts if (affinityGroups.isEmpty()) { return hosts; } Set<Guid> allVmIdsPositive = new HashSet<>(); Set<Guid> allVmIdsNegative = new HashSet<>(); // Group by all vms in affinity groups per positive or negative for (AffinityGroup affinityGroup : affinityGroups) { if (affinityGroup.isVmAffinityEnabled() && affinityGroup.isVmEnforcing() == enforcing) { for (Guid entityId : affinityGroup.getVmIds()) { // Skip current VM if (entityId.equals(vm.getId())) { continue; } if (affinityGroup.isVmPositive()) { allVmIdsPositive.add(entityId); } else if (affinityGroup.isVmNegative()) { allVmIdsNegative.add(entityId); } } } } // No entities, all hosts are valid if (allVmIdsPositive.isEmpty() && allVmIdsNegative.isEmpty()) { return hosts; } // Get all running VMs in cluster Map<Guid, VM> runningVMsMap = new HashMap<>(); for (VM iter : getVmDao().getAllRunningByCluster(vm.getClusterId())) { runningVMsMap.put(iter.getId(), iter); } // Update the VM list with pending VMs for (PendingVM resource: pendingResourceManager.pendingResources(PendingVM.class)) { VM pendingVm = new VM(); pendingVm.setId(resource.getVm()); pendingVm.setRunOnVds(resource.getHost()); runningVMsMap.put(pendingVm.getId(), pendingVm); } Set<Guid> acceptableHosts = new HashSet<>(); // Group all hosts for VMs with positive affinity for (Guid id : allVmIdsPositive) { VM runVm = runningVMsMap.get(id); if (runVm != null && runVm.getRunOnVds() != null) { acceptableHosts.add(runVm.getRunOnVds()); } } Set<Guid> unacceptableHosts = new HashSet<>(); // Group all hosts for VMs with negative affinity for (Guid id : allVmIdsNegative) { VM runVm = runningVMsMap.get(id); if (runVm != null && runVm.getRunOnVds() != null) { unacceptableHosts.add(runVm.getRunOnVds()); } } Map<Guid, VDS> hostMap = new HashMap<>(); for (VDS host : hosts) { hostMap.put(host.getId(), host); } // Compute the intersection of hosts with positive and negative affinity and report that // contradicting rules to the log unacceptableHosts.retainAll(acceptableHosts); for (Guid id: unacceptableHosts) { log.warn("Host '{}' ({}) belongs to both positive and negative affinity list" + " while scheduling VM '{}' ({})", hostMap.get(id).getName(), id, vm.getName(), vm.getId()); } // No hosts associated with positive affinity, all hosts are applicable. if (acceptableHosts.isEmpty()) { acceptableHosts.addAll(hostMap.keySet()); } else if (acceptableHosts.size() > 1) { log.warn("Invalid affinity situation was detected while scheduling VM '{}' ({})." + " VMs belonging to the same affinity groups are running on more than one host.", vm.getName(), vm.getId()); } // Report hosts that were removed because of violating the positive affinity rules for (VDS host : hosts) { if (!acceptableHosts.contains(host.getId())) { messages.addMessage(host.getId(), String.format("$affinityRules %1$s", "")); // TODO compute the affinity rule names messages.addMessage(host.getId(), EngineMessage.VAR__DETAIL__AFFINITY_FAILED_POSITIVE.toString()); } } // Remove hosts that contain VMs with negaive affinity to the currently scheduled Vm for (Guid id : allVmIdsNegative) { VM runVm = runningVMsMap.get(id); if (runVm != null && runVm.getRunOnVds() != null && acceptableHosts.contains(runVm.getRunOnVds())) { acceptableHosts.remove(runVm.getRunOnVds()); messages.addMessage(runVm.getRunOnVds(), String.format("$affinityRules %1$s", "")); // TODO compute the affinity rule names messages.addMessage(runVm.getRunOnVds(), EngineMessage.VAR__DETAIL__AFFINITY_FAILED_NEGATIVE.toString()); } } List<VDS> retList = new ArrayList<>(); for (VDS host : hosts) { if (acceptableHosts.contains(host.getId())) { retList.add(host); } } return retList; } protected static VdsStaticDao getVdsStaticDao() { return DbFacade.getInstance().getVdsStaticDao(); } protected static AffinityGroupDao getAffinityGroupDao() { return DbFacade.getInstance().getAffinityGroupDao(); } protected static VmDao getVmDao() { return DbFacade.getInstance().getVmDao(); } }