/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.placement;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.model.ExportPathParams;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.util.DataObjectUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.util.NetworkLite;
/**
* Assign StoragePorts to Initiators.
* There are subclasses corresponding to specific array types.
* This class contains the default implementation, which is one storage port
* assigned to each initiator. It is used for the VMAX.
*
* @author watsot3
*/
public class DefaultStoragePortsAssigner implements StoragePortsAssigner {
protected static final Logger _log = LoggerFactory
.getLogger(DefaultStoragePortsAssigner.class);
@Override
public int getNumberOfPortsPerPath() {
return 1;
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.volumecontroller.placement.StoragePortsAssigner#getPortsNeededPerNetwork(java.util.Map, int)
*/
@Override
public Map<URI, Integer> getPortsNeededPerNetwork(
Map<URI, List<Initiator>> net2InitiatorsMap, // new initiators only grouped by Network
ExportPathParams pathParams,
Map<URI, Set<StoragePort>> existingPortsMap, // existing ports grouped by Network
Map<URI, Set<Initiator>> existingInitiatorsMap, // existing initiators grouped by Network
boolean usingSwitchAffinity,
List<URI> networkOrder) // OUTPUT; order for network allocations
throws PlacementException {
if (existingPortsMap == null) {
existingPortsMap = new HashMap<URI, Set<StoragePort>>();
}
if (existingInitiatorsMap == null) {
existingInitiatorsMap = new HashMap<URI, Set<Initiator>>();
}
Integer allocated = 0;
Map<URI, Integer> net2NumPortsMap = new HashMap<URI, Integer>();
// Collect all the existing initiators by network.
Map<URI, Set<Initiator>> allInitiatorsMap = new HashMap<URI, Set<Initiator>>();
allInitiatorsMap.putAll(existingInitiatorsMap);
// Add in the initiators to be newly allocated.
for (URI netURI : net2InitiatorsMap.keySet()) {
List<Initiator> initiators = net2InitiatorsMap.get(netURI);
if (initiators == null || initiators.isEmpty()) {
continue;
}
if (allInitiatorsMap.get(netURI) == null) {
allInitiatorsMap.put(netURI, new HashSet<Initiator>());
}
allInitiatorsMap.get(netURI).addAll(initiators);
}
// Get the network URIs from all the initiators.
URI[] networkURIs = new URI[0];
networkURIs = allInitiatorsMap.keySet().toArray(networkURIs);
// Get the maximum number of initiators in a single host for each given Network.
// This is the combination of the existing initiators and the new initiators.
Map<URI, Integer> net2MaxHostInitiators = makeNetwork2MaxHostInitiators(
allInitiatorsMap);
// Determine the non-redundant networks. These are the networks where at least
// one host is using that single network. For non redundant networks, we
// will allow the lower of maxPaths or the maximum initiators for any host..
// If a network is redundant, we will allow the lower of (maxPaths+1)/2 or the
// maximum number of initiators for any host.
Map<URI, Set<URI>> hostToNetworksMap = new HashMap<URI, Set<URI>>();
Set<URI> nonRedundantNetworks = networksNotRedundant(allInitiatorsMap, hostToNetworksMap);
// Generate network allocation ordering.
networkOrder.clear();
networkOrder.addAll(orderNetworksForAllocation(hostToNetworksMap, net2MaxHostInitiators));
// Strategy: we iterate over the networks, adding pathsPerInitiator ports at a time
// to each network that has fewer paths than maxHostInitiators * pathsPerInitiator
// and that has not exceeded the pathLimit.
// Continue until do not add any more ports to the request for any network.
int maxPaths = pathParams.getMaxPaths();
int pathsPerInitiator = pathParams.getPathsPerInitiator();
Integer initiatorsPerPort = pathParams.getMaxInitiatorsPerPort();
boolean addedThisPass;
do {
addedThisPass = false;
for (URI networkURI : networkURIs) {
_log.debug("Processing network " + networkURI);
// If a nonRedundantNet, then at least one host is using only this network,
// so we set an upper bound of maxPaths on the number of ports requested.
// If we are using switch affinity, we need additional ports to allow matching
// of port switch to initiator switch.
// Otherwise we know there is at least one other network with initiators
// so set an upper bound of (max_paths+1)/2 which will assume we won't need
// more than half the ports in this network. We round up in the case of
// odd max_paths as we aren't sure which network should receive the
// extra port.
// The path limit keeps us from allocating a ridiculously large number of
// ports that won't be used when assigning as that reduces the chances for
// redundancy between networks in the Storage Ports Allocator.
boolean nonRedundantNet = nonRedundantNetworks.contains(networkURI);
int pathLimit = (nonRedundantNet ? maxPaths : ((maxPaths + pathsPerInitiator) / 2));
if (pathLimit < pathsPerInitiator) {
pathLimit = pathsPerInitiator;
}
// We also get the maxHostInitiators for this network.
// We never need to allocate more ports than maxHostInitiators * pathsPer_initiator
// for a given network.
int maxHostInitiators = net2MaxHostInitiators.get(networkURI);
if (net2NumPortsMap.get(networkURI) == null) {
net2NumPortsMap.put(networkURI, new Integer(0));
}
// If we currently have fewer paths in our network
// than maxHostInitiators * pathsPerInitiator,
// and we are under the pathLimit, add a paths_per_initiator ports
// to our request for this network.
Integer currentPorts = net2NumPortsMap.get(networkURI);
_log.info(String.format("Network %s pathLimit %d maxHostInitiators %d currentPorts %d",
networkURI, pathLimit, maxHostInitiators, currentPorts));
if (currentPorts <= (pathLimit - pathsPerInitiator)
&& currentPorts < (maxHostInitiators * pathsPerInitiator)) {
net2NumPortsMap.put(networkURI, currentPorts + pathsPerInitiator);
addedThisPass = true;
allocated += pathsPerInitiator;
}
}
} while (addedThisPass);
// If initiators per port is > 1, scale number of ports needed down appropriately,
// as long as we result in allocating at least paths pere initiator ports
if (initiatorsPerPort > 1 && !usingSwitchAffinity) {
for (Map.Entry<URI, Integer> entry : net2NumPortsMap.entrySet()) {
Double d = Math.ceil(entry.getValue().doubleValue() / initiatorsPerPort.doubleValue());
if (d.intValue() >= pathsPerInitiator) {
entry.setValue(Integer.valueOf(d.intValue()));
}
}
}
// Check that we are above minPaths
if (allocated < pathParams.getMinPaths()) {
int initiatorCount = 0;
for (Set<Initiator> initSet : allInitiatorsMap.values()) {
initiatorCount += initSet.size();
}
_log.info(String.format("Could not request min_path number of ports: ports needed %d "
+ "total initiators %d paths_per_initiator %d min_paths %d max_paths %d",
allocated, initiatorCount, pathParams.getPathsPerInitiator(),
pathParams.getMinPaths(), pathParams.getMaxPaths()));
throw PlacementException.exceptions.cannotAllocateMinPaths(allocated, initiatorCount,
pathParams.getPathsPerInitiator(), pathParams.getMinPaths(), pathParams.getMaxPaths());
}
return net2NumPortsMap;
}
/**
* Sorts the existing ports by the way they were previously grouped in
* existingAssignments, with initiators having the most ports first and the
* least ports last. Finally adds in the newly allocated ports and
* returns them in newPorts.
*
* @param storagePorts list of StoragePorts returned by allocator
* @param existingAssignments Map of Initiator to already allocated Ports
* @param newPorts OUT parameter containing the newly added ports.
* @return
*/
private List<StoragePort> sortPorts(List<StoragePort> storagePorts,
Map<Initiator, List<StoragePort>> existingAssignments,
List<StoragePort> newPorts) {
Set<URI> includedPorts = new HashSet<URI>();
List<StoragePort> sortedPorts = new ArrayList<StoragePort>();
// Construct a map of the stoarge ports to simplify lookup
Map<URI, StoragePort> portsMap = DataObjectUtils.toMap(storagePorts);
// Arrange the storagePorts based on the existing assignments.
// Start with initiators that have multiple port assignments first.
// The idea is that they should be kept together for proper redundancy.
for (int numPorts = 4; numPorts >= 1; numPorts--) {
for (List<StoragePort> ports : existingAssignments.values()) {
if (ports.size() >= numPorts) {
for (StoragePort port : ports) {
if (!includedPorts.contains(port.getId())
&& portsMap.containsKey(port.getId())) {
sortedPorts.add(port);
includedPorts.add(port.getId());
}
}
}
}
}
// Add any newly allocated ports.
for (StoragePort port : storagePorts) {
if (!includedPorts.contains(port.getId())) {
sortedPorts.add(port);
includedPorts.add(port.getId());
newPorts.add(port);
}
}
if (sortedPorts.size() != storagePorts.size()) {
_log.error("sortPorts size incorrect");
_log.error(sortedPorts.toString());
_log.error(storagePorts.toString());
return null;
}
return sortedPorts;
}
/**
* Returns a map from Host URI to the List<Initiator> list of initiators on that host.
* We process by host in the outer-most loop so that if ports have to be shared,
* they are shared across different hosts.
*
* @param initiators List<Initiator>
* @return Map<URI, List<Initiator>> map of host URI to that host's initiators
*/
static public Map<URI, List<Initiator>> makeHostInitiatorsMap(Collection<Initiator> initiators) {
Map<URI, List<Initiator>> hostInitiatorsMap = new HashMap<URI, List<Initiator>>();
for (Initiator initiator : initiators) {
URI host = initiator.getHost();
if (NullColumnValueGetter.isNullURI(host)) {
host = StoragePortsAssigner.unknown_host_uri;
}
if (hostInitiatorsMap.containsKey(host) == false) {
hostInitiatorsMap.put(host, new ArrayList<Initiator>());
}
hostInitiatorsMap.get(host).add(initiator);
}
return hostInitiatorsMap;
}
/**
* Sums the existing port counts per network into the net2NumPortsMap.
*
* @param net2NumPortsMap - will contain the total with the addition of net2NumExistingPortsMap
* @param net2NumExistingPortsMap
*/
protected void sumPortMaps(Map<URI, Integer> net2NumPortsMap, Map<URI, Integer> net2NumExistingPortsMap) {
for (URI networkURI : net2NumPortsMap.keySet()) {
Integer existing = net2NumExistingPortsMap.get(networkURI);
if (existing != null) {
Integer sum = existing + net2NumPortsMap.get(networkURI);
net2NumPortsMap.put(networkURI, sum);
}
}
}
/**
* Calculates the maximum number of host initiators in a single host within each Network.
*
* @param net2InitiatorsMap - map of Network to list of Host Initiators in that Network
* @return
*/
private Map<URI, Integer> makeNetwork2MaxHostInitiators(
Map<URI, Set<Initiator>> net2InitiatorsMap) {
Map<URI, Integer> net2MaxHostInitiators = new HashMap<URI, Integer>();
for (URI net : net2InitiatorsMap.keySet()) {
Map<URI, List<Initiator>> hostInitiatorsMap =
makeHostInitiatorsMap(net2InitiatorsMap.get(net));
int max = 0;
for (URI host : hostInitiatorsMap.keySet()) {
int thisHost = hostInitiatorsMap.get(host).size();
max = (thisHost > max) ? thisHost : max;
}
net2MaxHostInitiators.put(net, max);
_log.info(String.format("Network %s max initiators per host %d", net.toString(), max));
}
return net2MaxHostInitiators;
}
/**
* Determines if there are any hosts that only have connectivity to only one network.
* We favor allocating more ports in a network if it is the only one some host has access to.
* Returns a list of such networks.
*
* @param net2InitiatorsMap -- a map of Network URI to a set of Initiator objects in that network
* @param hostToNetworks - outputs a map of Host URI to Netowrk URIs used by that host
* @return URI set of networks that are not redundant
*/
private Set<URI> networksNotRedundant(Map<URI, Set<Initiator>> netToInitiatorsMap,
Map<URI, Set<URI>> hostToNetworks) {
hostToNetworks.clear();
// Reverse the netToInitiatorsMap to make an Initiator to Net map.
Map<Initiator, URI> initiatorsToNetMap = new HashMap<Initiator, URI>();
for (Map.Entry<URI, Set<Initiator>> entry : netToInitiatorsMap.entrySet()) {
for (Initiator initiator : entry.getValue()) {
initiatorsToNetMap.put(initiator, entry.getKey());
}
}
Set<URI> nonredundantNets = new HashSet<URI>();
Map<URI, List<Initiator>> hostInitiatorsMap = makeHostInitiatorsMap(initiatorsToNetMap.keySet());
for (Map.Entry<URI, List<Initiator>> entry : hostInitiatorsMap.entrySet()) {
hostToNetworks.put(entry.getKey(), new HashSet<URI>());
for (Initiator initiator : entry.getValue()) {
URI initiatorNet = initiatorsToNetMap.get(initiator);
if (initiatorNet != null) {
hostToNetworks.get(entry.getKey()).add(initiatorNet);
}
}
_log.info(String.format("Host %s uses networks %s", entry.getKey(), hostToNetworks.get(entry.getKey())));
}
// Identify any hosts only using one network and add that network to the set.
for (Set<URI> networks : hostToNetworks.values()) {
if (networks.size() == 1) {
_log.info("Non redundant network: " + networks.toString());
nonredundantNets.addAll(networks);
}
}
return nonredundantNets;
}
// A class relating the Network URI to initiator count that can be sorted by initiator count
// so as to sort the Networks by increasing initiator counts.
private class NetworkInitiatorCount implements Comparable<NetworkInitiatorCount> {
private final URI net;
private final int count;
NetworkInitiatorCount(URI net, int count) {
this.net = net;
this.count = count;
}
@Override
public int hashCode() {
return net.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!getClass().equals(obj.getClass()))
return false;
NetworkInitiatorCount other = (NetworkInitiatorCount) obj;
if (net == null) {
if (other.net != null)
return false;
} else if (!net.equals(other.net))
return false;
return true;
}
@Override
public int compareTo(NetworkInitiatorCount arg0) {
return this.count - arg0.count;
}
};
/**
* The goal of this algorithm is to order the Networks so as to pass useful context when the Storage Ports Allocator prcesses one
* network to the network it will process next. So we want networks used by the same host(s) to be processed together, and we
* defer any Networks that are the single network for a host to be processed at the end if they weren't already added
* by a host with multiple Networks.
* Within a host, we want to allocate ports for the Network requiring the least number of ports first, as that will
* make the fewest hardware unit constraints, thus giving the next network a wider variety of choices.
*
* @param hostToNetworks -- a map of Host URI to a set of Network URIs representing the Networks used by this host.
* @param net2MaxHostInitiators -- a map of Network URI to the maximum number of initiators any one host has on that network.
* This is an upper bound on the number of ports needed by the network (when multiplied by paths_per_initiator).
* @return -- an ordered list of Network URIs representing the order the Storage Ports Allocator should process the
* networks
*/
List<URI> orderNetworksForAllocation(Map<URI, Set<URI>> hostToNetworks, Map<URI, Integer> net2MaxHostInitiators) {
List<URI> orderedNetworks = new ArrayList<URI>();
List<URI> skippedNetworks = new ArrayList<URI>();
// Loop through each host, processing those with more than one network.
// Record any skipped networks.
for (Map.Entry<URI, Set<URI>> entry : hostToNetworks.entrySet()) {
if (entry.getValue().size() == 1) {
// process single networks last, as that net may be allocated with other net
skippedNetworks.addAll(entry.getValue());
continue;
}
// May a NetworkInitiatorCount for each network
List<NetworkInitiatorCount> netInitiatorCounts = new ArrayList<NetworkInitiatorCount>();
for (URI net : entry.getValue()) {
netInitiatorCounts.add(new NetworkInitiatorCount(net, net2MaxHostInitiators.get(net)));
}
// Sort the netInitiatorCounts list and process from fewest initiators to most.
// This will give better redundancy choices.
Collections.sort(netInitiatorCounts);
List<URI> ordered = new ArrayList<URI>();
for (NetworkInitiatorCount netInitiatorCount : netInitiatorCounts) {
ordered.add(netInitiatorCount.net);
if (!orderedNetworks.contains(netInitiatorCount.net)) {
orderedNetworks.add(netInitiatorCount.net);
}
}
_log.info(String.format("host %s ordered nets %s", entry.getKey(), ordered));
}
// Add any skipped networks that have not already been processed.
for (URI skippedNet : skippedNetworks) {
if (!orderedNetworks.contains(skippedNet)) {
_log.info("Adding skipped network " + skippedNet);
orderedNetworks.add(skippedNet);
}
}
_log.info("Ordered networks: " + orderedNetworks);
return orderedNetworks;
}
@Override
public boolean isPortAssignableToInitiator(NetworkLite initiatorNetwork, Initiator initiator, StoragePort port) {
return true;
}
@Override
/**
* The goal of this routine is to handle all the assignments for a single host.
* Outline of the algorithm for assignPortsToHost.
* 1. Look at each initiator that has an existing assignment.
* a. Validate paths per initiator
* b Tally up the current number of paths currently assigned
* 2. Loop through Networks, picking unassigned initiator one at a time, and assigning ports.
* Repeat until there are no more initiators or we have met max_paths.
* 3. If there are remaining unconfigured initiators, double up initiators on ports of maxinitiatorsperport is > 1.
*
*/
public void assignPortsToHost(Map<Initiator, List<StoragePort>> assignments,
Map<URI, List<Initiator>> netToNewInitiators, Map<URI, List<StoragePort>> netToAllocatedPorts,
ExportPathParams pathParams, Map<Initiator, List<StoragePort>> argExistingAssignments, URI hostURI,
Map<Initiator, NetworkLite> initiatorToNetworkLiteMap, Map<URI, Map<String, List<Initiator>>> switchToInitiatorsByNet,
Map<URI, Map<String, List<StoragePort>>> switchToStoragePortsByNet) {
_log.info("Assigning ports for host: " + hostURI);
Map<String, String> portAddressToSwitchName = makePortAddressToSwitchMap(switchToStoragePortsByNet);
Map<Initiator, String> initiatorToSwitchName = makeInitiatorToSwitchMap(switchToInitiatorsByNet);
// Make a map of port to the number of initiators using the port.
Map<StoragePort, Integer> portUseCounts = new HashMap<StoragePort, Integer>();
// Deal with existingAssignments passed in as null, meaning no assignments
Map<Initiator, List<StoragePort>> existingAssignments = nonNullAssignmentMap(argExistingAssignments);
// Determine the Initiators for this particular host.
Map<URI, List<Initiator>> existingInitiatorsMap = makeHostInitiatorsMap(existingAssignments.keySet());
List<Initiator> hostExistingInitiators = nonNullInitiatorList(existingInitiatorsMap.get(hostURI));
// Calculate port use counts from the existing assignments
for (Initiator hostExistingInitiator : hostExistingInitiators) {
List<StoragePort> portsAssigned = existingAssignments.get(hostExistingInitiator);
if (portsAssigned != null) {
for (StoragePort port : portsAssigned) {
_log.info(String.format("Existing assignment initiator %s (%s) port %s (%s/%s) net %s",
hostExistingInitiator.getInitiatorPort(), hostExistingInitiator.getHostName(),
port.getPortName(), port.getPortNetworkId(),
getPortSwitchName(port, portAddressToSwitchName), port.getNetwork()));
addPortUse(portUseCounts, port);
}
}
}
// Put any existing initiators assignments into the assignments.
assignments.putAll(existingAssignments);
// If we had existing assignments, sort the allocated ports, getting just the new ports.
if (!portUseCounts.isEmpty()) {
for (URI netURI : netToAllocatedPorts.keySet()) {
List<StoragePort> newPorts = new ArrayList<StoragePort>();
List<StoragePort> sortedPorts =
sortPorts(netToAllocatedPorts.get(netURI), existingAssignments, newPorts);
netToAllocatedPorts.put(netURI, sortedPorts);
}
}
if (switchToInitiatorsByNet != null && !switchToInitiatorsByNet.isEmpty()) {
// Pass 1: use only ports with Switch Affinity.
_log.info("PASS ONE: Assigning initiators with switch affinity");
iterateAssignmentPasses(assignments, netToNewInitiators, portUseCounts, netToAllocatedPorts,
initiatorToSwitchName, portAddressToSwitchName, switchToStoragePortsByNet, initiatorToNetworkLiteMap,
pathParams, true, false);
// Pass 2 - use combination switch affinity and non affinity ports
_log.info("PASS TWO: Assigning initiators with partial switch affinity");
iterateAssignmentPasses(assignments, netToNewInitiators, portUseCounts, netToAllocatedPorts,
initiatorToSwitchName, portAddressToSwitchName, switchToStoragePortsByNet, initiatorToNetworkLiteMap,
pathParams, true, true);
}
// Pass 3 - use only non switch affinity
_log.info("PASS THREE: Assigning initiators without switch affinity");
iterateAssignmentPasses(assignments, netToNewInitiators, portUseCounts, netToAllocatedPorts,
initiatorToSwitchName, portAddressToSwitchName, switchToStoragePortsByNet, initiatorToNetworkLiteMap,
pathParams, false, true);
// Now if we can map multiple initiators per port, fill in any unprovisoned initiators
if (pathParams.getMaxInitiatorsPerPort() > 1) {
int currentStoragePaths = portUseCounts.size();
_log.info("*** Adding assignments for multiple initiators using same ports, maxInitiatorsPerPort: "
+ pathParams.getMaxInitiatorsPerPort());
for (Map.Entry<URI, List<Initiator>> entry : netToNewInitiators.entrySet()) {
List<StoragePort> allocatedPorts = netToAllocatedPorts.get(entry.getKey());
// See if we can map the yet unprovisioned initiators to already used ports
for (Initiator initiator : entry.getValue()) {
// If initiator is already provisioned, skip it
if (assignments.containsKey(initiator)) {
continue;
}
// Check if provisioning another initiator would put us over max paths
if ((currentStoragePaths + pathParams.getPathsPerInitiator()) > pathParams.getMaxPaths()) {
break;
}
// Try to find available ports with switch affinity.
List<StoragePort> availPorts = new ArrayList<StoragePort>();
String switchName = initiatorToSwitchName.get(initiator);
if (switchToStoragePortsByNet != null && switchToStoragePortsByNet.containsKey(entry.getKey())) {
if (switchToStoragePortsByNet.get(entry.getKey()).containsKey(switchName)) {
List<StoragePort> availSwitchPorts = switchToStoragePortsByNet.get(entry.getKey()).get(switchName);
List<StoragePort> affinityPorts = getAvailablePorts(initiator, initiatorToNetworkLiteMap, availSwitchPorts,
portUseCounts, pathParams.getPathsPerInitiator(), pathParams.getMaxInitiatorsPerPort() - 1);
if (affinityPorts != null) {
availPorts.addAll(affinityPorts);
}
}
}
// Allocate paths per initiator number of ports without switch affinity
int numberPortsNeeded = pathParams.getPathsPerInitiator() - availPorts.size();
List<StoragePort> nonAffinityPorts = getAvailablePorts(initiator, initiatorToNetworkLiteMap,
allocatedPorts, portUseCounts, numberPortsNeeded,
pathParams.getMaxInitiatorsPerPort() - 1);
if (nonAffinityPorts != null) {
availPorts.addAll(nonAffinityPorts);
}
if (availPorts != null) {
// Assign them and update the current storage paths count
assignPorts(assignments, entry.getKey(), initiator, availPorts, portUseCounts,
portAddressToSwitchName, initiatorToSwitchName);
currentStoragePaths += pathParams.getPathsPerInitiator();
} else {
_log.info(String.format("No available ports for initiator %s",
initiator.getInitiatorPort()));
}
}
}
}
}
/**
* Iterates over the Networks, picking the first Initiator
*
* @param assignments
* @param netToNewInitiators
* @param portUseCounts
* @param netToAllocatedPorts
* @param initiatorToSwitchName
* @param portAddressToSwitchName
* @param switchToStoragePortsByNet
* @param initiatorToNetworkLiteMap
* @param pathParams
* @param useAffinity
* @param useNonAffinity
*/
private void iterateAssignmentPasses(
Map<Initiator, List<StoragePort>> assignments, Map<URI, List<Initiator>> netToNewInitiators,
Map<StoragePort, Integer> portUseCounts, Map<URI, List<StoragePort>> netToAllocatedPorts,
Map<Initiator, String> initiatorToSwitchName, Map<String, String> portAddressToSwitchName,
Map<URI, Map<String, List<StoragePort>>> switchToStoragePortsByNet,
Map<Initiator, NetworkLite> initiatorToNetworkLiteMap,
ExportPathParams pathParams, boolean useAffinity, boolean useNonAffinity) {
// Copy the map of network to initiators to be provisioned as this is destroyed
Map<URI, List<Initiator>> netToInitiatorsToProvision = new HashMap<URI, List<Initiator>>();
for (Map.Entry<URI, List<Initiator>> entry : netToNewInitiators.entrySet()) {
// N.B. We must copy the initiator list so as not to affect caller's data
netToInitiatorsToProvision.put(entry.getKey(), new ArrayList<Initiator>(entry.getValue()));
}
boolean hadInitiators = false;
// Loop while progress is being made
do {
hadInitiators = false;
// Loop over networks, adding equal numbers of ports per network
for (Map.Entry<URI, List<Initiator>> entry : netToInitiatorsToProvision.entrySet()) {
if (null == entry.getValue() || entry.getValue().isEmpty()) {
_log.info(String.format("No more initiators to provision net %s", entry.getKey()));
continue;
}
hadInitiators = true;
int currentStoragePaths = portUseCounts.size();
// Work on the first initiator
Initiator initiator = entry.getValue().get(0);
// Determine the ports we can use, based on using affinity, non-affinity, or both.
List<StoragePort> allocatedPorts = new ArrayList<StoragePort>();
if (useAffinity) {
allocatedPorts.addAll(getPortsWithSwitchAffinity(entry.getKey(), initiator,
initiatorToSwitchName.get(initiator), switchToStoragePortsByNet));
// If there are no ports, we cannot provision this initiator, so look at next Network
if (allocatedPorts.isEmpty()) {
entry.getValue().remove(initiator);
continue;
}
}
if (useNonAffinity) {
List<StoragePort> nonAffinityPorts = new ArrayList<StoragePort>();
if (netToAllocatedPorts != null && netToAllocatedPorts.get(entry.getKey()) != null) {
nonAffinityPorts.addAll(netToAllocatedPorts.get(entry.getKey()));
}
// Remove duplicates
nonAffinityPorts.removeAll(allocatedPorts);
allocatedPorts.addAll(nonAffinityPorts);
}
// If there are no ports, we cannot provision this initiator, so look at next Network
if (allocatedPorts.isEmpty()) {
entry.getValue().remove(initiator);
continue;
}
// Determine if ports have already been assigned (perhaps even partially but need additional ports.)
int alreadyAssigned = 0;
List<StoragePort> assignedPorts = assignments.get(initiator);
if (assignedPorts != null) {
alreadyAssigned = assignedPorts.size();
if (alreadyAssigned >= pathParams.getPathsPerInitiator()) {
// Sufficient ports have already been assigned.
entry.getValue().remove(initiator);
// This counts as we added something because we processed a previous mapping
continue;
} else {
_log.info(String.format("Retaining partial assignments Initiator %s (%s):",
initiator.getInitiatorPort(), initiator.getHostName()));
for (StoragePort port : assignedPorts) {
_log.info(String.format(" Port %s (%s/%s) network %s assigned to initiator %s/%s (%s)\n",
port.getPortName(), port.getPortNetworkId(),
getPortSwitchName(port, portAddressToSwitchName), entry.getKey(),
initiator.getInitiatorPort(), getInitiatorSwitchName(initiator, initiatorToSwitchName),
initiator.getHostName()));
addPortUse(portUseCounts, port);
}
}
}
if ((currentStoragePaths + pathParams.getPathsPerInitiator() - alreadyAssigned)
<= (pathParams.getMaxPaths() / pathParams.getMaxInitiatorsPerPort())) {
List<StoragePort> availPorts = getAvailablePorts(initiator, initiatorToNetworkLiteMap,
allocatedPorts, portUseCounts, pathParams.getPathsPerInitiator() - alreadyAssigned, 0);
if (availPorts != null) {
assignPorts(assignments, entry.getKey(), initiator, availPorts, portUseCounts, portAddressToSwitchName,
initiatorToSwitchName);
} else {
_log.info(String.format("Insufficient ports to provision initiator %s/%s (%s)",
initiator.getInitiatorPort(), getInitiatorSwitchName(initiator, initiatorToSwitchName),
initiator.getHostName()));
}
}
entry.getValue().remove(initiator);
}
} while (hadInitiators);
}
/**
* Given a network URI, initiator, and switchName of the initiator,
* returns switch ports (if any) with the same switch affinity.
*
* @param netURI
* @param initiator
* @param switchName
* @param switchToStoragePortsByNet
* @return - list of StoragePorts with same switch affinity
*/
private List<StoragePort> getPortsWithSwitchAffinity(URI netURI, Initiator initiator, String switchName,
Map<URI, Map<String, List<StoragePort>>> switchToStoragePortsByNet) {
List<StoragePort> portsWithAffinity = new ArrayList<StoragePort>();
// Find the initiators and storage ports are connected to the same switch.
Map<String, List<StoragePort>> switchPorts = switchToStoragePortsByNet.get(netURI);
if (switchPorts != null && switchPorts.containsKey(switchName)) {
portsWithAffinity.addAll(switchPorts.get(switchName));
}
return portsWithAffinity;
}
/**
* Adds a use count to a port, which indicates one initiator is using the port
* This is public static because the StoragePortsAssignerTest uses it.
*
* @param portUseCounts -- Map of StoragePort to use counts
* @param port -- Port being used
*/
public static void addPortUse(Map<StoragePort, Integer> portUseCounts, StoragePort port) {
if (!portUseCounts.containsKey(port)) {
portUseCounts.put(port, 1);
} else {
Integer newCount = portUseCounts.get(port) + 1;
portUseCounts.put(port, newCount);
}
}
/**
* Returns true if the port is being used
*
* @param portUseCounts -- Map of Storage Port to use counts
* @param port -- Port we are inquiring about
* @return
*/
private boolean isPortUsed(Map<StoragePort, Integer> portUseCounts, StoragePort port) {
return portUseCounts.containsKey(port);
}
/**
* Gets available ports with the lowest use count (must be <= maxUseCount).
*
* @param initiator -- the Initiator the ports are for
* @param allocatedPorts -- List of allocated ports from which we can choose
* @param portUseCounts -- Map of StoragePort to use counts
* @param numberOfPorts -- int number of ports required (returns all or null)
* @param maxUseCount -- The maximum use count we want.
* If zero, we want ports that are not previously used by this host (useCount == 0).
* If >zero, we can accept any ports for this host upto maxUseCount (1 <= useCount <= maxUseCount)
* @return List of the ports to be used with number of ports requested, or
* null if the required number of ports could not be found
*/
private List<StoragePort> getAvailablePorts(Initiator initiator,
Map<Initiator, NetworkLite> initiatorToNetworkLiteMap, List<StoragePort> allocatedPorts,
Map<StoragePort, Integer> portUseCounts, int numberOfPorts, int maxUseCount) {
List<StoragePort> availPorts = new ArrayList<StoragePort>();
if (allocatedPorts == null || allocatedPorts.isEmpty() || numberOfPorts < 1) {
return null;
}
// If maxUseCount > 0, we are trying to reuse ports for multiple initiators,
// so do not return any ports that have not already been used by this host.
int minUseCount = (maxUseCount > 0) ? 1 : 0;
for (int useCount = minUseCount; useCount <= maxUseCount; useCount++) {
for (StoragePort port : allocatedPorts) {
if (initiatorToNetworkLiteMap != null
&& !isPortAssignableToInitiator(initiatorToNetworkLiteMap.get(initiator), initiator, port)) {
// skip this port if not assignable (because of prezoning)
continue;
}
if (!portUseCounts.containsKey(port)) {
if (useCount == 0) {
availPorts.add(port);
}
} else if (portUseCounts.get(port) == useCount) {
availPorts.add(port);
}
if (availPorts.size() == numberOfPorts) {
return availPorts;
}
}
}
// not enough ports available
return null;
}
/**
* Assigns the ports, updates the port use counts
*
* @param assignments Map of Initiator to List<StoragePort> for new assignments
* @param netURI - Network these ports are in
* @param initiator -- The initiators ports are being assigned for
* @param assignedPorts -- The list of storage ports being assigned
* @param portUseCounts -- Map of ports to use counts
* @param portAddressToSwitchMap -- map of port Address to switch name
* @param initiatorToSwitchMap - map of initiator to switch map
*/
private void assignPorts(Map<Initiator, List<StoragePort>> assignments,
URI netURI,
Initiator initiator, List<StoragePort> assignedPorts,
Map<StoragePort, Integer> portUseCounts, Map<String, String> portAddressToSwitchMap,
Map<Initiator, String> initiatorToSwitchMap) {
for (StoragePort port : assignedPorts) {
_log.info(String.format("Port %s (%s/%s) network %s assigned to initiator %s/%s (%s)\n",
port.getPortName(), port.getPortNetworkId(),
getPortSwitchName(port, portAddressToSwitchMap), netURI,
initiator.getInitiatorPort(), getInitiatorSwitchName(initiator, initiatorToSwitchMap),
initiator.getHostName()));
addPortUse(portUseCounts, port);
}
if (assignments.get(initiator) != null) {
assignments.get(initiator).addAll(assignedPorts);
} else {
assignments.put(initiator, assignedPorts);
}
}
/**
* Will generate a default empty List<Initiator> if the passed one is null.
* Otherwise returns original list.
* Used to reduce cyclomatic complexity.
*
* @param initiatorList - null or a list of initiators
* @return a non null list of initiators, possibly empty
*/
private List<Initiator> nonNullInitiatorList(List<Initiator> initiatorList) {
if (initiatorList != null) {
return initiatorList;
}
return new ArrayList<Initiator>();
}
/**
* Will generate an empty Map<Initiator, List<StoragePort> if the passed assignmentMap is null.
* Otherwise returns the original map. Used to reduce cyclomatic complexity.
*
* @param assignmentMap -- Map(Initiator, List<StoragePort>> or null
* @return non null Map, possibly empty
*/
private Map<Initiator, List<StoragePort>> nonNullAssignmentMap(Map<Initiator, List<StoragePort>> assignmentMap) {
if (assignmentMap != null) {
return assignmentMap;
}
return new HashMap<Initiator, List<StoragePort>>();
}
/**
* Makes a map of StoragePort portNetworkId to switch name. Used for logging.
*
* @param switchToStoragePortsByNet Map of network to map of switch name to storage ports
* @return Map of port network id to switch name
*/
private Map<String, String> makePortAddressToSwitchMap(Map<URI, Map<String, List<StoragePort>>> switchToStoragePortsByNet) {
Map<String, String> result = new HashMap<String, String>();
if (switchToStoragePortsByNet == null) {
return result;
}
for (Map<String, List<StoragePort>> switchToPorts : switchToStoragePortsByNet.values()) {
for (Map.Entry<String, List<StoragePort>> entry : switchToPorts.entrySet()) {
for (StoragePort port : entry.getValue()) {
result.put(port.getPortNetworkId(), entry.getKey());
_log.info(String.format("%s WWN %s SWITCH %s NET %s",
port.getPortName(), port.getPortNetworkId(), entry.getKey(), port.getNetwork()));
}
}
}
return result;
}
/**
* Given a portAddressToSwitchMap and a port, returns the switch name
*
* @param port -- StoragePort
* @param portAddressToSwitchMap -- map of Port NetworkId to Switch Name
* @return -- Switch name string
*/
private String getPortSwitchName(StoragePort port, Map<String, String> portAddressToSwitchMap) {
if (portAddressToSwitchMap == null || !portAddressToSwitchMap.containsKey(port.getPortNetworkId())) {
return "sw?";
}
return portAddressToSwitchMap.get(port.getPortNetworkId());
}
/**
* Returns a map of Initiator to Switch Name
*
* @param switchToInitiatorsByNetwork -- Map of Network to Map of SwitchName to List of Initiators
* @return == Map of Initiator to Switch Name
*/
private Map<Initiator, String> makeInitiatorToSwitchMap(Map<URI, Map<String, List<Initiator>>> switchToInitiatorsByNetwork) {
Map<Initiator, String> initiatorToSwitchMap = new HashMap<Initiator, String>();
if (switchToInitiatorsByNetwork == null) {
return initiatorToSwitchMap;
}
for (Map<String, List<Initiator>> switchToInitiators : switchToInitiatorsByNetwork.values()) {
for (Map.Entry<String, List<Initiator>> entry : switchToInitiators.entrySet()) {
for (Initiator initiator : entry.getValue()) {
initiatorToSwitchMap.put(initiator, entry.getKey());
}
}
}
return initiatorToSwitchMap;
}
private String getInitiatorSwitchName(Initiator initiator, Map<Initiator, String> initiatorToSwitchMap) {
if (initiatorToSwitchMap == null || !initiatorToSwitchMap.containsKey(initiator)) {
return "sw?";
}
return initiatorToSwitchMap.get(initiator);
}
}