/* * Copyright (c) 2008-2012 EMC Corporation * All Rights Reserved */ package com.emc.storageos.networkcontroller.impl; import java.io.IOException; 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.Set; import java.util.Map.Entry; import org.eclipse.jetty.util.log.Log; 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.DataSource; import com.emc.storageos.customconfigcontroller.DataSourceFactory; import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.AbstractChangeTrackingSet; 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.DataCollectionJobStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus; import com.emc.storageos.db.client.model.ExportGroup; import com.emc.storageos.db.client.model.ExportMask; import com.emc.storageos.db.client.model.FCZoneReference; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.NetworkSystem; 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.StringMap; 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.model.Volume; import com.emc.storageos.db.client.util.DataObjectUtils; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.networkcontroller.NetworkFCZoneInfo; import com.emc.storageos.networkcontroller.exceptions.NetworkDeviceControllerException; import com.emc.storageos.networkcontroller.impl.mds.Zone; import com.emc.storageos.networkcontroller.impl.mds.ZoneMember; import com.emc.storageos.util.ConnectivityUtil; import com.emc.storageos.util.NetworkLite; import com.emc.storageos.util.NetworkUtil; import com.emc.storageos.util.VPlexUtil; import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils; import com.emc.storageos.volumecontroller.placement.BlockStorageScheduler; /** * NetworkScheduler service for FC connections. Zoning is done based on the host * and storage volume port numbers. */ public class NetworkScheduler { protected static final Logger _log = LoggerFactory.getLogger(NetworkScheduler.class); @Autowired private DataSourceFactory dataSourceFactory; @Autowired private CustomConfigHandler customConfigHandler; private DbClient _dbClient; private static final String LSAN = "LSAN_"; private static final int ZONE_NAME_LENGTH = 64; private static final int BROCADE_ZONE_NAME_IVR_LENGTH = 59; public void setDbClient(DbClient dbClient) { _dbClient = dbClient; } /** * Gets the network lite for a storage port when the port is in a * network that is active and registered. Returns null otherwise. * * @param port the storage port * @return the network lite for a storage port when the port is in a * network that is active and registered. Returns null otherwise. */ public NetworkLite getStoragePortNetwork(StoragePort port) { if (NullColumnValueGetter.isNullURI(port.getNetwork())) { _log.info("Port {} is not in a network", port.getPortNetworkId()); } NetworkLite portNetworkLite = NetworkUtil.getNetworkLite(port.getNetwork(), _dbClient); if (portNetworkLite == null || portNetworkLite.getInactive()) { _log.info("Port {} network cannot be found or is decativated", port.getPortNetworkId()); } else if (!portNetworkLite.registered()) { _log.info("Port {} network {} is deregistered", port.getPortNetworkId(), portNetworkLite.getNativeGuid()); } else { return portNetworkLite; } return null; } /** * Assign a name to a zone using the current zone name configuration. * The name is stored in the fabricInfo. * The system-default name is composed of the following fields separated by underscores: * 1. The prefix "SDS" * 2. Hostname (maximum 32 characters) * 3. The last twelve characters of the WWPN of the Initiator (without colons, upper case) * 4. The last four digits of the Storage Array serial number. * 5. The Storage Array Port Name (maximum 9 characters) (nothing but alpha-numeric characters). * * @param fabricInfo -- Contextual object for the operation. Contains the endpoints. * @param hostName -- The host name for the given initiator. * @param port -- The StoragePort used to find the StorageArray and get the PortName * @param lsanZone -- a flag that indicates if the zone to be created is an LSAN zone */ private void nameZone(NetworkFCZoneInfo fabricInfo, String systemType, String hostName, String initiatorport, StoragePort port, boolean lsanZone) { // use 1st two endpoints in name if (fabricInfo.getEndPoints().size() < 2) { throw NetworkDeviceControllerException.exceptions.nameZoneNotEnoughEndPoints(); } // Use the StoragePort to find the StorageSystem URI arrayUri = port.getStorageDevice(); StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayUri); if (array == null) { throw NetworkDeviceControllerException.exceptions.portStorageDeviceNotFound( port.getStorageDevice().toString(), port.getLabel()); } Initiator initiator = NetworkUtil.findInitiatorInDB(initiatorport, _dbClient); DataSource dataSource = dataSourceFactory.createZoneNameDataSource(hostName, initiator, port, fabricInfo.getFabricId(), array); if (array.getSystemType().equals(DiscoveredDataObject.Type.vplex.name())) { dataSource.addProperty(CustomConfigConstants.ARRAY_PORT_NAME, getVPlexPortName(port)); dataSource.addProperty(CustomConfigConstants.ARRAY_SERIAL_NUMBER, getVPlexClusterSerialNumber(port)); } String resolvedZoneName = customConfigHandler.resolve( CustomConfigConstants.ZONE_MASK_NAME, systemType, dataSource); validateZoneNameLength(resolvedZoneName, lsanZone, systemType); String zoneName = customConfigHandler.getComputedCustomConfigValue( CustomConfigConstants.ZONE_MASK_NAME, systemType, dataSource); if (lsanZone && DiscoveredDataObject.Type.brocade.name().equals(systemType)) { zoneName = LSAN + zoneName; } fabricInfo.setZoneName(zoneName); } /** * Generates a zoneName from the input parameters according to the CustomConfig handler. * @param arrayURI -- URI of StorageSystem * @param networkSystemURI -- URI of network system * @param initiatorPort -- Initiator port address * @param portNetworkAddress -- Port network address * @param fabricId -- Fabric id * @param lsanZone -- true if LSAN zone * @return -- zone name */ public String nameZone(URI arrayURI, URI networkSystemURI, String initiatorPort, String portNetworkAddress, String fabricId, boolean lsanZone) { StorageSystem array = _dbClient.queryObject(StorageSystem.class, arrayURI); NetworkSystem networkSystem = _dbClient.queryObject(NetworkSystem.class, networkSystemURI); Initiator initiator = NetworkUtil.findInitiatorInDB(initiatorPort, _dbClient); StoragePort port = NetworkUtil.getStoragePort(portNetworkAddress, _dbClient); String hostName = initiator.getHostName(); if (array == null || initiator == null || hostName == null) { throw DeviceControllerException.exceptions .unexpectedCondition("Cannot generate zone name because array, initiator, or hostName were null"); } DataSource dataSource = dataSourceFactory.createZoneNameDataSource(hostName, initiator, port, fabricId, array); if (array.getSystemType().equals(DiscoveredDataObject.Type.vplex.name())) { dataSource.addProperty(CustomConfigConstants.ARRAY_PORT_NAME, getVPlexPortName(port)); dataSource.addProperty(CustomConfigConstants.ARRAY_SERIAL_NUMBER, getVPlexClusterSerialNumber(port)); } String systemType = networkSystem.getSystemType(); String resolvedZoneName = customConfigHandler.resolve( CustomConfigConstants.ZONE_MASK_NAME, systemType, dataSource); validateZoneNameLength(resolvedZoneName, lsanZone, systemType); String zoneName = customConfigHandler.getComputedCustomConfigValue( CustomConfigConstants.ZONE_MASK_NAME, systemType, dataSource); if (lsanZone && DiscoveredDataObject.Type.brocade.name().equals(systemType)) { zoneName = LSAN + zoneName; } return zoneName; } /** * Validates if zone name length is within the allowed character limit on switches. */ private void validateZoneNameLength(String zoneName, boolean isIvrZone, String systemType) { // Checks for a different length for IVR zones as it should start with "LSAN" for brocade which is appended to zone name later if(isIvrZone && DiscoveredDataObject.Type.brocade.name().equals(systemType)) { if(zoneName.length() > BROCADE_ZONE_NAME_IVR_LENGTH) { throw NetworkDeviceControllerException.exceptions.nameZoneLongerThanAllowed(zoneName, BROCADE_ZONE_NAME_IVR_LENGTH); } } else { if(zoneName.length() > ZONE_NAME_LENGTH) { throw NetworkDeviceControllerException.exceptions.nameZoneLongerThanAllowed(zoneName, ZONE_NAME_LENGTH); } } } /** * Add the director digits to make the vplex port name unique within a vplex system * * @param port StoragePort * @param system StorageSystem * @return nine character maximum string generated from director and port fields */ private static String getVPlexPortName(StoragePort port) { String directorDigits = port.getPortGroup().substring(port.getPortGroup().indexOf("-") + 1, port.getPortGroup().lastIndexOf("-")); return directorDigits + port.getPortName(); } /** * For the passed VPLEX port, determines the VPLEX cluster for the port * and then returns the serial number for that cluster. Called when * configuring the zone name for a VPLEX port. * * @param port A reference to a VPLEX port. * @return The serial number for the port's cluster. */ private String getVPlexClusterSerialNumber(StoragePort port) { URI systemURI = port.getStorageDevice(); StorageSystem vplexSystem = _dbClient.queryObject(StorageSystem.class, systemURI); String portClusterId = ConnectivityUtil.getVplexClusterOfPort(port); return VPlexUtil.getVPlexClusterSerialNumber(portClusterId, vplexSystem); } /** * returns true if a zone name designates an LSAN zone * @param zoneName -- name of zone * @return -- true if LSAN zone */ public boolean isLSANZone(String zoneName) { return (zoneName.startsWith(LSAN)); } /** * Select the network device and VSAN or Brocade Fabric for the host/volume zoning. * * The selection is based on the end points (initiator and storage port) and * the availability of a network device that has ports connections to both * end points. If a fabric can't be identified with both endpoints, we fall back to * looking for a fabric with at least the storagePort discovered. * * @param exportGroupUri Export Group URI * @param varrayUri Virtual Array URI * @param protocol String (FC for this to do anything) * @param initiatorPort The WWN of the initiator * @param storagePort The StoragePort object * @param hostName Used for generating the zone name. * @param existingZones a list of existing zones for the initiator * @param checkZones Flag to enable or disable zoning check on a Network System * * @return NetworkFabricInfo configured for adding zones */ private NetworkFCZoneInfo placeZones(URI exportGroupUri, URI varrayUri, String protocol, String initiatorPort, StoragePort storagePort, String hostName, List<Zone> existingZones, boolean checkZones) throws DeviceControllerException { initiatorPort = formatWWN(initiatorPort); String storagePortWwn = formatWWN(storagePort.getPortNetworkId()); if (Transport.FC != StorageProtocol.block2Transport(protocol)) { return null; } _log.info("Placing a zone for initiator {} and port {}", initiatorPort, storagePortWwn); // do some validation NetworkLite iniNet = NetworkUtil.getEndpointNetworkLite(initiatorPort, _dbClient); NetworkLite portNet = getStoragePortNetwork(storagePort); if (iniNet == null || portNet == null || !NetworkUtil.checkInitiatorAndPortConnected(iniNet, portNet)) { _log.debug(String.format( "Initiator %s could not be paired with port %s", initiatorPort, storagePortWwn)); return null; } // Check whether to check zoning on the Network System // If True, we will check zoning on the Network System // False we will use the existing FCZoneReference info if (!checkZones) { _log.debug("Check Zones flag is false. Finding FCZoneReference for initiator {} and port {}", initiatorPort, storagePortWwn); // Find the FCZoneReference in ViPR for the port-initiator key and the network String key = FCZoneReference.makeEndpointsKey(initiatorPort, storagePortWwn); List<FCZoneReference> fcZoneRefs = getFCZoneReferencesForKey(key); FCZoneReference refTemplate = DataObjectUtils.findByProperty(fcZoneRefs, "groupUri", exportGroupUri); if (refTemplate != null) { _log.info("Already existing FCZoneReferences for initiator {} and port {} will be replicated for new volumes.", initiatorPort, storagePortWwn); return createZoneInfoForRef(refTemplate, null, initiatorPort, storagePortWwn, NetworkUtil.getEndpointNetworkLite(initiatorPort, _dbClient), exportGroupUri); } else { _log.info("FCZoneReferences doesnt exist for initiator {} and port {} for replication.", initiatorPort, storagePortWwn); return null; } } else { _log.debug("Check Zones flag is false. Placing a zone for initiator {} and port {}", initiatorPort, storagePortWwn); // If the zone already exists, just return its reference NetworkFCZoneInfo zoneInfo = getZoneInfoForExistingZone(iniNet, initiatorPort, storagePort.getPortNetworkId(), existingZones); if (zoneInfo != null) { zoneInfo.setExportGroup(exportGroupUri); _log.info("Already existing zone {} for initiator {} and port {} will be used.", new Object[] { zoneInfo.getZoneName(), initiatorPort, storagePortWwn }); return zoneInfo; } _log.debug("Could not find an existing zone for initiator {} and port {} to use." + "A new zone will be created.", new Object[] { initiatorPort, storagePortWwn }); // Create a the list of end points - List<String> endPoints = Arrays.asList(new String[] { initiatorPort, storagePortWwn }); List<NetworkSystem> networkSystems = getZoningNetworkSystems(iniNet, portNet); if (networkSystems.isEmpty()) { _log.info(String.format( "Could not find a network system with connection to storage port %s", storagePortWwn)); throw DeviceControllerException.exceptions.cannotFindSwitchConnectionToStoragePort(storagePortWwn); } // 2. Select the network system to use NetworkSystem networkSystem = networkSystems.get(0); // 3. identify an alternate network device, if any _log.debug("Network system {} was selected to be the primary network system. " + "Trying to select an alternate network system.", networkSystem.getNativeGuid()); NetworkSystem altNetworkSystem = networkSystem; for (NetworkSystem system : networkSystems) { if (altNetworkSystem != system) { altNetworkSystem = system; _log.debug("Network system {} was selected to be the alternate network system.", altNetworkSystem.getNativeGuid()); break; } } // 4. create the response NetworkFCZoneInfo networkFabricInfo = null; if (networkSystem != null) { networkFabricInfo = new NetworkFCZoneInfo(networkSystem.getId(), iniNet.getNativeId(), NetworkUtil.getNetworkWwn(iniNet)); networkFabricInfo.getEndPoints().addAll(endPoints); networkFabricInfo.setAltNetworkDeviceId(URI.create(altNetworkSystem.getId().toString())); networkFabricInfo.setExportGroup(exportGroupUri); networkFabricInfo.setCanBeRolledBack(true); nameZone(networkFabricInfo, networkSystem.getSystemType(), hostName, initiatorPort, storagePort, !portNet.equals(iniNet)); } return networkFabricInfo; } } /** * Looks at the varray to see if zoning is disabled, and looks to make * sure that there is at least one active NetworkSystem registered. * * @param dbClient DbClient * @param varrayUri the URI of the virtual array * @return true if zoning required, false if not */ public static boolean isZoningRequired(DbClient dbClient, URI varrayUri) { // If automatic zoning disabled, return false if (varrayUri != null) { VirtualArray nh = dbClient.queryObject(VirtualArray.class, varrayUri); if (nh != null) { return isZoningRequired(dbClient, nh); } } return false; } /** * Search the list of existing zones for the initiator-port pair to decide which to use. * Preference is given to zones according to this priority: * <ol> * <li>The zone is in ViPR DB and was created by ViPR</li> * <li>The zone is in ViPR DB but was not created by ViPR</li> * <li>The zone follows the single initiator-target pair per zone</li> * <li>The last zone in the list</li> * </ol> * If no zone can be found for the initiator-port pair, null is returned. * * @param network the network of the initiator * @param initiatorWwn the initiator WWN * @param portWwn the target WWN * @param existingZones a list of zones found on the network system for the initiator * @return an instance of Zone if one is found, otherwise null. */ public Zone selectExistingZoneForInitiatorPort(NetworkLite network, String initiatorWwn, String portWwn, List<Zone> existingZones) { // If we did not find zones, we need to create zones even if we have FCZoneReference if (existingZones == null || existingZones.isEmpty()) { return null; } // initialize variables boolean existingZone = true; Zone foundZone = null; // Find the FCZoneReference in ViPR for the port-initiator key and the network String key = FCZoneReference.makeEndpointsKey(initiatorWwn, portWwn); List<FCZoneReference> fcZoneRefs = getFCZoneReferencesForKey(key); if (!fcZoneRefs.isEmpty()) { Zone matchedZone = null; _log.info("Found {} FCZoneReference for key {}", fcZoneRefs.size(), key); // try to re-use zones known to ViPR as a first preference for (FCZoneReference fcZoneRef : fcZoneRefs) { // make sure the FCZoneReference matches the network and its network system a if (network.getNetworkSystems().contains(fcZoneRef.getNetworkSystemUri().toString()) && network.getNativeId().equals(fcZoneRef.getFabricId())) { _log.debug("Found an FCZoneReference for zone {}", fcZoneRef.getZoneName()); // do still have the zone on the network system matchedZone = findZoneByNameAndPort(fcZoneRef.getZoneName(), portWwn, existingZones); if (matchedZone != null) { _log.debug("Found the zone for FCZoneReference {} in the initiator existing zones", fcZoneRef.getZoneName()); _log.debug(matchedZone.getLogString()); foundZone = matchedZone; // if the zone was created by ViPR, the search ended if (!fcZoneRef.getExistingZone()) { existingZone = false; _log.debug("Selected zone {} because it was created by ViPR", foundZone.getName()); break; } } } } } if (foundZone != null) { _log.debug("Selected existing Zone {} as it is already used by ViPR", foundZone.getName()); } else { outer: for (Zone curZone : existingZones) { for (ZoneMember member : curZone.getMembers()) { if (member.getAddress() != null && member.getAddress().equals(portWwn)) { foundZone = curZone; if (curZone.getMembers().size() == 2) { // if the zone has only 2 members, the search ended _log.debug("Selected existing Zone {} as it has only 2 members", foundZone.getName()); break outer; } } } } } if (foundZone != null) { foundZone.setExistingZone(existingZone); } return foundZone; } /** * Find the FCZoneReferences for a given zone reference key. * * @param key - Endpoint key consisting of concatenated WWNs * @return List of FCZoneReference */ public List<FCZoneReference> getFCZoneReferencesForKey(String key) { List<FCZoneReference> list = new ArrayList<FCZoneReference>(); URIQueryResultList uris = new URIQueryResultList(); Iterator<FCZoneReference> itFCZoneReference = null; _dbClient.queryByConstraint(AlternateIdConstraint.Factory. getFCZoneReferenceKeyConstraint(key), uris); itFCZoneReference = _dbClient.queryIterativeObjects(FCZoneReference.class, DataObjectUtils.iteratorToList(uris), true); if (itFCZoneReference.hasNext()) { while (itFCZoneReference.hasNext()) { list.add(itFCZoneReference.next()); } } else { _log.info("No FC Zone References for key found"); } return list; } /** * Looks at the varray to see if zoning is disabled, and looks to make * sure that there is at least one active NetworkSystem registered. * * @param dbClient DbClient * @param nh Neighborhood * @return true if zoning required, false if not */ public static boolean isZoningRequired(DbClient dbClient, VirtualArray nh) { // If automatic zoning disabled, return false if (nh.getAutoSanZoning() == false) { _log.info("SAN Zoning is not enabled for Neighborhood: " + nh.getLabel()); return false; } return NetworkUtil.areNetworkSystemDiscovered(dbClient); } /** * Finds all the network systems that have access to the initiators and ' * storage port networks. When the initiators and storage port are in * different networks that are routed to each other, the assumption is that * there should exist and network system that can be used to managed both * networks. * * @param iniNetwork the initiator network * @param portNetwork the storage port network * @return the network systems that can be used to managed the port and initiator * networks. */ List<NetworkSystem> getZoningNetworkSystems(NetworkLite iniNetwork, NetworkLite portNetwork) { List<NetworkSystem> orderedNetworkSystems = new ArrayList<NetworkSystem>(); List<NetworkSystem> idleNetworkSystems = new ArrayList<NetworkSystem>(); List<NetworkSystem> deRegisteredNetworkSystems = new ArrayList<NetworkSystem>(); List<URI> iniNetSys = (iniNetwork == null) ? new ArrayList<URI>() : StringSetUtil.stringSetToUriList(new StringSet(iniNetwork.getNetworkSystems())); List<URI> portNetSys = (portNetwork == null) ? new ArrayList<URI>() : StringSetUtil.stringSetToUriList(new StringSet(portNetwork.getNetworkSystems())); // find the common network systems Collection<URI> allSys = new HashSet<URI>(); if (iniNetSys != null) { allSys.addAll(iniNetSys); } if (portNetSys != null) { allSys.addAll(portNetSys); } if (!allSys.isEmpty()) { orderedNetworkSystems = _dbClient.queryObject(NetworkSystem.class, allSys, true); if (!orderedNetworkSystems.isEmpty()) { for (NetworkSystem networkSystem : orderedNetworkSystems) { if (networkSystem.getRegistrationStatus().equals(RegistrationStatus.UNREGISTERED.toString())) { _log.info("Network System {} is not used as it is not registered.", networkSystem.getLabel()); deRegisteredNetworkSystems.add(networkSystem); } else if (networkSystem.getDiscoveryStatus().equals(DataCollectionJobStatus.ERROR.toString()) || networkSystem.getDiscoveryStatus().equals(DataCollectionJobStatus.CREATED.toString())) { _log.info("Network System {} is moved to the end of Network System list as its discovery is not successful.", networkSystem.getLabel()); idleNetworkSystems.add(networkSystem); } } orderedNetworkSystems.removeAll(deRegisteredNetworkSystems); orderedNetworkSystems.removeAll(idleNetworkSystems); Collections.shuffle(orderedNetworkSystems); Collections.shuffle(idleNetworkSystems); orderedNetworkSystems.addAll(idleNetworkSystems); } else { _log.warn("Could not find any active network systems that can be used to zone."); } } else { _log.warn("Could not find any network systems that can be used to zone."); } return orderedNetworkSystems; } /** * Check that the zoning map has been initialized and has entries for all initiators * in the Export Mask if required. * * @param varrayURI VirtualArray URI * @param mask ExportMask * @param dbClient database handle */ static public void checkZoningMap( ExportGroup exportGroup, ExportMask mask, Set<Initiator> initiators, DbClient dbClient) { // Normally we don't want to generate a full zone map except in the rare case // where there is no zone map set and the Export Group zoneAllInitiators flag // is set to true. Currently only needed by RecoverPoint. if (mask.getZoningMap().isEmpty() && exportGroup.getZoneAllInitiators() == true) { generateFullZoningMap(dbClient, exportGroup.getVirtualArray(), mask, initiators); } else { checkZoningMap(exportGroup.getVirtualArray(), mask, initiators); } } /** * Check that the zoning map has been initialized and has entries for a specified Collection of Initiators. * Otherwise info error message. * * @param varrayURI VirtualArray URI * @param mask ExportMask * @param initiators Collection<Initiator> */ static private void checkZoningMap(URI varrayURI, ExportMask mask, Collection<Initiator> initiators) { StringSetMap zoningMap = mask.getZoningMap(); for (Initiator initiator : initiators) { if (zoningMap == null || !zoningMap.containsKey(initiator.getId().toString())) { _log.info(String.format("No zoning map entry for initiator %s (%s), will not be zoned", initiator.getInitiatorPort(), initiator.getId())); } } } /** * Given a list of storage ports, the find the ones that can be targets for * the initiator in a given virtual array. The target port must be tagged to * the virtual array and have connectivity to the initiator either via that * same network as the initiator or via a network that is routed to the * initiator's network. When a mix of local and routed targets are found, * only the local ones are returned. * * @param varrayURI - VirtualArray URI * @param initiator Initiator * @param port StoragePort * @return a list of storage ports that can be the target of the initiator. */ public static List<URI> findInitiatorTargetsInVarray(DbClient dbClient, URI varrayURI, Initiator initiator, Set<StoragePort> storagePorts) { NetworkLite iniNetwork = BlockStorageScheduler.lookupNetworkLite(dbClient, Transport.FC, initiator.getInitiatorPort()); List<URI> targetPorts = new ArrayList<URI>(); if (iniNetwork != null) { for (StoragePort storagePort : storagePorts) { if (iniNetwork.getId().equals(storagePort.getNetwork()) && storagePort.getTaggedVirtualArrays() != null && storagePort.getTaggedVirtualArrays().contains(varrayURI.toString())) { targetPorts.add(storagePort.getId()); } } if (targetPorts.isEmpty()) { for (StoragePort storagePort : storagePorts) { if (iniNetwork.connectedToNetwork(storagePort.getNetwork()) && storagePort.getTaggedVirtualArrays() != null && storagePort.getTaggedVirtualArrays().contains(varrayURI.toString())) { targetPorts.add(storagePort.getId()); } } } } return targetPorts; } /** * Generate the zoning targets for a newly created ExportGroup. * The group may include arbitrary numbers of initiators, volumes, ports, export masks, etc. * * @param exportGroup ExportGroup * @param volumeURIs Collection of volumes to be generated * @param existingZonesMap a map of initiator ports WWN to its existing zones * @param checkZones Flag to enable or disable zoning check on a Network System * @param dbClient an instance of DbClient * @return List<NetworkFCZoneInfo> representing zones to be created * @throws DeviceControllerException */ public List<NetworkFCZoneInfo> getZoningTargetsForExportMasks( ExportGroup exportGroup, List<URI> exportMaskURIs, Collection<URI> volumeURIs, Map<String, List<Zone>> existingZonesMap, boolean checkZones, DbClient dbClient) { List<NetworkFCZoneInfo> zoneInfos = new ArrayList<NetworkFCZoneInfo>(); for (URI maskURI : exportMaskURIs) { ExportMask exportMask = ExportMaskUtils.getExportMask(_dbClient, maskURI); if (exportMask == null) { continue; } Collection<URI> filteredVolumesURIs = filterVolumes(volumeURIs, exportMask); if (filteredVolumesURIs.isEmpty()) { continue; } checkZoningMap(exportGroup, exportMask, ExportMaskUtils.getInitiatorsForExportMask(_dbClient, exportMask, Transport.FC), _dbClient); if (isZoningRequired(dbClient, exportGroup.getVirtualArray())) { _log.info(String.format("Generating zoning targets for ExportMask %s (%s)", exportMask.getMaskName(), exportMask.getId())); zoneInfos.addAll( generateRequestedZonesForExportMask(exportGroup.getVirtualArray(), exportGroup, exportMask, filteredVolumesURIs, existingZonesMap, checkZones)); } // If we're doing a VPlex export, it might use an alternate Varray (for HA export), // so check to see if we can add zones for the alternate Varray. if (exportGroup.hasAltVirtualArray(exportMask.getStorageDevice().toString())) { URI altVirtualArray = URI.create(exportGroup.getAltVirtualArrays() .get(exportMask.getStorageDevice().toString())); if (isZoningRequired(dbClient, altVirtualArray)) { zoneInfos.addAll(generateRequestedZonesForExportMask(altVirtualArray, exportGroup, exportMask, filteredVolumesURIs, existingZonesMap, checkZones)); } } } return zoneInfos; } /** * Filter so as to return all the volumes that are on the same array as indicated by the Export Mask * * @param volumeURIs * @param exportMask * @return Collection<URI> of volume ids */ private Collection<URI> filterVolumes(Collection<URI> volumeURIs, ExportMask exportMask) { List<URI> volumes = new ArrayList<URI>(); for (URI volumeURI : volumeURIs) { BlockObject volume = BlockObject.fetch(_dbClient, volumeURI); if (volume.getStorageController().equals(exportMask.getStorageDevice())) { volumes.add(volume.getId()); } } return volumes; } /** * The ExportMask has a valid zoningMap, which identifies the zones that * the StoragePortsAssigner wanted created. So we create specifically those zones. * * @param varrayURI Varray (Neighborhood) URI * @param exportGroup ExportGroup object * @param exportMask ExportMask object * @param volumeURIs - List of volume URIs using this ExportMask * @param existingZonesMap a map of initiator ports WWN to its existing zones * @param checkZones Flag to enable or disable zoning check on a Network System * @return List<NetworkFCZoneInfO representing zones to be created. * @throws DeviceControllerException */ private List<NetworkFCZoneInfo> generateRequestedZonesForExportMask( URI varrayURI, ExportGroup exportGroup, ExportMask exportMask, Collection<URI> volumeURIs, Map<String, List<Zone>> existingZonesMap, boolean checkZones) throws DeviceControllerException { List<NetworkFCZoneInfo> zoneInfos = new ArrayList<NetworkFCZoneInfo>(); if (exportMask.getZoningMap() == null) { _log.info(String.format("No zone map Export Mask %s (%s) systemCreated %s", exportMask.getMaskName(), exportMask.getId(), exportMask.getCreatedBySystem())); return zoneInfos; } Set<Initiator> initiators = ExportMaskUtils.getInitiatorsForExportMask( _dbClient, exportMask, Transport.FC); for (Initiator initiator : initiators) { StringSet portIds = exportMask.getZoningMap().get(initiator.getId().toString()); if (portIds != null) { for (String portId : portIds) { StoragePort sp = _dbClient.queryObject(StoragePort.class, URI.create(portId)); if (null != sp && sp.getTaggedVirtualArrays() != null && sp.getTaggedVirtualArrays().contains(varrayURI.toString())) { boolean placedZone = placeZone(zoneInfos, exportGroup, varrayURI, initiator, sp, volumeURIs, existingZonesMap.get(initiator.getInitiatorPort()), checkZones); if (placedZone == false && checkZones) { throw DeviceControllerException.exceptions.cannotMatchSanStoragePortInitiatorForVolume(sp.getPortName(), formatWWN(initiator.getInitiatorPort()), volumeURIs.toString()); } } } } } return zoneInfos; } /** * Place a zone described by its initiator and port, and add it to the zoneInfos list. * ZoneReferences and zones will be added for all volumes matching the storage port's device. * * @param zoneInfos List<NetworkFCZoneInfo> list of zones being built * @param exportGroup ExportGroup * @param varrayURI VirtualArray (Neighborhood) URI * @param initiator Initiator * @param sp StoragePort * @param volumeURIs * @param existingZones zones that already exist on the network system * @param checkZones Flag to enable or disable zoning check on a Network System * * @return true if could place the zone */ private boolean placeZone( List<NetworkFCZoneInfo> zoneInfos, ExportGroup exportGroup, URI varrayURI, Initiator initiator, StoragePort sp, Collection<URI> volumeURIs, List<Zone> existingZones, boolean checkZones) { boolean foundMatch = false; NetworkFCZoneInfo zoneInfo = placeZones( exportGroup.getId(), varrayURI, initiator.getProtocol(), formatWWN(initiator.getInitiatorPort()), sp, initiator.getHostName(), existingZones, checkZones); if (zoneInfo != null) { for (URI volumeURI : volumeURIs) { BlockObject volume = BlockObject.fetch(_dbClient, volumeURI); // If the volume is from a different device, don't create a reference. if (!volume.getStorageController().equals(sp.getStorageDevice())) { continue; } NetworkFCZoneInfo volZoneInfo = zoneInfo.clone(); volZoneInfo.setVolumeId(volumeURI); zoneInfos.add(volZoneInfo); } foundMatch = true; } return foundMatch; } /** * Search the list of existing zones for the initiator-port pair to decide which to use. * Preference is given to zones according to this priority: * <ol> * <li>The zone is in ViPR DB and was created by ViPR</li> * <li>The zone is in ViPR DB but was not created by ViPR</li> * <li>The zone follows the single initiator-target pair per zone</li> * <li>The last zone in the list</li> * </ol> * Create a new FCZoneInfo object from an existing zone, otherwise return a null. * * @param network the initiator network * @param initiatorWwn the initiator WWN * @param portWwn the target WWN * @param existingZones a list of zones found on the network system for the initiator * @return an instance of FCZoneInfo */ private NetworkFCZoneInfo getZoneInfoForExistingZone(NetworkLite network, String initiatorWwn, String portWwn, List<Zone> existingZones) { NetworkFCZoneInfo zoneInfo = null; Zone zone = selectExistingZoneForInitiatorPort(network, initiatorWwn, portWwn, existingZones); if (zone != null) { zoneInfo = new NetworkFCZoneInfo( URI.create(network.getNetworkSystems().iterator().next()), network.getNativeId(), NetworkUtil.getNetworkWwn(network)); zoneInfo.setEndPoints(Arrays.asList(new String[] { initiatorWwn, portWwn })); zoneInfo.setZoneName(zone.getName()); zoneInfo.setExistingZone(zone.getExistingZone()); zoneInfo.setCanBeRolledBack(true); } return zoneInfo; } /** * Performs a simple search to find a zone in a collection by name and port WWN * * @param name the name * @param portWwn the port WWN * @param zones the collection of zone * @return the matching if one is found, null otherwise. */ private Zone findZoneByNameAndPort(String name, String portWwn, List<Zone> zones) { if (zones != null) { for (Zone zone : zones) { if (zone.getName().equals(name)) { for (ZoneMember member : zone.getMembers()) { if (member.getAddress() != null && member.getAddress().equals(portWwn)) { _log.debug("Found a matching zone for name {} and port WWN {}", zone.getName(), portWwn); return zone; } } } } } _log.debug("Could not find a matching zone for name {} and port WWN {}", name, portWwn); return null; } /** * Returns a list of zoning targets for multiple export masks each adding their own * list of initiators. * * @param exportGroup ExportGroup * @param exportMasksToInitiators Map of ExportMask URI to List of Initiator URIs * @param zonesMap a list of existing zones mapped by the initiator port WWN * @param dbClient * @return List of Zones (NetworkFCZoneInfo) * @throws DeviceControllerException */ public List<NetworkFCZoneInfo> getZoningTargetsForInitiators( ExportGroup exportGroup, Map<URI, List<URI>> exportMasksToInitiators, Map<String, List<Zone>> zonesMap, DbClient dbClient) throws DeviceControllerException { List<NetworkFCZoneInfo> zones = new ArrayList<NetworkFCZoneInfo>(); for (URI maskURI : exportMasksToInitiators.keySet()) { ExportMask mask = ExportMaskUtils.getExportMask(_dbClient, maskURI); if (mask == null) { continue; } List<Initiator> initiators = getInitiators(exportMasksToInitiators.get(maskURI)); _log.info(String.format( "Generating zoning targets for ExportMask: %s (%s) Initiators: %s", mask.getMaskName(), mask.getId(), exportMasksToInitiators.get(maskURI).toString())); checkZoningMap(exportGroup.getVirtualArray(), mask, initiators); if (isZoningRequired(dbClient, exportGroup.getVirtualArray())) { zones.addAll(getZoningTargetsForInitiators(exportGroup, mask, exportGroup.getVirtualArray(), initiators, zonesMap)); } // If we're doing a VPlex export, it might use an alternate Varray (for HA export), // so check to see if we can add zones for the alternate Varray. if (exportGroup.hasAltVirtualArray(mask.getStorageDevice().toString())) { URI altVirtualArray = URI.create(exportGroup.getAltVirtualArrays() .get(mask.getStorageDevice().toString())); if (isZoningRequired(dbClient, altVirtualArray)) { zones.addAll(getZoningTargetsForInitiators(exportGroup, mask, altVirtualArray, initiators, zonesMap)); } } } return zones; } /** * Returns a list of initiators from a list of Initiator URIs * * @param initiatorURIs -- a List of Initiator URIs * @return List<Initiator> where each Initiator is active and type FC */ private List<Initiator> getInitiators(Collection<URI> initiatorURIs) { List<Initiator> initiators = new ArrayList<Initiator>(); Iterator<Initiator> queryIterativeInitiators = _dbClient.queryIterativeObjects(Initiator.class, initiatorURIs); while (queryIterativeInitiators.hasNext()) { Initiator initiator = queryIterativeInitiators.next(); if (initiator == null || initiator.getInactive() == true) { continue; } if (StorageProtocol.block2Transport(initiator.getProtocol()) != Transport.FC) { continue; } initiators.add(initiator); } return initiators; } /** * This is called when ExportGroupService adds initiators. * Creates list of NetworkFabricInfo structures for zoning each volume * to the newly added Initiator. * * @param exportGroup - The ExportGroup structure. * @param varrayUri - The URI of the virtual array, this can be the export group's * virtual array or its alternate virtual array * @param exportMask - The ExportMask structure. * @param initiators - Contains the initiators * @param zonesMap a list of existing zones mapped by the initiator port WWN * @return List<NetworkFCZoneInfo> indicating zones and zone references to be created. * @throws IOException * @throws DeviceControllerException */ private List<NetworkFCZoneInfo> getZoningTargetsForInitiators( ExportGroup exportGroup, ExportMask exportMask, URI varrayUri, List<Initiator> initiators, Map<String, List<Zone>> zonesMap) throws DeviceControllerException { List<NetworkFCZoneInfo> fabricInfos = new ArrayList<NetworkFCZoneInfo>(); for (Initiator initiator : initiators) { // Determine storage ports. StringSet storagePorts = null; if (exportMask.getZoningMap() != null) { // Get the explicit zone assignments from the port assigner storagePorts = exportMask.getZoningMap().get(initiator.getId().toString()); } if (storagePorts == null || storagePorts.isEmpty()) { continue; } if (StorageProtocol.block2Transport(initiator.getProtocol()) != Transport.FC) { continue; } for (String storagePort : storagePorts) { StoragePort sp = _dbClient.queryObject(StoragePort.class, URI.create(storagePort)); if (sp == null || sp.getTaggedVirtualArrays() == null || !sp.getTaggedVirtualArrays().contains(varrayUri.toString())) { continue; } if (!exportMask.getStorageDevice().equals(sp.getStorageDevice())) { continue; } try { NetworkFCZoneInfo fabricInfo = placeZones( exportGroup.getId(), varrayUri, initiator.getProtocol(), formatWWN(initiator.getInitiatorPort()), sp, initiator.getHostName(), zonesMap.get(initiator.getInitiatorPort()), true); if (fabricInfo != null) { for (String volId : exportMask.getVolumes().keySet()) { NetworkFCZoneInfo volFabricInfo = fabricInfo.clone(); volFabricInfo.setVolumeId(URI.create(volId)); fabricInfos.add(volFabricInfo); } } } catch (DeviceControllerException ex) { _log.info(String.format( "Initiator %s could not be paired with port %s", initiator.getInitiatorPort(), sp.getPortNetworkId())); } } } return fabricInfos; } /** * Generate the NetworkFCZoneInfo structures representing the zone/volume duples * that are fed to the actual zoning code to remove zones. * @param zoningParams -- Collection of NetworkZoningParam that contains initiator information * @param volumeURIs -- Optional Collection of volumes being affected by the zoning operation. * If null, the zoningParams.getVolumes() is used instead, which comes from the ExportMask volumes keyset. * @return list of NetworkFCZonInfos representing zones and FCZoneReferences to be removed */ public List<NetworkFCZoneInfo> getZoningRemoveTargets( Collection<NetworkZoningParam> zoningParams, Collection<URI> volumeURIs) { List<NetworkFCZoneInfo> zoningTargets = new ArrayList<NetworkFCZoneInfo>(); // For each set of zoning parameters, get the appropriate zones // and add them to the zoningTargets result list. for (NetworkZoningParam zoningParam : zoningParams) { URI varray = zoningParam.getVirtualArray(); _log.info(String.format("Generating remove zoning targets for ExportMask %s (%s)", zoningParam.getMaskName(), zoningParam.getMaskId())); // For each zoningMap entry consisting of an initiator key to port set, // validate the initiators are FC, and calculate the zone information for (Map.Entry<String, AbstractChangeTrackingSet<String>> entry : zoningParam.getZoningMap().entrySet()) { String initiatorId = entry.getKey(); Initiator initiator = _dbClient.queryObject(Initiator.class, URI.create(initiatorId)); if (initiator == null || initiator.getInactive()) { _log.info("Initiator inactive: " + initiatorId); continue; } if (StorageProtocol.block2Transport(initiator.getProtocol()) != Transport.FC) { _log.info(String.format( "Initiator not FC %s %s", initiator.getInitiatorPort(), initiatorId)); continue; } Set<String> portSet = entry.getValue(); if (portSet == null || portSet.isEmpty()) { _log.info(String.format( "No ports in zoningMap for initiator %s %s", initiator.getInitiatorPort(), initiatorId)); continue; } List<URI> exportGroupURIs = new ArrayList<URI>(); exportGroupURIs.add(zoningParam.getExportGroupId()); if (zoningParam.getAlternateExportGroupIds() != null) { exportGroupURIs.addAll(zoningParam.getAlternateExportGroupIds()); } // Calculate the zone information for each initiator/port combination for (String portId : portSet) { // Calculate the zone information List<NetworkFCZoneInfo> zoneInfos = unexportVolumes( varray, (volumeURIs != null ? volumeURIs : zoningParam.getVolumes()), exportGroupURIs, URI.create(portId), formatWWN(initiator.getInitiatorPort()), zoningParam.hasExistingVolumes()); if (zoneInfos != null) { zoningTargets.addAll(zoneInfos); } } } } return zoningTargets; } /** * Make a String key from two URIs. * * @param uri1 * @param uri2 * @return */ private String make2UriKey(URI uri1, URI uri2) { String part1 = "null"; String part2 = "null"; if (uri1 != null) { part1 = uri1.toString(); } if (uri2 != null) { part2 = uri2.toString(); } return part1 + "+" + part2; } /** * Called from the unexportVolume call. and others. This method builds the NetworkFabricInfo to be passed to the * NetworkDeviceController for automatic unzoning. * * @param volUris Collection of URIs for volumes whose references are to be deleted * @param exportGroupUris List of URIs of all the export groups being processed that might contain the volumes. * @param storagePortUri the URI of the StoragePort * @param initiatorPort String WWPN with colons * @param hasExistingVolumes If true, will not mark a zone as last reference, keeping them from being deleted * @return List<NetworkFCZoneInfo> detailing zones to be removed or at least unreferenced * @throws IOException */ public List<NetworkFCZoneInfo> unexportVolumes(URI varrayURI, Collection<URI> volUris, List<URI> exportGroupUris, URI storagePortUri, String initiatorPort, boolean hasExistingVolumes) { List<NetworkFCZoneInfo> ourReferences = new ArrayList<NetworkFCZoneInfo>(); VirtualArray virtualArray = _dbClient.queryObject(VirtualArray.class, varrayURI); if (virtualArray != null && virtualArray.getAutoSanZoning() == false) { _log.info("Automatic SAN zoning is disabled in virtual array: " + virtualArray.getLabel()); return null; } initiatorPort = formatWWN(initiatorPort); // Get the StoragePort StoragePort port = null; try { port = _dbClient.queryObject(StoragePort.class, storagePortUri); if (port == null) { return null; } } catch (DatabaseException ex) { return null; } // See if we can find our zone references List<String> endPoints = new ArrayList<String>(); endPoints.add(initiatorPort); endPoints.add(formatWWN(port.getPortNetworkId())); // Make the key for our endPoints String key = null; { NetworkFCZoneInfo fabricInfo = new NetworkFCZoneInfo(); fabricInfo.setEndPoints(endPoints); key = fabricInfo.makeEndpointsKey(); } // Create a map of the references keyed by volUri concatenated with export group URI. // This allows for multiple export groups to export the same volume, and the zone will not // be deleted until the volume's references are removed from all export groups. // Then we can tell if other volumes are using this. Map<String, FCZoneReference> volRefMap = makeExportToReferenceMap(key); // If there were no references at all, we don't do anything. if (volRefMap.isEmpty()) { return null; } else { // Loop through all the volumes we're removing, removing them from the set. // Do this for each of the Export Groups being processed. for (URI volUri : volUris) { for (URI exportGroupUri : exportGroupUris) { FCZoneReference ourReference = volRefMap.get(make2UriKey(volUri, exportGroupUri)); if (ourReference == null) { continue; } // We need a fabricInfo for each, // so as to remove the FCZoneReference that is keyed on volume/exportGroup. NetworkFCZoneInfo fabricInfo = createZoneInfoForRef( ourReference, volUri, initiatorPort, port.getPortNetworkId(), null, exportGroupUri); ourReferences.add(fabricInfo); volRefMap.remove(make2UriKey(volUri, exportGroupUri)); } } // See if all the remaining entries have been marked for deletion. boolean live = false; for (FCZoneReference ref : volRefMap.values()) { if (ref.getInactive() == false) { // Here is an apparent live reference; look up the volume and make // sure it's still active too. BlockObject vol = BlockObject.fetch(_dbClient, ref.getVolumeUri()); ExportGroup group = _dbClient.queryObject(ExportGroup.class, ref.getGroupUri()); if (vol != null && vol.getInactive() == false && group != null && group.getInactive() == false) { live = true; } else { // mark the errant reference inactive _dbClient.markForDeletion(ref); } } } // If there are still live references, can't delete the zone. (lstReference = false) // Cannot delete the zones if the ExportMask has existing volumes either, this // sets existingZone which will prohibit deletion. for (NetworkFCZoneInfo fabricInfo : ourReferences) { fabricInfo.setLastReference(!live); if (hasExistingVolumes) { fabricInfo.setExistingZone(true); } // Pick an alternate device, just in case NetworkLite portNet = getStoragePortNetwork(port); NetworkLite iniNet = BlockStorageScheduler.lookupNetworkLite(_dbClient, StorageProtocol.block2Transport("FC"), initiatorPort); List<NetworkSystem> networkSystems = getZoningNetworkSystems(iniNet, portNet); for (NetworkSystem ns : networkSystems) { if (!ns.getId().equals(fabricInfo.getNetworkDeviceId())) { fabricInfo.setAltNetworkDeviceId(ns.getId()); break; } } } return ourReferences; } } /** * Makes a map from a volume/export group key to the FCZoneReference. * * @param key - Endpoint key consisting of concatenated WWNs * @return Map of volume/export group key to FCZoneReference */ public Map<String, FCZoneReference> makeExportToReferenceMap(String key) { Map<String, FCZoneReference> volRefMap = new HashMap<String, FCZoneReference>(); List<FCZoneReference> refs = getFCZoneReferencesForKey(key); for (FCZoneReference ref : refs) { String uri2key = make2UriKey(ref.getVolumeUri(), ref.getGroupUri()); volRefMap.put(uri2key, ref); } return volRefMap; } /** * Normalize WWN. Inserts colons if needed an converts to upper case. * * @param wwn * @return */ protected String formatWWN(String wwn) { if (wwn.contains(":")) { return wwn.toUpperCase(); } char[] chars = wwn.toUpperCase().toCharArray(); StringBuilder buf = new StringBuilder(); for (int i = 0; i < chars.length;) { buf.append(chars[i++]); if (i < chars.length && (i % 2) == 0) { buf.append(":"); } } return buf.toString().toUpperCase(); } /** * Normalize endpoint collection. * * @param endpoints * @return Collection<String> */ protected Collection<String> formatEndpoints(Collection<String> endpoints) { List<String> eps = new ArrayList<String>(); for (String endpoint : endpoints) { String ep = formatWWN(endpoint); eps.add(ep); } return eps; } /** * Get varray URI for a volume, be it a real volume, or a block snapshot * * @param uri - URI of Volume or BlockSnapshot * @return */ private URI getNeighborhoodURIForVolume(URI uri) { URI nhURI = null; // Volume is the normal case Volume volume = _dbClient.queryObject(Volume.class, uri); if (volume != null) { nhURI = volume.getVirtualArray(); } // Check if snapshot else { BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, uri); if (snap != null) { volume = _dbClient.queryObject(Volume.class, snap.getParent().getURI()); if (volume != null) { nhURI = volume.getVirtualArray(); } } } return nhURI; } /** * Generates a zoning map that maps all initiators to all ports on the same Network. * * @param varrayURI - the ExportGroup varray * @param mask - the Export Mask * @param initiators - List of Initiators to be zoned */ public static void generateFullZoningMap(DbClient dbClient, URI varrayURI, ExportMask mask, Collection<Initiator> initiators) { boolean changed = false; Set<StoragePort> storagePorts = ExportMaskUtils.getPortsForExportMask( dbClient, mask, Transport.FC); for (Initiator initiator : initiators) { if (mask.getZoningMap() == null || mask.getZoningMap().get(initiator.getId().toString()) == null) { _log.info(String.format("No zoning map entry for initiator %s (%s), will zone to all ports", initiator.getInitiatorPort(), initiator.getId())); List<URI> targetPorts = findInitiatorTargetsInVarray(dbClient, varrayURI, initiator, storagePorts); if (!targetPorts.isEmpty()) { changed = true; mask.addZoningMapEntry(initiator.getId().toString(), StringSetUtil.uriListToStringSet(targetPorts)); } } } if (changed) { // Update the mask to save the zoningMap entries. dbClient.updateObject(mask); } } /** * Creates a NetworkFCZoneInfo from a {@link FCZoneReference} * * @param ourReference * @param volUri * @param initiator * @param port * @param network * @param exportGroup * @return */ private NetworkFCZoneInfo createZoneInfoForRef(FCZoneReference ourReference, URI volUri, String initiator, String port, NetworkLite network, URI exportGroup) { if (ourReference == null) { return null; } // We need a fabricInfo for each, // so as to remove the FCZoneReference that is keyed on volume/exportGroup. NetworkFCZoneInfo fabricInfo = new NetworkFCZoneInfo(); fabricInfo.setEndPoints(Arrays.asList(new String[] { initiator, port })); fabricInfo.setFcZoneReferenceId(ourReference.getId()); fabricInfo.setNetworkDeviceId(ourReference.getNetworkSystemUri()); fabricInfo.setFabricId(ourReference.getFabricId()); fabricInfo.setZoneName(ourReference.getZoneName()); fabricInfo.setVolumeId(volUri); fabricInfo.setExportGroup(exportGroup); fabricInfo.setExistingZone(ourReference.getExistingZone()); if (network != null) { fabricInfo.setFabricWwn(NetworkUtil.getNetworkWwn(network)); } return fabricInfo; } /** * Returns the flag settable by the user in the custom config that indicates if port allocation should * consider existing zones in port allocation logic or if it should proceed with allocations using * port metrics and hard redundancy only as criteria. * * @param storageSystemType the type storage system of the ports * @param backend a flag to indicate if this is a host or backend export * * @return true/false */ public boolean portAllocationUseExistingZones(String storageSystemType, boolean backend) { if (backend) { return customConfigHandler.getComputedCustomConfigBooleanValue( CustomConfigConstants.PORT_ALLOCATION_USE_PREZONED_PORT_BACKEND, storageSystemType, null); } else { return customConfigHandler.getComputedCustomConfigBooleanValue( CustomConfigConstants.PORT_ALLOCATION_USE_PREZONED_PORT_FRONTEND, CustomConfigConstants.DEFAULT_KEY, null); } } /** * Returns a list of zoning targets for adding paths to the export mask. * * @param exportGroup ExportGroup * @param exportMaskURIs export mask URIs * @param path Map of initiator URI to List of storage port URIs * @param zonesMap a list of existing zones mapped by the initiator port WWN * @param dbClient * @return List of Zones (NetworkFCZoneInfo) * @throws DeviceControllerException */ public List<NetworkFCZoneInfo> getZoningTargetsForPaths( URI storageSystemURI, ExportGroup exportGroup, Collection<URI> exportMaskURIs, Map<URI, List<URI>> paths, Map<String, List<Zone>> zonesMap, DbClient dbClient) throws DeviceControllerException { List<NetworkFCZoneInfo> zones = new ArrayList<NetworkFCZoneInfo>(); if (!isZoningRequired(dbClient, exportGroup.getVirtualArray())) { _log.info("Zoning not required, returning empty zones list"); return zones; } for (URI maskURI : exportMaskURIs) { ExportMask mask = _dbClient.queryObject(ExportMask.class, maskURI); StringMap volumeMap = mask.getVolumes(); if (volumeMap == null || volumeMap.isEmpty()) { _log.info(String.format("There are no volumes in the export mask %s, skipping", mask.getMaskName())); continue; } List<URI> maskVolumes = StringSetUtil.stringSetToUriList(volumeMap.keySet()); List<ExportGroup> maskExportGroups = ExportMaskUtils.getExportGroups(dbClient, mask); // Filter the paths based on the initiators in zoningMap of this ExportMask Map<URI, List<URI>> pathsForMask = new HashMap<URI, List<URI>>(); for (String initiatorString : mask.getInitiators()) { URI initiatorKey = URI.create(initiatorString); if (paths.containsKey(initiatorKey)) { pathsForMask.put(initiatorKey, paths.get(initiatorKey)); } } // Process the volumes in each Export Group that contains the mask separately. // Use the intersection of the volumes in the ExportMask and ExportGroup. for (ExportGroup group : maskExportGroups) { if (group.getVolumes() != null) { List<URI> volumes = new ArrayList<URI>(maskVolumes); volumes.retainAll(StringSetUtil.stringSetToUriList(group.getVolumes().keySet())); zones.addAll(getZoningTargetsForPaths(group, volumes, exportGroup.getVirtualArray(), pathsForMask, zonesMap)); } } // Check for zones needed in alternate varray for VPLEX cross-connected if (exportGroup.hasAltVirtualArray(storageSystemURI.toString())) { URI altVirtualArray = URI.create(exportGroup.getAltVirtualArrays().get(storageSystemURI.toString())); if (isZoningRequired(dbClient, altVirtualArray)) { for (ExportGroup group : maskExportGroups) { if (group.getVolumes() != null) { List<URI> volumes = new ArrayList<URI>(maskVolumes); volumes.retainAll(StringSetUtil.stringSetToUriList(group.getVolumes().keySet())); zones.addAll(getZoningTargetsForPaths(group, volumes, altVirtualArray, pathsForMask, zonesMap)); } } } } } return zones; } /** * This is called when ExportGroupService adds paths. * Creates list of NetworkFabricInfo structures for zoning each volume * to the newly added paths * * @param exportGroup - The ExportGroup structure. * @param varrayUri - The URI of the virtual array, this can be the export group's * virtual array or its alternate virtual array * @param exportMask - The ExportMask structure. * @param initiators - Contains the initiators * @param zonesMap a list of existing zones mapped by the initiator port WWN * @return List<NetworkFCZoneInfo> indicating zones and zone references to be created. * @throws IOException * @throws DeviceControllerException */ private List<NetworkFCZoneInfo> getZoningTargetsForPaths( ExportGroup exportGroup, List<URI> volumes, URI varrayUri, Map<URI, List<URI>> paths, Map<String, List<Zone>> zonesMap) throws DeviceControllerException { List<NetworkFCZoneInfo> fabricInfos = new ArrayList<NetworkFCZoneInfo>(); for (Map.Entry<URI, List<URI>> entry : paths.entrySet()) { URI initiatorURI = entry.getKey(); List<URI> ports = entry.getValue(); Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI); if (StorageProtocol.block2Transport(initiator.getProtocol()) != Transport.FC) { continue; } for (URI storagePort : ports) { StoragePort sp = _dbClient.queryObject(StoragePort.class, storagePort); if (sp == null || sp.getTaggedVirtualArrays() == null || !sp.getTaggedVirtualArrays().contains(varrayUri.toString())) { _log.warn(String.format("The storage port %s is not in the virtual array %s", sp.getNativeGuid(), varrayUri.toString())); continue; } try { NetworkFCZoneInfo fabricInfo = placeZones( exportGroup.getId(), varrayUri, initiator.getProtocol(), formatWWN(initiator.getInitiatorPort()), sp, initiator.getHostName(), zonesMap.get(initiator.getInitiatorPort()), true); if (fabricInfo != null) { for (URI volId : volumes) { NetworkFCZoneInfo volFabricInfo = fabricInfo.clone(); volFabricInfo.setVolumeId(volId); fabricInfos.add(volFabricInfo); } } } catch (DeviceControllerException ex) { _log.error(String.format( "Initiator %s could not be paired with port %s", initiator.getInitiatorPort(), sp.getPortNetworkId())); } } } return fabricInfos; } }