/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.utils; import static com.google.common.collect.Sets.newHashSet; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationContext; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.customconfigcontroller.DataSource; import com.emc.storageos.customconfigcontroller.DataSourceFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.DbModelClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.AlternateIdConstraint; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.BlockMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.DataObject.Flag; 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.FCZoneReference; import com.emc.storageos.db.client.model.Host; import com.emc.storageos.db.client.model.Initiator; 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.Volume; import com.emc.storageos.db.client.model.ZoneInfo; import com.emc.storageos.db.client.model.ZoneInfoMap; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedExportMask; import com.emc.storageos.db.client.util.CommonTransformerFunctions; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.db.client.util.DataObjectUtils; import com.emc.storageos.db.client.util.ExportMaskNameGenerator; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.util.StringMapUtil; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.db.joiner.Joiner; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.networkcontroller.impl.NetworkScheduler; import com.emc.storageos.util.ExportUtils; import com.emc.storageos.volumecontroller.impl.ControllerServiceImpl; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.block.ExportMaskPolicy; import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.Sets; import sun.net.util.IPAddressUtil; public class ExportMaskUtils { private static final Logger _log = LoggerFactory.getLogger(ExportMaskUtils.class); private static final ExportMaskNameGenerator nameGenerator = new ExportMaskNameGenerator(); private static NetworkScheduler networkScheduler = null; private static CoordinatorClient coordinator = null; private static final String INGEST_ZONES_WITH_NON_VIPR_NAME = "controller_ingest_zones_with_non_vipr_name"; /** * Look up export mask for the storage array in the exports. * This method is not suitable for VNX/VMAX because an ExportGroup can now have multiple * ExportMasks for the same Storage System (one for each different host). * It is still used for VPlex. * * @param exportGroup export mask belongs to this export group * @param sdUri export mask is on this storage system * @return * @throws DatabaseException * */ public static ExportMask getExportMask(DbClient dbClient, ExportGroup exportGroup, URI sdUri) throws DatabaseException { if (ExportMaskUtils.getExportMasks(dbClient, exportGroup).isEmpty()) { return null; } // At this point, exportGroup.exportMasks should be non-null and non-empty, // so try to find the ExportMask that has the same StorageSystem URI ExportMask foundExportMask = null; for (ExportMask exportMask : ExportMaskUtils.getExportMasks(dbClient, exportGroup)) { if (exportMask != null && exportMask.getStorageDevice().equals(sdUri)) { foundExportMask = exportMask; break; } } return foundExportMask; } /** * Returns a list of ExportMasks from an ExportGroup that are for a specified storage-system. * * @param dbClient - database client. * @param exportGroup - the ExportGroup to be examined * @param ssysURI - the StorageSystem URI; if NULL returns ALL ExportMasks * @return List<ExportMask> -- an empty list is returned if there are no matches. */ public static List<ExportMask> getExportMasks(DbClient dbClient, ExportGroup exportGroup, URI ssysURI) { List<ExportMask> returnMasks = new ArrayList<ExportMask>(); if (exportGroup == null || exportGroup.getExportMasks() == null) { return returnMasks; } for (String maskUriStr : exportGroup.getExportMasks()) { URI maskUri = URI.create(maskUriStr); ExportMask exportMask = dbClient.queryObject(ExportMask.class, maskUri); if (exportMask == null || exportMask.getInactive()) { continue; } if (ssysURI == null || exportMask.getStorageDevice().equals(ssysURI)) { returnMasks.add(exportMask); } } return returnMasks; } /** * Returns all ExportMasks in an ExportGroup. * * @param dbClient * @param exportGroup * @return */ public static List<ExportMask> getExportMasks(DbClient dbClient, ExportGroup exportGroup) { return getExportMasks(dbClient, exportGroup, null); } /** * Returns a list of ExportMasks that are for a specified Storage System. * * @param dbClient - reference to the database client * @param ssysURI - the StorageSystem URI * @return List<ExportMask> -- an empty list is returned if there are no matches. */ public static List<ExportMask> getExportMasksForStorageSystem(DbClient dbClient, URI ssysURI) { List<ExportMask> returnMasks = new ArrayList<ExportMask>(); if (!URIUtil.isValid(ssysURI)) { _log.warn("invalid storage system URI: {}", ssysURI); return returnMasks; } URIQueryResultList exportMaskUris = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getStorageDeviceExportMaskConstraint(ssysURI), exportMaskUris); Iterator<ExportMask> exportMaskIterator = dbClient.queryIterativeObjects(ExportMask.class, exportMaskUris, true); while (exportMaskIterator.hasNext()) { ExportMask exportMask = exportMaskIterator.next(); if (null != exportMask) { returnMasks.add(exportMask); } } _log.info("found {} export masks for storage system {}", returnMasks.size(), ssysURI); return returnMasks; } /** * Find all export groups that are referencing the export mask * * @param dbClient db client * @param exportMask export mask * @return list of export groups referring to the export mask */ public static List<ExportGroup> getExportGroups(DbClient dbClient, ExportMask exportMask) { URIQueryResultList exportGroupURIs = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getExportMaskExportGroupConstraint(exportMask.getId()), exportGroupURIs); List<ExportGroup> exportGroups = new ArrayList<ExportGroup>(); for (URI egURI : exportGroupURIs) { ExportGroup exportGroup = dbClient.queryObject(ExportGroup.class, egURI); if (exportGroup == null || exportGroup.getInactive() == true) { continue; } exportGroups.add(exportGroup); } return exportGroups; } public static String getExportType(DbClient dbClient, ExportMask exportMask) { String maskResource = exportMask.getResource(); if (!NullColumnValueGetter.isNullValue(maskResource)) { if (maskResource.contains("urn:storageos:Host")) { return ExportGroupType.Host.name(); } else { return ExportGroupType.Cluster.name(); } } else { // handle the case of brownfield scenario where the resource will not be set in the mask List<ExportGroup> groups = getExportGroups(dbClient, exportMask); // Applies only for VMAX where even if the export mask may belong to more than one export group, // they all should be of the same type. if (!groups.isEmpty()) { return groups.get(0).getType(); } } return null; } /** * Return all initiators, existing and user added, associated with this mask. * * @param dbClient db client * @param mask export mask * @return set of initiator URIs */ public static Set<URI> getAllInitiatorsForExportMask(DbClient dbClient, ExportMask mask) { Set<URI> initiators = new HashSet<>(); for (Initiator existingInitiator : getExistingInitiatorsForExportMask(dbClient, mask, null)) { initiators.add(existingInitiator.getId()); } if (mask.getInitiators() != null) { for (String initiatorId : mask.getInitiators()) { initiators.add(URI.create(initiatorId)); } } if (mask.getUserAddedInitiators() != null) { for (String initiatorId : mask.getUserAddedInitiators().values()) { initiators.add(URI.create(initiatorId)); } } return initiators; } /** * Returns all Initiators of the specified Transport type in the ExportMask. * * @param dbClient * @param exportMask -- ExportMask to be examined * @param transportType Transport enum typically Transport.FC * @return Set<Initiator> */ public static Set<Initiator> getInitiatorsForExportMask(DbClient dbClient, ExportMask exportMask, Transport transportType) { Set<Initiator> initiators = new HashSet<Initiator>(); if (exportMask.getInitiators() != null) { for (String initiatorId : exportMask.getInitiators()) { Initiator initiator = dbClient.queryObject( Initiator.class, URI.create(initiatorId)); if (initiator == null) { continue; } if (transportType == null || transportType == StorageProtocol.block2Transport(initiator.getProtocol())) { initiators.add(initiator); } } } return initiators; } /** * Get the Initiators that correspond to the addresses in an ExportMask existingInitiators * field. (These will have no entries in ExportMask.initiators.) * * @param dbClient -- Database client. * @param exportMask -- An existing ExportMask * @param transportType -- Transport type, e.g. FC * @return Set<Inititator> of initiators having same address as those in ExportMask.existingInitiators */ public static Set<Initiator> getExistingInitiatorsForExportMask(DbClient dbClient, ExportMask exportMask, Transport transportType) { Set<Initiator> initiators = new HashSet<Initiator>(); if (exportMask.getExistingInitiators() != null) { for (String address : exportMask.getExistingInitiators()) { String normalizedAddress = Initiator.toPortNetworkId(address); URIQueryResultList result = new URIQueryResultList(); dbClient.queryByConstraint( AlternateIdConstraint.Factory.getInitiatorPortInitiatorConstraint(normalizedAddress), result); for (URI uri : result) { Initiator initiator = dbClient.queryObject(Initiator.class, uri); if (initiator != null && !initiator.getInactive()) { if (transportType == null || transportType == StorageProtocol.block2Transport(initiator.getProtocol())) { initiators.add(initiator); } } } } } return initiators; } /** * Checks if the given initiators belong to vBlock host. * * @param initiatorURIs the initiator uris * @param dbClient the db client * @return true, if the given initiators belong to vBlock host */ public static boolean isVblockHost(List<URI> initiatorURIs, DbClient dbClient) { Iterator<Initiator> initiators = dbClient.queryIterativeObjects(Initiator.class, initiatorURIs); while (initiators.hasNext()) { Initiator initiator = initiators.next(); URI hostURI = initiator.getHost(); if (hostURI != null) { Host host = dbClient.queryObject(Host.class, hostURI); if (host != null && !NullColumnValueGetter.isNullURI(host.getComputeElement())) { return true; } } } return false; } /** * Return all the StoragePorts in an ExportMask of a specificed Transport type. * * @param dbClient * @param exportMask * @param transportType * @return */ public static Set<StoragePort> getPortsForExportMask(DbClient dbClient, ExportMask exportMask, Transport transportType) { Set<StoragePort> ports = new HashSet<StoragePort>(); if (exportMask.getStoragePorts() == null) { return ports; } for (String portId : exportMask.getStoragePorts()) { StoragePort port = dbClient.queryObject(StoragePort.class, URI.create(portId)); if (port == null) { continue; } if (transportType == Transport.valueOf(port.getTransportType())) { ports.add(port); } } return ports; } /** * Return the StoragePort ids for an Export Mask of a specificed Transport type. * * @param dbClient * @param exportMask * @param transportType * @return */ public static Set<String> getPortIdsForExportMask(DbClient dbClient, ExportMask exportMask, Transport transportType) { Set<String> portIds = new HashSet<String>(); Set<StoragePort> ports = getPortsForExportMask(dbClient, exportMask, transportType); for (StoragePort port : ports) { portIds.add(port.getId().toString()); } return portIds; } /** * Create an export mask object. The actual mask is created at the array by the controller service. * * @param dbClient db client * @param exportGroup export group * @param sdUri storage device ID * @param maskName name to give the mask. * @return Export Mask object */ public static ExportMask createExportMask(DbClient dbClient, ExportGroup exportGroup, URI sdUri, String maskName) throws DatabaseException { ExportMask exportMask = new ExportMask(); exportMask.setId(URIUtil.createId(ExportMask.class)); exportMask.setMaskName(maskName); exportMask.setStorageDevice(sdUri); dbClient.createObject(exportMask); exportGroup.addExportMask(exportMask.getId()); dbClient.updateObject(exportGroup); return exportMask; } public static boolean hasExportMaskForStorage(DbClient dbClient, ExportGroup exportGroup, URI storageURI) { List<ExportMask> masks = ExportMaskUtils.getExportMasks(dbClient, exportGroup); if (!masks.isEmpty()) { for (ExportMask mask : masks) { URI maskStorageURI = mask.getStorageDevice(); if (maskStorageURI.equals(storageURI)) { return true; } } } return false; } /** * For a given export group and storage system, this will check wheather there are any export mask exists * in storage system which matches export group and storage ports in VArray * * @param dbClient * @param exportGroup * @param storageURI * @return */ public static boolean hasExportMaskForStorageAndVArray(DbClient dbClient, ExportGroup exportGroup, URI storageURI) { Set<String> storagePortURIsAssociatedWithVArrayAndStorageArray = ExportMaskUtils.getStoragePortUrisAssociatedWithVarrayAndStorageArray( storageURI, exportGroup.getVirtualArray(), dbClient); StringSet maskUriSet = exportGroup.getExportMasks(); if (maskUriSet != null) { for (String maskUriString : maskUriSet) { ExportMask mask = dbClient.queryObject(ExportMask.class, URI.create(maskUriString)); URI maskStorageURI = mask.getStorageDevice(); if (maskStorageURI.equals(storageURI)) { for (String storagePort : mask.getStoragePorts()) { if(storagePortURIsAssociatedWithVArrayAndStorageArray.contains(storagePort)) return true; } } } } return false; } /** * For a given storage system and varray this will fetch the set of storage ports which are part of both * the storage system and varray. * @param storageURI * @param varray * @param dbClient * @return */ public static Set<String> getStoragePortUrisAssociatedWithVarrayAndStorageArray(URI storageURI, URI varray, DbClient dbClient) { URIQueryResultList storagePortsAssociatedWithVarray = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory.getVirtualArrayStoragePortsConstraint(varray.toString()), storagePortsAssociatedWithVarray); //Get all the storage ports that are in the varray belonging to the storage array Set<URI> storagePortsSetAssociatedWithVarray = new HashSet<>(); storagePortsSetAssociatedWithVarray.addAll(storagePortsAssociatedWithVarray); URIQueryResultList storagePortsAssociatedWithStorageSystem = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory.getStoragePortsForStorageSystemConstraint(storageURI.toString()), storagePortsAssociatedWithStorageSystem); Set<URI> storagePortsSetAssociatedWithStorageSystem = new HashSet<>(); storagePortsSetAssociatedWithStorageSystem.addAll(storagePortsAssociatedWithStorageSystem); return Sets.intersection(storagePortsSetAssociatedWithVarray, storagePortsSetAssociatedWithStorageSystem) .stream().map(storagePortUri -> storagePortUri.toString()).collect(Collectors.toSet()); } /** * Generate a name for the export mask based on the initiators sent in. * If there are no initiators, just use the generated export group name. * * It needs to be guaranteed that initiators in the list all belong to * one host. * * * @param dbClient database handle * @param initiators initiators that are going to be used * @param exportGroup export group to get generated name from * @param storageSystem [in] - StorageSystem object * @return a string to use for the name */ public static String getMaskName(DbClient dbClient, List<Initiator> initiators, ExportGroup exportGroup, StorageSystem storageSystem) { // Critical assertion if (exportGroup == null) { return null; // You've got bigger problems than the name. ;) } // Important assertions if (initiators == null || dbClient == null || initiators.isEmpty()) { return exportGroup.getGeneratedName(); } // Create a set of unique host and cluster names Set<String> hosts = new HashSet<String>(); Set<String> clusters = new HashSet<String>(); for (Initiator initiator : initiators) { String host = initiator.getHostName(); if (host != null) { String fixedHost = host; // In case this is a hostname, extract the first part, // which generally is the unique part of the hostname if (!IPAddressUtil.isIPv4LiteralAddress(host) && !IPAddressUtil.isIPv6LiteralAddress(host)) { int firstDot = host.indexOf('.'); if (firstDot != -1) { fixedHost = host.substring(0, firstDot); } } hosts.add(fixedHost); } String cluster = initiator.getClusterName(); if (cluster != null) { clusters.add(cluster); } } // Attempt to generate a unique name based on the sets that were generated. // First start with the ExportGroup.label String cluster = null; // If there is unique cluster name, append to the name if (clusters.size() == 1) { cluster = clusters.iterator().next(); } String host = null; // If there is a unique host name, append to the name if (hosts.size() == 1) { host = hosts.iterator().next(); } // Get the last 3 digits of the array serial number and append it // to the name. Where we append the name depends on whether the // cluster and/or host Strings are filled in String serialNo = storageSystem.getSerialNumber(); String lastDigitsOfSerialNo = serialNo.substring(serialNo.length() - 3); // Append SerialNo to host if cluster is empty and host is non-empty OR // if cluster and host are both non-empty if ((Strings.isNullOrEmpty(cluster) && !Strings.isNullOrEmpty(host)) || (!Strings.isNullOrEmpty(cluster) && !Strings.isNullOrEmpty(host))) { host = String.format("%s_%s", host, lastDigitsOfSerialNo); } // Append SerialNo to cluster if cluster is non-empty and host is empty if (!Strings.isNullOrEmpty(cluster) && Strings.isNullOrEmpty(host)) { cluster = String.format("%s_%s", cluster, lastDigitsOfSerialNo); } String alternateName = (cluster == null && host == null) ? exportGroup.getLabel() : null; return nameGenerator.generate(cluster, host, alternateName); } /** * Fetches an ExportMask from the database. Returns null if not found or * if inactive. * * @param dbClient * @param exportMaskURI * @return ExportMask object */ static public ExportMask getExportMask(DbClient dbClient, URI exportMaskURI) { ExportMask mask = dbClient.queryObject(ExportMask.class, exportMaskURI); if (mask == null || mask.getInactive() == true) { _log.error(String.format("ExportMask %s was null or inactive", exportMaskURI)); return null; } return mask; } /** * Returns a list of all the Volumes in an ExportMask * * @param exportMask * @return */ static public List<URI> getVolumeURIs(ExportMask exportMask) { List<URI> volumeURIs = new ArrayList<URI>(); if (exportMask.getVolumes() != null) { volumeURIs.addAll(Collections2.transform(exportMask.getVolumes().keySet(), CommonTransformerFunctions.FCTN_STRING_TO_URI)); } return volumeURIs; } /** * Returns a list of all the user added Volumes in an ExportMask * * @param exportMask * @return */ static public List<URI> getUserAddedVolumeURIs(ExportMask exportMask) { List<URI> volumeURIs = new ArrayList<URI>(); if (exportMask.getUserAddedVolumes() != null) { volumeURIs.addAll( Collections2.transform(exportMask.getUserAddedVolumes().values(), CommonTransformerFunctions.FCTN_STRING_TO_URI)); } return volumeURIs; } public static DataSource getExportDatasource(StorageSystem storage, List<Initiator> initiators, DataSourceFactory factory, String configName) { if (configName == null || configName.isEmpty()) { return null; } // Create a set of unique host and cluster names SortedSet<String> hosts = new TreeSet<String>(); Set<String> clusters = new HashSet<String>(); boolean rp = false; for (Initiator initiator : initiators) { String host = initiator.getHostName(); if (initiator.checkInternalFlags(Flag.RECOVERPOINT)) { rp = true; } if (host != null) { hosts.add(host); } String cluster = initiator.getClusterName(); if (cluster != null) { clusters.add(cluster); } } // Attempt to generate a unique name based on the sets that were generated. String cluster = null; // If there is unique cluster name, append to the name if (clusters.size() == 1) { cluster = clusters.iterator().next(); } String host = null; // Get the first host name if (!hosts.isEmpty()) { host = hosts.iterator().next(); } // In the case of RP, we want the naming defaults to use the cluster name as the hostname. // This assumes: // 1. initiator list is all RP initiators // 2. cluster name is filled-in in each initiator // 3. Default custom naming is being used if (rp && clusters.iterator().hasNext()) { host = clusters.iterator().next(); } return factory.createExportMaskDataSource(configName, host, cluster, storage); } /** * Create and initialize the ExportMask. To do this we: * 1. Create and persist the ExportMask. * 2. Save our targets and exportMaskURI in the ExportGroupCreateData. * * * @param storage - Storage System * @param exportGroup - ExportGroup object this ExportMask will apply to * @param initiators - Initiator objects pointing to initiators for this mask * @param volumeMap - Map of Volume URIs to Integer HLUs * @param targets List<URI> of StoragePorts * @param zoneAssignments - Map from InitiatorURI to List of assigned port URIs. * @param maskName the mask name * @param dbClient an instance of DbClient * @return new ExportMask object, persisted in database * @throws Exception */ static public ExportMask initializeExportMask( StorageSystem storage, ExportGroup exportGroup, List<Initiator> initiators, Map<URI, Integer> volumeMap, List<URI> targets, Map<URI, List<URI>> zoneAssignments, String maskName, DbClient dbClient) throws Exception { if (maskName == null) { maskName = ExportMaskUtils.getMaskName(dbClient, initiators, exportGroup, storage); } ExportMask exportMask = ExportMaskUtils.createExportMask(dbClient, exportGroup, storage.getId(), maskName); String resourceRef; if (exportGroup.getType() != null) { if (exportGroup.getType().equals(ExportGroup.ExportGroupType.Cluster.name())) { resourceRef = initiators.get(0).getClusterName(); } else { resourceRef = initiators.get(0).getHost().toString(); } exportMask.setResource(resourceRef); } else { // This resource is used when we add initiators to existing masks on VMAX, which should not be // case with VPLEX and RP, which do not associate their initiators with hosts or clusters. exportMask.setResource(NullColumnValueGetter.getNullURI().toString()); } exportMask.setCreatedBySystem(true); exportMaskUpdate(exportMask, volumeMap, initiators, targets); if (!exportGroup.getZoneAllInitiators() && null != zoneAssignments) { StringSetMap zoneMap = getZoneMapFromAssignments(zoneAssignments); if (!zoneMap.isEmpty()) { exportMask.setZoningMap(zoneMap); } } dbClient.updateObject(exportMask); return exportMask; } // don't want to disturb the existing method, hence overloaded static public <T extends BlockObject> ExportMask initializeExportMaskWithVolumes( URI storage, ExportGroup exportGroup, String maskName, String maskLabel, List<Initiator> initiators, Map<URI, Integer> volumeMap, List<URI> targets, ZoneInfoMap zoneInfoMap, T volume, Set<String> unManagedInitiators, String nativeId, List<Initiator> userAddedInis, DbClient dbClient, Map<String, Integer> wwnToHluMap) throws Exception { ExportMask exportMask = new ExportMask(); exportMask.setId(URIUtil.createId(ExportMask.class)); exportMask.setMaskName(maskName); exportMask.setStorageDevice(storage); String resourceRef; if (exportGroup.getType() != null) { if (exportGroup.getType().equals(ExportGroup.ExportGroupType.Cluster.name())) { resourceRef = initiators.get(0).getClusterName(); } else { resourceRef = initiators.get(0).getHost().toString(); } exportMask.setResource(resourceRef); } else { // This resource is used when we add initiators to existing masks on VMAX, which should not be // case with VPLEX and RP, which do not associate their initiators with hosts or clusters. exportMask.setResource(NullColumnValueGetter.getNullURI().toString()); } exportMask.setLabel(maskLabel); exportMask.setCreatedBySystem(true); exportMaskUpdate(exportMask, null, initiators, targets); StringSetMap zoneMap = getZoneMapFromZoneInfoMap(zoneInfoMap, initiators); if (!zoneMap.isEmpty()) { exportMask.setZoningMap(zoneMap); } exportMask.addToExistingInitiatorsIfAbsent(new ArrayList<String>(unManagedInitiators)); exportMask.addToUserCreatedInitiators(userAddedInis); // if the block object is marked as internal, then add to existing volumes of the mask if (volume.checkInternalFlags(Flag.PARTIALLY_INGESTED)) { _log.info("Block object {} is marked internal. Adding to existing volumes of the mask {}", volume.getNativeGuid(), exportMask.getMaskName()); String hlu = ExportGroup.LUN_UNASSIGNED_STR; if (wwnToHluMap.containsKey(volume.getWWN())) { hlu = String.valueOf(wwnToHluMap.get(volume.getWWN())); } exportMask.addToExistingVolumesIfAbsent(volume, hlu); } else { exportMask.addToUserCreatedVolumes(volume); exportMask.removeFromExistingVolumes(volume); } Integer hlu = wwnToHluMap.get(volume.getWWN()) != null ? wwnToHluMap.get(volume.getWWN()) : ExportGroup.LUN_UNASSIGNED; exportMask.addVolume(volume.getId(), hlu); exportMask.setNativeId(nativeId); // need to sync up all remaining existing volumes exportMask.addToExistingVolumesIfAbsent(wwnToHluMap); // Update the FCZoneReferences if zoning is enables for the varray updateFCZoneReferences(exportGroup, exportMask, volume, zoneInfoMap, initiators, dbClient); return exportMask; } /** * Creates new zone references and updates any as necessary for exported volume ingestion. * @param exportGroup -- an ExportGroup * @param exportMask -- an ExportMask * @param volume -- a BlockObject (determines the return type) * @param zoneInfoMap -- -- a ZoneInfoMap * @param initiators -- a List of Initiators * @param dbClient -- database handle */ public static <T extends BlockObject> void updateFCZoneReferences(ExportGroup exportGroup, ExportMask exportMask, T volume, ZoneInfoMap zoneInfoMap, List<Initiator> initiators, DbClient dbClient) { if (NetworkScheduler.isZoningRequired(dbClient, exportGroup.getVirtualArray())) { List<FCZoneReference> updatedRefs = new ArrayList<FCZoneReference>(); List<FCZoneReference> refs = createFCZoneReferences(volume, exportGroup, exportMask, zoneInfoMap, initiators, updatedRefs, dbClient); dbClient.createObject(refs); if (!updatedRefs.isEmpty()) { dbClient.updateObject(updatedRefs); } } } /** * Given zoneInfoMap stored in an UnManagedExportMask, create a zone map for the * initiators in the list. * * @param zoneInfoMap zoneInfoMap stored from a UnManagedExportMask * @param initiators a list of initiators for which the zone map should created * @return a zone map of initiator-uri-to-list-of-ports-uris */ public static StringSetMap getZoneMapFromZoneInfoMap(ZoneInfoMap zoneInfoMap, List<Initiator> initiators) { StringSetMap zoneMap = new StringSetMap(); if (zoneInfoMap != null && initiators != null) { for (Initiator initiator : initiators) { for (ZoneInfo info : zoneInfoMap.values()) { if (info.getInitiatorId().equals(initiator.getId().toString())) { zoneMap.put(initiator.getId().toString(), info.getPortId()); } } } } _log.info("getZoneMapFromZoneInfoMap created zone map {}", zoneMap); return zoneMap; } /** * Create FCZoneReference objects according to the list of zoneInfoMap. * This is used for ingestion. * * The zones will be ingested as existingZone = false (allowing deletion of the zone when the last reference is removed) * if all the following criteria are met: * 1. The export mask being ingested must have no existing (non-vipr-created) volumes. * 2. If the zone must not have more than two members (smartZone), as Vipr cannot yet manage smart zones. * 3. The zone name that would be generated for the zone must be the same as the ingested zone's name, unless the * controllersvc config variable controller_ingest_zones_with_non_vipr_name is set to true, bypassing the zone name check. * 4. The zone must not be used in any other UnManagedExportMask (export mask which has not yet been ingested). * * @param volume the FCZoneReference volume * @param exportGroup the FCZoneReference export group * @param exportMask the ExportMask being ingested or initialized * @param zoneInfoMap the zone info maps * @param initiators the initiators * @param updatedZoneReferences OUT parameter of zone references to be updated * @param dbClient -- Database handle * @return a list of FCZoneReference */ private static <T extends BlockObject> List<FCZoneReference> createFCZoneReferences( T volume, ExportGroup exportGroup, ExportMask exportMask, ZoneInfoMap zoneInfoMap, List<Initiator> initiators, List<FCZoneReference> updatedZoneReferences, DbClient dbClient) { boolean bypassNameCheck = false; if (coordinator == null) { ApplicationContext context = AttributeMatcherFramework.getApplicationContext(); coordinator = (CoordinatorClient) context.getBean("coordinator"); } bypassNameCheck = Boolean.valueOf(ControllerUtils.getPropertyValueFromCoordinator( coordinator, INGEST_ZONES_WITH_NON_VIPR_NAME)); List<FCZoneReference> createdRefs = new ArrayList<FCZoneReference>(); if (networkScheduler == null) { ApplicationContext context = AttributeMatcherFramework.getApplicationContext(); networkScheduler = (NetworkScheduler) context.getBean("networkScheduler"); } for (Initiator initiator : initiators) { for (ZoneInfo info : zoneInfoMap.values()) { if (info.getInitiatorId().equals(initiator.getId().toString())) { Boolean hasExistingVolumes = exportMask.hasAnyExistingVolumes(); // Determine if the zone to be ingested has a compatible zone name. Boolean lsanZone = networkScheduler.isLSANZone(info.getZoneName()); // Determine if the zone has more than two members. We can't currently manage smart zones fully. Boolean smartZone = (info.getMemberCount() > 2); _log.info(String.format("zone %s existingVolumes %s lsanZone %s smartZone %s", info.getZoneName(), hasExistingVolumes.toString(), lsanZone.toString(), smartZone.toString())); String generatedZoneName = networkScheduler.nameZone(volume.getStorageController(), URI.create(info.getNetworkSystemId()),info.getInitiatorWwn(), info.getPortWwn(), info.getFabricId(), lsanZone); _log.info(String.format("Zone name %s generated zone name %s", info.getZoneName(), generatedZoneName)); boolean usedInUnmanagedMasks = checkZoneUseInUnManagedExportMasks( exportMask.getMaskName(), info, dbClient); boolean externalZone = hasExistingVolumes || ( !bypassNameCheck && !generatedZoneName.equals(info.getZoneName()) ) || usedInUnmanagedMasks || smartZone; if (!externalZone) { // Find any existing zone references so they can be updated to fully managed (i.e. not existingZone) List<FCZoneReference> existingZoneRefs = networkScheduler.getFCZoneReferencesForKey(info.getZoneReferenceKey()); for (FCZoneReference zoneRef : existingZoneRefs) { // If we're not in the process of ingesting this volume (in ingesting it would be in ExportGroup) // and the zoneRef URI is invalid, mark the zone reference for deletion as it is stale, and do not process it. if (!exportGroup.hasBlockObject(zoneRef.getVolumeUri())) { BlockObject blockObject = BlockObject.fetch(dbClient, zoneRef.getVolumeUri()); if (blockObject == null) { _log.info(String.format("Deleting FCZoneReference %s which has invalid volume URI %s", zoneRef.getZoneName(), zoneRef.getVolumeUri())); dbClient.markForDeletion(zoneRef); continue; } } // Update this reference to fully managed (existingZone = false) zoneRef.setExistingZone(false); updatedZoneReferences.add(zoneRef); } } createdRefs.add(createFCZoneReference(info, initiator, volume, exportGroup, externalZone)); } } } return createdRefs; } /** * Check if a particular zone (as represented by a ZoneInfo) is in use by an UnManagedExportMask * (excluding the UnManagedExportMask corresponding to the named exportMask. * @param exportMaskName -- ExportMask name filed * @param info -- ZoneInfo representing the Zone * @param dbClient == databasehandle * @return -- true IFF the same zone was found in other UnManagedExportMasks */ static private boolean checkZoneUseInUnManagedExportMasks(String exportMaskName, ZoneInfo info, DbClient dbClient) { String initiatorWwn = info.getInitiatorWwn(); boolean inUse = false; List<UnManagedExportMask> unmanagedMasks = CustomQueryUtility.queryActiveResourcesByAltId(dbClient, UnManagedExportMask.class, "knownInitiatorNetworkIds", initiatorWwn); for (UnManagedExportMask umask : unmanagedMasks) { if (umask.getKnownInitiatorNetworkIds().contains(initiatorWwn) && !umask.getUnmanagedVolumeUris().isEmpty()) { for (ZoneInfo umZoneInfo : umask.getZoningMap().values()) { if (exportMaskName.equals(umask.getMaskName())) { continue; } if (info.getInitiatorWwn().equals(umZoneInfo.getInitiatorWwn()) && info.getPortWwn().equals(umZoneInfo.getPortWwn())) { _log.info(String.format("UnManagedExportMask %s zone %s is using the same initiator %s and port %s", umask.getNativeId(), umZoneInfo.getZoneName(), umZoneInfo.getInitiatorWwn(), umZoneInfo.getPortWwn())); inUse = true; break; } } } } return inUse; } /** * Creates an instance of FCZoneReference * * @param info the zone info containing the zone, its network, * its network system, ... * @param initiator the zone initiator * @param volume volume the FCZoneReference volume * @param exportGroup the FCZoneReference export group * @param externalZone - should be true for externally managed zone * @return an instance of FCZoneReference */ private static <T extends BlockObject> FCZoneReference createFCZoneReference(ZoneInfo info, Initiator initiator, T volume, ExportGroup exportGroup, boolean externalZone) { FCZoneReference ref = new FCZoneReference(); ref.setPwwnKey(info.getZoneReferenceKey()); ref.setFabricId(info.getFabricId()); ref.setNetworkSystemUri(URI.create(info.getNetworkSystemId())); ref.setVolumeUri(volume.getId()); ref.setGroupUri(exportGroup.getId()); ref.setZoneName(info.getZoneName()); ref.setId(URIUtil.createId(FCZoneReference.class)); ref.setLabel(FCZoneReference.makeLabel(ref.getPwwnKey(), volume.getId().toString())); ref.setExistingZone(externalZone); return ref; } /** * Adds the volumes, initiators, and targets to an ExportMask. * * @param exportMask * @param volumeMap * @param initiators * @param targets */ static void exportMaskUpdate(ExportMask exportMask, Map<URI, Integer> volumeMap, List<Initiator> initiators, List<URI> targets) { if (volumeMap != null) { for (URI volume : volumeMap.keySet()) { exportMask.addVolume(volume, volumeMap.get(volume)); } } if (initiators != null) { for (Initiator initiator : initiators) { exportMask.addInitiator(initiator); } } if (targets != null) { for (URI target : targets) { exportMask.addTarget(target); } } } /** * Returns a StringSetMap containing the Initiator to StoragePort URIs from zoning assignments. * * @param assignments Map<URI, List<URI>> of zoning assignments. * @return StringSetMap with same information encoded as */ static public 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; } static public boolean isUsable(ExportMask mask) { return (mask != null && !mask.getInactive()); } static public ExportMask asExportMask(DbClient dbClient, String maskUriStr) { URI maskURI = URI.create(maskUriStr); return dbClient.queryObject(ExportMask.class, maskURI); } public static String[] getBlockObjectAlternateNames(Collection<URI> uris, DbClient dbClient) throws Exception { String[] results = {}; Set<String> names = new HashSet<String>(); for (URI uri : uris) { names.add(getBlockObjectAlternateName(uri, dbClient)); } return names.toArray(results); } /** * This method will take a URI and return alternateName for the BlockObject object to which the * URI applies. * * @param uri * - URI * @return Returns a nativeId String value * @throws DeviceControllerException.exceptions.notAVolumeOrBlocksnapshotUri * if URI is not a Volume/BlockSnapshot URI */ public static String getBlockObjectAlternateName(URI uri, DbClient dbClient) throws Exception { String nativeId; if (URIUtil.isType(uri, Volume.class)) { Volume volume = dbClient.queryObject(Volume.class, uri); nativeId = volume.getAlternateName(); } else if (URIUtil.isType(uri, BlockSnapshot.class)) { BlockSnapshot blockSnapshot = dbClient.queryObject(BlockSnapshot.class, uri); nativeId = blockSnapshot.getAlternateName(); } else if (URIUtil.isType(uri, BlockMirror.class)) { BlockMirror blockMirror = dbClient.queryObject(BlockMirror.class, uri); nativeId = blockMirror.getAlternateName(); } else { throw DeviceControllerException.exceptions.notAVolumeOrBlocksnapshotUri(uri); } return nativeId; } static public Map<String, Set<URI>> mapComputeResourceToExportMask(DbClient dbClient, ExportGroup exportGroup, URI storage) { Map<String, Set<URI>> computeResourceToExportMaskURIs = new HashMap<String, Set<URI>>(); if (exportGroup != null && exportGroup.getExportMasks() != null) { for (String exportMaskURIStr : exportGroup.getExportMasks()) { ExportMask mask = asExportMask(dbClient, exportMaskURIStr); if (mask == null || (mask.getStorageDevice() != null && !mask.getStorageDevice().equals(storage))) { continue; } Set<Initiator> initiators = getInitiatorsForExportMask(dbClient, mask, null); for (Initiator initiator : initiators) { String key = NullColumnValueGetter.getNullURI().toString(); if (exportGroup.forCluster() && initiator.getClusterName() != null) { key = initiator.getClusterName(); } else if (initiator.getHost() != null) { key = initiator.getHost().toString(); } if (computeResourceToExportMaskURIs.get(key) == null) { computeResourceToExportMaskURIs.put(key, new HashSet<URI>()); } computeResourceToExportMaskURIs.get(key).add(mask.getId()); } } } return computeResourceToExportMaskURIs; } static public Map<String, URI> mapHostToExportMask(DbClient dbClient, ExportGroup exportGroup, URI storage) { Map<String, URI> hostToExportMaskURI = new HashMap<String, URI>(); for (ExportMask exportMask : ExportMaskUtils.getExportMasks(dbClient, exportGroup)) { if (exportMask == null || (exportMask.getStorageDevice() != null && !exportMask.getStorageDevice().equals(storage))) { continue; } Set<Initiator> initiators = getInitiatorsForExportMask(dbClient, exportMask, null); for (Initiator initiator : initiators) { String key = NullColumnValueGetter.getNullURI().toString(); if (initiator.getHost() != null) { key = initiator.getHost().toString(); hostToExportMaskURI.put(key, exportMask.getId()); } else { _log.info("Host Name empty in initiator {}", initiator.getId()); } } } return hostToExportMaskURI; } static public Map<String, Set<URI>> groupInitiatorsByProtocol(Set<String> iniStrList, DbClient dbClient) { Map<String, Set<URI>> iniByProtocol = new HashMap<String, Set<URI>>(); List<URI> iniList = new ArrayList<URI>(Collections2.transform( iniStrList, CommonTransformerFunctions.FCTN_STRING_TO_URI)); List<Initiator> initiators = dbClient.queryObject(Initiator.class, iniList); for (Initiator ini : initiators) { if (null == ini.getProtocol()) { _log.warn("Initiator {} with protocol set to Null", ini.getId()); continue; } if (!iniByProtocol.containsKey(ini.getProtocol())) { iniByProtocol.put(ini.getProtocol(), new HashSet<URI>()); } iniByProtocol.get(ini.getProtocol()).add(ini.getId()); } return iniByProtocol; } /** * Method returns a mapping of ExportMask URIs to a Map of Volume URIs to Integers. * The Mapping of Volume URIs to Integers represents how many ExportGroups that the * volume belongs to. * * @param dbClient [in] - DbClient for accessing DB * @param volumeURIs [in] - List of volume URIs to check * @param initiatorURIs [in] - List of Initiator URIs * @return Map of URI:ExportMask to (Map of URI:Volume to Integer). The Integer count * represents the total number of Export*Group*s that the volume belongs to. */ static public Map<URI, Map<URI, Integer>> mapExportMaskToVolumeShareCount(DbClient dbClient, List<URI> volumeURIs, List<URI> initiatorURIs) { List<Initiator> initiators = dbClient.queryObject(Initiator.class, initiatorURIs); // Generate a mapping of volume URIs to the # of // ExportGroups that it is associated with Map<URI, Map<URI, Integer>> exportMaskToVolumeCount = new HashMap<>(); for (URI volumeURI : volumeURIs) { for (Initiator initiator : initiators) { Integer count = ExportUtils.getNumberOfExportGroupsWithVolume(initiator, volumeURI, dbClient); List<ExportMask> exportMasks = ExportUtils.getInitiatorExportMasks(initiator, dbClient); for (ExportMask exportMask : exportMasks) { if (!exportMask.hasVolume(volumeURI)) { continue; } Map<URI, Integer> countMap = exportMaskToVolumeCount.get(exportMask.getId()); if (countMap == null) { countMap = new HashMap<>(); exportMaskToVolumeCount.put(exportMask.getId(), countMap); } countMap.put(volumeURI, count); } } } return exportMaskToVolumeCount; } /** * Sorts export masks by eligibility. * For instance, less utilized export masks will be listed before more utilized ones. * * @param maskSet list of export masks * @return list of sorted export masks */ static public List<ExportMask> sortMasksByEligibility(Map<ExportMask, ExportMaskPolicy> maskMap, ExportGroup exportGroup) { List<ExportMaskComparatorContainer> exportMaskContainerList = new ArrayList<ExportMaskComparatorContainer>(); for (Map.Entry<ExportMask, ExportMaskPolicy> entry : maskMap.entrySet()) { exportMaskContainerList.add(new ExportMaskComparatorContainer(entry.getKey(), entry.getValue(), exportGroup)); } Collections.sort(exportMaskContainerList, new ExportMaskComparator()); List<ExportMask> sortedMasks = new ArrayList<ExportMask>(); for (ExportMaskComparatorContainer container : exportMaskContainerList) { ExportMaskPolicy policy = container.policy; ExportMask mask = container.mask; _log.info(String.format( "Sorted ExportMasks by eligibility: %s { isSimple:%s, igType:%s, xpType:%s, localAutoTier:%s, autoTiers:%s }", mask.getMaskName(), policy.isSimpleMask(), policy.getIgType(), policy.getExportType(), policy.localTierPolicy, CommonTransformerFunctions.collectionToString(policy.getTierPolicies()))); sortedMasks.add(container.mask); } return sortedMasks; } /** * Determine if the ExportMask is "in" a given Varray. * This is determined by if all the target ports are tagged for for the Varray. * * @param exportMask -- ExportMask * @param varrayURI -- Varray URI * @return -- true if ExportMask in given Varray */ public static boolean exportMaskInVarray(DbClient dbClient, ExportMask exportMask, URI varrayURI) { if (exportMask.getStoragePorts() == null || exportMask.getStoragePorts().isEmpty()) { return false; } List<URI> targetURIs = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts()); List<StoragePort> ports = dbClient.queryObject(StoragePort.class, targetURIs); for (StoragePort port : ports) { if (port.getTaggedVirtualArrays() == null || !port.getTaggedVirtualArrays().contains(varrayURI.toString())) { return false; } } return true; } /** * Get storage ports not in the given varray. * * @param exportMask -- ExportMask * @param varrayURI -- Varray URI * @return list of storage ports not tagged for the given Varray */ public static List<URI> getExportMaskStoragePortsNotInVarray(DbClient dbClient, ExportMask exportMask, URI varrayURI) { List<URI> ports = new ArrayList<URI> (); if (exportMask.getStoragePorts() == null || exportMask.getStoragePorts().isEmpty()) { return ports; } List<URI> targetURIs = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts()); List<StoragePort> sports = dbClient.queryObject(StoragePort.class, targetURIs); for (StoragePort port : sports) { if (port.getTaggedVirtualArrays() == null || !port.getTaggedVirtualArrays().contains(varrayURI.toString())) { ports.add(port.getId()); } } return ports; } /** * Filter the volumeMap to only contain the desired includedVolumes. * * @param volumeMap -- Map of volumes to LUN ids * @param includedVolumes -- Set of included volumes * @return -- Filter volumeMap containing only the includedVolumes */ public static Map<URI, Integer> filterVolumeMap(Map<URI, Integer> volumeMap, Set<URI> includedVolumes) { Map<URI, Integer> result = new HashMap<URI, Integer>(); if (includedVolumes == null) { return result; } for (URI includedVolume : includedVolumes) { result.put(includedVolume, volumeMap.get(includedVolume)); } return result; } /** * Filters a volume list to only those in the ExportMask.volumes * * @param volumeURIs -- list of Volume URIs * @param exportMask -- ExportMask * @return List<URI> of volume URIs filtered */ public static List<URI> filterVolumesByExportMask(List<URI> volumeURIs, ExportMask exportMask) { List<URI> result = new ArrayList<URI>(); for (URI uri : volumeURIs) { if (exportMask.hasVolume(uri)) { result.add(uri); } } return result; } /** * Routine will examine the ExportMask and determine if any of its initiators or volumes no * longer exist in the database or are marked as inactive. If so, they will get removed * from the list. * * @param dbClient [in] - DbClient object * @param exportMask [in] - ExportMask object to check and sanitize */ public static void sanitizeExportMaskContainers(DbClient dbClient, ExportMask exportMask) { if (exportMask != null) { List<URI> initiatorURIs = StringSetUtil.stringSetToUriList(exportMask.getInitiators()); List<URI> initiatorsToRemove = new ArrayList<>(); for (URI initiatorURI : initiatorURIs) { DataObject initiator = dbClient.queryObject(initiatorURI); if (initiator == null || initiator.getInactive()) { initiatorsToRemove.add(initiatorURI); } } if (!initiatorsToRemove.isEmpty()) { _log.info(String.format( "sanitizeExportMaskContainers - Removing non-existent/inactive Initiators from ExportMask %s (%s): %s", exportMask.getMaskName(), exportMask.getId(), CommonTransformerFunctions.collectionToString(initiatorsToRemove))); exportMask.removeInitiatorURIs(initiatorsToRemove); exportMask.removeFromUserAddedInitiatorsByURI(initiatorsToRemove); } Map<URI, Integer> volumeMap = StringMapUtil.stringMapToVolumeMap(exportMask.getVolumes()); List<URI> volumesToRemove = new ArrayList<>(); for (URI volumeURI : volumeMap.keySet()) { DataObject volume = dbClient.queryObject(volumeURI); if (volume == null || volume.getInactive()) { volumesToRemove.add(volumeURI); } } if (!volumesToRemove.isEmpty()) { _log.info(String.format( "sanitizeExportMaskContainers - Removing non-existent/inactive BlockObjects from ExportMask %s (%s): %s", exportMask.getMaskName(), exportMask.getId(), CommonTransformerFunctions.collectionToString(volumesToRemove))); exportMask.removeVolumes(volumesToRemove); exportMask.removeFromUserAddedVolumesByURI(volumesToRemove); } List<URI> storagePorts = StringSetUtil.stringSetToUriList(exportMask.getStoragePorts()); List<URI> storagePortsToRemove = new ArrayList<>(); for (URI storagePortURI : storagePorts) { DataObject storagePort = dbClient.queryObject(storagePortURI); if (storagePort == null || storagePort.getInactive()) { storagePortsToRemove.add(storagePortURI); } } if (!storagePortsToRemove.isEmpty()) { _log.info(String.format( "sanitizeExportMaskContainers - Removing non-existent/inactive StoragePorts from ExportMask %s (%s): %s", exportMask.getMaskName(), exportMask.getId(), CommonTransformerFunctions.collectionToString(storagePortsToRemove))); exportMask.removeTargets(storagePortsToRemove); } } } /** * When a co-exist initiator is delete in ViPR, no action will be taken on the storage array * but the ExportMask and FCZoneReferences in ViPR need to be updated. Note that the only * time a co-exist initiator can be removed is by actually deleting this initiator in ViPR. * This means all references will need to be deleted. * * @param dbModelClient an instance of DbModelClient * @param exportMaskUri the export mask being updates * @param initiatorsUris the ids of the initiators being removed. */ public static void removeMaskCoexistInitiators(DbModelClient dbModelClient, URI exportMaskUri, List<URI> initiatorsUris) { _log.info("removeMaskEoexistInitiators - Removing FCZoneReferences for initiators {}", initiatorsUris); ExportMask mask = dbModelClient.find(ExportMask.class, exportMaskUri); if (mask == null || mask.getInactive() || initiatorsUris == null) { return; } // Get the initiators that are removed and all ports in the mask. Generate all possible keys. List<Initiator> initiators = DataObjectUtils.iteratorToList(dbModelClient.find(Initiator.class, initiatorsUris)); List<StoragePort> ports = DataObjectUtils.iteratorToList(dbModelClient.find(StoragePort.class, StringSetUtil.stringSetToUriList(mask.getStoragePorts()))); List<String> keys = new ArrayList<String>(); for (Initiator initiator : initiators) { for (StoragePort port : ports) { keys.add(FCZoneReference.makeEndpointsKey(initiator.getInitiatorPort(), port.getPortNetworkId())); } } if (!keys.isEmpty()) { _log.info("removeMaskEoexistInitiators - Removing FCZoneReferences for keys {}", keys); Joiner joiner = dbModelClient.join(FCZoneReference.class, "refs", "pwwnKey", keys).go(); List<FCZoneReference> list = joiner.list("refs"); if (list != null && !list.isEmpty()) { _log.info("removeMaskEoexistInitiators - found {} FCZoneReferences for keys {}", list.size(), keys); dbModelClient.remove(list); } } // now clean the export mask mask.removeInitiators(initiators); for (URI uri : initiatorsUris) { mask.removeZoningMapEntry(uri.toString()); } _log.info("removeMaskEoexistInitiators - removed initiators {} from mask {}", initiatorsUris, mask.getMaskName()); dbModelClient.update(mask); } /** * Checks if any of the initiators belongs to a true Host and returns true * if all initiators do not belong to a host. This effectively checks if all * initiators are for an RP or vplex system because the host id is set to * null for these initiators. * * @param initiators a list of initiators. * @return true if all initiators have a null host id. */ public static boolean areBackendInitiators(List<Initiator> initiators) { boolean backend = true; for (Initiator initiator : initiators) { if (!NullColumnValueGetter.isNullURI(initiator.getHost()) && URIUtil.isType(initiator.getHost(), Host.class)) { backend = false; break; } } return backend; } /** * Is this export mask a backend mask for VPLEX or RP? * * @param dbClient * db client * @param exportMask * export mask * @return true if RP/VPLEX mask, false otherwise */ public static boolean isBackendExportMask(DbClient dbClient, ExportMask exportMask) { Set<URI> initiatorURIs = ExportMaskUtils.getAllInitiatorsForExportMask(dbClient, exportMask); if (initiatorURIs != null && !initiatorURIs.isEmpty()) { List<Initiator> initiators = dbClient.queryObject(Initiator.class, initiatorURIs); if (initiators != null) { return areBackendInitiators(initiators); } } return false; } /** * Find a set of ExportMasks to which the given Initiators belong. * * @param dbClient [IN] - For accessing DB * @param initiators [IN] - List of initiators to search for among the ExportMasks found in the DB. * @return HashMap of ExportMask URI to ExportMask object (Using HashMap, since URI is Comparable) */ public static HashMap<URI, ExportMask> getExportMasksWithInitiators(DbClient dbClient, List<Initiator> initiators) { List<String> initiatorPorts = new ArrayList<>(); for (Initiator initiator : initiators) { initiatorPorts.add(initiator.getInitiatorPort()); } return getExportMasksWithInitiatorPorts(dbClient, initiatorPorts); } /** * Find a set of ExportMasks to which the given Initiators belong. * * @param dbClient [IN] - For accessing DB * @param initiatorPorts [IN] - List of initiator ports to search for among the ExportMasks found in the DB. * @return HashMap of ExportMask URI to ExportMask object (Using HashMap, since URI is Comparable) */ public static HashMap<URI, ExportMask> getExportMasksWithInitiatorPorts(DbClient dbClient, List<String> initiatorPorts) { final String initiatorAliasStr = "initiator"; final String portNameAliasStr = "iniport"; final String exportMaskAliasStr = "em"; final String initiatorStr = "initiators"; // Find all the ExportMasks that contain the 'initiators' HashMap<URI, ExportMask> exportMasksWithInitiator = new HashMap<>(); for (String initiatorPort : initiatorPorts) { Joiner joiner = new Joiner(dbClient); Joiner query = joiner.join(Initiator.class, initiatorAliasStr).match(portNameAliasStr, initiatorPort) .join(initiatorAliasStr, ExportMask.class, exportMaskAliasStr, initiatorStr).go(); Set<ExportMask> matchedMasks = query.set(exportMaskAliasStr); for (ExportMask exportMask : matchedMasks) { exportMasksWithInitiator.put(exportMask.getId(), exportMask); } } return exportMasksWithInitiator; } /** * Compare the ExportMask's volumes with a map containing the latest discovered volumes. Return a map of volumes * that are new. * * @param mask [IN] - ExportMask to check * @param discoveredVolumes [IN] - Map of Volume WWN to Integer HLU representing discovered volumes. * @return Map of Volume WWN (normalized) to Integer HLU representing new volumes, which do not exist in the ExportMask. */ public static Map<String, Integer> diffAndFindNewVolumes(final ExportMask mask, final Map<String, Integer> discoveredVolumes) { Map<String, Integer> volumesToAdd = new HashMap<>(); // Iterate through the volume WWNs for (String volumeWWN : discoveredVolumes.keySet()) { Integer hlu = discoveredVolumes.get(volumeWWN); // Normalize the WWN, so that we can look it up in the ExportMask String normalizedWWN = BlockObject.normalizeWWN(volumeWWN); if (!mask.hasExistingVolume(normalizedWWN) && !mask.hasUserCreatedVolume(normalizedWWN)) { // https://coprhd.atlassian.net/browse/COP-18518. If the HLU is null, then it's possible that some // other process just added the volume to the ExportMask, but the HLU selection by the array has // not completed. In that case, we won't indicate that the volume is added just yet. // That other process should add the volume and its HLU in the ExportMask addVolume post process. if (hlu != null) { volumesToAdd.put(normalizedWWN, hlu); } else { _log.info("Volume {} does not have an HLU. It could be getting assigned.", normalizedWWN); } } } return volumesToAdd; } /** * Update HLUs for volumes in export mask and export group with the discovered information from array. * * @param mask the export mask * @param discoveredVolumes the discovered volumes * @param dbClient the db client */ public static void updateHLUsInExportMask(ExportMask mask, Map<String, Integer> discoveredVolumes, DbClient dbClient) { boolean updateMask = false; for (String wwn : discoveredVolumes.keySet()) { URIQueryResultList volumeList = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory.getVolumeWwnConstraint(wwn), volumeList); while (volumeList.iterator().hasNext()) { URI volumeURI = volumeList.iterator().next(); if (!NullColumnValueGetter.isNullURI(volumeURI)) { BlockObject bo = BlockObject.fetch(dbClient, volumeURI); if (bo != null && !bo.getInactive() && mask.getStorageDevice() != null && mask.getStorageDevice().equals(bo.getStorageController())) { Integer discoveredHLU = discoveredVolumes.get(wwn); if (mask.hasVolume(volumeURI) && discoveredHLU != ExportGroup.LUN_UNASSIGNED) { mask.addVolume(volumeURI, discoveredHLU); updateMask = true; break; } } } } } if (updateMask) { dbClient.updateObject(mask); } List<ExportGroup> exportGroups = getExportGroups(dbClient, mask); for (ExportGroup exportGroup : exportGroups) { ExportUtils.reconcileExportGroupsHLUs(dbClient, exportGroup); } dbClient.updateObject(exportGroups); } /** * Routine returns the ExportMask by name from the DB that is associated with the StorageSystem. * Inactive ExportMasks are ignored. * * @param dbClient [IN] - DbClient to access DB * @param storageSystemId [IN] - StorageSystem URI against which to search the ExportMask name * @param name [IN] - Name of ExportMask to lookup * @return ExportMask object where its storageDevice = storageSystemId and maskName = name. * Returns null if not found. */ public static ExportMask getExportMaskByName(DbClient dbClient, URI storageSystemId, String name) { ExportMask exportMask = null; ExportMask result = null; URIQueryResultList uriQueryList = new URIQueryResultList(); dbClient.queryByConstraint(AlternateIdConstraint.Factory .getExportMaskByNameConstraint(name), uriQueryList); while (uriQueryList.iterator().hasNext()) { URI uri = uriQueryList.iterator().next(); exportMask = dbClient.queryObject(ExportMask.class, uri); if (exportMask != null && !exportMask.getInactive() && exportMask.getStorageDevice().equals(storageSystemId)) { // We're expecting there to be only one export mask of a // given name for any storage array. result = exportMask; break; } } return result; } /** * Given a list of Initiators, find if any are in an ExportMask's userAddedInitiators list- other than the 'exportMask' passed into * the routine. If such an ExportMask is found, then the initiator will be removed from 'newInitiators' and added to the result list. * * @param dbClient [IN] - DbClient to access DB * @param exportMask [IN] - ExportMask that should be excluded from matches. This is the ExportMask that we're checking against. * @param newInitiators [OUT] - List of Initiators that need to be added to 'exportMask'. We need to determine if they need to go into * the existingInitiators list or the userAddedInitiators list. * @return List of Initiators that should added to exportMask's userAddedInitiator list. ExportMasks should be on the same array as * 'exportMask'. */ public static List<Initiator> findIfInitiatorsAreUserAddedInAnotherMask(ExportMask exportMask, List<Initiator> newInitiators, DbClient dbClient) { List<Initiator> userAddedInitiators = new ArrayList<>(); // Iterate through the set of ExportMasks that contain 'newInitiators' and find if it has the initiator in its userAddedInitiator // list. If it does, we add the initiator to the result list and remove it from 'newInitiator'. for (ExportMask matchedMask : ExportMaskUtils.getExportMasksWithInitiators(dbClient, newInitiators).values()) { // Exclude 'exportMask' and ExportMasks on different arrays from the search if (matchedMask.getId().equals(exportMask.getId()) || !matchedMask.getStorageDevice().equals(exportMask.getStorageDevice())) { continue; } // Iterate through the set of initiators and find if any exist in the ExportMask's userAddedInitiator list Iterator<Initiator> iterator = newInitiators.iterator(); while (iterator.hasNext()) { Initiator initiator = iterator.next(); if (matchedMask.hasUserInitiator(initiator.getId())) { // This initiator is user-added for this matchedMask userAddedInitiators.add(initiator); // Since this ExportMask has the initiator, we need to remove it from 'newInitiators'. iterator.remove(); } } } Collection portNames = Collections2.transform(userAddedInitiators, CommonTransformerFunctions.fctnInitiatorToPortName()); if (!portNames.isEmpty()) { _log.info(String.format("The following initiators were found in another ExportMask as user-added initiators: %s", CommonTransformerFunctions.collectionToString(portNames))); } return userAddedInitiators; } /** * Check if the mask and the initiator belong to different resource. */ public static boolean checkIfDifferentResource(ExportMask mask, Initiator existingInitiator) { boolean differentResource = false; String maskResource = mask.getResource(); if (!NullColumnValueGetter.isNullValue(maskResource)) { // check only if the mask has resource if (maskResource.startsWith("urn:storageos:Host")) { // We found scenarios where VPLEX Initiators/ports do not have the Host Name set and this is handled below. if (!NullColumnValueGetter.isNullURI(existingInitiator.getHost())) { differentResource = !maskResource.equals(existingInitiator.getHost().toString()); } else { differentResource = true; } } else { differentResource = !maskResource.equals(existingInitiator.getClusterName()); } } return differentResource; } /** * Set the resource on an ExportMask, if it hasn't already been set. * * @param dbClient Database client * @param exportGroup ExportGroup * @param exportMask ExportMask * @return true if the resource field was set, false otherwise. */ public static boolean setExportMaskResource(DbClient dbClient, ExportGroup exportGroup, ExportMask exportMask) { if (NullColumnValueGetter.isNotNullValue(exportMask.getResource())) { return false; } Set<Initiator> initiators = getInitiatorsForExportMask(dbClient, exportMask, null); String resourceRef = null; if (exportGroup.getType() != null && !initiators.isEmpty()) { Initiator firstInitiator = initiators.iterator().next(); if (exportGroup.getType().equals(ExportGroup.ExportGroupType.Cluster.name())) { resourceRef = firstInitiator.getClusterName(); } else { resourceRef = firstInitiator.getHost() == null ? null : firstInitiator.getHost().toString(); } } if (Strings.isNullOrEmpty(resourceRef)){ // This resource is used when we add initiators to existing masks on VMAX, which should not be // case with VPLEX and RP, which do not associate their initiators with hosts or clusters. resourceRef = NullColumnValueGetter.getNullURI().toString(); } exportMask.setResource(resourceRef); return true; } /** * Check to see if the export mask contains exactly the ports sent in. * * @param mask * export mask * @param ports * ports of a compute resource * @param dbClient * db client * @return true if contains a subset and ONLY that subset */ public static boolean hasExactlyTheseInitiators(ExportMask mask, Collection<String> ports, DbClient dbClient) { Collection<String> normalizedPorts = new HashSet<String>(); for (String port : ports) { normalizedPorts.add(Initiator.normalizePort(port)); } Collection<String> maskInitiators = new HashSet<String>(); if (mask.getExistingInitiators() != null) { maskInitiators.addAll(mask.getExistingInitiators()); } if (mask.getInitiators() != null) { for (String initiatorId : mask.getInitiators()) { Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(initiatorId)); if (initiator != null & initiator.getInitiatorPort() != null) { maskInitiators.add(Initiator.normalizePort(initiator.getInitiatorPort())); } } } if (mask.getUserAddedInitiators() != null) { maskInitiators.addAll(mask.getUserAddedInitiators().keySet()); } return (normalizedPorts.size() == maskInitiators.size()) && maskInitiators.containsAll(normalizedPorts); } /** * Contains a "perfect subset" of the ports sent in. Contains a subset, and no other initiators. * * @param mask * export mask * @param ports * ports of a compute resource * @param dbClient * db client * @return true if contains a subset and ONLY that subset */ public static boolean hasExactlySubsetOfTheseInitiators(ExportMask mask, List<String> ports, DbClient dbClient) { Collection<String> normalizedPorts = new HashSet<String>(); for (String port : ports) { normalizedPorts.add(Initiator.normalizePort(port)); } Collection<String> maskInitiators = new HashSet<String>(); if (mask.getExistingInitiators() != null) { maskInitiators.addAll(mask.getExistingInitiators()); } if (mask.getInitiators() != null) { for (String initiatorId : mask.getInitiators()) { Initiator initiator = dbClient.queryObject(Initiator.class, URI.create(initiatorId)); if (initiator != null & initiator.getInitiatorPort() != null) { maskInitiators.add(Initiator.normalizePort(initiator.getInitiatorPort())); } } } if (mask.getUserAddedInitiators() != null) { maskInitiators.addAll(mask.getUserAddedInitiators().keySet()); } return normalizedPorts.containsAll(maskInitiators); } /** * Given a zone map as a Map of initiators to List of corresponding ports, * return the union of all ports in the map. * @param zoneMap Map<URI initiator, List<URI> portList> * @return Set of all ports. */ public static Set<URI> getAllPortsInZoneMap(Map<URI, List<URI>> zoneMap) { Set<URI> result = new HashSet<URI>(); for (List<URI> ports : zoneMap.values()) { result.addAll(ports); } return result; } /* * Get new paths which are not in any of the export masks zoning maps from the given paths * * @param dbClient * @param exportMasks * @param maskURIs - OUTPUT the export masks URI list which have zoning map entries * @param paths - new and retained paths * @return - the new paths for the export masks */ public static Map<URI, List<URI>> getNewPaths(DbClient dbClient, List<ExportMask> exportMasks, List<URI> maskURIs, Map<URI, List<URI>> paths) { Map<URI, List<URI>> newPaths = new HashMap<URI, List<URI>>(); StringSetMap allZoningMap = new StringSetMap(); for (ExportMask mask : exportMasks) { StringSetMap map = mask.getZoningMap(); if (map != null && !map.isEmpty()) { for (String init : map.keySet()) { StringSet allPorts = allZoningMap.get(init); if (allPorts == null) { allPorts = new StringSet(); allZoningMap.put(init, allPorts); } allPorts.addAll(map.get(init)); } maskURIs.add(mask.getId()); } } for (Map.Entry<URI, List<URI>> entry : paths.entrySet()) { URI init = entry.getKey(); List<URI> entryPorts = entry.getValue(); StringSet zoningPorts = allZoningMap.get(init.toString()); if (zoningPorts != null && !zoningPorts.isEmpty()) { List<URI> diffPorts = new ArrayList<URI>(Sets.difference(newHashSet(entryPorts), zoningPorts)); if (diffPorts != null && !diffPorts.isEmpty()) { newPaths.put(init, diffPorts); } } else { newPaths.put(init, entryPorts); } } return newPaths; } /** * Get the remove path list for the exportMask in the given removedPaths. * The given removedPaths could be paths from all the exportMasks belonging to one export group. * * @param exportMask * @param removedPaths - The list paths. some of them may not belong to the export mask. * @return - The list of paths are going to be removed from the export mask. */ public static Map<URI, List<URI>> getRemovePathsForExportMask(ExportMask exportMask, Map<URI, List<URI>> removedPaths) { Map<URI, List<URI>> result = new HashMap<URI, List<URI>>(); StringSetMap zoningMap = exportMask.getZoningMap(); StringSet maskInitiators = exportMask.getInitiators(); if (removedPaths == null || removedPaths.isEmpty()) { return result; } for (Map.Entry<URI, List<URI>> entry : removedPaths.entrySet()) { URI initiator = entry.getKey(); if (!maskInitiators.contains(initiator.toString())) { continue; } List<URI> ports = entry.getValue(); List<URI> removePorts = new ArrayList<URI> (); StringSet targets = zoningMap.get(initiator.toString()); if (targets != null && !targets.isEmpty()) { for (URI port : ports) { if (targets.contains(port.toString())) { removePorts.add(port); } } if (!removePorts.isEmpty()) { result.put(initiator, removePorts); } } } return result; } /** * Get adjusted paths per export mask. The members in the given adjusted paths could belong to different export masks in the same export group * This method would check on the initiators in the export mask, if the path initiator belong to the same host as the initiators in the * export mask, then the path belongs to the export mask. * * @param exportMask - export mask * @param adjustedPaths - The list of the adjusted paths (new and retained) for the export group * @param dbClient * @return The adjusted paths (map of initiator to storage ports) for the export mask that is desired as a result of path adjustment */ public static Map<URI, List<URI>> getAdjustedPathsForExportMask(ExportMask exportMask, Map<URI, List<URI>> adjustedPaths, DbClient dbClient) { Map<URI, List<URI>> result = new HashMap<URI, List<URI>> (); Set<String> hostsInMask = getHostNamesInMask(exportMask, dbClient); for (Map.Entry<URI, List<URI>> entry : adjustedPaths.entrySet()) { URI initURI = entry.getKey(); if (exportMask.getInitiators().contains(initURI.toString())) { result.put(initURI, entry.getValue()); } else { Initiator initiator = dbClient.queryObject(Initiator.class, initURI); String hostName = initiator.getHostName(); if (hostName != null && !hostName.isEmpty()) { if (hostsInMask.contains(hostName)) { result.put(initURI, entry.getValue()); } } } } return result; } /** * Returns the set of Hosts Names in an ExportMask * @param exportMask * @param dbClient * @return Set of Hostnames */ public static Set<String> getHostNamesInMask(ExportMask exportMask, DbClient dbClient) { Set<String> hostsInMask = new HashSet<String> (); Set<Initiator> initiators = getInitiatorsForExportMask(dbClient, exportMask, Transport.FC); if (initiators == null || initiators.isEmpty()) { return hostsInMask; } for (Initiator init : initiators) { String hostName = init.getHostName(); if (hostName != null && !hostName.isEmpty()) { hostsInMask.add(hostName); } } return hostsInMask; } /** * Builds a default zoneMap from the initiators and ports. * For the targets in the mask, they are paired with the initiators they can service, * i.e. that are on the same or a route-able network, and are usable in the varray, * and the corresponding zones are put in the zoning map. * * @param mask -- The ExportMask being manipulated * @param varray -- The Virtual Array (normally from the ExportGroup) * @param dbClient -- DbClient * @return - The zoning map represented as a string set map that is constructed from the * cross product of mask initiators and mask storage ports. Initiators are only paired * with ports on the same network. * * Assumption: the export mask has up to date initiators and storage ports */ public static StringSetMap buildZoningMapFromInitiatorsAndPorts(ExportMask mask, URI varray, DbClient dbClient) { _log.info(String.format("Creating zoning map for ExportMask %s (%s) from the initiator and port sets", mask.getMaskName(), mask.getId())); StringSetMap zoningMap = new StringSetMap(); // 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; } 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); if (!port.isUsable()) { _log.debug( "Storage port {} is not selected because it is inactive, is not compatible, is not visible, not on a network, " + "is not registered, 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 connectivity was checked in getInitiatorPortsInMask() if (port.getTaggedVirtualArrays().contains(varray.toString())) { storagePorts.add(portURI.toString()); } else { _log.debug( "Storage port {} is not selected because it is not in the specified varray {}", port.getLabel(), varray.toString()); } } if (!storagePorts.isEmpty()) { zoningMap.put(initiatorURIStr, storagePorts); } } _log.info("Constructed zoningMap -" + zoningMap.toString()); return zoningMap; } /** * Method to determine if a volume in a volume map is a boot volume for compute services. * * @param dbClient db client * @param volumeMap volume map (should just be one volume) * @return true if this volume is a boot volume */ public static boolean isBootVolume(DbClient dbClient, Map<URI, Integer> volumeMap) { // First, check to make sure we have a valid map if (volumeMap == null) { return false; } // Second, check to make sure we only have one volume in our map. // It doesn't make sense to have two boot volumes. if (volumeMap.size() != 1) { return false; } // Make sure we have a valid volume object Volume volume = dbClient.queryObject(Volume.class, volumeMap.keySet().iterator().next()); if (volume == null) { return false; } // Now make sure the boot volume tag is filled-in. if (volume.bootVolumeTagValue() != null) { return true; } // Otherwise return false return false; } }