/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.block; import java.net.URI; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.StoragePort; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.util.ConnectivityUtil; import com.emc.storageos.util.ExportUtils; import com.emc.storageos.util.NetworkLite; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.placement.BlockStorageScheduler; import com.emc.storageos.volumecontroller.placement.StoragePortsAllocator; import com.emc.storageos.volumecontroller.placement.StoragePortsAssigner; import com.emc.storageos.volumecontroller.placement.StoragePortsAllocator.PortAllocationContext; public class VPlexBackEndOrchestratorUtil { private static final Logger _log = LoggerFactory.getLogger(VPlexBackEndOrchestratorUtil.class); public static final String DIRECTOR_MIN_PORT_COUNT_SETTING = "controller_vplex_director_min_port_count"; public static List<StoragePort> allocatePorts(StoragePortsAllocator allocator, List<StoragePort> candidatePorts, int portsRequested, NetworkLite net, URI varrayURI, boolean simulation, BlockStorageScheduler blockScheduler, DbClient dbClient, Map<String, Integer> switchToPortNumber, PortAllocationContext context) { Collections.shuffle(candidatePorts); if (simulation) { if (context == null) { context = StoragePortsAllocator.getPortAllocationContext(net, "arrayX", allocator.getContext()); for (StoragePort port : candidatePorts) { context.addPort(port, null, null, null, null); } } List<StoragePort> portsAllocated = allocator.allocatePortsForNetwork(portsRequested, context, false, null, false, switchToPortNumber); allocator.setContext(context); return portsAllocated; } else { Map<StoragePort, Long> sportMap = blockScheduler .computeStoragePortUsage(candidatePorts); List<StoragePort> portsAllocated = allocator.selectStoragePorts(dbClient, sportMap, net, varrayURI, portsRequested, null, false, switchToPortNumber); return portsAllocated; } } public static StringSetMap configureZoning(Map<URI, List<List<StoragePort>>> portGroup, Map<String, Map<URI, Set<Initiator>>> initiatorGroup, Map<URI, NetworkLite> networkMap, StoragePortsAssigner assigner, Map<URI, String> initiatorSwitchMap, Map<URI, Map<String, List<StoragePort>>> switchStoragePortsMap, Map<URI, String> portSwitchMap) { StringSetMap zoningMap = new StringSetMap(); // Set up a map to track port usage so that we can use all ports more or less equally. Map<StoragePort, Integer> portUsage = new HashMap<StoragePort, Integer>(); // check if switch affinity is on boolean isSwitchAffinity = false; if (initiatorSwitchMap != null && !initiatorSwitchMap.isEmpty() && switchStoragePortsMap != null && !switchStoragePortsMap.isEmpty()) { isSwitchAffinity = true; } // Iterate through each of the directors, matching each of its initiators // with one port. This will ensure not to violate four paths per director. for (String director : initiatorGroup.keySet()) { for (URI networkURI : initiatorGroup.get(director).keySet()) { NetworkLite net = networkMap.get(networkURI); for (Initiator initiator : initiatorGroup.get(director).get(networkURI)) { // If there are no ports on the initiators network, too bad... if (portGroup.get(networkURI) == null) { _log.info(String.format("%s -> no ports in network", initiator.getInitiatorPort())); continue; } // find a port for the initiator List<StoragePort> assignablePorts = portGroup.get(networkURI).iterator().next(); if (isSwitchAffinity) { // find the ports with the same switch as the initiator String switchName = initiatorSwitchMap.get(initiator.getId()); if (!switchName.equals(NullColumnValueGetter.getNullStr())) { Map<String, List<StoragePort>>switchMap = switchStoragePortsMap.get(networkURI); if (switchMap != null) { List<StoragePort> switchPorts = switchMap.get(switchName); if (switchPorts != null && !switchPorts.isEmpty()) { _log.info(String.format("Found the same switch ports, switch is %s", switchName)); assignablePorts = switchPorts; } else { _log.info(String.format("Switch affinity is not honored, because no storage port from the switch %s for the initiator %s", switchName, initiator.getInitiatorPort())); } } } } StoragePort storagePort = assignPortToInitiator(assigner, assignablePorts, net, initiator, portUsage, null); if (storagePort != null) { _log.info(String.format("%s %s %s %s -> %s %s %s", director, net.getLabel(), initiator.getInitiatorPort(), initiatorSwitchMap.get(initiator.getId()),storagePort.getPortNetworkId(), storagePort.getPortName(), portSwitchMap.get(storagePort.getId()))); StringSet ports = new StringSet(); ports.add(storagePort.getId().toString()); zoningMap.put(initiator.getId().toString(), ports); } else { _log.info(String.format("A port could not be assigned for %s %s %s", director, net.getLabel(), initiator.getInitiatorPort())); } } } } return zoningMap; } /** * Validates that an ExportMask can be used. * There are comments for each rule that is validated below. * * @param map of Network to Vplex StoragePort list. * @param mask * @param invalidMaskscontains * @param returns true if passed validation */ public static boolean validateExportMask(URI varrayURI, Map<URI, List<StoragePort>> initiatorPortMap, ExportMask mask, Set<URI> invalidMasks, Map<String, Set<String>> directorToInitiatorIds, Map<String, Initiator> idToInitiatorMap, DbClient dbClient, CoordinatorClient coordinator, Map<String, String> portWwnToClusterMap) { boolean passed = true; Integer directorMinPortCount = Integer.valueOf(ControllerUtils.getPropertyValueFromCoordinator( coordinator, DIRECTOR_MIN_PORT_COUNT_SETTING)); // Rule 1. An Export Mask must have at least two initiators from each director. // This is a warning if the ExportMask is non-ViPR. for (String director : directorToInitiatorIds.keySet()) { int portsInDirector = 0; for (String initiatorId : directorToInitiatorIds.get(director)) { Initiator initiator = idToInitiatorMap.get(initiatorId); String initiatorPortWwn = Initiator.normalizePort(initiator.getInitiatorPort()); if (mask.hasExistingInitiator(initiatorPortWwn)) { portsInDirector++; } else if (mask.hasUserInitiator(initiatorPortWwn)) { portsInDirector++; } else if (mask.hasInitiator(initiatorId)) { portsInDirector++; } } if (portsInDirector < directorMinPortCount) { if (mask.getCreatedBySystem()) { // ViPR created _log.info(String.format( "ExportMask %s disqualified because it only has %d back-end ports from %s (requires two)", mask.getMaskName(), portsInDirector, director)); if(null!=invalidMasks) { invalidMasks.add(mask.getId()); } passed = false; } else { // non ViPR created _log.info(String.format( "Warning: ExportMask %s only has %d back-end ports from %s (should have at least two)", mask.getMaskName(), portsInDirector, director)); } } } // Rule 2. The Export Mask should have at least two ports. Four are recommended. Set<String> usablePorts = new StringSet(); if (mask.getStoragePorts() != null) { for (String portId : mask.getStoragePorts()) { StoragePort port = dbClient.queryObject(StoragePort.class, URI.create(portId)); if (port == null || port.getInactive() || NullColumnValueGetter.isNullURI(port.getNetwork())) { continue; } // Validate port network overlaps Initiators and port is tagged for Varray StringSet taggedVarrays = port.getTaggedVirtualArrays(); if (ConnectivityUtil.checkNetworkConnectedToAtLeastOneNetwork(port.getNetwork(), initiatorPortMap.keySet(), dbClient) && taggedVarrays != null && taggedVarrays.contains(varrayURI.toString())) { usablePorts.add(port.getLabel()); } } } if (usablePorts.size() < 2) { _log.info(String.format("ExportMask %s disqualified because it has less than two usable target ports;" + " usable ports: %s", mask.getMaskName(), usablePorts.toString())); passed = false; } else if (usablePorts.size() < 4) { // This is a warning _log.info(String.format("Warning: ExportMask %s has only %d usable target ports (best practice is at least four);" + " usable ports: %s", mask.getMaskName(), usablePorts.size(), usablePorts.toString())); } // Rule 3. No mixing of WWNs from both VPLEX clusters. // Add the clusters for all existingInitiators to the sets computed from initiators above. Set<String> clusters = new HashSet<String>(); for (String portWwn : portWwnToClusterMap.keySet()) { if (mask.hasExistingInitiator(portWwn) || mask.hasUserInitiator(portWwn)) { clusters.add(portWwnToClusterMap.get(portWwn)); } } if (clusters.size() > 1) { _log.info(String.format("ExportMask %s disqualified because it contains wwns from both Vplex clusters", mask.getMaskName())); passed = false; } // Rule 4. The ExportMask name should not have NO_VIPR in it. if (mask.getMaskName().toUpperCase().contains(ExportUtils.NO_VIPR)) { _log.info(String.format("ExportMask %s disqualified because the name contains %s (in upper or lower case) to exclude it", mask.getMaskName(), ExportUtils.NO_VIPR)); passed = false; } // Rule 5. Every port in the ExportMask must have the varray in its tagged varray set. StringBuilder portsNotInVarray = new StringBuilder(); if (mask.getStoragePorts() != null) { for (String portId : mask.getStoragePorts()) { StoragePort port = dbClient.queryObject(StoragePort.class, URI.create(portId)); if (port == null || port.getInactive()) { continue; } // Validate port is tagged for Varray StringSet taggedVarrays = port.getTaggedVirtualArrays(); if (taggedVarrays == null || taggedVarrays.isEmpty() || !taggedVarrays.contains(varrayURI.toString())) { portsNotInVarray.append(port.getPortName() + " "); } } } if (portsNotInVarray.length() > 0) { String virtualArrayName = varrayURI.toString(); VirtualArray virtualArray = dbClient.queryObject(VirtualArray.class, varrayURI); if (virtualArray != null) { virtualArrayName = virtualArray.getLabel(); } _log.warn(String.format("Validation of ExportMask %s failed; the mask has ports which are not in varray %s;\n" + " \tPorts not in varray: %s", mask.getMaskName(), virtualArrayName, portsNotInVarray)); passed = false; } int volumeCount = (mask.getVolumes() != null) ? mask.getVolumes().size() : 0; if (mask.getExistingVolumes() != null) { volumeCount += mask.getExistingVolumes().keySet().size(); } if (passed) { _log.info(String.format("Validation of ExportMask %s passed; it has %d volumes", mask.getMaskName(), volumeCount)); } else { if(null!=invalidMasks) { invalidMasks.add(mask.getId()); } } return passed; } /** * This function tries to select a port for a given initiator and tries to minimize the number * of initiators using the same port. When the ports being assigned are pre-zoned, this * function also ensures that the port assigned to an initiator is pre-zoned to the initiator. * * @param assigner -- the port assigner which ensures that when the ports being assigned are * pre-zoned, this function also ensures that the port assigned to an initiator is pre-zoned * to the initiator. * @param ports -- the list of ports to allocate from * @param net -- the initiator network * @param initiator -- the initiator * @param portUsage -- a IN/OUT parameter that tracks the port usage * @param groupId -- if the port must be in a given group like SP_A or SP_B. If null, this check not needed. * @return the storage port assigned to the initiator if one could be assigned. Null if a port * could not be assigned. */ public static StoragePort assignPortToInitiator(StoragePortsAssigner assigner, List<StoragePort> ports, NetworkLite net, Initiator initiator, Map<StoragePort, Integer> portUsage, String groupId) { StoragePort foundPort = null; for (StoragePort port : ports) { if (groupId == null || port.getPortGroup().equals(groupId)) { // make sure we have an entry in the map if (portUsage.get(port) == null) { portUsage.put(port, 0); } if (assigner.isPortAssignableToInitiator(net, initiator, port)) { if (foundPort == null) { foundPort = port; } else { if (portUsage.get(port) < portUsage.get(foundPort)) { foundPort = port; } } } } } if (foundPort != null) { portUsage.put(foundPort, portUsage.get(foundPort) + 1); } return foundPort; } }