package org.infinispan.distribution.topologyaware; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.infinispan.remoting.transport.Address; import org.infinispan.remoting.transport.TopologyAwareAddress; /** * This class holds the topology hierarchy of a cache's members. * * @author Dan Berindei * @since 5.2 */ public class TopologyInfo { private final Map<String, Site> allSites = new HashMap<String, Site>(); private List<Rack> allRacks = new ArrayList<Rack>(); private List<Machine> allMachines = new ArrayList<Machine>(); private List<Address> sortedNodes; private Map<Address, Float> capacityFactors; private Map<Address, Float> maxSegments; public TopologyInfo(Collection<Address> members, Map<Address, Float> capacityFactors) { this.capacityFactors = capacityFactors; this.sortedNodes = sortMembers(members, capacityFactors); // This way, all the nodes collections at the site/rack/machine levels will be sorted for (Address node : sortedNodes) { if (capacityFactors == null || capacityFactors.get(node) != 0.0) { addTopology(node); } } } private List<Address> sortMembers(Collection<Address> members, final Map<Address, Float> capacityFactors) { List<Address> sortedList = new ArrayList<Address>(members); Collections.sort(sortedList, new Comparator<Address>() { @Override public int compare(Address o1, Address o2) { // Sort descending by capacity factor and ascending by address (UUID) int capacityComparison = capacityFactors != null ? capacityFactors.get(o1).compareTo(capacityFactors.get(o2)) : 0; return capacityComparison != 0 ? -capacityComparison : o1.compareTo(o2); } }); return sortedList; } private void addTopology(Address node) { TopologyAwareAddress taNode = (TopologyAwareAddress) node; String siteId = taNode.getSiteId(); String rackId = taNode.getRackId(); String machineId = taNode.getMachineId(); Site site = allSites.get(siteId); if (site == null) { site = new Site(siteId); allSites.put(siteId, site); } Rack rack = site.racks.get(rackId); if (rack == null) { rack = new Rack(siteId, rackId); site.racks.put(rackId, rack); allRacks.add(rack); } Machine machine = rack.machines.get(machineId); if (machine == null) { machine = new Machine(siteId, rackId, machineId); rack.machines.put(machineId, machine); allMachines.add(machine); } machine.nodes.add(node); rack.nodes.add(node); site.nodes.add(node); } public Collection<Address> getSiteNodes(String site) { return allSites.get(site).nodes; } public Collection<Address> getRackNodes(String site, String rack) { return allSites.get(site).racks.get(rack).nodes; } public Collection<Address> getMachineNodes(String site, String rack, String machine) { return allSites.get(site).racks.get(rack).machines.get(machine).nodes; } public Set<String> getAllSites() { return allSites.keySet(); } public Set<String> getSiteRacks(String site) { return allSites.get(site).racks.keySet(); } public Set<String> getRackMachines(String site, String rack) { return allSites.get(site).racks.get(rack).machines.keySet(); } public int getAllSitesCount() { return allSites.size(); } public int getAllRacksCount() { return allRacks.size(); } public int getAllMachinesCount() { return allMachines.size(); } public int getAllNodesCount() { return sortedNodes.size(); } public int getDistinctLocationsCount(TopologyLevel level, int numOwners) { switch (level) { case NODE: return Math.min(numOwners, getAllNodesCount()); case MACHINE: return Math.min(numOwners, getAllMachinesCount()); case RACK: return Math.min(numOwners, getAllRacksCount()); case SITE: return Math.min(numOwners, getAllSitesCount()); default: throw new IllegalArgumentException("Unexpected topology level: " + level); } } @Override public String toString() { StringBuilder sb = new StringBuilder("TopologyInfo{\n"); for (Map.Entry<String, Site> site : allSites.entrySet()) { String siteId = site.getKey(); sb.append(String.format("%s: {", siteId)); for (Map.Entry<String, Rack> rack : site.getValue().racks.entrySet()) { String rackId = rack.getKey(); sb.append(String.format("%s: {", rackId)); for (Map.Entry<String, Machine> machine : rack.getValue().machines.entrySet()) { String machineId = machine.getKey(); sb.append(String.format("%s: {", machineId)); for (Address node : machine.getValue().nodes) { sb.append(node); sb.append(", "); } sb.setLength(sb.length() - 2); sb.append("}, "); } sb.setLength(sb.length() - 3); sb.append("}, "); } sb.setLength(sb.length() - 3); sb.append("}, "); } sb.setLength(sb.length() - 3); sb.append('}'); return sb.toString(); } /** * The nodes collection must be sorted in descending order by their capacity. */ private double computeMaxSegmentsForNode(int numSegments, double numCopies, Collection<Address> nodes, Address node) { if (capacityFactors == null) { if (nodes.size() < numCopies) { return numSegments; } else { // The number of segment copies on each node should be the same return numCopies * numSegments / nodes.size(); } } Float nodeCapacityFactor = capacityFactors.get(node); if (nodeCapacityFactor == 0) return 0; double remainingCapacity = computeTotalCapacity(nodes, capacityFactors); double remainingCopies = numCopies * numSegments; for (Address a : nodes) { float capacityFactor = capacityFactors.get(a); double nodeSegments = capacityFactor / remainingCapacity * remainingCopies; if (nodeSegments > numSegments) { nodeSegments = numSegments; remainingCapacity -= capacityFactor; remainingCopies -= nodeSegments; if (node.equals(a)) return nodeSegments; } else { // All the nodes from now on will have less than numSegments segments, so we can stop the iteration if (!node.equals(a)) { nodeSegments = nodeCapacityFactor / remainingCapacity * remainingCopies; } return nodeSegments; } } throw new IllegalStateException("The nodes collection does not include " + node); } public float computeTotalCapacity(Collection<Address> nodes, Map<Address, Float> capacityFactors) { if (capacityFactors == null) return nodes.size(); float totalCapacity = 0; for (Address node : nodes) { totalCapacity += capacityFactors.get(node); } return totalCapacity; } private double computeMaxSegmentsForMachine(int numSegments, double numCopies, Collection<Machine> machines, Machine machine, Address node) { // The number of segment copies on each machine should be the same, except where not possible double copiesPerMachine = numCopies / machines.size(); if (machine.nodes.size() <= copiesPerMachine) { copiesPerMachine = 1; } else { int fullMachines = 0; for (Machine m : machines) { if (m.nodes.size() <= copiesPerMachine) { fullMachines++; } } copiesPerMachine = (numCopies - fullMachines) / (machines.size() - fullMachines); } return computeMaxSegmentsForNode(numSegments, copiesPerMachine, machine.nodes, node); } private double computeMaxSegmentsForRack(int numSegments, double numCopies, Collection<Rack> racks, Rack rack, Machine machine, Address node) { // Not enough racks to have an owner in each rack. // The number of segment copies on each Rack should be the same, except where not possible double copiesPerRack = numCopies / racks.size(); if (rack.machines.size() <= copiesPerRack) { copiesPerRack = 1; } else { int fullRacks = 0; for (Rack m : racks) { if (m.machines.size() <= copiesPerRack) { fullRacks++; } } copiesPerRack = (numCopies - fullRacks) / (racks.size() - fullRacks); } if (copiesPerRack <= 1) { return computeMaxSegmentsForNode(numSegments, copiesPerRack, rack.nodes, node); } else { return computeMaxSegmentsForMachine(numSegments, copiesPerRack, rack.machines.values(), machine, node); } } private double computeMaxSegmentsForSite(int numSegments, double numCopies, Collection<Site> sites, Site site, Rack rack, Machine machine, Address node) { // Not enough allSites to have an owner in each site. // The number of segment copies on each Site should be the same, except where not possible double copiesPerSite = numCopies / sites.size(); if (site.racks.size() <= copiesPerSite) { copiesPerSite = 1; } else { int fullSites = 0; for (Site s : sites) { if (s.racks.size() <= copiesPerSite) { fullSites++; } } // need to compute for racks if there are enough racks in total copiesPerSite = (numCopies - fullSites) / (sites.size() - fullSites); } if (copiesPerSite <= 1) { return computeMaxSegmentsForNode(numSegments, copiesPerSite, site.nodes, node); } else { return computeMaxSegmentsForRack(numSegments, copiesPerSite, site.racks.values(), rack, machine, node); } } public int computeExpectedSegments(int numSegments, int numOwners, Address node) { if (capacityFactors != null && capacityFactors.get(node) == 0.0) return 0; TopologyAwareAddress taa = (TopologyAwareAddress) node; String siteId = taa.getSiteId(); String rackId = taa.getRackId(); String machineId = taa.getMachineId(); Site site = allSites.get(siteId); Rack rack = site.racks.get(rackId); Machine machine = rack.machines.get(machineId); double maxSegments; if (numOwners == 1) { maxSegments = computeMaxSegmentsForNode(numSegments, numOwners, sortedNodes, node); } else if (getAllNodesCount() <= numOwners) { maxSegments = numSegments; } else if (getAllMachinesCount() <= numOwners) { maxSegments = computeMaxSegmentsForMachine(numSegments, numOwners, allMachines, machine, node); } else if (getAllRacksCount() <= numOwners) { maxSegments = computeMaxSegmentsForRack(numSegments, numOwners, allRacks, rack, machine, node); } else { maxSegments = computeMaxSegmentsForSite(numSegments, numOwners, allSites.values(), site, rack, machine, node); } return (int) Math.round(maxSegments); } private static class Site { String site; Map<String, Rack> racks = new HashMap<String, Rack>(); List<Address> nodes = new ArrayList<Address>(); private Site(String site) { this.site = site; } } private static class Rack { String site; String rack; Map<String, Machine> machines = new HashMap<String, Machine>(); List<Address> nodes = new ArrayList<Address>(); private Rack(String site, String rack) { this.site = site; this.rack = rack; } } private static class Machine { String site; String rack; String machine; List<Address> nodes = new ArrayList<Address>(); private Machine(String site, String rack, String machine) { this.site = site; this.rack = rack; this.machine = machine; } } }