/*
* Copyright (c) 2008-2012 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.placement;
import static com.google.common.collect.Collections2.transform;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.emc.storageos.customconfigcontroller.CustomConfigConstants;
import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject.DiscoveryStatus;
import com.emc.storageos.db.client.model.DiscoveredSystemObject;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportGroup.ExportGroupType;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.ExportPathParams;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.Network;
import com.emc.storageos.db.client.model.StoragePort;
import com.emc.storageos.db.client.model.StorageProtocol;
import com.emc.storageos.db.client.model.StorageProtocol.Transport;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.util.CommonTransformerFunctions;
import com.emc.storageos.db.client.util.DataObjectUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringMapUtil;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.networkcontroller.impl.NetworkDeviceController;
import com.emc.storageos.networkcontroller.impl.NetworkScheduler;
import com.emc.storageos.networkcontroller.impl.mds.Zone;
import com.emc.storageos.util.ConnectivityUtil;
import com.emc.storageos.util.ExportUtils;
import com.emc.storageos.util.NetworkLite;
import com.emc.storageos.util.NetworkUtil;
import com.emc.storageos.volumecontroller.impl.hds.prov.utils.HDSUtils;
import com.emc.storageos.volumecontroller.impl.plugins.metering.smis.processor.PortMetricsProcessor;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.emc.storageos.workflow.WorkflowService;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
/**
* StorageScheduler service for block and file storage. StorageScheduler is done based on desired
* class-of-service parameters for the provisioned storage.
*/
public class BlockStorageScheduler {
protected static final Logger _log = LoggerFactory.getLogger(BlockStorageScheduler.class);
private DbClient _dbClient;
private PortMetricsProcessor _portMetricsProcessor;
private NetworkScheduler _networkScheduler;
private static CustomConfigHandler customConfigHandler;
public void setDbClient(DbClient dbClient) {
_dbClient = dbClient;
}
public void setPortMetricsProcessor(PortMetricsProcessor portMetricsProcessor) {
if (_portMetricsProcessor == null) {
_portMetricsProcessor = portMetricsProcessor;
}
}
public void setNetworkScheduler(NetworkScheduler networkScheduler) {
if (_networkScheduler == null) {
_networkScheduler = networkScheduler;
}
}
public void setCustomConfigHandler(CustomConfigHandler configHandler) {
customConfigHandler = configHandler;
}
/**
* Invoke placement to select storage ports for export, and then
* to assign specific storage ports to specific initiators.
*
* Note: This method returns only new assignments but validates minPaths
* for all assignments(existing and new).
*
* @param storage
* @param initiators - The new initiators to be provisioned.
* @param pathParams - the Export Path parameters (maxPaths, pathsPerInitiator)
* @param existingZoningMap the zones that already exist on the mask plus any new mapping added
* by assigning pre-zoned ports.
* @param volumeURIs the URIs of the volumes in the mask
* @param the export virtual array
* @return Map<URI, List<URI> map of Initiator URIs to list of StoragePort URIs to be used by Initiator
* @throws DeviceControllerException if there is an unexpected error
* @throws PlacementException if we were unable to meet the placement constraints (such as minPaths.)
*/
public Map<URI, List<URI>> assignStoragePorts(StorageSystem storage, URI virtualArray,
List<Initiator> initiators,
ExportPathParams pathParams,
StringSetMap existingZoningMap,
Collection<URI> volumeURIs) throws DeviceControllerException {
Map<URI, List<URI>> assignmentMap = null;
boolean backend = ExportMaskUtils.areBackendInitiators(initiators);
if (!allocateFromPrezonedPortsOnly(virtualArray, storage.getSystemType(), backend)) {
try {
assignmentMap = internalAssignStoragePorts(storage, virtualArray,
initiators, volumeURIs, pathParams, existingZoningMap);
} catch (PlacementException e) {
_log.error("Unable to assign storage Ports", e);
throw DeviceControllerException.exceptions.exceptionAssigningStoragePorts(e.getMessage(), e);
} catch (Exception e) {
_log.error("Unable to assign Storage Ports", e);
throw DeviceControllerException.exceptions.unexpectedExceptionAssigningPorts(e);
}
} else {
// assignment should be made solely based on pre-zoned ports
_log.info("Manual zoning is specified for this virtual array and the system configuration is to use "
+ "existing zones. Assign port storage will not add any additional ports.");
assignmentMap = new HashMap<URI, List<URI>>();
}
return assignmentMap;
}
/**
* Assigns storage ports to initiators.
*
* @param system storage system which contains storage ports
* @param storagePorts storage ports to assign
* @param virtualArray virtual array which contains storage ports
* @param initiators list of initiators
* @param pathParams the export path parameters
* @param existingZoningMap existing zoning map in the mask
* @return assignments of storage ports to initiators
* @throws DeviceControllerException
*/
public Map<URI, List<URI>> assignSelectedStoragePorts(StorageSystem system, List<StoragePort> storagePorts, URI virtualArray,
List<Initiator> initiators,
ExportPathParams pathParams,
StringSetMap existingZoningMap) throws DeviceControllerException {
Map<Initiator, List<StoragePort>> assignments = new HashMap<>();
try {
// Group the new initiators by their networks - filter out those not in a network
Map<NetworkLite, List<Initiator>> initiatorsByNetwork = getInitiatorsByNetwork(initiators, existingZoningMap, _dbClient);
Map<Initiator, NetworkLite> initiatorsToNetworkLiteMap = getInitiatorToNetworkLiteMap(initiatorsByNetwork);
// Get the storage ports that can be used in the initiators networks
Map<NetworkLite, List<StoragePort>> portsByNetwork =
selectStoragePortsInNetworks(storagePorts, initiatorsByNetwork.keySet(), virtualArray, pathParams);
StoragePortsAssigner assigner = StoragePortsAssignerFactory.getAssigner(system.getSystemType());
// Call StoragePortsAssigner once per host to do the assignments
Map <URI, Map<URI, List<Initiator>>> hostsToNetToInitiators =
getHostInitiatorsMapFromNetworkLite(initiatorsByNetwork);
Map<URI, List<StoragePort>> allocatedPortsMap = getAllocatedPortsMap(portsByNetwork);
// Get the existing assignments in object form.
Map<Initiator, List<StoragePort>> existingAssignments =
generateInitiatorsToStoragePortsMap(existingZoningMap, virtualArray);
// For each host, assign the ports to the appropriate initiators.
for (URI hostURI : hostsToNetToInitiators.keySet()) {
assigner.assignPortsToHost(assignments, hostsToNetToInitiators.get(hostURI),
allocatedPortsMap, pathParams, existingAssignments, hostURI, initiatorsToNetworkLiteMap, null, null);
}
// Validate that minPaths was met across all assignments (existing and new).
validateMinPaths(system, pathParams, existingAssignments, assignments, initiators);
return convertAssignmentsToURIs(assignments);
} catch (PlacementException e) {
_log.error("Unable to assign storage Ports", e);
throw DeviceControllerException.exceptions.exceptionAssigningStoragePorts(e.getMessage(), e);
} catch (Exception e) {
_log.error("Unable to assign Storage Ports", e);
throw DeviceControllerException.exceptions.unexpectedExceptionAssigningPorts(e);
}
}
/**
* Allocates and assigns StoragePorts.
*
* @param system - The StorageSystem the ports will be assigned from.
* @param varray - The VirtualArray (ex. Neighborhood) the initiators should be found in.
* @param newInitiators - The new initiators to be provisioned.
* @param volumeURIs - list of volumes
* @param pathParams - Export path parameters (maxPaths, pathsPerInitiator)
* @param existingZoningMap - A map of initiators to a set of previously allocated port URI strings.
* @return Map<URI, List<URI>> Initiator URI to list of Target StoragePort URIs
*/
private Map<URI, List<URI>> internalAssignStoragePorts(StorageSystem system,
URI varray, List<Initiator> newInitiators,
Collection<URI> volumeURIs,
ExportPathParams pathParams,
StringSetMap existingZoningMap) {
// Make reasonable defaults for the path parameters.
checkPathParams(pathParams, system);
_log.info(String.format("Assigning Ports for Array %s params %s Varray %s",
system.getNativeGuid(),
pathParams.toString(), varray));
// Get the existing assignments in object form.
Map<Initiator, List<StoragePort>> existingAssignments =
generateInitiatorsToStoragePortsMap(existingZoningMap, varray);
// Group the new initiators by their networks - filter out those not in a network
Map<NetworkLite, List<Initiator>> initiatorsByNetwork = getInitiatorsByNetwork(newInitiators, existingZoningMap, _dbClient);
Map<Initiator, NetworkLite> initiatorsToNetworkLiteMap = getInitiatorToNetworkLiteMap(initiatorsByNetwork);
// Get the storage ports in the storage system that can be used in the initiators networks
Map<NetworkLite, List<StoragePort>> portsByNetwork =
selectStoragePortsInNetworks(system.getId(), initiatorsByNetwork.keySet(), varray, pathParams);
// allocate ports balancing across networks and considering port metrics
Map<NetworkLite, List<StoragePort>> allocatedPorts = allocatePorts(system,
varray, initiatorsByNetwork, portsByNetwork, volumeURIs, pathParams, existingZoningMap);
StoragePortsAssigner assigner = StoragePortsAssignerFactory.getAssigner(system.getSystemType());
Map<Initiator, List<StoragePort>> assignments = new HashMap<Initiator, List<StoragePort>>();
// Call StoragePortsAssigner once per host to do the assignments
Map <URI, Map<URI, List<Initiator>>> hostsToNetToInitiators =
getHostInitiatorsMapFromNetworkLite(initiatorsByNetwork);
Map<URI, List<StoragePort>> allocatedPortsMap = getAllocatedPortsMap(allocatedPorts);
// For each host, assign the ports to the appropriate initiators.
for (URI hostURI : hostsToNetToInitiators.keySet()) {
Map<URI, Map<String, List<Initiator>>> switchInitiatorsByNet = new HashMap<URI, Map<String, List<Initiator>>>();
Map<URI, Map<String, List<StoragePort>>> switchStoragePortsByNet = new HashMap<URI, Map<String, List<StoragePort>>>();
Map<URI, List<Initiator>> initiatorByNetMap = hostsToNetToInitiators.get(hostURI);
PlacementUtils.getSwitchfoForInititaorsStoragePorts(initiatorByNetMap, allocatedPortsMap, _dbClient,
system, switchInitiatorsByNet, switchStoragePortsByNet);
assigner.assignPortsToHost(assignments, initiatorByNetMap,
allocatedPortsMap, pathParams, existingAssignments, hostURI, initiatorsToNetworkLiteMap,
switchInitiatorsByNet, switchStoragePortsByNet);
}
// Validate that minPaths was met across all assignments (existing and new).
validateMinPaths(system, pathParams, existingAssignments, assignments, newInitiators);
return convertAssignmentsToURIs(assignments);
}
/**
* This function performs checks to ensure the minimum path requirement is met for the export.
* If this call is for adding additional paths, the validation is across existing and new assignments.
*
* @param system the storage system of the export
* @param pathParams the aggregate pathParam for all the export volumes
* @param existingAssignments the existing initiator-port assignments. This can be empty for new assignments.
* @param assignments the assignments made by this call to assign ports
* @param existingInitiatorsMap the existing host-initiators map. This can be empty for new assignments.
* @param newInitiators the list of initiators that received assignments
* @throws PlacementException if minimum paths not met
*/
private void validateMinPaths(StorageSystem system, ExportPathParams pathParams,
Map<Initiator, List<StoragePort>> existingAssignments,
Map<Initiator, List<StoragePort>> assignments,
List<Initiator> newInitiators) {
// Validate that minPaths was met across all assignments (existing and new).
// Get a map of Network URIs to Initiators for the existing Initiators.
Map<URI, Set<Initiator>> existingInitiatorsMap = generateNetworkToInitiatorsMap(existingAssignments, _dbClient);
Map<Initiator, List<StoragePort>> allAssignments = new HashMap<Initiator, List<StoragePort>>();
allAssignments.putAll(existingAssignments);
allAssignments.putAll(assignments);
// This min path check is done on allInitiators
Collection<Initiator> allInitiators = new HashSet<Initiator>(newInitiators);
for (Set<Initiator> existingInitiators : existingInitiatorsMap.values()) {
allInitiators.addAll(existingInitiators);
}
Map<URI, List<Initiator>> hostInitiatorsMap = DefaultStoragePortsAssigner.makeHostInitiatorsMap(allInitiators);
validateMinPaths(pathParams, hostInitiatorsMap, allAssignments);
if (needToValidateHA(system)) {
validateHACapabilities(pathParams, allAssignments);
}
}
/**
* Given a collection of storage ports on a storage system, a given list of initiators
* belonging to a host or cluster, and given an export for which some port may already
* be allocated ('add initiator' use case), apply the port selection algorithm to find
* the ports that should be used by (or 'added to' for 'add initiator' use case).
*
* @param system the storage system of the export
* @param varray the export varray
* @param initiatorsByNetwork the initiators to which the ports will be assigned,
* grouped by network
* @param portsByNetwork the ports from which the allocation will be made. Note
* this function can be called to select ports from pre-zoned ports, in
* which can be a subset of all the available ports on the storage system.
* @param volumeURIs all the export volumes, new and existing
* @param pathParams the export path parameter which accounts for the paths
* requirement for all the volumes vpools.
* @param existingZoningMap existing allocations, null is no allocations exist.
* @return the selected ports to be used in the export.
*
*/
public Map<NetworkLite, List<StoragePort>> allocatePorts(StorageSystem system, URI varray,
Map<NetworkLite, List<Initiator>> initiatorsByNetwork,
Map<NetworkLite, List<StoragePort>> portsByNetwork,
Collection<URI> volumeURIs,
ExportPathParams pathParams,
StringSetMap existingZoningMap) {
// Make reasonable defaults for the path parameters.
checkPathParams(pathParams, system);
_log.info(String.format("Assigning Ports for Array %s params %s Varray %s",
system.getNativeGuid(),
pathParams.toString(), varray));
boolean isSwitchLocalityEnabled = isSwitchAffinityAllocationEnabled(system.getSystemType());
_log.info(String.format("The switch affinity is %s .", isSwitchLocalityEnabled));
// Get the existing assignments in object form.
Map<Initiator, List<StoragePort>> existingAssignments =
generateInitiatorsToStoragePortsMap(existingZoningMap, varray);
// Get a map of Network URIs to Initiators for the existing Initiators.
Map<URI, Set<Initiator>> existingInitiatorsMap = generateNetworkToInitiatorsMap(existingAssignments, _dbClient);
// Get a map of Network URIs to StoragePorts for the existing Storage Ports.
Map<URI, Set<StoragePort>> existingPortsMap = generateNetworkToStoragePortsMap(existingAssignments, existingInitiatorsMap);
// Make Net to Initiators Map and URI to Network map.
Map<URI, List<Initiator>> net2InitiatorsMap = new HashMap<URI, List<Initiator>>();
Map<URI, NetworkLite> networkMap = new HashMap<URI, NetworkLite>();
for (NetworkLite network : initiatorsByNetwork.keySet()) {
if (!networkMap.containsKey(network.getId())) {
networkMap.put(network.getId(), network);
net2InitiatorsMap.put(network.getId(), initiatorsByNetwork.get(network));
}
}
// Filter Initiators by access - if a host has local access to the storage system
// remove initiators that are routed to it - Should this be done when initiators are added or is this an override?
filterRemoteInitiators(system, varray, net2InitiatorsMap, networkMap);
// Compute the number of Ports needed for each Network, and get the network ordering for allocation.
List<URI> orderedNetworks = new ArrayList<URI>();
StoragePortsAssigner assigner = StoragePortsAssignerFactory.getAssigner(system.getSystemType());
Map<URI, Integer> net2PortsNeeded = assigner.getPortsNeededPerNetwork(net2InitiatorsMap,
pathParams, existingPortsMap, existingInitiatorsMap, isSwitchLocalityEnabled, orderedNetworks);
for (Map.Entry<URI, Integer> entry : net2PortsNeeded.entrySet()) {
if (networkMap.get(entry.getKey()) != null) {
_log.info(String.format("Network %s (%s) requested ports %d",
networkMap.get(entry.getKey()).getLabel(), entry.getKey().toString(), entry.getValue()));
}
}
// For each Network, allocate the ports required, and then assign the ports.
StoragePortsAllocator allocator = new StoragePortsAllocator();
// In case this is an update of existing allocation, add all the previously
// allocated ports into the context _alreadyAllocatedXXX fields.
// This allows knowledge that an previous allocation in director A was made
// so that in a Vpool upgrade we can allocate a different director
// in a different network.
for (URI netURI : existingPortsMap.keySet()) {
NetworkLite network = networkMap.get(netURI);
Set<StoragePort> existingPorts = existingPortsMap.get(netURI);
allocator.addPortsToAlreadyAllocatedContext(_dbClient, network, existingPorts);
}
// Compute the StoragePort usage map.
Map<URI, Map<StoragePort, Long>> portUsageMap =
computeStoragePortUsageMapForPorts(system.getId(),
networkMap, varray, portsByNetwork);
// Filter out the ports in the case of VMAX and RP splitting: (CTRL-7288)
// https://support.emc.com/docu10627_RecoverPoint-Deploying-with-Symmetrix-Arrays-and-Splitter-Technical-Notes.pdf?language=en_US
// We need to align the masking of volumes to hosts to the same ports as the RP masking view.
portUsageMap = filterStoragePortsForRPVMAX(system.getId(), networkMap, varray, portUsageMap, volumeURIs);
// Loop through all the required Networks, allocating ports as necessary.
Map<NetworkLite, List<StoragePort>> portsAllocated = new HashMap<NetworkLite, List<StoragePort>>();
for (URI netURI : orderedNetworks) {
// This is the network of the initiator
NetworkLite network = networkMap.get(netURI);
Integer portsNeeded = net2PortsNeeded.get(netURI);
if (portsNeeded == null || portsNeeded == 0) {
_log.info("No ports to be assigned for network: " + netURI);
continue;
}
List<Initiator> initiators = net2InitiatorsMap.get(netURI);
// Check that there are initiators to get assignments. This check is
// needed for when initiators were eliminate by #filterRemoteInitiators
if (initiators == null || initiators.isEmpty()) {
_log.info("No initiators to be assigned for network: " + netURI);
continue;
}
if (portUsageMap.get(netURI).isEmpty()) {
_log.warn(String.format("No ports available for network: %s. Hence skipping allocation of ports in this network", netURI));
continue;
}
Map<String, Integer> switchToMaxPortNumber = null;
if (isSwitchLocalityEnabled) {
switchToMaxPortNumber = getSwitchToMaxPortNumberMap(initiators, pathParams);
}
// Allocate the storage ports.
portsAllocated.put(network, allocatePortsFromNetwork(
system.getId(), network, varray, portsNeeded,
portUsageMap.get(netURI), allocator, existingPortsMap.get(netURI),
true, switchToMaxPortNumber));
}
return portsAllocated;
}
/**
* If this is RP and VMAX, we need to filter out storage ports that aren't part of the existing RP mask,
* otherwise the masking operation will fail.
*
* @param storage - storage system
* @param networkMap - network map
* @param varray - virtual array
* @param portUsageMap - storage port map to draw from
* @return portUsageMap updated
*/
private Map<URI, Map<StoragePort, Long>> filterStoragePortsForRPVMAX(
URI storage, Map<URI, NetworkLite> networkMap, URI varray,
Map<URI, Map<StoragePort, Long>> portUsageMap,
Collection<URI> volumeURIs) {
// First, make sure this is VMAX
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
if (storageSystem == null || storageSystem.getSystemType() == null ||
!StorageSystem.Type.vmax.name().equalsIgnoreCase(storageSystem.getSystemType()))
{
// Bail out, not VMAX
return portUsageMap;
}
// Next, check to see if any volumes in this list is part of an RP export group already.
if (volumeURIs == null || volumeURIs.isEmpty()) {
// Bail out, no volumes specified. So definitely not RP.
return portUsageMap;
}
// Log the port usage map before we modify it.
logPortUsageMap(portUsageMap);
for (URI volumeId : volumeURIs) {
// Check to see if this volume is in an RP export group
URIQueryResultList exportGroupURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeExportGroupConstraint(
volumeId), exportGroupURIs);
while (exportGroupURIs.iterator().hasNext()) {
URI exportGroupURI = exportGroupURIs.iterator().next();
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI);
if (ExportUtils.checkIfExportGroupIsRP(exportGroup)) {
_log.info("filterStoragePortsForRPVMAX - Found that exporting volumes that are being split by VMAX. "
+ "Examining storage ports and qualifying only storage ports used in the RecoverPoint masking view(s)");
// Explore storage ports in the masking views and collect them.
Set<URI> rpTargetPorts = new HashSet<URI>();
if (exportGroup == null || exportGroup.getExportMasks() == null || exportGroup.getExportMasks().isEmpty()) {
continue;
}
List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(_dbClient, exportGroup);
for (ExportMask exportMask : exportMasks) {
rpTargetPorts.addAll(Collections2.transform(exportMask.getStoragePorts(),
CommonTransformerFunctions.FCTN_STRING_TO_URI));
}
// Print up a good log message using the storage ports we found.
for (URI storagePortId : rpTargetPorts) {
List<StoragePort> ports = _dbClient.queryObjectField(StoragePort.class, "portName", Arrays.asList(storagePortId));
if (ports == null) {
continue;
}
StringSet portNames = new StringSet();
for (StoragePort port : ports) {
portNames.add(port.getPortName());
}
_log.info("filterStoragePortsForRPVMAX - Found that RP masking view is using ports: {}",
Joiner.on(',').join(portNames));
}
// Go through all of the storage ports in all networks and remove those that do not appear in this list.
Set<StoragePort> removePorts = new HashSet<StoragePort>();
for (Map.Entry<URI, Map<StoragePort, Long>> entry : portUsageMap.entrySet()) {
if (entry.getValue().keySet() == null) {
continue;
}
for (StoragePort port : entry.getValue().keySet()) {
if (!rpTargetPorts.contains(port.getId())) {
removePorts.add(port);
_log.info(
"filterStoragePortsForRPVMAX - Found that RP masking view does not use port {}, "
+ "so it does not qualify for host/cluster export",
port.getPortName());
} else {
_log.info(
"filterStoragePortsForRPVMAX - Found that RP masking view uses port {}, "
+ "so it qualifies for host/cluster export",
port.getPortName());
}
}
}
// Remove the storage ports from the usage map.
for (StoragePort removePort : removePorts) {
for (Map.Entry<URI, Map<StoragePort, Long>> entry : portUsageMap.entrySet()) {
entry.getValue().remove(removePort);
}
}
}
}
}
// Log the port usage map after we modify it.
logPortUsageMap(portUsageMap);
return portUsageMap;
}
private void logPortUsageMap(Map<URI, Map<StoragePort, Long>> portUsageMap) {
// Print up the resulting port usage map.
StringBuilder sb = new StringBuilder();
sb.append("filterStoragePortsForRPVMAX - Resulting storage port map: ");
for (Map.Entry<URI, Map<StoragePort, Long>> entry : portUsageMap.entrySet()) {
if (entry.getValue().keySet() == null) {
continue;
}
NetworkLite nl = NetworkUtil.getNetworkLite(entry.getKey(), _dbClient);
Collection<String> portNames = transform(entry.getValue().keySet(), CommonTransformerFunctions.fctnStoragePortToPortName());
sb.append(String.format("Network: %s, Ports: %s", nl.getLabel(), Joiner.on(',').join(portNames)));
}
_log.info(sb.toString());
}
/**
* After all initiators are accounted for, for each host, check if the initiators
* are routed or local and update the list:
* <ul>
* <li>If the host's initiators are all local, keep them all</li>
* <li>If the host's initiators are all routed, keep them all</li>
* <li>If the host's initiators are a mix of local and routed, remove the routed initiators</li>
* </ul>
* For a given network, if all initiators are not used, remove the network entry
* from the map so that its ports are no longer considered in the port allocation
*
* @param system the storage system for the export
* @param varray the varray of the export
* @param net2InitiatorsMap a map of network-to-initiators for the export request
* @param networkMap Map of network URI to network, it must have the same keys as
* net2InitiatorsMap
*/
private void filterRemoteInitiators(
StorageSystem system, URI varray, Map<URI, List<Initiator>> net2InitiatorsMap, Map<URI, NetworkLite> networkMap) {
Set<URI> localNetworks = getStorageSystemLocalNetworks(system, varray.toString());
Map<URI, Map<URI, List<Initiator>>> hostInitiatorsMap = getHostInitiatorsMap(net2InitiatorsMap);
Iterator<URI> itr = net2InitiatorsMap.keySet().iterator();
while (itr.hasNext()) {
URI key = itr.next();
for (URI hostURI : hostInitiatorsMap.keySet()) {
Collection<URI> hostNetworks = getHostFilteredNetworks(localNetworks, hostInitiatorsMap.get(hostURI).keySet());
if (!hostNetworks.contains(key)) {
if (hostInitiatorsMap.get(hostURI).get(key) != null) {
_log.info("Removing initiators {} for host {} because they have routed access " +
" to the storage system while other initiators have local access.",
hostInitiatorsMap.get(hostURI).get(key),
hostURI);
net2InitiatorsMap.get(key).removeAll(hostInitiatorsMap.get(hostURI).get(key));
}
}
}
if (net2InitiatorsMap.get(key).isEmpty()) {
itr.remove();
networkMap.remove(key);
}
}
}
/**
* Given the list of host initiators networks and the local networks on the
* storage system, filter out the remote networks if the host has local access
* by local networks to the storage system
*
* @param localNetworks the storage system local networks
* @param hostNetworks the host networks as computed from its initiators
* @return the list of networks that should be used for the host.
*/
private Collection<URI> getHostFilteredNetworks(Set<URI> localNetworks, Set<URI> hostNetworks) {
if (Collections.disjoint(localNetworks, hostNetworks)) { // all the networks have remote access, use all initiators
return hostNetworks;
} else {
HashSet<URI> temp = new HashSet<URI>();
for (URI net : hostNetworks) {
if (localNetworks.contains(net)) {
temp.add(net);
}
}
return temp;
}
}
/**
* Given a list of networks-to-initiators, further break down the map by host so
* that the end result is a map of hosts-to-networks-to-initiators.
*
* @param net2InitiatorsMap networks-to-initiators map
* @return a map of hosts-to-network-to-initiators
*/
private Map<URI, Map<URI, List<Initiator>>> getHostInitiatorsMap(Map<URI, List<Initiator>> net2InitiatorsMap) {
Map<URI, Map<URI, List<Initiator>>> hostInitiatorsMap = new HashMap<URI, Map<URI, List<Initiator>>>();
for (Map.Entry<URI, List<Initiator>> entry : net2InitiatorsMap.entrySet()) {
List<Initiator> initiators = entry.getValue();
for (Initiator initiator : initiators) {
URI host = initiator.getHost();
if (NullColumnValueGetter.isNullURI(host)) {
host = StoragePortsAssigner.unknown_host_uri;
}
Map<URI, List<Initiator>> hostMap = hostInitiatorsMap.get(host);
if (hostMap == null) {
hostMap = new HashMap<URI, List<Initiator>>();
hostInitiatorsMap.put(host, hostMap);
}
if (hostMap.get(entry.getKey()) == null) {
hostMap.put(entry.getKey(), new ArrayList<Initiator>());
}
hostMap.get(entry.getKey()).add(initiator);
}
}
return hostInitiatorsMap;
}
/**
* Given a list of networks-to-initiators, further break down the map by host so
* that the end result is a map of hosts-to-networks-to-initiators.
*
* @param net2InitiatorsMap networks-to-initiators map
* @return a map of hosts-to-network-to-initiators
*/
private Map<URI, Map<URI, List<Initiator>>> getHostInitiatorsMapFromNetworkLite(
Map<NetworkLite, List<Initiator>> net2InitiatorsMap) {
Map<URI, Map<URI, List<Initiator>>> hostNetworkInitiatorsMap = new HashMap<URI, Map<URI, List<Initiator>>>();
for (Map.Entry<NetworkLite, List<Initiator>> entry : net2InitiatorsMap.entrySet()) {
List<Initiator> initiators = entry.getValue();
for (Initiator initiator : initiators) {
URI host = initiator.getHost();
if (NullColumnValueGetter.isNullURI(host)) {
host = StoragePortsAssigner.unknown_host_uri;
}
Map<URI, List<Initiator>> hostMap = hostNetworkInitiatorsMap.get(host);
if (hostMap == null) {
hostMap = new HashMap<URI, List<Initiator>>();
hostNetworkInitiatorsMap.put(host, hostMap);
}
if (hostMap.get(entry.getKey().getId()) == null) {
hostMap.put(entry.getKey().getId(), new ArrayList<Initiator>());
}
hostMap.get(entry.getKey().getId()).add(initiator);
}
}
return hostNetworkInitiatorsMap;
}
/**
* For a given storage system, find all the networks that can be used to access
* the storage system without routing in a given varray.
*
* @param system the storage system
* @param varray the varray
* @return find all the networks that can access the storage
* system without routing
*/
private Set<URI> getStorageSystemLocalNetworks(StorageSystem system, String varray) {
Set<URI> networks = new HashSet<URI>();
List<StoragePort> ports =
ConnectivityUtil.getStoragePortsForSystem(_dbClient,
system.getId());
for (StoragePort port : ports) {
if (!NullColumnValueGetter.isNullURI(port.getNetwork())
&& port.getTaggedVirtualArrays() != null &&
port.getTaggedVirtualArrays().contains(varray)) {
networks.add(port.getNetwork());
}
}
return networks;
}
private void checkPathParams(ExportPathParams pathParams, StorageSystem system) {
// Make a reasonable default for ExportPathParams if not set.
// For VNX, default is two ports per initiator. For others, one port per initiator.
if (pathParams == null) {
pathParams = ExportPathParams.getDefaultParams();
}
if (pathParams.getPathsPerInitiator() == 0) {
if (pathParams.getMaxPaths() >= 2
&& system.getSystemType().equals(DiscoveredDataObject.Type.vnxblock.name())) {
pathParams.setPathsPerInitiator(2);
} else {
pathParams.setPathsPerInitiator(1);
}
}
if (pathParams.getMinPaths() == 0) {
pathParams.setMinPaths(1);
}
}
/**
* Convert assignment map of Initiators to StoragePorts to the URI values for each.
*
* @param assignments Map<Initiator, List<StoragePort>
* @return Map<URI, List<URI>> Map of Initiator URI to a list of StoragePort URIs
*/
private Map<URI, List<URI>> convertAssignmentsToURIs(Map<Initiator, List<StoragePort>> assignments) {
HashMap<URI, List<URI>> assignmentMap = new HashMap<URI, List<URI>>();
for (Initiator initiator : assignments.keySet()) {
URI key = initiator.getId();
if (assignmentMap.get(key) == null) {
assignmentMap.put(key, new ArrayList<URI>());
}
for (StoragePort port : assignments.get(initiator)) {
assignmentMap.get(key).add(port.getId());
}
}
return assignmentMap;
}
/**
* Add new assignments to the zoning map.
*
* @param assignments Map<Initiator, List<StoragePort> the new assignments
* @param newZoningMap the new zoning map being generated.
*/
private void addAssignmentsToZoningMap(Map<Initiator, List<StoragePort>> assignments, StringSetMap newZoningMap) {
for (Initiator initiator : assignments.keySet()) {
String key = initiator.getId().toString();
List<StoragePort> ports = assignments.get(initiator);
if (ports != null && !ports.isEmpty()) {
if (newZoningMap.get(key) == null) {
newZoningMap.put(key, new StringSet());
}
if (ports != null) {
for (StoragePort port : ports) {
newZoningMap.get(key).add(port.getId().toString());
}
}
}
}
}
/**
* Allocate ports from a Network. It is assumed that the port usage metrics have
* already been calculated.
*
* @param storageURI Storage System
* @param network NetworkLite
* @param varrayURI Virtual Array
* @param numPaths Number of ports to be allocated
* @param portUsageMap The port usage map computed by computeStoragePortUsageMap
* @param allocator The StoragePortsAllocator to use
* @param previouslyAllocatedPorts A set of previously allocated ports in this Network
* @param allowFewerPorts Boolean if true allows allocating fewer ports than requested
* @param switchToMaxPortNumber The map of switch name to storage port numbers to be allocated for the network
* @return List of the allocated ports in an order for good redundancy.
* @throws PlacementException
*/
private List<StoragePort> allocatePortsFromNetwork(
URI storageURI, NetworkLite network, URI varrayURI, int numPaths,
Map<StoragePort, Long> portUsageMap,
StoragePortsAllocator allocator, Set<StoragePort> previouslyAllocatedPorts,
boolean allowFewerPorts, Map<String, Integer> switchToMaxPortNumber) throws PlacementException {
List<StoragePort> sports = new ArrayList<StoragePort>();
if (network.getTransportType().equals(StorageProtocol.Transport.FC.name()) ||
network.getTransportType().equals(StorageProtocol.Transport.IP.name())) {
List<StoragePort> portList = allocator.selectStoragePorts(
_dbClient, portUsageMap, network, varrayURI, numPaths, previouslyAllocatedPorts, allowFewerPorts, switchToMaxPortNumber);
for (StoragePort port : portList) {
if (!sports.contains(port)) {
sports.add(port);
}
}
} else {
// Otherwise, follow the existing code and select one storage port per network
List<StoragePort> spList = new ArrayList<StoragePort>();
spList.addAll(portUsageMap.keySet());
StoragePort storagePort = selectStoragePort(spList);
if (!sports.contains(storagePort)) {
sports.add(storagePort);
}
}
return sports;
}
/**
* Compute the ports available and their usage.
*
* @param storageUri -- StorageSystem URI
* @param networkMap -- a map of Network URI to NetworkLite indicating networks to process
* @param varrayURI -- the Virtual Array URI
* @param storagePortsMap a map of network-to-ports of ports that can be allocated
* @return -- a Map of Network URI to a Map of Storage Port to Long usage factor
*/
private Map<URI, Map<StoragePort, Long>> computeStoragePortUsageMapForPorts(
URI storageUri, Map<URI, NetworkLite> networkMap, URI varrayURI,
Map<NetworkLite, List<StoragePort>> storagePortsMap)
throws PlacementException {
Map<URI, Map<StoragePort, Long>> result = new HashMap<URI, Map<StoragePort, Long>>();
// Then put them in the result map and the usageQueue.
for (URI networkURI : networkMap.keySet()) {
NetworkLite network = networkMap.get(networkURI);
List<StoragePort> spList = storagePortsMap.get(network);
if (spList == null || spList.isEmpty()) {
throw PlacementException.exceptions.noStoragePortsInNetwork(network.getLabel());
}
if (network.getTransportType().equals(StorageProtocol.Transport.FC.name()) ||
network.getTransportType().equals(StorageProtocol.Transport.IP.name())) {
Map<StoragePort, Long> portUsage = computeStoragePortUsage(spList);
// If there are no ports in the requested network, throw an error
if (portUsage.isEmpty()) {
throw PlacementException.exceptions.noStoragePortsInNetwork(network.getLabel());
}
result.put(networkURI, portUsage);
} else {
Map<StoragePort, Long> portUsage = new HashMap<StoragePort, Long>();
for (StoragePort sp : spList) {
portUsage.put(sp, 0L);
}
result.put(networkURI, portUsage);
}
}
return result;
}
/**
* Find the storage port usage map for all the storage ports on the storage system
* and the list of networks and a varray.
*
* @param storageUri the storage system
* @param networkMap the networks
* @param varrayURI the varrays
* @param orderedNetworks IN-OUT parameter
* @return
* @throws PlacementException
*/
// DEAD CODE ? TLW
// private Map<URI, Map<StoragePort, Long>> computeStoragePortUsageMap(
// URI storageUri, Map<URI, NetworkLite> networkMap, URI varrayURI, List<URI> orderedNetworks)
// throws PlacementException {
// Map<NetworkLite, List<StoragePort>> selectedStoragePortsMap =
// selectStoragePortsInNetworks(storageUri, networkMap.values(), varrayURI);
// return computeStoragePortUsageMapForPorts(storageUri, networkMap, varrayURI, orderedNetworks, selectedStoragePortsMap);
// }
/**
* Inner class for sorting Network Usage.
* Note that highest metric will be the highest priority (lowest value in compareTo).
*/
class NetworkUsage implements Comparable {
URI network;
long metric;
NetworkUsage(URI network, long metric) {
this.network = network;
this.metric = metric;
}
// Suppressing Sonar violation: This class overrides "equals()" and should therefore also override "hashCode()
// CTRL-12976
@SuppressWarnings("squid:S1206")
@Override
public boolean equals(Object obj) {
if (!(obj instanceof NetworkUsage)) {
return false;
}
NetworkUsage x = (NetworkUsage) obj;
return this.metric == x.metric;
}
@Override
public int compareTo(Object obj) {
if (!(obj instanceof NetworkUsage)) {
return 1;
}
NetworkUsage x = (NetworkUsage) obj;
if (x.metric == this.metric) {
return 0;
}
return (x.metric > this.metric ? 1 : -1);
}
};
/**
* Returns a list of Storage Port URIs for a list of StoragePorts.
*
* @param ports List<StoragePort>
* @return List<URI> of StoragePorts
*/
private List<URI> getPortURIs(List<StoragePort> ports) {
ArrayList<URI> uris = new ArrayList<URI>();
for (StoragePort port : ports) {
uris.add(port.getId());
}
return uris;
}
/**
* Select a storage port from a list of all ports in transport zone and its subset of ports
* already used for export.
*
* TODO:
* - select ports based on load
* - select ports based on multipath requirement
* - select ports based on fault domains
*
* @param spList
* @return
*/
private StoragePort selectStoragePort(List<StoragePort> spList) {
Collections.shuffle(spList);
return spList.get(0);
}
/**
* Return list of storage ports for the passed storage device connected
* to the given network and with connectivity to the passed virtual
* array.
*
* Port must be REGISTERED, and the OperationalStatus must not be NOT_OK
* and it must be a frontend port.
*
* @param storageSystemURI The URI of the storage system
* @param networkURI The URI of the network.
* @param varrayURI The URI of the virtual array.
*
* @return The list of storage ports.
*/
public List<StoragePort> selectStoragePorts(URI storageSystemURI, URI networkURI, URI varrayURI) {
NetworkLite networkLite = NetworkUtil.getNetworkLite(networkURI, _dbClient);
_log.info("Selecting ports for network {} {}", networkLite.getLabel(), networkLite.getId());
// The list of storage ports in networkURI
List<StoragePort> spList = new ArrayList<StoragePort>();
// The list of storage ports in networks that are routed to networkURI
List<StoragePort> rspList = new ArrayList<StoragePort>();
List<String> unroutedPorts = new ArrayList<String>();
List<String> routedPorts = new ArrayList<String>();
List<String> notRegisteredOrOk = new ArrayList<String>();
List<String> notInVarray = new ArrayList<String>();
List<String> wrongNetwork = new ArrayList<String>();
URIQueryResultList sports = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.
getStorageDeviceStoragePortConstraint(storageSystemURI), sports);
Iterator<URI> it = sports.iterator();
while (it.hasNext()) {
StoragePort sp = _dbClient.queryObject(StoragePort.class, it.next());
if (sp.getInactive() || sp.getNetwork() == null
|| !DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name().equals(sp.getCompatibilityStatus())
|| !DiscoveryStatus.VISIBLE.name().equals(sp.getDiscoveryStatus())
|| !sp.getRegistrationStatus().equals(StoragePort.RegistrationStatus.REGISTERED.name())
|| StoragePort.OperationalStatus.NOT_OK.equals(StoragePort.OperationalStatus.valueOf(sp.getOperationalStatus()))
|| StoragePort.PortType.valueOf(sp.getPortType()) != StoragePort.PortType.frontend) {
_log.debug(
"Storage port {} is not selected because it is inactive, is not compatible, "
+ "is not visible, has no network assignment, "
+ "is not registered, has a status other than OK, or is not a frontend port", sp.getLabel());
notRegisteredOrOk.add(portName(sp));
continue;
}
// Make sure the storage port is in the passed network.
if (sp.getNetwork().equals(networkURI) || (networkLite != null &&
networkLite.hasRoutedNetworks(sp.getNetwork()))) {
// Now make sure the port has connectivity/assignment
// to the passed virtual array.
StringSet spVArrayIds = sp.getTaggedVirtualArrays();
if (spVArrayIds != null && spVArrayIds.contains(varrayURI.toString())) {
if (sp.getNetwork().equals(networkURI)) {
spList.add(sp);
unroutedPorts.add(portName(sp));
} else {
_log.debug("Storage port {} is not in the requested network {} " +
"but it is routed to it.", sp.getNativeGuid(), networkURI);
rspList.add(sp);
// Duplicate list with just name for better error message.
routedPorts.add(portName(sp));
}
} else {
_log.debug("Storage port {} not selected because it is not connected " +
"or assigned to requested virtual array {}", sp.getNativeGuid(), varrayURI);
notInVarray.add(portName(sp));
}
} else {
_log.debug("Storage port {} not selected because its network {} " +
"is not the requested network {}",
new Object[] { sp.getNativeGuid(), sp.getNetwork(), networkURI });
wrongNetwork.add(portName(sp));
}
}
if (!notRegisteredOrOk.isEmpty()) {
_log.info("Ports not selected because they are inactive, have no network assignment, " +
"are not registered, bad operational status, or not type front-end: "
+ Joiner.on(" ").join(notRegisteredOrOk));
}
if (!notInVarray.isEmpty()) {
_log.info("Ports not selected because they are not assigned to the requested virtual array: "
+ varrayURI + " " + Joiner.on(" ").join(notInVarray));
}
if (!wrongNetwork.isEmpty()) {
_log.info("Ports not selected because they are not in the requested network: "
+ networkURI + " " + Joiner.on(" ").join(wrongNetwork));
}
if (!rspList.isEmpty() && !spList.isEmpty()) {
_log.info("Ports not selected because they are routed and local ports are available: "
+ networkURI + " " + Joiner.on(" ").join(routedPorts));
}
_log.info("Ports that were selected: " +
(spList.isEmpty() ? Joiner.on(" ").join(routedPorts) : Joiner.on(" ").join(unroutedPorts)));
return spList.isEmpty() ? rspList : spList;
}
/**
* Return list of storage ports for the passed storage device connected
* to the given network and with connectivity to the passed virtual
* array.
*
* Port must be REGISTERED, and the OperationalStatus must not be NOT_OK
* and it must be a frontend port.
*
* @param storageSystemURI The URI of the storage system
* @param networks collection of networks
* @param varrayURI The URI of the virtual array.
* @param pathParams The ExportPathParameter settings which may contain a set of allowed ports.
* Optional, can be null.
*
* @return The list of storage ports.
*/
public Map<NetworkLite, List<StoragePort>> selectStoragePortsInNetworks(URI storageSystemURI, Collection<NetworkLite> networks,
URI varrayURI, ExportPathParams pathParams) {
List<StoragePort> storagePorts = ExportUtils.getStorageSystemAssignablePorts(
_dbClient, storageSystemURI, varrayURI, pathParams);
return selectStoragePortsInNetworks(storagePorts, networks, varrayURI, pathParams);
}
/**
* Return list of storage ports from the given storage ports connected
* to the given network and with connectivity to the passed virtual
* array.
*
* @param storagePorts storage ports to process
* @param networks collection of networks
* @param varrayURI The URI of the virtual array.
* @param pathParams The ExportPathParameter settings which may contain a set of allowed ports.
* Optional, can be null.
*
* @return The list of storage ports.
*/
public Map<NetworkLite, List<StoragePort>> selectStoragePortsInNetworks(List<StoragePort> storagePorts, Collection<NetworkLite> networks,
URI varrayURI, ExportPathParams pathParams) {
Map<NetworkLite, List<StoragePort>> portsInNetwork = new HashMap<>();
for (NetworkLite networkLite : networks) {
URI networkURI = networkLite.getId();
_log.info("Selecting ports for network {} {}", networkLite.getLabel(), networkLite.getId());
// The list of storage ports in networkURI
List<StoragePort> spList = new ArrayList<StoragePort>();
// The list of storage ports in networks that are routed to networkURI
List<StoragePort> rspList = new ArrayList<StoragePort>();
List<String> unroutedPorts = new ArrayList<String>();
List<String> routedPorts = new ArrayList<String>();
List<String> wrongNetwork = new ArrayList<String>();
for (StoragePort sp : storagePorts) {
// Make sure the storage port is in the passed network.
if (sp.getNetwork().equals(networkURI) || (networkLite != null &&
networkLite.hasRoutedNetworks(sp.getNetwork()))) {
// Now make sure the port has connectivity/assignment
// to the passed virtual array.
if (sp.getNetwork().equals(networkURI)) {
spList.add(sp);
unroutedPorts.add(portName(sp));
} else {
_log.debug("Storage port {} is not in the requested network {} " +
"but it is routed to it.", sp.getNativeGuid(), networkURI);
rspList.add(sp);
// Duplicate list with just name for better error message.
routedPorts.add(portName(sp));
}
} else {
_log.debug("Storage port {} not selected because its network {} " +
"is not the requested network {}",
new Object[] { sp.getNativeGuid(), sp.getNetwork(), networkURI });
wrongNetwork.add(portName(sp));
}
}
if (!wrongNetwork.isEmpty()) {
_log.info("Ports not selected because they are not in the requested network: "
+ networkURI + " " + Joiner.on(" ").join(wrongNetwork));
}
if (!rspList.isEmpty() && !spList.isEmpty()) {
_log.info("Ports not selected because they are routed and local ports are available: "
+ networkURI + " " + Joiner.on(" ").join(routedPorts));
}
_log.info("Ports that were selected: " +
(spList.isEmpty() ? Joiner.on(" ").join(routedPorts) : Joiner.on(" ").join(unroutedPorts)));
portsInNetwork.put(networkLite, spList.isEmpty() ? rspList : spList);
}
return portsInNetwork;
}
/**
* Returns a port name guaranteed to have the director identification.
*
* @param port
* @return
*/
public static String portName(StoragePort port) {
if (port.getPortName().startsWith(port.getPortGroup())) {
return port.getPortName();
} else {
return port.getPortGroup() + ":" + port.getPortName();
}
}
/**
* Look up NetworkLite, given an endpoint and virtual array. It returns
* active, registered networks that are of the requested transport type
* only. To looking up an endpoint network without the added checks use {@link NetworkUtil#getEndpointNetworkLite(String, DbClient)}
*
*
* @param dbClient Reference to a DB client.
* @param transportType Transport type
* @param endpoint The endpoint
* @return NetworkLite reference if found, else null.
*/
public static NetworkLite lookupNetworkLite(DbClient dbClient, StorageProtocol.Transport transportType,
String endpoint) {
try {
NetworkLite netLite = NetworkUtil.getEndpointNetworkLite(endpoint, dbClient);
if (netLite == null) {
return null;
}
if (!netLite.registered()) {
_log.info("Network lookup: the endpoint network is deregistered.");
return null;
}
if (!netLite.getTransportType().equals(transportType.toString())) {
_log.info("Network lookup: the endpoint network is not of transport type {}.", transportType.toString());
return null;
}
return netLite;
} catch (DatabaseException e) {
_log.error("Network is not found for endpoint: ", endpoint);
}
_log.info("Network lookup: endpoint not found.");
return null;
}
/**
* Look up Network, given an endpoint and virtual array.
*
* @param dbClient Reference to a DB client.
* @param transportType Transport type
* @param endpoint The endpoint
* @return Network reference if found, else null.
*/
public static Network lookupNetworkFull(DbClient dbClient, StorageProtocol.Transport transportType,
String endpoint) {
_log.info(String.format("Network lookup: type(%s), endpoint(%s)",
transportType.name(), endpoint));
try {
return NetworkUtil.getEndpointNetwork(endpoint, dbClient);
} catch (DatabaseException e) {
_log.error("Network is not found for endpoint: ", endpoint);
}
_log.info("Network lookup: endpoint not found.");
return null;
}
/**
* Finds and returns the NetworkLite for an initiator
*
* @param initiator
* @param _dbClient an instance if {@link DbClient}
* @return the Network (lite) that contains the initator's endpoint
* @throws DeviceControllerException when a transport zone cannot be found
*/
public static NetworkLite getInitiatorNetwork(Initiator initiator, DbClient _dbClient) {
NetworkLite netLite = lookupNetworkLite(_dbClient, StorageProtocol.block2Transport(initiator.getProtocol()),
initiator.getInitiatorPort());
return netLite;
}
/**
* Given the existingZoningMap from the ExportMask, get a map of
* Initiators to a List<StoragePort> of the StoragePorts assigned to that Initiator.
*
* Note: This is varray aware, so even if zoning map has storage ports for the initiator
* but they are not in the varray that is been looked for then those ports are not returned
* in the map. This will be the case where volumes from two different varrays are exported to
* the same host and the storage ports in those varrays might be different.
*
* @param existingZoningMap -- StringSetMap
* @return Map<Initiator, List<StoragePort>> existing assignment map with Objects
*/
public Map<Initiator, List<StoragePort>> generateInitiatorsToStoragePortsMap(StringSetMap existingZoningMap, URI varray) {
Map<Initiator, List<StoragePort>> initiatorsToStoragePortsMap = new HashMap<Initiator, List<StoragePort>>();
if (existingZoningMap == null) {
return initiatorsToStoragePortsMap;
}
for (String initiatorId : existingZoningMap.keySet()) {
Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId));
if (initiator == null || initiator.getInactive()) {
continue;
}
NetworkLite network = getInitiatorNetwork(initiator, _dbClient);
String networkLabel = (network != null ? network.getLabel() : "<unknown network>");
StringSet ports = existingZoningMap.get(initiatorId);
if (ports == null) {
continue;
}
StringBuilder portNames = new StringBuilder();
for (String portId : ports) {
StoragePort port = _dbClient.queryObject(StoragePort.class, URI.create(portId));
if (port != null && port.getTaggedVirtualArrays() != null
&& port.getTaggedVirtualArrays().contains(varray.toString())
&& port.getRegistrationStatus().toString()
.equals(DiscoveredDataObject.RegistrationStatus.REGISTERED.name())
&& DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name().equals(port.getCompatibilityStatus().toString())
&& DiscoveryStatus.VISIBLE.name().equals(port.getDiscoveryStatus().toString())) {
if (initiatorsToStoragePortsMap.get(initiator) == null) {
initiatorsToStoragePortsMap.put(initiator, new ArrayList<StoragePort>());
}
initiatorsToStoragePortsMap.get(initiator).add(port);
portNames.append(port.getPortName() + " (" + port.getPortNetworkId() + ") ");
}
}
_log.info(String.format("Existing initiator %s (%s) network %s ports %s",
initiator.getInitiatorPort(), initiator.getHostName(), networkLabel, portNames.toString()));
}
return initiatorsToStoragePortsMap;
}
/**
* Creates a map of Initiator Network URI to a Set<StoragePort> of ports in that Network.
* When one or more of the initiator have routed access to the storage ports allocated,
* the storage ports mapped to the network may not all be in the network.
*
* @param existingAssignments -- Map of Initiator to a list of Storage Port assignments
* @param existingInitiatorsMap -- Map of network to initiators
* @return Map of Network URI to set of Storage Ports in that Network
*/
private static Map<URI, Set<StoragePort>> generateNetworkToStoragePortsMap(
Map<Initiator, List<StoragePort>> existingAssignments, Map<URI, Set<Initiator>> existingInitiatorsMap) {
Map<URI, Set<StoragePort>> network2StoragePortsMap = new HashMap<URI, Set<StoragePort>>();
if (existingAssignments == null) {
return network2StoragePortsMap;
}
for (Entry<URI, Set<Initiator>> networkInitiators : existingInitiatorsMap.entrySet()) {
network2StoragePortsMap.put(networkInitiators.getKey(), new HashSet<StoragePort>());
for (Initiator initiator : networkInitiators.getValue()) {
List<StoragePort> ports = existingAssignments.get(initiator);
for (StoragePort port : ports) {
if (port.getRegistrationStatus().toString()
.equals(DiscoveredDataObject.RegistrationStatus.REGISTERED.name())
&& port.getCompatibilityStatus().toString()
.equals(DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name())
&& port.getDiscoveryStatus().toString().equals(DiscoveryStatus.VISIBLE.name())) {
network2StoragePortsMap.get(networkInitiators.getKey()).add(port);
}
}
}
}
return network2StoragePortsMap;
}
/**
* Creates a map of Network URI to a Set<Initiator> initiators in that Network.
*
* @param existingAssignments -- Map of Initiator to a list of Storage Port assignments
* @return Map of Network URI to a set of Initiators in that Network
*/
private static Map<URI, Set<Initiator>> generateNetworkToInitiatorsMap(
Map<Initiator, List<StoragePort>> existingAssignments, DbClient dbClient) {
Map<URI, Set<Initiator>> network2InitiatorsMap = new HashMap<URI, Set<Initiator>>();
if (existingAssignments == null) {
return network2InitiatorsMap;
}
NetworkLite network = null;
for (Initiator initiator : existingAssignments.keySet()) {
network = getInitiatorNetwork(initiator, dbClient);
if (network == null) {
continue;
}
if (network2InitiatorsMap.get(network.getId()) == null) {
network2InitiatorsMap.put(network.getId(), new HashSet<Initiator>());
}
network2InitiatorsMap.get(network.getId()).add(initiator);
}
return network2InitiatorsMap;
}
/**
* Emits an info message about an initiator containing the address, id, and network name.
*
* @param initiator
* @param network
*/
public static void logInitiator(Initiator initiator, NetworkLite network) {
String networkName = (network != null ? network.getLabel() : "<unknown network>");
_log.info(String.format("Attempting to assign port(s) to initiator: %s (%s) in network: %s",
initiator.getInitiatorPort(), initiator.getHostName(), networkName));
}
/**
* Returns a list of StoragePort URIs (with no duplicates) from the
* assignments map returned by assignStoragePorts and the preZonedZoningMap
* that contains old assignments as well as any pre-zoned ports that were assigned.
*
* @param assignments -- the ports that were assigned
* @return list URI of assigned port with no duplicates
*/
static public List<URI> getTargetURIsFromAssignments(Map<URI, List<URI>> assignments) {
Set<URI> targets = new HashSet<URI>();
for (List<URI> portList : assignments.values()) {
targets.addAll(portList);
}
return new ArrayList<URI>(targets);
}
/**
* Returns a set of StoragePort id strings from assignments.
*
* @param assignments map returned by exportMask.getZoningMap()
* @return Set<String> ids of targets
*/
static public Set<String> getTargetIdsFromAssignments(StringSetMap assignments) {
Set<String> targets = new HashSet<String>();
if (assignments == null || assignments.isEmpty()) {
return targets;
}
for (Set<String> targetSet : assignments.values()) {
targets.addAll(targetSet);
}
return targets;
}
/**
* Calculates the path parameters for an existing ExportMask.
* This has to be calculated per host, since maxPaths is per host.
*
* @param dbClient
* @param mask
* @return ExportPathParams with calculated values
*/
public static ExportPathParams calculateExportPathParamForExportMask(DbClient dbClient, ExportMask mask) {
Map<String, Integer> hostInitiatorCounts = new HashMap<String, Integer>();
// Calculate the path parameters.
ExportPathParams param = new ExportPathParams(0, 0, 0);
// If there is a zoningMap, use that.
if (mask.getZoningMap() != null) {
for (String initiatorId : mask.getZoningMap().keySet()) {
Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(initiatorId));
if (initiator == null || initiator.getInactive()) {
continue;
}
String host = (initiator.getHost() != null) ? initiator.getHost().toString() : "<unknown>";
if (hostInitiatorCounts.get(host) == null) {
hostInitiatorCounts.put(host, 0);
}
Set<String> portIds = mask.getZoningMap().get(initiatorId);
if (portIds == null) {
continue;
}
int ppi = 0;
for (String portId : portIds) {
Integer newValue = hostInitiatorCounts.get(host) + 1;
hostInitiatorCounts.put(host, newValue);
ppi++;
}
if (ppi > param.getPathsPerInitiator()) {
param.setPathsPerInitiator(ppi);
}
}
// Return the maximum of any host.
for (Integer value : hostInitiatorCounts.values()) {
if (value > param.getMaxPaths()) {
param.setMaxPaths(value);
}
}
} else {
// If there is not a zoning map, we won't change things.
_log.info(String.format("No zoning map for mask %s (%s), will not change zoning",
mask.getMaskName(), mask.getId()));
param.setMaxPaths(Integer.MAX_VALUE);
}
return param;
}
/**
* Given a collection of volume URIs, generates the ExportPathParam
* values for all volumes (block objects)
* in the collection. These are assumed to belong to (or about to belong to) one ExportMask.
* The maxPath value from any of the volumes will be returned, along with
* the corresponding pathsPerInitiator.
*
* @param blockObjectURIs Collection<URI>
* @param overrideNumPaths - if greater than zero, will override the calculation and be returned.
* @return numPaths
*/
// public ExportPathParams calculateExportPathParmForVolumes(Collection<URI> blockObjectURIs,
// Integer overrideNumPaths) {
// return calculateExportPathParamForVolumes(blockObjectURIs, overrideNumPaths, null);
// }
/**
* Given a collection of volume URIs, generates the ExportPathParam
* values for all volumes (block objects) in the collection.
* These are assumed to belong to (or about to belong to) one ExportMask.
* The maxPath value from any of the volumes will be returned, along with
* the corresponding pathsPerInitiator.
*
* @param blockObjectURIs Collection<URI>
* @param overrideNumPaths - if greater than zero, will override the calculation and be returned.
* @param storageSystemURI URI of Storage System, if not null, filters out
* BlockObjects created on other systems
* @param exportGroupURI exportGroupURI
* @return numPaths
*/
public ExportPathParams calculateExportPathParamForVolumes(Collection<URI> blockObjectURIs,
Integer overrideNumPaths, URI storageSystemURI, URI exportGroupURI) {
ExportPathParams param = new ExportPathParams(0, 0, 0);
// Look up the exportGroup
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI);
// If overrideNumPaths is set, do that with pathsPerInitiator=2
if (overrideNumPaths != null && overrideNumPaths > 0) {
param = new ExportPathParams(overrideNumPaths, 0, 0);
param.setAllowFewerPorts(true);
return param;
}
if (blockObjectURIs != null) {
for (URI uri : blockObjectURIs) {
BlockObject blockObject = BlockObject.fetch(_dbClient, uri);
if (blockObject == null) {
continue;
}
if (storageSystemURI != null &&
!storageSystemURI.equals(blockObject.getStorageController())) {
continue;
}
ExportPathParams volParam = null;
if (exportGroup != null) {
// Check to see if the ExportGroup has path parameters for volume
if (exportGroup.getPathParameters().containsKey(uri.toString())) {
URI exportPathParamsUri = URI.create(exportGroup.getPathParameters().get(uri.toString()));
volParam = _dbClient.queryObject(ExportPathParams.class, exportPathParamsUri);
}
}
if (volParam == null) {
// Otherwise check use the Vpool path parameters
URI vPoolURI = getBlockObjectVPoolURI(blockObject, _dbClient);
volParam = getExportPathParam(blockObject, vPoolURI, _dbClient);
}
if (volParam.getMaxPaths() > param.getMaxPaths()) {
param = volParam;
}
}
}
if (param.getMaxPaths() == 0) {
param = ExportPathParams.getDefaultParams();
}
return param;
}
/**
* Get the ExportPathParams (maxPaths and pathsPerInitiator variables)
* from the VirtualPool belonging to a volume
* (or the parent volume of a snapshot).
* For backward compatibility, if the fields other than num_paths in the
* Vpool are empty, they are defaulted.
*
* @TODO For ingestion, if there are more than 1 supported virtual pool,
* then consider the path params with least path settings.
*
* @param block
* @return Integer num_paths from VirtualPool
*/
public static ExportPathParams getExportPathParam(BlockObject block, URI vPoolURI, DbClient dbClient) {
if (vPoolURI == null) {
return ExportPathParams.getDefaultParams();
}
VirtualPool vPool = dbClient.queryObject(VirtualPool.class, vPoolURI);
if (vPool == null) {
return ExportPathParams.getDefaultParams();
}
if (vPool.getNumPaths() == null) {
return ExportPathParams.getDefaultParams();
}
Integer minPaths = vPool.getMinPaths();
if (minPaths == null) {
minPaths = 0;
}
Integer pathsPerInitiator = vPool.getPathsPerInitiator();
if (pathsPerInitiator == null) {
pathsPerInitiator = 0;
}
return new ExportPathParams(vPool.getNumPaths(), minPaths, pathsPerInitiator);
}
/**
* Computes the usage of a set of candidate StoragePorts.
* This is done by finding all the ExportMasks containing the ports, and then
* totaling the number of Initiators across all masks that are using the port.
*
* @param candidatePorts
* @return Map of StoragePort to Integer usage metric that is count of Initiators using port
*/
public Map<StoragePort, Long> computeStoragePortUsage(List<StoragePort> candidatePorts) {
Map<StoragePort, Long> usages = new HashMap<StoragePort, Long>();
if (candidatePorts.isEmpty()) {
return usages;
}
StorageSystem system = _dbClient.queryObject(StorageSystem.class, candidatePorts.get(0).getStorageDevice());
// This is needed for the API path, which will not have a PortMetricsProcessor from Spring injection
return _portMetricsProcessor.computeStoragePortUsage(candidatePorts, system, true);
}
/**
* Validate against minPaths for each host.
* Additionally, if the Export type is Initiator, verify all initiators are assigned ports.
*
* @param pathParams
* @param hostInitiatorsMap
* @param assignments
*/
private void validateMinPaths(ExportPathParams pathParams,
Map<URI, List<Initiator>> hostInitiatorsMap, Map<Initiator, List<StoragePort>> assignments) {
// Do not validate ExportGroup Initiator type exports
if (pathParams.returnExportGroupType() == null
|| pathParams.returnExportGroupType().equals(ExportGroup.ExportGroupType.Initiator)) {
return;
}
for (URI hostURI : hostInitiatorsMap.keySet()) {
// Sum up the ports used for this host.
String hostName = "<unknown>";
int unassignedInitiators = 0;
int totalPorts = 0;
for (Initiator initiator : hostInitiatorsMap.get(hostURI)) {
if (initiator.getHostName() != null) {
hostName = initiator.getHostName();
}
List<StoragePort> ports = assignments.get(initiator);
if (ports == null || ports.isEmpty()) {
unassignedInitiators++;
}
if (ports != null) {
totalPorts += ports.size();
}
}
if (totalPorts < pathParams.getMinPaths()) {
_log.info(String.format("Host %s (%s) has fewer ports assigned %d than min_paths %d",
hostName, hostURI, totalPorts, pathParams.getMinPaths()));
throw PlacementException.exceptions.hostHasFewerThanMinPaths(
hostName, hostURI.toString(), totalPorts, pathParams.getMinPaths());
}
if (pathParams.returnExportGroupType() == ExportGroupType.Initiator && unassignedInitiators > 0) {
_log.info(String.format("Host %s (%s) has %d initiators that were not assigned ports even though type Initiator",
hostName, hostURI, unassignedInitiators));
throw PlacementException.exceptions.hostHasUnusedInitiators(hostName, hostURI.toString());
}
}
}
/**
* Validates that at least two HA paths were allocated if there is more than one assignment.
*
* @param pathParams -- the ExportPath parameters
* @param assignments -- Map of Initiator to List of Storage Ports
*/
private void validateHACapabilities(ExportPathParams pathParams, Map<Initiator, List<StoragePort>> assignments) {
// Do not validate ExportGroup Initiator type exports
if (pathParams.returnExportGroupType() == null
|| pathParams.returnExportGroupType().equals(ExportGroup.ExportGroupType.Initiator)) {
return;
}
Set<URI> haDomains = null;
Map<URI, List<Initiator>> initiatorsByHostMap = getInitiatorsByHostMap(assignments.keySet());
for (List<Initiator> initiators : initiatorsByHostMap.values()) {
haDomains = new HashSet<URI>();
int portCount = 0;
// Max paths must be two or greater
if (pathParams.getMaxPaths() < 2) {
continue;
}
for (Initiator initiator : initiators) {
for (StoragePort port : assignments.get(initiator)) {
portCount++;
haDomains.add(port.getStorageHADomain());
}
}
// If two or more ports were allocated, but less than two HA domains, throw PlacementException
if (portCount >= 2 && haDomains.size() < 2) {
throw PlacementException.exceptions.insufficientRedundancy(pathParams.getMaxPaths(), haDomains.size());
}
}
}
/**
* Returns a StringSetMap containing the Initiator to StoragePort URIs.
*
* @param assignments Map<URI, List<URI>>
* @return StringSetMap with same information encoded
*/
public static StringSetMap getZoneMapFromAssignments(Map<URI, List<URI>> assignments) {
StringSetMap zoneMap = new StringSetMap();
for (URI initiatorURI : assignments.keySet()) {
StringSet portIds = new StringSet();
List<URI> portURIs = assignments.get(initiatorURI);
for (URI portURI : portURIs) {
portIds.add(portURI.toString());
}
zoneMap.put(initiatorURI.toString(), portIds);
}
return zoneMap;
}
/**
* Updates the ExportMask's zoning map after the initiator to port associations
* have been discovered from an array like Cinder. This routine is needed when we
* are masking first, and cannot tell the array what ports are assigned to what initiators,
* i.e. rather the array tells us what it did. In particular, this is applicable to storage
* arrays managed by Southbound SDK drivers.
* This routine is not needed when the array can be told what initiators to map to what ports.
* 1. All zoning map entries for the initiators in the mask are removed.
* 2. For the targets in the mask, they are paired with the initiators they can service,
* i.e. that are on the same or a routeable network, and are usable in the varray,
* and the corresponding zones are put in the zoning map.
* Then the path parameters are enforced, based on the path parameter data discovered from
* the ExportMask.
* 3. Any initiators with more than paths_per_initiator ports are reduced to have
* only pathsPerInitiator number of ports. We try not to remove the same port from
* multiple initiators. (Note: we do not declare it an error if there are fewer
* than pathsPerInitiator ports assigned to an initiator.)
* 4. Then we verify we are not over maxPaths. This is done by counting up the number of
* paths currently in the mask, and if there are excess, cycling through the networks
* (starting with the network with the most initiators) and removing initiators until
* we are under paths per initiator.
* 5. Finally we sum up the paths in the mask, and verify that we have at least as
* many as minPaths.
*
* @param mask -- The ExportMask being manipulated
* @param varray -- The Virtual Array (normally from the ExportGroup)
* @param exportGroupURI -- URI of the ExportGroup
*
* Assumption: the export mask has up to date initiators and storage ports
*/
public void updateZoningMap(ExportMask mask, URI varray, URI exportGroupURI) {
// Convert the volumes to a Collection.
List<URI> volumeURIs = ExportMaskUtils.getVolumeURIs(mask);
// Determine the number of paths required for the volumes in the export mask.
ExportPathParams pathParams = calculateExportPathParamForVolumes(
volumeURIs, 0, mask.getStorageDevice(), exportGroupURI);
_log.info(String.format("Updating zoning map for ExportMask %s (%s) pathParams %s",
mask.getMaskName(), mask.getId(), pathParams.toString()));
// Data structures for mapping Network to Initiators and Network to StoragePorts
Map<URI, Set<Initiator>> network2InitiatorsMap = new HashMap<URI, Set<Initiator>>();
Map<URI, Set<StoragePort>> network2PortsMap = new HashMap<URI, Set<StoragePort>>();
_log.debug("Export Mask before zoning map update -" + mask.toString());
// Make a new zoning map.getDefaultParams
// Remove all zoning map entries and set the current zoning map to null
// so it is not considered by getInitiatorPortsInMask().
for (String initiatorURIStr : mask.getZoningMap().keySet()) {
mask.removeZoningMapEntry(initiatorURIStr);
}
mask.setZoningMap(null);
// Loop through the Initiators, looking for ports in the mask
// corresponding to the Initiator.
for (String initiatorURIStr : mask.getInitiators()) {
Initiator initiator = _dbClient.queryObject(Initiator.class,
URI.create(initiatorURIStr));
if (initiator == null || initiator.getInactive()) {
continue;
}
// Add the initiator to the net2InitiatorsMap
NetworkLite initiatorNetwork = getInitiatorNetwork(initiator, _dbClient);
if (!network2InitiatorsMap.containsKey(initiatorNetwork.getId())) {
network2InitiatorsMap.put(initiatorNetwork.getId(), new HashSet<Initiator>());
}
network2InitiatorsMap.get(initiatorNetwork.getId()).add(initiator);
List<URI> storagePortList = ExportUtils.getPortsInInitiatorNetwork(
mask, initiator, _dbClient);
if (storagePortList.isEmpty()) {
continue;
}
StringSet storagePorts = new StringSet();
for (URI portURI : storagePortList) {
StoragePort port = _dbClient.queryObject(StoragePort.class, portURI);
URI portNetworkId = port.getNetwork();
if (!DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name()
.equals(port.getCompatibilityStatus())
|| !DiscoveryStatus.VISIBLE.name().equals(port.getDiscoveryStatus())
|| NullColumnValueGetter.isNullURI(portNetworkId)
|| !port.getRegistrationStatus().equals(
StoragePort.RegistrationStatus.REGISTERED.name())
|| StoragePort.OperationalStatus.NOT_OK
.equals(StoragePort.OperationalStatus
.valueOf(port.getOperationalStatus()))
|| StoragePort.PortType.valueOf(port.getPortType()) != StoragePort.PortType.frontend) {
_log.debug(
"Storage port {} is not selected because it is inactive, is not compatible, is not visible, not on a network, "
+ "is not registered, has a status other than OK, or is not a frontend port",
port.getLabel());
continue;
}
// If the port can be used in the varray,
// include it in the zone map port entries for the initiator.
// Network cnnectivity was checked in getInitiatorPortsInMask()
if (port.getTaggedVirtualArrays().contains(varray.toString())) {
storagePorts.add(portURI.toString());
if (!network2PortsMap.containsKey(portNetworkId)) {
network2PortsMap.put(portNetworkId, new HashSet<StoragePort>());
}
network2PortsMap.get(portNetworkId).add(port);
} else {
_log.debug(
"Storage port {} is not selected because it is not in the specified varray {}",
port.getLabel(), varray.toString());
}
}
mask.addZoningMapEntry(initiatorURIStr, storagePorts);
}
_log.debug("Export Mask after zoning map update -" + mask.toString());
// Now that we have constructed an initial cut at the zoning map, enforce the path parameters.
// 1. Ensure that no initiator has more than the paths_per_initiator variable allows.
// For every initiator, make sure it doesn't have more than paths_per_initiator ports.
// Try not to remove the same port multiple times.
Set<String> removedPorts = new HashSet<String>();
for (URI networkURI : network2InitiatorsMap.keySet()) {
for (Initiator initiator : network2InitiatorsMap.get(networkURI)) {
StringSet ports = mask.getZoningMap().get(initiator.getId().toString());
if ((null == ports) || (ports.size() <= pathParams.getPathsPerInitiator())) {
continue;
}
_log.info(String.format("Limiting paths for initiator %s to %s; initial ports %s",
initiator.getInitiatorPort(), pathParams.getPathsPerInitiator().toString(), ports));
boolean removedPort = true;
outer: while (removedPort && ports.size() > pathParams.getPathsPerInitiator()) {
// First try not removing an already removed port
removedPort = false;
for (String port : ports) {
if (!removedPorts.contains(port)) {
removedPorts.add(port);
ports.remove(port);
removedPort = true;
continue outer;
}
}
// As a last resort, remove a port that is duplicated
for (String port : ports) {
removedPorts.add(port);
ports.remove(port);
removedPort = true;
continue outer;
}
}
_log.info(String.format("Limited ports for initiator %s to %s", initiator.getInitiatorPort(), ports));
}
}
// Now check that the total number of entries is not higher than maxPaths.
// Remove paths from Networks with the most initiators to the list by removing initiators.
ExportPathParams currentPathParams = calculateExportPathParamForExportMask(_dbClient, mask);
Integer overMaxPaths = currentPathParams.getMaxPaths() - pathParams.getMaxPaths();
// Make a sorted map of initiator count to networks.
SortedMap<Integer, Set<URI>> initiatorCountToNetwork = new TreeMap<Integer, Set<URI>>();
for (URI networkURI : network2InitiatorsMap.keySet()) {
Integer count = network2InitiatorsMap.get(networkURI).size();
if (!initiatorCountToNetwork.containsKey(count)) {
initiatorCountToNetwork.put(count, new HashSet<URI>());
}
initiatorCountToNetwork.get(count).add(networkURI);
}
while (overMaxPaths > 0) {
// Go backwards from last key (highest count) to first (lowest count).
Integer lastKey = initiatorCountToNetwork.lastKey();
Integer firstKey = initiatorCountToNetwork.firstKey();
for (Integer count = lastKey; overMaxPaths > 0 && count >= firstKey; count--) {
// Remove an Initiator from each network
Set<URI> networks = initiatorCountToNetwork.get(count);
if (networks == null) {
continue;
}
for (URI networkURI : networks) {
Iterator<Initiator> iter = network2InitiatorsMap.get(networkURI).iterator();
if (iter.hasNext()) {
// Remove an initiator
Initiator initiator = iter.next();
StringSet ports = mask.getZoningMap().get(initiator.getId().toString());
overMaxPaths -= ports.size();
_log.info(String.format("Removing initiator %s to comply with maxPaths", initiator.getInitiatorPort()));
mask.removeZoningMapEntry(initiator.getId().toString());
network2InitiatorsMap.get(networkURI).remove(initiator);
}
if (overMaxPaths <= 0) {
break;
}
}
}
}
// Finally, count the resulting number of paths, and make sure it is over minPaths.
Integer pathCount = 0;
Integer initiatorCount = 0;
for (String initiatorId : mask.getZoningMap().keySet()) {
initiatorCount++;
StringSet ports = mask.getZoningMap().get(initiatorId);
pathCount += ports.size();
}
_log.info(String.format("ExportMask %s (%s) pathCount %s", mask.getMaskName(), mask.getId(), pathCount.toString()));
if (pathCount < pathParams.getMinPaths()) {
throw PlacementException.exceptions.cannotAllocateMinPaths(
pathParams.getMinPaths(), initiatorCount,
pathParams.getPathsPerInitiator(), pathParams.getMinPaths(), pathParams.getMaxPaths());
}
// Save the updated ExportMask
_dbClient.updateObject(mask);
}
/**
* Returns true if validateHACapabilities needs to be run
* For some of the Hitachi models like AMS & HUS series don't support
* clusters and hence redundancy couldn't be achieved across clusters
* and hence bypassing this check for these models.
*
* @param system [in] - StorageSystem object
* @return true - if validateHACapabilities should be run
*/
private boolean needToValidateHA(StorageSystem system) {
// For now ScaleIO systems are the only ones that do not require
// the validation, since port selection is meaningless (it's all
// IP network based connectivity)
// This check is needed for arrays with single storage HA domain
return (!(system.getSystemType().equals(DiscoveredSystemObject.Type.scaleio.name()))
&& !(system.getSystemType().equals(DiscoveredSystemObject.Type.xtremio.name()))
&& !(system.getSystemType().equals(DiscoveredSystemObject.Type.ceph.name()))
&& (HDSUtils.checkForAMSSeries(system) || HDSUtils.checkForHUSSeries(system)));
}
/**
* Groups initiators by host and returns a map of host-uri-to-initiators-list
*
* @param initiators initiators to sort
* @return a map of host URIs to initiators for that host
*/
public static Map<URI, List<Initiator>> getInitiatorsByHostMap(Collection<Initiator> initiators) {
List<Initiator> hostInitiators = null;
Map<URI, List<Initiator>> map = new HashMap<URI, List<Initiator>>();
if (initiators != null) {
for (Initiator initiator : initiators) {
URI hostURI = initiator.getHost() == null ?
URI.create(initiator.getHostName().replaceAll("\\s", "")) : initiator.getHost();
if (NullColumnValueGetter.isNullURI(hostURI)) {
hostURI = StoragePortsAssigner.unknown_host_uri;
}
hostInitiators = map.get(hostURI);
if (hostInitiators == null) {
hostInitiators = new ArrayList<Initiator>();
map.put(hostURI, hostInitiators);
}
hostInitiators.add(initiator);
}
}
return map;
}
/**
* This function wraps the two calls necessary to get port assignments, the first one is getting
* pre-zoned ports assigned followed by the call to add any additional ports.
*
* @param storage the storage system where the mask will be or was created
* @param exportGroup the export group of the mask
* @param initiators the initiators being added to the mask.
* @param existingZoningMap this is the zoning map for an existing mask to which
* initiators are being added. It is null for new masks.
* @param pathParams the export group aggregated path parameter
* @param volumeURIs the volumes in the export mask
* @param virtualArrayUri the URI of the export virtual array
* @param token the workflow step Id
* @return a map of existing zones paths between the storage system ports and the
* mask initiators.
* @return
*/
public Map<URI, List<URI>> assignStoragePorts(StorageSystem storage, ExportGroup exportGroup,
List<Initiator> initiators, StringSetMap existingZoningMap,
ExportPathParams pathParams, Collection<URI> volumeURIs,
NetworkDeviceController networkDeviceController, URI virtualArrayUri, String token) {
StringSetMap preZonedZoningMap = assignPrezonedStoragePorts(storage, exportGroup, initiators,
existingZoningMap, pathParams, volumeURIs, networkDeviceController, virtualArrayUri, token);
Map<URI, List<URI>> assignments =
assignStoragePorts(storage, virtualArrayUri, initiators,
pathParams, preZonedZoningMap, volumeURIs);
ExportUtils.addPrezonedAssignments(existingZoningMap, assignments, preZonedZoningMap);
return assignments;
}
/**
* Find the existing zones for a list of initiators. The initiators can be for a mask
* that is being created or for a mask to which new initiators are added. In the latter
* case the list of initiators would include only what is being added to the mask.
* <p>
* This existing zoning map returned by this function will be used by the port allocation code to give already zoned port priority over
* other ports.
* <p>
* This function will only do its work if the config item controller_port_alloc_by_metrics_only is set to false. The default value of
* the config is true.
* <p>
* This function will find existing zones for the list of initiators on the network system for all the storage system assignable ports.
* If duplicate zones are found for an initiator-port pair, the existing zone selection algorithm is applied.
* <p>
* If zones were found on the network systems for the initiators and ports, there can be 3 possible scenarios
* <ol>
* <li>the number of existing paths is equal that what is requested in the vpool, in this case the port allocation/assignment code would
* still be invoked but it will return no additional assignments</li>
* <li>the number of existing paths is less that what is requested in the vpool, additional assignments will be made by the port
* allocation/assignment code</li>
* <li>Not all existing paths should be used, for example there are more paths than requested by the vpool or it could be that some
* initiators have more paths that requested. The port allocation code is invoked to select the most favorable paths of the existing
* ones.</li>
* </ol>
* Note that by using existing zones, the path-per-initiator may be violated.
*
* @param storage the storage system where the mask will be or was created
* @param exportGroup the export group of the mask
* @param initiators the initiators being added to the mask.
* @param existingZoningMap this is the zoning map for an existing mask to which
* initiators are being added. It is null for new masks.
* @param pathParams the export group aggregated path parameter
* @param volumeURIs the volumes in the export mask
* @param virtualArrayUri the URI of the export virtual array
* @param token the workflow step Id
* @return a map of existing zones paths between the storage system ports and the
* mask initiators.
*/
public StringSetMap assignPrezonedStoragePorts(StorageSystem storage, ExportGroup exportGroup,
List<Initiator> initiators, StringSetMap existingZoningMap,
ExportPathParams pathParams, Collection<URI> volumeURIs,
NetworkDeviceController networkDeviceController, URI virtualArrayUri, String token) {
// Prime the new zoning map with existing ones
StringSetMap newZoningMap = new StringSetMap();
if (existingZoningMap != null) {
newZoningMap.putAll(existingZoningMap);
}
// check if this is a backend export
boolean backend = ExportMaskUtils.areBackendInitiators(initiators);
// Adjust the paths param based on whether at the end of this call the ports selected should meet the paths requirement
ExportPathParams prezoningPathParams = getPrezoningPathParam(virtualArrayUri, pathParams, storage, backend);
try {
if (networkDeviceController == null) {
return newZoningMap;
}
if (!NetworkUtil.areNetworkSystemDiscovered(_dbClient)) {
_log.info("Cannot discover existing zones. There are no network systems discovered.");
return newZoningMap;
}
if (!_networkScheduler.portAllocationUseExistingZones(storage.getSystemType(), backend)) {
_log.info("The system configuration requests port selection to be based on metrics only "
+ "i.e. ignore existing zones when selecting ports.");
return newZoningMap;
}
_log.info("Checking for existing zoned ports for export {} before invoking port allocation.",
exportGroup.getGeneratedName());
List<Initiator> newInitiators = new ArrayList<Initiator>();
for (Initiator initiator : initiators) {
if (!newZoningMap.containsKey(initiator.getId().toString())) {
newInitiators.add(initiator);
}
}
Map<Initiator, List<StoragePort>> assignments = new HashMap<Initiator, List<StoragePort>>();
Map<Initiator, List<StoragePort>> existingAssignments =
generateInitiatorsToStoragePortsMap(existingZoningMap, virtualArrayUri);
if (!newInitiators.isEmpty()) {
// discover existing zones that are for the storage system and varray
// At this time we are not discovering routed zones but we will take care of this
Collection<StoragePort> ports = ExportUtils.getStorageSystemAssignablePorts(
_dbClient, storage.getId(), virtualArrayUri, pathParams);
Map<NetworkLite, List<Initiator>> initiatorsByNetwork = NetworkUtil.getInitiatorsByNetwork(newInitiators, _dbClient);
Map<Initiator, NetworkLite> initiatorToNetworkLiteMap = getInitiatorToNetworkLiteMap(initiatorsByNetwork);
Map<NetworkLite, List<StoragePort>> portByNetwork = ExportUtils.mapStoragePortsToNetworks(ports,
initiatorsByNetwork.keySet(), _dbClient);
Map<NetworkLite, StringSetMap> zonesByNetwork = new HashMap<NetworkLite, StringSetMap>();
// get all the prezoned ports for the initiators
Map<NetworkLite, List<StoragePort>> preZonedPortsByNetwork =
getPrezonedPortsForInitiators(networkDeviceController, portByNetwork, initiatorsByNetwork, zonesByNetwork, token);
if (!preZonedPortsByNetwork.isEmpty()) {
// trim the initiators to the pre-zoned ports
StringMapUtil.retainAll(initiatorsByNetwork, preZonedPortsByNetwork);
Map<NetworkLite, List<StoragePort>> allocatedPortsByNetwork = allocatePorts(
storage, virtualArrayUri, initiatorsByNetwork,
preZonedPortsByNetwork, volumeURIs, prezoningPathParams, existingZoningMap);
Map<URI, List<StoragePort>> allocatedPortsMap = getAllocatedPortsMap(allocatedPortsByNetwork);
// Get a map of Host to Network to Initiators
Map <URI, Map<URI, List<Initiator>>> hostsToNetToInitiators =
getHostInitiatorsMapFromNetworkLite(initiatorsByNetwork);
// Compute the number of Ports needed for each Network
StoragePortsAssigner assigner = StoragePortsAssignerFactory
.getAssignerForZones(storage.getSystemType(), zonesByNetwork);
// Assign the storage ports on a per host basis.
for (Map.Entry<URI, Map<URI, List<Initiator>>> entry : hostsToNetToInitiators.entrySet()) {
URI hostURI = entry.getKey();
// The map of switch name to Initiators per network
Map<URI, Map<String, List<Initiator>>> switchInitiatorsByNet = new HashMap<URI, Map<String, List<Initiator>>>();
// The map of swtich name to storage ports per network
Map<URI, Map<String, List<StoragePort>>> switchStoragePortsByNet = new HashMap<URI, Map<String, List<StoragePort>>>();
Map<URI, List<Initiator>> initiatorByNetMap = entry.getValue();
PlacementUtils.getSwitchfoForInititaorsStoragePorts(initiatorByNetMap, allocatedPortsMap, _dbClient,
storage, switchInitiatorsByNet, switchStoragePortsByNet);
assigner.assignPortsToHost(assignments, entry.getValue(), allocatedPortsMap, prezoningPathParams,
existingAssignments, hostURI, initiatorToNetworkLiteMap, switchInitiatorsByNet, switchStoragePortsByNet);
}
addAssignmentsToZoningMap(assignments, newZoningMap);
}
// if manual zoning is on, then make sure the paths discovered meet the path requirement
if (allocateFromPrezonedPortsOnly(virtualArrayUri, storage.getSystemType(), backend)) {
try {
validateMinPaths(storage, prezoningPathParams, existingAssignments, assignments,
newInitiators);
} catch (PlacementException pex) {
_log.error("There are fewer pre-zoned paths than required by the virtual pool."
+ " Please either add the needed paths or enable automatic SAN zoning in the virtual array"
+ " so that the additional paths can be added by the application.", pex);
throw pex;
}
}
}
_log.info("Zoning map after the assignment of pre-zoned ports: {}", newZoningMap);
} catch (Exception ex) {
_log.error("Failed to assign from pre-zoned storage ports because: ", ex);
if (allocateFromPrezonedPortsOnly(virtualArrayUri, storage.getSystemType(), backend)) {
_log.error("The virtual array is configured for manual zoning and the application "
+ "cannot assign from other storage ports. Failing the workflow.");
throw ex;
} else {
_log.info("The virtual array is configured for auto zoning and the application "
+ "will attempt to assign from other storage ports. Resuming the workflow.");
}
}
return newZoningMap;
}
/**
* This function changes the exportPaths parameter to be used when allocating from pre-zoned ports. When additional
* paths can be added to the pre-zoned ones, the pre-zoned ports allocation need not verify paths requirements.
* When additional paths cannot be added, then pre-zoned ports allocation need not verify paths requirements.
* This function ensures the proper paths parameters are passed to pre-zoned ports allocation.
*
* @param virtualArrayUri -- the export virtual array
* @param exportPathParams -- the required paths
* @param storage TODO
* @param backend TODO
* @return the paths parameter for allocating pre-zoned ports.
*/
private ExportPathParams getPrezoningPathParam(URI virtualArrayUri,
ExportPathParams exportPathParams, StorageSystem storage, boolean backend) {
ExportPathParams prezoningPathParams = new ExportPathParams(exportPathParams.getMaxPaths(),
exportPathParams.getMinPaths(), exportPathParams.getPathsPerInitiator(), exportPathParams.returnExportGroupType());
if (!allocateFromPrezonedPortsOnly(virtualArrayUri, null, false)) {
// If the application is expected to supply missing paths, then the export path needs to be
// changed to avoid failure on minPath check when prezoned paths do not meet the requirements
prezoningPathParams.setMinPaths(0);
prezoningPathParams.setAllowFewerPorts(true);
}
return prezoningPathParams;
}
/**
* When the configuration is for pre-zoned ports to be use and, at the same time, auto-zoning is off, additional
* paths will not be added. In this case we need to ensure the pre-zoned paths meet the required paths. This
* check indicates if additional paths will or will not be added.
*
* @param virtualArrayUri -- the export virtual array
* @param storageSystemType the type storage system of the ports
* @param backend TODO
*
* @return true if additional paths can be added to pre-zoned ones
*/
private boolean allocateFromPrezonedPortsOnly(URI virtualArrayUri, String storageSystemType, boolean backend) {
return _networkScheduler.portAllocationUseExistingZones(storageSystemType, backend)
&& !NetworkScheduler.isZoningRequired(_dbClient, virtualArrayUri);
}
/**
* Return the VirtualPool URI of the blockObject.
*
* @param blockObject
* @param _dbClient
* @return
*/
private URI getBlockObjectVPoolURI(BlockObject blockObject, DbClient _dbClient) {
Volume volume = null;
URI vPoolURI = null;
if (blockObject instanceof BlockSnapshot) {
BlockSnapshot snap = (BlockSnapshot) blockObject;
if (!NullColumnValueGetter.isNullNamedURI(snap.getParent())) {
volume = _dbClient.queryObject(Volume.class, snap.getParent().getURI());
vPoolURI = volume.getVirtualPool();
}
} else if (blockObject instanceof Volume) {
volume = (Volume) blockObject;
vPoolURI = volume.getVirtualPool();
}
return vPoolURI;
}
/**
* Creates a map of initiators grouped and keyed by their network.
* Initiators which are not in any network are not returned.
*
* @param initiators the initiators
* @param client
* @return a map of network-to-initiators
*/
private Map<NetworkLite, List<Initiator>> getInitiatorsByNetwork(Collection<Initiator> initiators,
StringSetMap zoninMap, DbClient dbClient) {
Map<NetworkLite, List<Initiator>> map = new HashMap<NetworkLite, List<Initiator>>();
NetworkLite network = null;
for (Initiator initiator : initiators) {
network = NetworkUtil.getEndpointNetworkLite(initiator.getInitiatorPort(), dbClient);
if (network == null) {
_log.info(String.format("Initiator %s (%s) is being removed from initiator list because it has no network association",
initiator.getInitiatorPort(), initiator.getHostName()));
continue;
}
StringMapUtil.addToListMap(map, network, initiator);
_log.info(String.format("Processing initiator %s (%s) network %s",
initiator.getInitiatorPort(), initiator.getHostName(), network.getLabel()));
}
return map;
}
/**
* Inverts the sense of the initiatorsByNetwork map to get a map of InitiatorToNetworkLite
* @param initiatorsByNetwork Map of NetworkLite to a list of Initiators it contains
* @return map of Initiator to Network Lite
*/
public Map<Initiator, NetworkLite> getInitiatorToNetworkLiteMap(Map<NetworkLite, List<Initiator>> initiatorsByNetwork) {
Map<Initiator, NetworkLite> initiatorToNetworkLiteMap = new HashMap<Initiator, NetworkLite>();
for (Map.Entry<NetworkLite, List<Initiator>> entry : initiatorsByNetwork.entrySet()) {
for (Initiator initiator : entry.getValue()) {
initiatorToNetworkLiteMap.put(initiator, entry.getKey());
}
}
return initiatorToNetworkLiteMap;
}
/**
* Reads the existing zones for the initiators from the network system and finds all ports that are
* already prezoned to one or more of the initiators.
*
* @param networkDeviceController an instance of networkDeviceController
* @param portByNetwork the ports in the export mask grouped by network
* @param initiatorsByNetwork the initiators of interest grouped by network
* @param zonesByNetwork an OUT param to collect the zones found grouped by network
* @param token the workflow step id
* @return a map of ports in networks that are already zoned to one or more of the initiators
*/
public Map<NetworkLite, List<StoragePort>> getPrezonedPortsForInitiators(NetworkDeviceController networkDeviceController,
Map<NetworkLite, List<StoragePort>> portByNetwork, Map<NetworkLite, List<Initiator>> initiatorsByNetwork,
Map<NetworkLite, StringSetMap> zonesByNetwork, String token) {
// so now we have a a collection of initiators and ports, let's get the zones
Map<NetworkLite, List<StoragePort>> preZonedPortsByNetwork = new HashMap<NetworkLite, List<StoragePort>>();
StringSetMap zonesInNetwork = null;
Map<String, List<Zone>> initiatorWwnToZonesMap = new HashMap<String, List<Zone>>();
for (NetworkLite network : portByNetwork.keySet()) {
if (!Transport.FC.toString().equals(network.getTransportType())) {
continue;
}
List<Initiator> networkInitiators = initiatorsByNetwork.get(network);
if (networkInitiators == null || networkInitiators.isEmpty()) {
continue;
}
Map<String, StoragePort> portByWwn = DataObjectUtils.mapByProperty(portByNetwork.get(network), "portNetworkId");
URI[] networkSystemURIUsed = new URI[1];
zonesInNetwork = networkDeviceController.getZoningMap(network, networkInitiators,
portByWwn, initiatorWwnToZonesMap, networkSystemURIUsed);
_log.info("Existing zones in network {} are {}", network.getNativeGuid(), zonesInNetwork);
// if the OUT parameter is not null, fill in the discovered zones
if (zonesByNetwork != null && !zonesInNetwork.isEmpty()) {
zonesByNetwork.put(network, zonesInNetwork);
}
for (String iniId : zonesInNetwork.keySet()) {
for (String portId : zonesInNetwork.get(iniId)) {
StringMapUtil.addToListMap(preZonedPortsByNetwork,
network, DataObjectUtils.findInCollection(portByNetwork.get(network), URI.create(portId)));
}
}
}
// now store the retrieved zones in ZK
if (!initiatorWwnToZonesMap.isEmpty()) {
Map<String, List<Zone>> zonesMap = (Map<String, List<Zone>>) WorkflowService.getInstance().loadWorkflowData(token, "zonemap");
// some workflows call port allocation more than one time, rather than overriding, add to these zones to already stored zones.
if (zonesMap == null) {
zonesMap = initiatorWwnToZonesMap;
} else {
zonesMap.putAll(initiatorWwnToZonesMap);
}
WorkflowService.getInstance().storeWorkflowData(token, "zonemap", zonesMap);
}
return preZonedPortsByNetwork;
}
/**
* Converts Map<NetworkLite, List<StoragePort> to Map<URI, List<StoragePort>
* @param allocatedPorts map from NetworkLite to allocated ports
* @return map from network URI to allocated ports
*/
private Map<URI, List<StoragePort>> getAllocatedPortsMap(Map<NetworkLite, List<StoragePort>> allocatedPorts) {
Map<URI, List<StoragePort>> returnedPortMap = new HashMap<URI, List<StoragePort>>();
for (Map.Entry<NetworkLite, List<StoragePort>> entry : allocatedPorts.entrySet()) {
URI netURI = entry.getKey().getId();
returnedPortMap.put(netURI, entry.getValue());
}
return returnedPortMap;
}
/**
* Get the map of number of max path numbers could be per switch based on the initiators.
*
* @param initiators initiators
* @param pathParams the export path params
* @return the map
*/
private Map<String, Integer> getSwitchToMaxPortNumberMap(List<Initiator> initiators, ExportPathParams pathParams) {
Map<String, Integer> result = new HashMap<String, Integer>();
for (Initiator initiator : initiators) {
String switchName = PlacementUtils.getSwitchName(initiator.getInitiatorPort(), _dbClient);
if (switchName != null && !switchName.isEmpty()) {
Integer count = result.get(switchName);
int numberOfPath = pathParams.getMaxInitiatorsPerPort();
if (count != null) {
count += numberOfPath;
} else {
result.put(switchName, numberOfPath);
}
}
}
return result;
}
/**
* Check whether switch locality is enabled.
*
* @return
*/
static public Boolean isSwitchAffinityAllocationEnabled(String systemType) {
Boolean switchLocalityAllocationEnabled = false;
try {
switchLocalityAllocationEnabled = Boolean.valueOf(
customConfigHandler.getComputedCustomConfigValue(
CustomConfigConstants.PORT_ALLOCATION_SWITCH_AFFINITY_ENABLED,
systemType, null));
} catch (Exception e) {
_log.warn("exception while getting custom config value", e);
}
_log.info("Switch affinity is " + switchLocalityAllocationEnabled);
return switchLocalityAllocationEnabled;
}
}