package org.ovirt.engine.core.bll.scheduling.arem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.scheduling.AffinityGroup;
import org.ovirt.engine.core.common.scheduling.EntityAffinityRule;
import org.ovirt.engine.core.compat.Guid;
public class AffinityRulesUtils {
/**
* Take the unified positive groups and check whether a conflict exists between positive
* and negative VM affinity.
*
* @param affinityGroups - All affinity groups
*/
public static void checkForAffinityGroupConflict(List<AffinityGroup> affinityGroups,
List<AffinityGroupConflicts> conflicts) {
Set<Set<Guid>> unifiedPositiveGroups = getUnifiedPositiveAffinityGroups(affinityGroups.stream()
.filter(AffinityGroup::isVmAffinityEnabled)
.collect(Collectors.toList()));
affinityGroups.stream()
.filter(AffinityGroup::isVmAffinityEnabled)
.filter(AffinityGroup::isVmNegative)
.forEach(ag -> {
for (Set<Guid> positiveGroup : unifiedPositiveGroups) {
Set<Guid> intersection = new HashSet<>(ag.getVmIds());
intersection.retainAll(positiveGroup);
if (intersection.size() > 1) {
conflicts.add(new AffinityGroupConflicts(new HashSet<>(Arrays.asList(ag)),
AffinityRulesConflicts.VM_TO_VM_AFFINITY_CONFLICTS,
AuditLogType.VM_TO_VM_AFFINITY_CONFLICTS, positiveGroup, intersection)
);
}
}
});
}
/**
* Take all positive affinity groups and merge all groups that reference the same VM
* together.
*
* Ex. groups A+B, B+C, D+E, A+D, F+G are in fact just two bigger groups:
* A+B+C+D+E and F+G
*
* The algorithm starts by creating single element groups from all VMs
* It then goes through all affinity groups and merges all VM groups that contain
* VMs from the currently processed AffinityGroup.
*/
public static Set<Set<Guid>> getUnifiedPositiveAffinityGroups(List<AffinityGroup> affinityGroups) {
Set<Set<Guid>> uag = new HashSet<>();
Map<Guid, Set<Guid>> vmIndex = new HashMap<>();
/**
* Initialize the single element groups by taking all VMs that are referenced
* from any affinity group
*/
for(Iterator<AffinityGroup> it = affinityGroups.iterator(); it.hasNext();) {
AffinityGroup ag = it.next();
for(Guid id : ag.getVmIds()) {
Set<Guid> temp = new HashSet<>();
temp.add(id);
uag.add(temp);
vmIndex.put(id, temp);
}
}
// Go through each positive affinity group and merge all existing groups
// that contain the referenced VMs into one.
for(AffinityGroup ag : affinityGroups) {
if(ag.isVmPositive()) {
Set<Guid> mergedGroup = new HashSet<>();
for(Guid id : ag.getVmIds()) {
// Get the current groups VM(id) belongs to
Set<Guid> existingGroup = vmIndex.get(id);
// Merge it with the currently computed mergeGroup
mergedGroup.addAll(existingGroup);
// And remove it from the valid groups
// (it will be re-added as part of a bigger group)
uag.remove(existingGroup);
// Update the per-VM index
for (Guid vm: existingGroup) {
vmIndex.put(vm, mergedGroup);
}
}
uag.add(mergedGroup);
}
}
return uag;
}
/**
* Convert positive affinity group sets to a proper AffinityGroup objects.
* Use lists so the order is defined as we want an deterministic algorithm further on.
*
* @param uag A set of sets containing the unified POSITIVE affinity groups.
* @return List of AffinityGroup objects representing the provided unified affinity groups
*/
static List<AffinityGroup> setsToAffinityGroups(Set<Set<Guid>> uag) {
List<AffinityGroup> output = new ArrayList<>();
for(Set<Guid> s : uag) {
AffinityGroup temp = new AffinityGroup();
temp.setVmAffinityRule(EntityAffinityRule.POSITIVE);
List<Guid> entities = new ArrayList<>();
entities.addAll(s);
temp.setVmIds(entities);
output.add(temp);
}
return output;
}
/**
* Check for conflicts in VMs to hosts affinity groups.
* These conflicts will include :
* Hosts that have enforcing positive and negative affinity conflicts.
* Hosts that have positive and negative affinity conflicts.
* Non intersecting hosts conflicts.
* Vm to host affinity with positive vm to vm conflicts
* Vm to host affinity with negative vm to vm conflicts
* Vms have positive and negative affinity conflicts.
*
* @param affinityGroups affinity groups to examine.
* @return An object that contains a list of host ids for each of the described conflicts.
*/
public static List<AffinityGroupConflicts> checkForAffinityGroupHostsConflict(List<AffinityGroup>
affinityGroups) {
List<AffinityGroupConflicts> conflicts = new ArrayList<>();
checkHostsInPositiveAndNegativeAffinity(affinityGroups.stream()
.filter(AffinityGroup::isVdsEnforcing)
.collect(Collectors.toList()),
true,
conflicts);
checkHostsInPositiveAndNegativeAffinity(affinityGroups, false, conflicts);
checkVmToHostWithPositiveVmToVmConflict(affinityGroups, conflicts);
checkVmToHostWithNegativeVmToVmConflicts(affinityGroups, conflicts);
checkNonIntersectingPositiveHosts(affinityGroups, conflicts);
checkForAffinityGroupConflict(affinityGroups, conflicts);
return conflicts;
}
/**
* Checks for affinity groups that have positive and negative affinity conflicts:
* A host that has negative enforcing/non enforcing affinity with a VM in one or more affinity groups
* and the same host that has positive enforcing affinity with that VM in one or more affinity groups.
*
* example of conflicts :
* {vm1 + host1} , {vm1 - host1}
* {vm1 [+] host1} , {vm1 - host1}
* {vm1 [+] host1} , {vm1 [-] host1}
*
* ( + is enforcing positive affinity)
* ( [+] is non enforcing positive affinity)
* ( - is enforcing negative affinity)
* ( [-] is non enforcing negative affinity)
* ( {} is an affinity group)
*
* @param affinityGroups affinity groups to examine.
* @param isVdsEnforcing is check done against enforcing affinity or not
* @param conflicts affinity conflicts list
*/
private static void checkHostsInPositiveAndNegativeAffinity(List<AffinityGroup> affinityGroups,
boolean isVdsEnforcing, List<AffinityGroupConflicts> conflicts) {
Set<AffinityGroup> conflictingAffinityGroups = new HashSet<>();
Set<Guid> conflictingVMs = new HashSet<>();
Set<Guid> conflictingHosts = new HashSet<>();
List<AffinityGroup> affinityGroupsWithHosts = affinityGroups.stream()
.filter(ag -> !ag.getVdsIds().isEmpty())
.collect(Collectors.toList());
affinityGroupsWithHosts.stream()
.filter(ag -> !ag.isVdsPositive())
.forEach(negativeGroup -> {
affinityGroupsWithHosts.stream()
.filter(AffinityGroup::isVdsPositive)
.forEach(positiveGroup -> {
List<Guid> intersectingVMs = new ArrayList<>(negativeGroup.getVmIds());
intersectingVMs.retainAll(positiveGroup.getVmIds());
if (!intersectingVMs.isEmpty()) {
List<Guid> intersectingHosts = new ArrayList<>(negativeGroup.getVdsIds());
intersectingHosts.retainAll(positiveGroup.getVdsIds());
if (!intersectingHosts.isEmpty()) {
conflictingAffinityGroups.add(positiveGroup);
conflictingAffinityGroups.add(negativeGroup);
conflictingVMs.addAll(intersectingVMs);
conflictingHosts.addAll(intersectingHosts);
}
}
});
});
if (!conflictingAffinityGroups.isEmpty()) {
if (isVdsEnforcing) {
conflicts.add(new AffinityGroupConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
AffinityRulesConflicts.VM_TO_HOST_CONFLICT_IN_ENFORCING_POSITIVE_AND_NEGATIVE_AFFINITY,
AuditLogType.VM_TO_HOST_CONFLICT_IN_ENFORCING_POSITIVE_AND_NEGATIVE_AFFINITY));
} else {
conflicts.add(new AffinityGroupConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
AffinityRulesConflicts.VM_TO_HOST_CONFLICT_IN_POSITIVE_AND_NEGATIVE_AFFINITY,
AuditLogType.VM_TO_HOST_CONFLICT_IN_POSITIVE_AND_NEGATIVE_AFFINITY));
}
}
}
/**
* Checks vm to host affinity with positive vm to vm conflicts.
* Hosts that have positive and negative affinity to vms and those vms have positive affinity
* can cause a conflict.
*
* example of conflicts :
* {vm1 + host1},{vm1+vm2},{vm2 - host1}
*
* ( + is enforcing positive affinity)
* ( [+] is non enforcing positive affinity)
* ( - is enforcing negative affinity)
* ( [-] is non enforcing negative affinity)
* ( {} is an affinity group)
*
* @param affinityGroups affinity groups to examine.
* @param conflicts affinity conflicts list
*/
private static void checkVmToHostWithPositiveVmToVmConflict(List<AffinityGroup> affinityGroups,
List<AffinityGroupConflicts> conflicts) {
Set<AffinityGroup> conflictingAffinityGroups = new HashSet<>();
Set<Guid> conflictingVMs = new HashSet<>();
Set<Guid> conflictingHosts = new HashSet<>();
List<AffinityGroup> affinityGroupsWithHosts = affinityGroups.stream().filter(affinityGroup -> !affinityGroup
.getVdsIds().isEmpty()).collect(Collectors.toList());
Set<Set<Guid>> unifiedPositive = AffinityRulesUtils.getUnifiedPositiveAffinityGroups(affinityGroups.stream()
.filter(AffinityGroup::isVmAffinityEnabled)
.collect(Collectors.toList()));
unifiedPositive.forEach(vms_list -> {
vms_list.forEach(vm_first -> {
vms_list.stream()
.filter(vm_second -> !vm_first.equals(vm_second))
.forEach(vm_second -> {
findVMtoHostWithPositiveVmToVmConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
affinityGroupsWithHosts,
vm_first,
vm_second);
});
});
});
if (!conflictingAffinityGroups.isEmpty()) {
conflicts.add(new AffinityGroupConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
AffinityRulesConflicts.VM_TO_HOST_CONFLICTS_POSITIVE_VM_TO_VM_AFFINITY,
AuditLogType.VM_TO_HOST_CONFLICTS_POSITIVE_VM_TO_VM_AFFINITY));
}
}
/**
* For each pair of vms : vm_first and vm_second , find all the pairs of
* affinity groups that each have one of these vms (each group with a different vm),
* with different affinity polarity to hosts and at least one intersecting host.
*/
private static void findVMtoHostWithPositiveVmToVmConflicts(Set<AffinityGroup> conflictingAffinityGroups,
Set<Guid> conflictingVMs,
Set<Guid> conflictingHosts,
List<AffinityGroup> affinityGroupsWithHosts,
Guid vm_first, Guid vm_second) {
affinityGroupsWithHosts.stream()
//get all affinity groups that contain vm_first
.filter(affinityGroup -> affinityGroup.getVmIds().contains(vm_first))
.forEach(affinityGroupFirst -> {
affinityGroupsWithHosts.stream()
//get all affinity groups that contain vm_second
.filter(ag -> ag.getVmIds().contains(vm_second))
.forEach(affinityGroupSecond -> {
//check if the affinity groups have different polarity to hosts
if (affinityGroupFirst.isVdsPositive() != affinityGroupSecond.isVdsPositive()) {
Set<Guid> vds_ids_first =
new HashSet<>(affinityGroupFirst.getVdsIds());
Set<Guid> vds_ids_second =
new HashSet<>(affinityGroupSecond.getVdsIds());
vds_ids_first.retainAll(vds_ids_second);
//check if there are intersecting hosts
if (!vds_ids_first.isEmpty()) {
conflictingAffinityGroups.addAll(Arrays.asList
(affinityGroupFirst, affinityGroupSecond));
conflictingVMs.addAll(Arrays.asList(vm_first, vm_second));
conflictingHosts.addAll(vds_ids_first);
}
}
});
});
}
/**
* Checks vm to host affinity with negative vm to vm conflicts.
* Hosts that have positive affinity to vms and those vms have negative affinity
* can cause a conflict.
*
* example of conflicts :
*
* {vm1 + host1},{vm1 - vm2},{vm2 + host1}
*
* ( + is enforcing positive affinity)
* ( [+] is non enforcing positive affinity)
* ( - is enforcing negative affinity)
* ( [-] is non enforcing negative affinity)
* ( {} is an affinity group)
*
* @param affinityGroups affinity groups to examine.
* @param conflicts affinity conflicts list
*/
private static void checkVmToHostWithNegativeVmToVmConflicts(List<AffinityGroup> affinityGroups,
List<AffinityGroupConflicts> conflicts) {
Set<AffinityGroup> conflictingAffinityGroups = new HashSet<>();
Set<Guid> conflictingVMs = new HashSet<>();
Set<Guid> conflictingHosts = new HashSet<>();
affinityGroups.stream()
//get all affinity groups with negative vm to vm affinity
.filter(AffinityGroup::isVmAffinityEnabled)
.filter(affinityGroup -> !affinityGroup.isVmPositive())
.forEach(affinityGroup -> {
affinityGroup.getVmIds()
.forEach(vm_first -> {
affinityGroup.getVmIds().stream()
//get all affinity groups that do not contain vm_first
.filter(vm_second -> !vm_second.equals(vm_first))
.forEach(vm_second -> {
Set<AffinityGroup> conflictingAffinityGroupsCandidates = new HashSet<>();
//add the first affinity group as a candidate for conflict
conflictingAffinityGroupsCandidates.add(affinityGroup);
Set<Guid> intersectingHosts =
findIntersectingPositiveHostsForVms(affinityGroups,
vm_first,
vm_second,
conflictingAffinityGroupsCandidates);
//check if there are intersecting hosts
if (!intersectingHosts.isEmpty()) {
conflictingAffinityGroups.addAll(conflictingAffinityGroupsCandidates);
conflictingVMs.addAll(Arrays.asList(vm_first,
vm_second));
conflictingHosts.addAll(intersectingHosts);
}
});
});
});
if (!conflictingAffinityGroups.isEmpty()) {
conflicts.add(new AffinityGroupConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
AffinityRulesConflicts.VM_TO_HOST_CONFLICTS_NEGATIVE_VM_TO_VM_AFFINITY,
AuditLogType.VM_TO_HOST_CONFLICTS_NEGATIVE_VM_TO_VM_AFFINITY));
}
}
/**
* Get intersecting hosts from positive affinity groups that either contain vm_first
* or vm_second
*/
private static Set<Guid> findIntersectingPositiveHostsForVms(List<AffinityGroup> affinityGroups,
Guid vm_first,
Guid vm_second, Set<AffinityGroup> conflictingAffinityGroupsCandidates) {
Set<Guid> vds_ids_first = new HashSet<>();
Set<Guid> vds_ids_second = new HashSet<>();
affinityGroups.stream()
.filter(AffinityGroup::isVdsPositive)
.forEach(ag -> {
if (ag.getVmIds().contains(vm_first)) {
vds_ids_first.addAll(ag.getVdsIds());
} else if (ag.getVmIds().contains(vm_second)) {
vds_ids_second.addAll(ag.getVdsIds());
}
//add the second affinity group as a candidate for conflict
conflictingAffinityGroupsCandidates.add(ag);
});
vds_ids_first.retainAll(vds_ids_second);
return vds_ids_first;
}
/**
* Checks for non intersecting hosts conflicts: One or more hosts that have positive enforcing affinity to a VM in
* an
* affinity
* group and different host/hosts that have positive enforcing affinity to that VM in a different affinity group.
*
* example of conflict :
* {vm1 + host1,host2} , {vm1 + host1,host3}
*
* host2 and host3 will be included in the conflicting hosts since they are
* not in the intersection of vm1 affinity groups.
*
* ( + is enforcing positive affinity)
* ( [+] is non enforcing positive affinity)
* ( - is enforcing negative affinity)
* ( [-] is non enforcing negative affinity)
* ( {} is an affinity group)
*
* @param affinityGroups affinity groups to examine.
* @param conflicts affinity conflicts list
*/
private static void checkNonIntersectingPositiveHosts(List<AffinityGroup> affinityGroups,
List<AffinityGroupConflicts> conflicts) {
Set<Guid> conflictingVMs = new HashSet<>();
Set<Guid> conflictingHosts = new HashSet<>();
List<AffinityGroup> vdsPositiveAffinityGroups = affinityGroups.stream()
.filter(AffinityGroup::isVdsPositive)
.filter(ag -> !ag.getVdsIds().isEmpty())
.collect(Collectors.toList());
Set<AffinityGroup> conflictingAffinityGroups = new HashSet<>();
vdsPositiveAffinityGroups
.forEach(ag -> {
ag.getVmIds().forEach(
vm_id -> {
addConflictsForIntersectingHostsByVm(conflictingVMs,
conflictingHosts,
vdsPositiveAffinityGroups,
conflictingAffinityGroups,
ag,
vm_id);
}
);
});
if (!conflictingAffinityGroups.isEmpty()) {
conflicts.add(new AffinityGroupConflicts(conflictingAffinityGroups,
conflictingVMs,
conflictingHosts,
AffinityRulesConflicts.NON_INTERSECTING_POSITIVE_HOSTS_AFFINITY_CONFLICTS,
AuditLogType.NON_INTERSECTING_POSITIVE_HOSTS_AFFINITY_CONFLICTS));
}
}
/**
* Find intersecting hosts from affinity groups that contain vm_id
*/
private static void addConflictsForIntersectingHostsByVm(Set<Guid> conflictingVMs,
Set<Guid> conflictingHosts,
List<AffinityGroup> vdsPositiveAffinityGroups,
Set<AffinityGroup> conflictingAffinityGroups,
AffinityGroup ag, Guid vm_id) {
vdsPositiveAffinityGroups.stream()
.filter(affinityGroup -> affinityGroup.getVmIds().contains(vm_id))
.forEach(affinityGroup -> {
Set<Guid> vds_ids = new HashSet<>(ag.getVdsIds());
vds_ids.removeAll(affinityGroup.getVdsIds());
//check if there are intersecting hosts
if (!vds_ids.isEmpty()) {
conflictingAffinityGroups.add(ag);
conflictingAffinityGroups.add(affinityGroup);
conflictingHosts.addAll(vds_ids);
conflictingVMs.add(vm_id);
}
});
}
public static class AffinityGroupConflicts {
final Set<AffinityGroup> affinityGroups;
final Set<Guid> vms;
final Set<Guid> negativeVms;
final Set<Guid> hosts;
final AffinityRulesConflicts type;
final AuditLogType auditLogType;
final boolean isVmToVmAffinity;
public AffinityGroupConflicts(Set<AffinityGroup> affinityGroups,
Set<Guid> vms,
Set<Guid> hosts,
AffinityRulesConflicts type, AuditLogType auditLogType) {
this.affinityGroups = affinityGroups;
this.vms = vms;
this.hosts = hosts;
this.type = type;
this.auditLogType = auditLogType;
this.isVmToVmAffinity = false;
this.negativeVms = new HashSet<>();
}
public AffinityGroupConflicts(Set<AffinityGroup> affinityGroups, AffinityRulesConflicts type,
AuditLogType auditLogType, Set<Guid> positiveVms, Set<Guid> negativeVms) {
this.affinityGroups = affinityGroups;
this.vms = positiveVms;
this.negativeVms = negativeVms;
this.type = type;
this.auditLogType = auditLogType;
this.isVmToVmAffinity = true;
this.hosts = new HashSet<>();
}
public Set<AffinityGroup> getAffinityGroups() {
return affinityGroups;
}
public Set<Guid> getVms() {
return vms;
}
public Set<Guid> getHosts() {
return hosts;
}
public AffinityRulesConflicts getType() {
return type;
}
public AuditLogType getAuditLogType() {
return auditLogType;
}
public Set<Guid> getNegativeVms() {
return negativeVms;
}
public boolean isVmToVmAffinity() {
return isVmToVmAffinity;
}
}
public static String getAffinityGroupsNames(Set<AffinityGroup> affinityGroups) {
return affinityGroups.stream()
.map(ag -> ag.getName())
.collect(Collectors.joining(","));
}
}