/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.plugins; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.BlockObject; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject.CompatibilityStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.DataCollectionJobStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.DiscoveryStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.RegistrationStatus; import com.emc.storageos.db.client.model.DiscoveredDataObject.Type; import com.emc.storageos.db.client.model.Initiator; import com.emc.storageos.db.client.model.Stat; import com.emc.storageos.db.client.model.StorageHADomain; import com.emc.storageos.db.client.model.StoragePort; import com.emc.storageos.db.client.model.StoragePort.PortType; import com.emc.storageos.db.client.model.StorageProtocol; import com.emc.storageos.db.client.model.StorageProvider; import com.emc.storageos.db.client.model.StorageProvider.ConnectionStatus; 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.VirtualPool; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedExportMask; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume.SupportedVolumeCharacterstics; import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedVolume.SupportedVolumeInformation; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.util.WWNUtility; import com.emc.storageos.db.client.util.iSCSIUtility; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.plugins.AccessProfile; import com.emc.storageos.plugins.BaseCollectionException; import com.emc.storageos.plugins.StorageSystemViewObject; import com.emc.storageos.plugins.common.Constants; import com.emc.storageos.plugins.common.PartitionManager; import com.emc.storageos.plugins.metering.vplex.VPlexCollectionException; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper; import com.emc.storageos.recoverpoint.utils.WwnUtils; import com.emc.storageos.util.ConnectivityUtil; import com.emc.storageos.util.NetworkUtil; import com.emc.storageos.util.VersionChecker; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator; import com.emc.storageos.volumecontroller.impl.StoragePoolAssociationHelper; import com.emc.storageos.volumecontroller.impl.StoragePortAssociationHelper; import com.emc.storageos.volumecontroller.impl.plugins.metering.vplex.VPlexStatsCollector; import com.emc.storageos.volumecontroller.impl.smis.srdf.SRDFUtils; import com.emc.storageos.volumecontroller.impl.utils.DiscoveryUtils; import com.emc.storageos.volumecontroller.impl.utils.ImplicitUnManagedObjectsMatcher; import com.emc.storageos.vplex.api.VPlexApiClient; import com.emc.storageos.vplex.api.VPlexApiConstants; import com.emc.storageos.vplex.api.VPlexApiException; import com.emc.storageos.vplex.api.VPlexApiFactory; import com.emc.storageos.vplex.api.VPlexClusterInfo; import com.emc.storageos.vplex.api.VPlexConsistencyGroupInfo; import com.emc.storageos.vplex.api.VPlexDirectorInfo; import com.emc.storageos.vplex.api.VPlexPortInfo; import com.emc.storageos.vplex.api.VPlexPortInfo.PortRole; import com.emc.storageos.vplex.api.VPlexPortInfo.SpeedUnits; import com.emc.storageos.vplex.api.VPlexStorageViewInfo; import com.emc.storageos.vplex.api.VPlexTargetInfo; import com.emc.storageos.vplex.api.VPlexVirtualVolumeInfo; import com.emc.storageos.vplexcontroller.VPlexControllerUtils; import com.emc.storageos.vplexcontroller.VplexBackendIngestionContext; import com.google.common.base.Joiner; import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import sun.net.util.IPAddressUtil; /** * Discovery framework plug-in class for discovering VPlex storage systems. */ public class VPlexCommunicationInterface extends ExtendedCommunicationInterfaceImpl { // string constants public static final String VPLEX_INITIATOR_HOSTNAME_PREFIX = "vplex_"; private static final String ISCSI_PATTERN = "^(iqn|IQN|eui).*$"; private static final String REGISTERED_PORT_PREFIX = "REGISTERED_0X"; private static final String REGISTERED_PATTERN = "^" + REGISTERED_PORT_PREFIX + ".*$"; private static final String ALLOW_LOCAL_TO_METRO_AUTO_UPGRADE = "controller_vplex_allow_local_to_metro_auto_upgrade"; private static final String TRUE = "true"; private static final String FALSE = "false"; private static final String LOCAL = "local"; private static int BATCH_SIZE = 10; // WWN for offline ports public static final String OFFLINE_PORT_WWN = "00:00:00:00:00:00:00:00"; // Cluster assembly id delimiter for a VPLEX metro. private static final String ASSEMBY_DELIM = ":"; // Logger reference. private static Logger s_logger = LoggerFactory.getLogger(VPlexCommunicationInterface.class); // Reference to the VPlex API factory allows us to get a VPlex API client // and execute requests to the VPlex storage system. private VPlexApiFactory _apiFactory; // PartitionManager used for batch database persistence. private PartitionManager _partitionManager; // Statistics collection implementation private VPlexStatsCollector _statsCollector; /** * Public constructor for Spring bean creation. */ public VPlexCommunicationInterface() { } /** * Setter for the VPlex API factory for Spring bean configuration. * * @param apiFactory A reference to the VPlex API factory. */ public void setVPlexApiFactory(VPlexApiFactory apiFactory) { _apiFactory = apiFactory; } /** * Setter for the PartitionManager for batch database persistence. * * @param partitionManager */ @Override public void setPartitionManager(PartitionManager partitionManager) { _partitionManager = partitionManager; } /** * Setter for statistics collector */ public void setStatsCollector(VPlexStatsCollector statsCollector) { _statsCollector = statsCollector; } /** * Implementation for scan for VPlex storage systems. * * @param accessProfile * * @throws BaseCollectionException */ @Override public void scan(AccessProfile accessProfile) throws BaseCollectionException { URI mgmntServerURI = accessProfile.getSystemId(); StorageProvider mgmntServer = null; String scanStatusMessage = "Unknown Status"; VPlexApiClient client = null; try { // Get the storage provider representing a VPLEX management server. mgmntServer = _dbClient.queryObject(StorageProvider.class, mgmntServerURI); // Get the Http client for getting information about the VPLEX // cluster(s) managed by the VPLEX management server. client = getVPlexAPIClient(accessProfile); s_logger.debug("Got handle to VPlex API client"); // Verify the connectivity to the VPLEX management server. verifyConnectivity(client, mgmntServer); // Verify the VPLEX system firmware version is supported. verifyMinimumSupportedFirmwareVersion(client, mgmntServer); // Determine the VPLEX system managed by this management server. Map<String, StorageSystemViewObject> scanCache = accessProfile.getCache(); s_logger.info("Storage System scanCache before scanning:" + scanCache); scanManagedSystems(client, mgmntServer, scanCache); s_logger.info("Storage System scanCache after scanning:" + scanCache); scanStatusMessage = String.format("Scan job completed successfully for " + "VPLEX management server: %s", mgmntServerURI.toString()); } catch (Exception e) { if (null != client) { // clear cached discovery data in the VPlexApiClient client.clearCaches(); } VPlexCollectionException vce = VPlexCollectionException.exceptions .failedScan(mgmntServer.getIPAddress(), e.getLocalizedMessage()); scanStatusMessage = vce.getLocalizedMessage(); throw vce; } finally { if (mgmntServer != null) { try { mgmntServer.setLastScanStatusMessage(scanStatusMessage); _dbClient.updateObject(mgmntServer); } catch (Exception e) { s_logger.error("Error persisting scan status message for management server {}", mgmntServerURI.toString(), e); } } } } /** * Verifies the connectivity of the passed management server. * * @param client The VPlex API client. * @param mgmntServer A reference to the VPlex management server. * * @throws VPlexApiException When management server cannot be accessed. */ private void verifyConnectivity(VPlexApiClient client, StorageProvider mgmntServer) throws VPlexApiException { try { client.verifyConnectivity(); mgmntServer.setConnectionStatus(ConnectionStatus.CONNECTED.name()); } catch (Exception e) { mgmntServer.setConnectionStatus(ConnectionStatus.NOTCONNECTED.name()); throw e; } finally { try { _dbClient.updateObject(mgmntServer); } catch (Exception e) { s_logger.error("Error persisting connection status for management server {}", mgmntServer.getId(), e); } } } /** * Verifies the firmware version of the VPLEX management server is supported, * otherwise aborts the scan. * * @param client The VPlex API client. * @param mgmntServer A reference to the VPlex management server. * * @throws VPlexCollectionException When an error occurs discovering the * firmware version or the firmware version is less than the minimum * supported version. */ private void verifyMinimumSupportedFirmwareVersion(VPlexApiClient client, StorageProvider mgmntServer) throws VPlexCollectionException { try { String fwVersion = client.getManagementSoftwareVersion(); mgmntServer.setVersionString(fwVersion); String minFWVersion = VersionChecker.getMinimumSupportedVersion(Type .valueOf(mgmntServer.getInterfaceType())); s_logger.info("Verifying VPLEX management server version: Minimum Supported Version {} - " + "Discovered Version {}", minFWVersion, fwVersion); if (VersionChecker.verifyVersionDetails(minFWVersion, fwVersion) < 0) { setStorageProviderCompatibilityStatus(mgmntServer, CompatibilityStatus.INCOMPATIBLE); throw VPlexCollectionException.exceptions .unsupportedManagementServerVersion(fwVersion, mgmntServer.getIPAddress(), minFWVersion); } else { setStorageProviderCompatibilityStatus(mgmntServer, CompatibilityStatus.COMPATIBLE); } } catch (VPlexCollectionException vce) { s_logger.error("Error verifying management server version {}:", mgmntServer.getIPAddress(), vce); throw vce; } catch (Exception e) { s_logger.error("Error verifying management server version {}:", mgmntServer.getIPAddress(), e); throw VPlexCollectionException.exceptions .failedVerifyingManagementServerVersion(mgmntServer.getIPAddress(), e); } } /** * Sets the compatibility status on a VPLEX StorageProvider and all its underlying * StorageSystems and StoragePorts. * * @param provider the StorageProvider * @param status the CompatibilityStatus to set (COMPATIBLE or INCOMPATIBLE) */ private void setStorageProviderCompatibilityStatus(StorageProvider provider, CompatibilityStatus status) { if (provider == null) { s_logger.warn("The requested StorageProvider was null."); return; } if (status == null) { s_logger.warn("No updated CompatibilityStatus was provided for StorageProvider {}.", provider.getLabel()); return; } s_logger.info("Setting compatibility status on Storage Provider {} to {}", provider.getLabel(), status.toString()); provider.setCompatibilityStatus(status.toString()); StringSet storageSystemURIStrs = provider.getStorageSystems(); if (storageSystemURIStrs != null) { for (String storageSystemURIStr : storageSystemURIStrs) { // update storage system compatibility status StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, URI.create(storageSystemURIStr)); if (storageSystem != null) { s_logger.info("- Setting compatibility status on Storage System {} to {}", storageSystem.getLabel(), status.toString()); storageSystem.setCompatibilityStatus(status.toString()); // update port compatibility status URIQueryResultList storagePortURIs = new URIQueryResultList(); _dbClient.queryByConstraint( ContainmentConstraint.Factory.getStorageDeviceStoragePortConstraint(storageSystem.getId()), storagePortURIs); Iterator<URI> storagePortIter = storagePortURIs.iterator(); while (storagePortIter.hasNext()) { StoragePort port = _dbClient.queryObject(StoragePort.class, storagePortIter.next()); if (port != null) { s_logger.info("-- Setting compatibility status on Storage Port {} to {}", port.getLabel(), status.toString()); port.setCompatibilityStatus(status.name()); _dbClient.updateObject(port); } } _dbClient.updateObject(storageSystem); } } } _dbClient.updateObject(provider); } /** * First gets the VPLEX cluster(s) that are manageable by the VPLEX management * server. For a VPLEX local configuration there will be one cluster. For * a VPLEX Metro configuration there will be two clusters. We then create a * StorageSystemViewObject to represent the managed clusters as a storage system * to be managed by this management server. * * @param client The VPlex API client. * @param mgmntServer A reference to the VPlex management server. * @param scanCache A map holding previously found systems during a scan. * * @throws VPlexCollectionException When an error occurs getting the VPLEX * information. */ private void scanManagedSystems(VPlexApiClient client, StorageProvider mgmntServer, Map<String, StorageSystemViewObject> scanCache) throws VPlexCollectionException { try { List<String> clusterAssemblyIds = new ArrayList<String>(); String systemSerialNumber = getSystemSerialNumber(client, mgmntServer, clusterAssemblyIds); // Get the native GUID for the system using the constructed // serial number. String systemNativeGUID = NativeGUIDGenerator.generateNativeGuid( mgmntServer.getInterfaceType(), systemSerialNumber); s_logger.info("Scanned VPLEX system {}", systemNativeGUID); // Check for potential cluster hardware changes from local to metro, or vice versa checkForClusterHardwareChange(clusterAssemblyIds, systemNativeGUID, systemSerialNumber, client, scanCache, mgmntServer); // Determine if the VPLEX system was already scanned by another // VPLEX management server by checking the scan cache. If not, // then we create a StorageSystemViewObject to represent this // VPLEX system and add it to the scan cache. StorageSystemViewObject systemViewObj = null; if (scanCache.containsKey(systemNativeGUID)) { s_logger.info("VPLEX system {} was previously found.", systemNativeGUID); systemViewObj = scanCache.get(systemNativeGUID); } else { s_logger.info("Found new VPLEX system {}, adding to scan cache.", systemNativeGUID); systemViewObj = new StorageSystemViewObject(); } systemViewObj.setDeviceType(mgmntServer.getInterfaceType()); systemViewObj.addprovider(mgmntServer.getId().toString()); systemViewObj.setProperty(StorageSystemViewObject.SERIAL_NUMBER, systemSerialNumber); systemViewObj.setProperty(StorageSystemViewObject.STORAGE_NAME, systemNativeGUID); scanCache.put(systemNativeGUID, systemViewObj); } catch (Exception e) { s_logger.error("Error scanning managed systems for {}:", mgmntServer.getIPAddress(), e); throw VPlexCollectionException.exceptions.failedScanningManagedSystems( mgmntServer.getIPAddress(), e.getLocalizedMessage(), e); } } /** * Checks for hardware change from local to metro or vice versa. If the * system flag controller_vplex_allow_local_to_metro_auto_upgrade is set to * true, then the StorageSystem object and related StoragePorts will be updated * with the new serial number information. * * @param clusterAssembyIds the cluster assembly IDs detected * @param systemNativeGUID the storage system native GUID * @param systemSerialNumber the storage system serial number * @param client the VPLEX api client * @param scanCache the scan cache * @param mgmntServer the StorageProvider * @throws Exception if an error occurred */ private void checkForClusterHardwareChange(List<String> clusterAssembyIds, String systemNativeGUID, String systemSerialNumber, VPlexApiClient client, Map<String, StorageSystemViewObject> scanCache, StorageProvider mgmntServer) throws Exception { // we need to ensure the discovered storage system is not a result // of a hardware reconfiguration from local to metro, or vice versa s_logger.info("clusterAssembyIds is " + clusterAssembyIds); if (VPlexApiConstants.VPLEX_METRO_ASSEMBLY_COUNT == clusterAssembyIds.size()) { // check if this system could have been upgraded from local to metro List<StorageSystem> vplexLocalStorageSystems = VPlexControllerUtils.getAllVplexLocalStorageSystems(_dbClient); for (StorageSystem vplex : vplexLocalStorageSystems) { if (null != vplex && null != vplex.getVplexAssemblyIdtoClusterId()) { // because this is a local system, it should have only one assembly id String assemblyId = vplex.getVplexAssemblyIdtoClusterId().keySet().iterator().next(); if (systemNativeGUID.contains(assemblyId)) { String message = String.format("The VPLEX storage system serial number unexpectedly changed. " + "Existing VPLEX local assembly id %s is a substring of the newly-discoverd system GUID %s, " + "which indicates a change in VPLEX hardware configuration from local to metro. " + "Scanning of this Storage Provider cannot continue. Recommended course of action is " + "to contact EMC Customer Support.", assemblyId, systemNativeGUID); boolean allowAutoUpgrade = Boolean.valueOf(ControllerUtils .getPropertyValueFromCoordinator( _coordinator, ALLOW_LOCAL_TO_METRO_AUTO_UPGRADE)); if (!allowAutoUpgrade) { if (null != vplex && systemNativeGUID.contains(vplex.getNativeGuid())) { s_logger.error(message); vplex.setDiscoveryStatus(DataCollectionJobStatus.ERROR.name()); vplex.setLastDiscoveryStatusMessage(message); vplex.setLastDiscoveryRunTime(System.currentTimeMillis()); _dbClient.updateObject(vplex); throw VPlexApiException.exceptions .vplexClusterConfigurationChangedFromLocalToMetro(assemblyId, systemNativeGUID); } } else { s_logger.warn(message); s_logger.warn("Auto upgrade is allowed, will attempt to automatically upgrade to Metro"); // we have to (possibly temporarily) persist the new serial number so that the // storage port native guid generator works, but we need to hold on to the old // serial number so that we can roll back if necessary String oldSerialNumber = vplex.getSerialNumber(); vplex.setSerialNumber(systemSerialNumber); _dbClient.updateObject(vplex); // a failed discovery attempt will null out all the provider info, so we need to reset it. // this data will be transient unless the upgrade process completes if (null == vplex.getSmisProviderIP()) { s_logger.info("The provider information was nulled out by a previous upgrade attempt, resetting"); vplex.setActiveProviderURI(mgmntServer.getId()); StringSet providers = new StringSet(); providers.add(mgmntServer.getId().toString()); vplex.setProviders(providers); vplex.setSmisProviderIP(mgmntServer.getIPAddress()); vplex.setSmisPortNumber(mgmntServer.getPortNumber()); vplex.setSmisUserName(mgmntServer.getUserName()); vplex.setSmisPassword(mgmntServer.getPassword()); } // update scan cache StorageSystemViewObject systemViewObject = scanCache.get(vplex.getNativeGuid()); if (null == systemViewObject) { systemViewObject = new StorageSystemViewObject(); } else { // this is a strange scanCache state that doesn't make any sense. just fail for safety reasons throw VPlexApiException.exceptions .vplexClusterConfigurationChangedFromLocalToMetro(assemblyId, systemNativeGUID); } s_logger.info("adding systemNativeGuid {} storage view object {} to scan cache", systemNativeGUID, systemViewObject); scanCache.put(systemNativeGUID, systemViewObject); // update the label if it has not been changed from the default native GUID if (vplex.getLabel().equals(vplex.getNativeGuid())) { vplex.setLabel(systemNativeGUID); } vplex.setNativeGuid(systemNativeGUID); // clear the assembly id collection, which will be updated by discoverClusterIdentification vplex.setVplexAssemblyIdtoClusterId(new StringMap()); // update the assembly id map in the VPLEX storage system object discoverClusterIdentification(vplex, client); // update storage port native guids and labels // and add them to an autoUpgradePortsMap to short circuit // another database check and hold them through until they can be persisted // at the end of the discoverPorts method call below Map<String, StoragePort> autoUpgradePortsMap = new HashMap<String, StoragePort>(); List<StoragePort> storagePorts = ControllerUtils.getSystemPortsOfSystem(_dbClient, vplex.getId()); for (StoragePort storagePort : storagePorts) { String nativeGuid = NativeGUIDGenerator.generateNativeGuid(_dbClient, storagePort); s_logger.info("autoUpgradePortsMap: setting native guid {} on storage port {}", nativeGuid, storagePort.forDisplay()); storagePort.setNativeGuid(nativeGuid); storagePort.setLabel(nativeGuid); autoUpgradePortsMap.put(storagePort.getPortNetworkId(), storagePort); } boolean doPersist = false; try { // now rediscover all storage ports to pull in the new second cluster's ports discoverPorts(client, vplex, new ArrayList<StoragePort>(), autoUpgradePortsMap); doPersist = true; } catch (Exception ex) { s_logger.error("Failed to discover ports. ", ex); // we've encountered and error and shouldn't persist any of this. // throw an exception to tell the user to contact customer support. throw VPlexApiException.exceptions .vplexClusterConfigurationChangedFromLocalToMetro(assemblyId, systemNativeGUID); } finally { if (doPersist) { // storage ports would have been updated by the discoverPorts method // but we still need to update the vplex object _dbClient.updateObject(vplex); } else { // we failed, so we want to set the serial number back to the old // serial number that was set above on the copy in the database StorageSystem databaseVplex = _dbClient.queryObject(StorageSystem.class, vplex.getId()); databaseVplex.setSerialNumber(oldSerialNumber); _dbClient.updateObject(databaseVplex); } } } } } } } else if (VPlexApiConstants.VPLEX_LOCAL_ASSEMBLY_COUNT == clusterAssembyIds.size()) { // check if this system could have been downgraded from metro to local List<StorageSystem> vplexMetroStorageSystems = VPlexControllerUtils.getAllVplexMetroStorageSystems(_dbClient); for (StorageSystem vplex : vplexMetroStorageSystems) { if (null != vplex && null != vplex.getVplexAssemblyIdtoClusterId()) { for (String assemblyId : vplex.getVplexAssemblyIdtoClusterId().keySet()) { if (systemNativeGUID.contains(assemblyId)) { // THIS IS VERY BAD String message = String.format("The VPLEX storage system serial number unexpectedly changed. " + "The newly-discovered native GUID %s contains the existing VPLEX metro system assembly " + "id %s of VPLEX %s, which indicates a change in VPLEX hardware configuration from metro to local. " + "Scanning of this Storage Provider cannot continue. Recommended course of action is " + "to contact EMC Customer Support.", systemNativeGUID, assemblyId, vplex.forDisplay()); s_logger.error(message); vplex.setDiscoveryStatus(DataCollectionJobStatus.ERROR.name()); vplex.setLastDiscoveryStatusMessage(message); vplex.setLastDiscoveryRunTime(System.currentTimeMillis()); _dbClient.updateObject(vplex); throw VPlexApiException.exceptions .vplexClusterConfigurationChangedFromMetroToLocal(systemNativeGUID, assemblyId); } } } } } else { s_logger.warn("Unexpected assembly id count {}", clusterAssembyIds.size()); } } /** * Implementation for discovery of VPLEX storage systems. * * @param accessProfile providing context for this discovery session * * @throws BaseCollectionException */ @Override public void discover(AccessProfile accessProfile) throws BaseCollectionException { s_logger.info("initiating discovery of VPLEX system {}", accessProfile.getProfileName()); if ((null != accessProfile.getnamespace()) && (accessProfile.getnamespace() .equals(StorageSystem.Discovery_Namespaces.UNMANAGED_VOLUMES .toString()))) { try { VPlexApiClient client = getVPlexAPIClient(accessProfile); long timer = System.currentTimeMillis(); UnmanagedDiscoveryPerformanceTracker tracker = new UnmanagedDiscoveryPerformanceTracker(); tracker.discoveryMode = ControllerUtils.getPropertyValueFromCoordinator( _coordinator, VplexBackendIngestionContext.DISCOVERY_MODE); // get all the detailed virtual volume info from the VPLEX API Map<String, VPlexVirtualVolumeInfo> vvolMap = client.getVirtualVolumes(true); tracker.virtualVolumeFetch = System.currentTimeMillis() - timer; tracker.totalVolumesFetched = vvolMap.size(); // discover unmanaged storage views timer = System.currentTimeMillis(); Map<String, Set<UnManagedExportMask>> volumeToExportMasksMap = new HashMap<String, Set<UnManagedExportMask>>(); Map<String, Set<VPlexStorageViewInfo>> volumeToStorageViewMap = new HashMap<String, Set<VPlexStorageViewInfo>>(); Set<String> recoverPointExportMasks = new HashSet<String>(); discoverUnmanagedStorageViews(accessProfile, client, vvolMap, volumeToExportMasksMap, volumeToStorageViewMap, recoverPointExportMasks); tracker.storageViewFetch = System.currentTimeMillis() - timer; // discover unmanaged volumes timer = System.currentTimeMillis(); discoverUnmanagedVolumes(accessProfile, client, vvolMap, volumeToExportMasksMap, volumeToStorageViewMap, recoverPointExportMasks, tracker); tracker.unmanagedVolumeProcessing = System.currentTimeMillis() - timer; // re-run vpool matching for all vpools so that backend volumes will // be updated after vvol discovery to match their parent's matched vpools timer = System.currentTimeMillis(); List<URI> vpoolURIs = _dbClient.queryByType(VirtualPool.class, true); List<VirtualPool> vpoolList = _dbClient.queryObject(VirtualPool.class, vpoolURIs); Set<URI> srdfEnabledTargetVPools = SRDFUtils.fetchSRDFTargetVirtualPools(_dbClient); Set<URI> rpEnabledTargetVPools = RPHelper.fetchRPTargetVirtualPools(_dbClient); for (VirtualPool vpool : vpoolList) { ImplicitUnManagedObjectsMatcher.matchVirtualPoolsWithUnManagedVolumes( vpool, srdfEnabledTargetVPools, rpEnabledTargetVPools, _dbClient, true); } tracker.vpoolMatching = System.currentTimeMillis() - timer; s_logger.info(tracker.getPerformanceReport()); } catch (URISyntaxException ex) { s_logger.error(ex.getLocalizedMessage()); throw VPlexCollectionException.exceptions.vplexUnmanagedVolumeDiscoveryFailed( accessProfile.getSystemId().toString(), ex.getLocalizedMessage()); } } else { discoverAll(accessProfile); } } /** * Implementation for discovering assembly ID (serial number) to cluster id (0 or 1) * mapping used in placement algorithms such as RP and VPLEX. * * @param vplex VPLEX storage system to discovery cluster info for * @param client a reference to the VPLEX API client */ private void discoverClusterIdentification(StorageSystem vplex, VPlexApiClient client) { try { if (vplex.getVplexAssemblyIdtoClusterId() != null && !vplex.getVplexAssemblyIdtoClusterId().isEmpty()) { // We've already retrieved this information during registration (scan), so there's no reason // to retrieve it again. (This information is not expected to change) return; } // Get the cluster information List<VPlexClusterInfo> clusterInfoList = client.getClusterInfoLite(); // Get the cluster assembly identifiers and form the // system serial number based on these identifiers. StringMap assemblyIdToClusterId = new StringMap(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { String assemblyId = clusterInfo.getTopLevelAssembly(); if (VPlexApiConstants.NULL_ATT_VAL.equals(assemblyId)) { throw VPlexCollectionException.exceptions .failedScanningManagedSystemsNullAssemblyId( vplex.getIpAddress(), clusterInfo.getName()); } assemblyIdToClusterId.put(assemblyId, clusterInfo.getClusterId()); } // Store the vplex assembly ID -> cluster ID mapping if (vplex.getVplexAssemblyIdtoClusterId() == null) { vplex.setVplexAssemblyIdtoClusterId(assemblyIdToClusterId); } else { vplex.getVplexAssemblyIdtoClusterId().putAll(assemblyIdToClusterId); } } catch (Exception e) { if (vplex != null) { s_logger.error("Error discovering cluster identification for the VPLEX storage system {}:", vplex.getIpAddress(), e); throw VPlexCollectionException.exceptions.failedPortsDiscovery( vplex.getId().toString(), e.getLocalizedMessage(), e); } s_logger.error("Error discovering cluster identification for the VPLEX storage system", e); throw VPlexCollectionException.exceptions.failedPortsDiscovery( "None", e.getLocalizedMessage(), e); } } /** * Implementation for discovering unmanaged virtual volumes in a VPLEX storage system. * * @param accessProfile providing context for this discovery session * @param client a reference to the VPLEX API client * @param vvolMap map of virtual volume names to virtual volume info objects * @param volumeToExportMasksMap map of volumes to a set of associated UnManagedExportMasks * @param volumeToStorageViewMap map of volumes to a set of associated VPlexStorageViewInfos * @param recoverpointExportMasks recoverpoint export mask uris * @param tracker the performance report tracking object for this discovery process * @throws BaseCollectionException */ private void discoverUnmanagedVolumes(AccessProfile accessProfile, VPlexApiClient client, Map<String, VPlexVirtualVolumeInfo> allVirtualVolumes, Map<String, Set<UnManagedExportMask>> volumeToExportMasksMap, Map<String, Set<VPlexStorageViewInfo>> volumeToStorageViewMap, Set<String> recoverPointExportMasks, UnmanagedDiscoveryPerformanceTracker tracker) throws BaseCollectionException { String statusMessage = "Starting discovery of Unmanaged VPLEX Volumes."; s_logger.info(statusMessage + " Access Profile Details : IpAddress : " + "PortNumber : {}, namespace : {}", accessProfile.getIpAddress() + accessProfile.getPortNumber(), accessProfile.getnamespace()); URI vplexUri = accessProfile.getSystemId(); StorageSystem vplex = _dbClient.queryObject(StorageSystem.class, vplexUri); if (null == vplex) { s_logger.error("No VPLEX Device was found in ViPR for URI: " + vplexUri); s_logger.error("Unmanaged VPLEX Volume discovery cannot continue."); return; } Set<URI> allUnmanagedVolumes = new HashSet<URI>(); List<UnManagedVolume> newUnmanagedVolumes = new ArrayList<UnManagedVolume>(); List<UnManagedVolume> knownUnmanagedVolumes = new ArrayList<UnManagedVolume>(); List<UnManagedExportMask> unmanagedExportMasksToUpdate = new ArrayList<UnManagedExportMask>(); Map<String, String> backendVolumeGuidToVvolGuidMap = new HashMap<String, String>(); try { long timer = System.currentTimeMillis(); Map<String, String> volumesToCgs = new HashMap<String, String>(); List<VPlexConsistencyGroupInfo> cgs = client.getConsistencyGroups(); s_logger.info("Found {} Consistency Groups.", cgs.size()); for (VPlexConsistencyGroupInfo cg : cgs) { for (String volumeName : cg.getVirtualVolumes()) { volumesToCgs.put(volumeName, cg.getName()); } } s_logger.info("Volume to Consistency Group Map is: " + volumesToCgs.toString()); tracker.consistencyGroupFetch = System.currentTimeMillis() - timer; Map<String, String> clusterIdToNameMap = client.getClusterIdToNameMap(); Map<String, String> varrayToClusterIdMap = new HashMap<String, String>(); Map<String, String> distributedDevicePathToClusterMap = VPlexControllerUtils.getDistributedDevicePathToClusterMap(vplexUri, _dbClient); if (null != allVirtualVolumes) { // Pre-populate the virtual pools List<URI> allVpoolUris = _dbClient.queryByType(VirtualPool.class, true); Iterator<VirtualPool> vpoolIter = _dbClient.queryIterativeObjects(VirtualPool.class, allVpoolUris); List<VirtualPool> allVpools = new ArrayList<VirtualPool>(); while (vpoolIter.hasNext()) { VirtualPool vpool = vpoolIter.next(); // Only cache vplex virtual pools for vpool filtering if (VirtualPool.vPoolSpecifiesHighAvailability(vpool)) { allVpools.add(vpool); } } for (String name : allVirtualVolumes.keySet()) { timer = System.currentTimeMillis(); s_logger.info("Discovering Virtual Volume {}", name); // UnManagedVolume discover does a pretty expensive // iterative call into the VPLEX API to get extended details String discoveryKillSwitch = ControllerUtils .getPropertyValueFromCoordinator( _coordinator, VplexBackendIngestionContext.DISCOVERY_KILL_SWITCH); if ("stop".equals(discoveryKillSwitch)) { s_logger.warn("discovery kill switch was set to stop, " + "so discontinuing unmanaged volume discovery"); return; } // on every volume in each cluster. First it gets all the // volume names/paths (the inexpensive "lite" call), then // iterates through them getting the details to populate the String discoveryFilter = ControllerUtils .getPropertyValueFromCoordinator( _coordinator, VplexBackendIngestionContext.DISCOVERY_FILTER); if ((discoveryFilter != null && !discoveryFilter.isEmpty()) && !(name.matches(discoveryFilter))) { s_logger.warn("name {} doesn't match discovery filter {}", name, discoveryFilter); continue; } // VPlexVirtualVolumeInfo objects with extended details VPlexVirtualVolumeInfo info = allVirtualVolumes.get(name); // needed for unmanaged volume discovery. // In my testing, I ran into situations where this took so // long that by the time it got to some arbitrary volume to // populate with more details, that volume had been deleted // by some other process and the VPLEX API threw a 404 Not // Found. ...which then caused the whole unmanaged volume // discovery process to fail. // So, there is a very rare chance that processing could get // to this point and the name would would be in the key set, // but the info object would be null... basically if it got // to here null, it would mean a 404 happened earlier. // by some other process and the VPLEX API threw a 404 Not // Found. ...which then caused the whole unmanaged volume // discovery process to fail. // So, there is a very rare chance that processing could get // to this point and the name would would be in the key set, // but the info object would be null... basically if it got // to here null, it would mean a 404 happened earlier. if (null == info) { continue; } Volume managedVolume = findVirtualVolumeManagedByVipr(info); UnManagedVolume unmanagedVolume = findUnmanagedVolumeKnownToVipr(info); if (null == managedVolume) { s_logger.info("Virtual Volume {} is not managed by ViPR", name); if (null != unmanagedVolume) { // just refresh / update the existing unmanaged volume s_logger.info("Unmanaged Volume {} is already known to ViPR", name); updateUnmanagedVolume(info, vplex, unmanagedVolume, volumesToCgs, clusterIdToNameMap, varrayToClusterIdMap, distributedDevicePathToClusterMap, backendVolumeGuidToVvolGuidMap, volumeToStorageViewMap, allVpools); knownUnmanagedVolumes.add(unmanagedVolume); } else { // set up new unmanaged vplex volume s_logger.info("Unmanaged Volume {} is not known to ViPR", name); unmanagedVolume = createUnmanagedVolume(info, vplex, volumesToCgs, clusterIdToNameMap, varrayToClusterIdMap, distributedDevicePathToClusterMap, backendVolumeGuidToVvolGuidMap, volumeToStorageViewMap, allVpools); newUnmanagedVolumes.add(unmanagedVolume); } boolean nonRpExported = false; Set<UnManagedExportMask> uems = volumeToExportMasksMap.get(unmanagedVolume.getNativeGuid()); if (uems != null) { s_logger.info("{} UnManagedExportMasks found in the map for volume {}", uems.size(), unmanagedVolume.getNativeGuid()); for (UnManagedExportMask uem : uems) { s_logger.info(" adding UnManagedExportMask {} to UnManagedVolume", uem.getMaskingViewPath()); unmanagedVolume.getUnmanagedExportMasks().add(uem.getId().toString()); uem.getUnmanagedVolumeUris().add(unmanagedVolume.getId().toString()); unmanagedExportMasksToUpdate.add(uem); // add the known initiators, too for (String initUri : uem.getKnownInitiatorUris()) { s_logger.info(" adding known Initiator URI {} to UnManagedVolume", initUri); unmanagedVolume.getInitiatorUris().add(initUri); Initiator init = _dbClient.queryObject(Initiator.class, URI.create(initUri)); unmanagedVolume.getInitiatorNetworkIds().add(init.getInitiatorPort()); } // log this info for debugging for (String path : uem.getUnmanagedInitiatorNetworkIds()) { s_logger.info(" UnManagedExportMask has this initiator unknown to ViPR: {}", path); } // Check if this volume is in an RP mask, and mark it as an RP // volume if it is if (!recoverPointExportMasks.isEmpty() && recoverPointExportMasks.contains(uem.getId().toString())) { s_logger.info("unmanaged volume {} is an RP volume", unmanagedVolume.getLabel()); unmanagedVolume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_RECOVERPOINT_ENABLED.toString(), TRUE); } else { // this volume is contained in at least one export mask that is non-RP nonRpExported = true; } } } persistUnManagedExportMasks(null, unmanagedExportMasksToUpdate, false); // If this mask isn't RP, then this volume is exported to a host/cluster/initiator or VPLEX. Mark // this as a convenience to ingest features. if (nonRpExported) { s_logger.info("unmanaged volume {} is exported to something other than RP. Marking IS_NONRP_EXPORTED.", unmanagedVolume.getLabel()); unmanagedVolume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_NONRP_EXPORTED.toString(), TRUE); unmanagedVolume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_VOLUME_EXPORTED.toString(), TRUE); } else { s_logger.info( "unmanaged volume {} is not exported OR not exported to something other than RP. Not marking IS_NONRP_EXPORTED.", unmanagedVolume.getLabel()); unmanagedVolume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_NONRP_EXPORTED.toString(), FALSE); unmanagedVolume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_VOLUME_EXPORTED.toString(), FALSE); } persistUnManagedVolumes(newUnmanagedVolumes, knownUnmanagedVolumes, false); } else { s_logger.info("Virtual Volume {} is already managed by ViPR", managedVolume.forDisplay()); Long currentCapacity = info.getCapacityBytes(); if (currentCapacity != null && currentCapacity > managedVolume.getCapacity()) { // update the managed volume's capacity if it changed. this could possibly happen // if the volume were expanded and the final status was not processed successfully by ViPR due to timeout s_logger.info("Virtual Volume {} capacity on VPLEX is different ({}) than in database ({}), updating...", managedVolume.forDisplay(), info.getCapacityBytes(), managedVolume.getCapacity()); managedVolume.setAllocatedCapacity(Long.parseLong(String.valueOf(0))); managedVolume.setProvisionedCapacity(currentCapacity); managedVolume.setCapacity(currentCapacity); _dbClient.updateObject(managedVolume); } } if (null != unmanagedVolume && !unmanagedVolume.getInactive()) { allUnmanagedVolumes.add(unmanagedVolume.getId()); } tracker.volumeTimeResults.put(name, System.currentTimeMillis() - timer); tracker.totalVolumesDiscovered++; s_logger.info("estimated discovery time remaining: " + tracker.getDiscoveryTimeRemaining()); } } else { s_logger.warn("No virtual volumes were found on VPLEX."); } persistUnManagedVolumes(newUnmanagedVolumes, knownUnmanagedVolumes, true); persistUnManagedExportMasks(null, unmanagedExportMasksToUpdate, true); cleanUpOrphanedVolumes(vplex.getId(), allUnmanagedVolumes); // this has to happen at the very end so that the map is complete, // and by supplying the vplex id, we'll re-fetch all the volumes // now that everything has been persisted and orphans cleared out processBackendClones(vplex.getId(), backendVolumeGuidToVvolGuidMap); } catch (Exception ex) { s_logger.error("An error occurred during VPLEX unmanaged volume discovery", ex); String vplexLabel = vplexUri.toString(); if (null != vplex) { vplexLabel = vplex.getLabel(); } throw VPlexCollectionException.exceptions.vplexUnmanagedVolumeDiscoveryFailed( vplexLabel, ex.toString()); } finally { if (null != vplex) { try { vplex.setLastDiscoveryStatusMessage(statusMessage); _dbClient.updateObject(vplex); } catch (Exception ex) { s_logger.error("Error while saving VPLEX discovery status message: {} - Exception: {}", statusMessage, ex.getLocalizedMessage()); } } } } /** * This method iterates through all the front-end virtual volumes * checking for the presence of HAS_REPLICAS or IS_FULL_COPY which were * found earlier in discovery. If found, it will swap the backend * volume GUID with the front-end volume GUID using the * backendVolumeGuidToVvolGuidMap. * * In so doing, the vvols will be relative to each other for FULL_COPIES * and LOCAL_REPLICA_SOURCE_VOLUME, just like the backend volumes are, * which will enable us to link them up like this: * * source vvol * fullCopies: target vvol * source bvol * fullCopies: target bvol * target vvol * associatedSourceVolume: source vvol * target bvol * associatedSourceVolume: source bvol * * @param vplexUri the VPLEX whose unmanaged volumes should be processed * @param backendVolumeGuidToVvolGuidMap a map of backend volume GUIDs * to the GUID of the front volume containing it */ private void processBackendClones(URI vplexUri, Map<String, String> backendVolumeGuidToVvolGuidMap) { URIQueryResultList results = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getStorageSystemUnManagedVolumeConstraint(vplexUri), results); List<UnManagedVolume> changedVolumes = new ArrayList<UnManagedVolume>(); Iterator<UnManagedVolume> allUnmanagedVolumes = _dbClient.queryIterativeObjects(UnManagedVolume.class, results, true); while (allUnmanagedVolumes.hasNext()) { UnManagedVolume unManagedVolume = allUnmanagedVolumes.next(); String isFullCopyStr = unManagedVolume.getVolumeCharacterstics() .get(SupportedVolumeCharacterstics.IS_FULL_COPY.toString()); boolean isFullCopy = (null != isFullCopyStr && Boolean.parseBoolean(isFullCopyStr)); if (isFullCopy) { String backendFullCopySource = VplexBackendIngestionContext .extractValueFromStringSet( SupportedVolumeInformation.LOCAL_REPLICA_SOURCE_VOLUME.name(), unManagedVolume.getVolumeInformation()); if (backendFullCopySource != null && !backendFullCopySource.isEmpty()) { // we're going to swap the backend volume guid for the // front-end virtual volume guid String frontendFullCopySource = backendVolumeGuidToVvolGuidMap.get(backendFullCopySource); StringSet replacementSet = new StringSet(); replacementSet.add(frontendFullCopySource); unManagedVolume.putVolumeInfo( SupportedVolumeInformation.LOCAL_REPLICA_SOURCE_VOLUME.name(), replacementSet); changedVolumes.add(unManagedVolume); } } String hasReplicasStr = unManagedVolume.getVolumeCharacterstics() .get(SupportedVolumeCharacterstics.HAS_REPLICAS.toString()); boolean hasReplicas = (null != hasReplicasStr && Boolean.parseBoolean(hasReplicasStr)); if (hasReplicas) { // HAS_REPLICAS was set during backend volume discovery a little earlier StringSet backendfullCopyTargets = unManagedVolume.getVolumeInformation() .get(SupportedVolumeInformation.FULL_COPIES.name()); if (backendfullCopyTargets != null && !backendfullCopyTargets.isEmpty()) { // we're going to swap the backend volume guids for the // front-end virtual volume guids StringSet replacementSet = new StringSet(); for (String backendfullCopyTarget : backendfullCopyTargets) { String frontendfullCopyTarget = backendVolumeGuidToVvolGuidMap.get(backendfullCopyTarget); replacementSet.add(frontendfullCopyTarget); } unManagedVolume.putVolumeInfo( SupportedVolumeInformation.FULL_COPIES.name(), replacementSet); changedVolumes.add(unManagedVolume); } } // persist any changed volumes if batch limit has been reached persistUnManagedVolumes(null, changedVolumes, false); } // flush any remaining changed volumes persistUnManagedVolumes(null, changedVolumes, true); } /** * Determines if the given VPLEX volume information represents a * virtual volume that is already managed by ViPR. * * @param info a VPlexVirtualVolumeInfo descriptor * @return a Volume object if a match is found in the ViPR database */ private Volume findVirtualVolumeManagedByVipr(VPlexVirtualVolumeInfo info) { if (info != null) { s_logger.info("Determining if Virtual Volume {} is managed by ViPR", info.getName()); String volumeNativeGuid = info.getPath(); s_logger.info("...checking ViPR's Volume table for volume native guid {}", volumeNativeGuid); URIQueryResultList result = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getVolumeNativeIdConstraint(volumeNativeGuid), result); if (result.iterator().hasNext()) { Volume volume = _dbClient.queryObject(Volume.class, result.iterator().next()); // only return active volumes if (null != volume && !volume.getInactive()) { return volume; } } } return null; } /** * Determines if the given VPLEX volume information represents an * unmanaged virtual volume that is already known to ViPR, and * returns the UnManagedVolume object if it is found. * * @param info a VPlexVirtualVolumeInfo descriptor * @return an UnManagedVolume object if found, otherwise null */ private UnManagedVolume findUnmanagedVolumeKnownToVipr(VPlexVirtualVolumeInfo info) { s_logger.info("Determining if Unmanaged Volume {} is known to ViPR", info.getName()); String volumeNativeGuid = info.getPath(); s_logger.info("...checking ViPR's UnManagedVolume table for volume native guid {}", volumeNativeGuid); URIQueryResultList result = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getVolumeInfoNativeIdConstraint(volumeNativeGuid), result); if (result.iterator().hasNext()) { UnManagedVolume unManagedVolume = _dbClient.queryObject(UnManagedVolume.class, result.iterator().next()); if (null != unManagedVolume && !unManagedVolume.getInactive()) { return unManagedVolume; } } return null; } /** * Updates an existing UnManagedVolume with the latest info from * the VPLEX virtual volume. * * @param info a VPlexVirtualVolumeInfo descriptor * @param vplex the VPLEX storage system managing the volume * @param volume the existing UnManagedVolume * @param volumesToCgs a Map of volume labels to consistency group names * @param allVPools cache of all virtual pools for filtering */ private void updateUnmanagedVolume(VPlexVirtualVolumeInfo info, StorageSystem vplex, UnManagedVolume volume, Map<String, String> volumesToCgs, Map<String, String> clusterIdToNameMap, Map<String, String> varrayToClusterIdMap, Map<String, String> distributedDevicePathToClusterMap, Map<String, String> backendVolumeGuidToVvolGuidMap, Map<String, Set<VPlexStorageViewInfo>> volumeToStorageViewMap, Collection<VirtualPool> allVpools) { s_logger.info("Updating UnManagedVolume {} with latest from VPLEX volume {}", volume.getLabel(), info.getName()); volume.setStorageSystemUri(vplex.getId()); volume.setNativeGuid(info.getPath()); volume.setLabel(info.getName()); volume.setWwn(info.getWwn()); volume.getUnmanagedExportMasks().clear(); volume.getInitiatorUris().clear(); volume.getInitiatorNetworkIds().clear(); // set volume characteristics and volume information StringSetMap unManagedVolumeInformation = new StringSetMap(); StringMap unManagedVolumeCharacteristics = new StringMap(); // Set up default MAXIMUM_IO_BANDWIDTH and MAXIMUM_IOPS StringSet bwValues = new StringSet(); bwValues.add("0"); if (unManagedVolumeInformation.get(SupportedVolumeInformation.EMC_MAXIMUM_IO_BANDWIDTH.toString()) == null) { unManagedVolumeInformation.put(SupportedVolumeInformation.EMC_MAXIMUM_IO_BANDWIDTH.toString(), bwValues); } else { unManagedVolumeInformation.get(SupportedVolumeInformation.EMC_MAXIMUM_IO_BANDWIDTH.toString()).replace( bwValues); } StringSet iopsVal = new StringSet(); iopsVal.add("0"); if (unManagedVolumeInformation.get(SupportedVolumeInformation.EMC_MAXIMUM_IOPS.toString()) == null) { unManagedVolumeInformation.put(SupportedVolumeInformation.EMC_MAXIMUM_IOPS.toString(), iopsVal); } else { unManagedVolumeInformation.get(SupportedVolumeInformation.EMC_MAXIMUM_IOPS.toString()).replace(iopsVal); } // check if volume is part of a consistency group, and set the name if so if (volumesToCgs.containsKey(info.getName())) { unManagedVolumeCharacteristics.put( SupportedVolumeCharacterstics.IS_VOLUME_ADDED_TO_CONSISTENCYGROUP.toString(), TRUE); StringSet set = new StringSet(); set.add(volumesToCgs.get(info.getName())); unManagedVolumeInformation.put( SupportedVolumeInformation.VPLEX_CONSISTENCY_GROUP_NAME.toString(), set); } else { unManagedVolumeCharacteristics.put( SupportedVolumeCharacterstics.IS_VOLUME_ADDED_TO_CONSISTENCYGROUP.toString(), FALSE); } // set system type StringSet systemTypes = new StringSet(); systemTypes.add(vplex.getSystemType()); unManagedVolumeInformation.put(SupportedVolumeInformation.SYSTEM_TYPE.toString(), systemTypes); // set volume capacity // For Vplex virtual volumes set allocated capacity to 0 (cop-18608) StringSet provCapacity = new StringSet(); provCapacity.add(String.valueOf(info.getCapacityBytes())); StringSet allocatedCapacity = new StringSet(); allocatedCapacity.add(String.valueOf(0)); unManagedVolumeInformation.put(SupportedVolumeInformation.PROVISIONED_CAPACITY.toString(), provCapacity); unManagedVolumeInformation.put(SupportedVolumeInformation.ALLOCATED_CAPACITY.toString(), allocatedCapacity); // set vplex virtual volume properties unManagedVolumeCharacteristics.put(SupportedVolumeCharacterstics.IS_VPLEX_VOLUME.toString(), TRUE); StringSet locality = new StringSet(); locality.add(info.getLocality()); unManagedVolumeInformation.put(SupportedVolumeInformation.VPLEX_LOCALITY.toString(), locality); StringSet supportingDevice = new StringSet(); supportingDevice.add(info.getSupportingDevice()); unManagedVolumeInformation.put( SupportedVolumeInformation.VPLEX_SUPPORTING_DEVICE_NAME.toString(), supportingDevice); StringSet volumeClusters = new StringSet(); volumeClusters.addAll(info.getClusters()); unManagedVolumeInformation.put(SupportedVolumeInformation.VPLEX_CLUSTER_IDS.toString(), volumeClusters); StringSet accesses = new StringSet(); accesses.add(Volume.VolumeAccessState.READWRITE.getState()); unManagedVolumeInformation.put(SupportedVolumeInformation.ACCESS.toString(), accesses); // set supported vpool list StringSet matchedVPools = new StringSet(); String highAvailability = info.getLocality().equals(LOCAL) ? VirtualPool.HighAvailabilityType.vplex_local.name() : VirtualPool.HighAvailabilityType.vplex_distributed.name(); s_logger.info("finding valid virtual pools for UnManagedVolume {}", volume.getLabel()); for (VirtualPool vpool : allVpools) { // Check to see if: // - The vpool's HA type doesn't match the volume's, unless... // - The vpool is RPVPLEX and this is a VPLEX local volume (likely a journal) if (!vpool.getHighAvailability().equals(highAvailability) && !(VirtualPool.vPoolSpecifiesRPVPlex(vpool) && highAvailability.equals(VirtualPool.HighAvailabilityType.vplex_local.name()))) { s_logger.info(" virtual pool {} is not valid because " + "its high availability setting does not match the unmanaged volume", vpool.getLabel()); continue; } // If the volume is in a CG, the vpool must specify multi-volume consistency. Boolean mvConsistency = vpool.getMultivolumeConsistency(); if ((TRUE.equals(unManagedVolumeCharacteristics.get( SupportedVolumeCharacterstics.IS_VOLUME_ADDED_TO_CONSISTENCYGROUP.toString()))) && ((mvConsistency == null) || (mvConsistency == Boolean.FALSE))) { s_logger.info(" virtual pool {} is not valid because it does not have the " + "multi-volume consistency flag set, and the unmanaged volume is in a consistency group", vpool.getLabel()); continue; } // VPool must be assigned to a varray corresponding to volumes clusters. StringSet varraysForVpool = vpool.getVirtualArrays(); for (String varrayId : varraysForVpool) { String varrayClusterId = varrayToClusterIdMap.get(varrayId); if (null == varrayClusterId) { varrayClusterId = ConnectivityUtil.getVplexClusterForVarray(URI.create(varrayId), vplex.getId(), _dbClient); varrayToClusterIdMap.put(varrayId, varrayClusterId); } if (!ConnectivityUtil.CLUSTER_UNKNOWN.equals(varrayClusterId)) { String varrayClusterName = clusterIdToNameMap.get(varrayClusterId); if (volumeClusters.contains(varrayClusterName)) { matchedVPools.add(vpool.getId().toString()); break; } } } if (!matchedVPools.contains(vpool.getId().toString())) { s_logger.info(" virtual pool {} is not valid because " + "the volume resides on a cluster that does not match the varray(s) associated with the vpool", vpool.getLabel()); } } // set thin provisioning state from virtual-volume thin-enabled property String thinlyProvisioned = info.isThinEnabled() ? TRUE : FALSE; unManagedVolumeCharacteristics.put(SupportedVolumeCharacterstics.IS_THINLY_PROVISIONED.toString(), thinlyProvisioned); // add this info to the unmanaged volume object volume.setVolumeCharacterstics(unManagedVolumeCharacteristics); volume.setVolumeInformation(unManagedVolumeInformation); // discover backend volume data String discoveryMode = ControllerUtils.getPropertyValueFromCoordinator( _coordinator, VplexBackendIngestionContext.DISCOVERY_MODE); if (!VplexBackendIngestionContext.DISCOVERY_MODE_INGESTION_ONLY.equals(discoveryMode)) { try { VplexBackendIngestionContext context = new VplexBackendIngestionContext(volume, _dbClient); context.setDistributedDevicePathToClusterMap(distributedDevicePathToClusterMap); context.discover(); for (UnManagedVolume bvol : context.getUnmanagedBackendVolumes()) { // map this backend volume's GUID to its parent front-end volume GUID backendVolumeGuidToVvolGuidMap.put(bvol.getNativeGuid(), volume.getNativeGuid()); // check if this backend volume is a full copy (and is target of clone) // if so, write this volume's GUID to the parent vvol's LOCAL_REPLICA_SOURCE_VOLUME // so that we can swap it out for the backend parent vvol's GUID String isFullCopyStr = bvol.getVolumeCharacterstics() .get(SupportedVolumeCharacterstics.IS_FULL_COPY.toString()); boolean isFullCopy = (null != isFullCopyStr && Boolean.parseBoolean(isFullCopyStr)); if (isFullCopy) { String fullCopySourceBvol = VplexBackendIngestionContext .extractValueFromStringSet( SupportedVolumeInformation.LOCAL_REPLICA_SOURCE_VOLUME.name(), bvol.getVolumeInformation()); if (fullCopySourceBvol != null && !fullCopySourceBvol.isEmpty()) { StringSet set = new StringSet(); set.add(fullCopySourceBvol); volume.putVolumeInfo( SupportedVolumeInformation.LOCAL_REPLICA_SOURCE_VOLUME.name(), set); volume.putVolumeCharacterstics( SupportedVolumeCharacterstics.IS_FULL_COPY.toString(), Boolean.TRUE.toString()); } } // check if this backend volume has a replica (and is source of clone) // if so, write this volume's GUID to the parent vvol's FULL_COPIES // so that we can swap it out for the backend parent vvol's GUID String hasReplicasStr = bvol.getVolumeCharacterstics() .get(SupportedVolumeCharacterstics.HAS_REPLICAS.toString()); boolean hasReplicas = (null != hasReplicasStr && Boolean.parseBoolean(hasReplicasStr)); if (hasReplicas) { StringSet fullCopyTargetBvols = bvol.getVolumeInformation().get(SupportedVolumeInformation.FULL_COPIES.name()); if (fullCopyTargetBvols != null && !fullCopyTargetBvols.isEmpty()) { // if this backend volume has FULL_COPIES, add them // to the parent virtual volume's FULL_COPIES // and make HAS_REPLICAS is set. StringSet parentSet = volume.getVolumeInformation() .get(SupportedVolumeInformation.FULL_COPIES.name()); if (parentSet == null) { parentSet = new StringSet(); } for (String fullCopyTargetBvol : fullCopyTargetBvols) { parentSet.add(fullCopyTargetBvol); } volume.putVolumeInfo( SupportedVolumeInformation.FULL_COPIES.name(), parentSet); volume.putVolumeCharacterstics( SupportedVolumeCharacterstics.HAS_REPLICAS.toString(), Boolean.TRUE.toString()); } } // set replica state on parent if found in backend volume String replicaState = VplexBackendIngestionContext .extractValueFromStringSet( SupportedVolumeInformation.REPLICA_STATE.name(), bvol.getVolumeInformation()); if (replicaState != null && !replicaState.isEmpty()) { StringSet set = new StringSet(); set.add(replicaState); volume.putVolumeInfo( SupportedVolumeInformation.REPLICA_STATE.name(), set); } // set sync active state on parent if found in backend volume String syncActive = VplexBackendIngestionContext .extractValueFromStringSet( SupportedVolumeInformation.IS_SYNC_ACTIVE.name(), bvol.getVolumeInformation()); if (syncActive != null && !syncActive.isEmpty()) { StringSet set = new StringSet(); set.add(syncActive); volume.putVolumeInfo( SupportedVolumeInformation.IS_SYNC_ACTIVE.name(), set); } } s_logger.info(context.getPerformanceReport()); } catch (Exception ex) { s_logger.warn("error discovering backend structure for {}: ", volume.getNativeGuid(), ex); // no need to throw further } } unManagedVolumeCharacteristics.put( SupportedVolumeCharacterstics.IS_INGESTABLE.toString(), TRUE); if (null == matchedVPools || matchedVPools.isEmpty()) { // clean all supported vpools. volume.getSupportedVpoolUris().clear(); s_logger.info("No matching VPOOLS found for unmanaged volume " + volume.getLabel()); } else { // replace with new StringSet volume.getSupportedVpoolUris().replace(matchedVPools); s_logger.info("Replaced Pools : {}", volume.getSupportedVpoolUris()); } Set<VPlexStorageViewInfo> svs = volumeToStorageViewMap.get(info.getName()); if (svs != null) { updateWwnAndHluInfo(volume, info.getName(), svs); } } /** * For a given UnManagedVolume, determine the wwn from the storage views it is in. * * @param unManagedVolume the UnManagedVolume to check * @param volumeName the original name of the Virtual Volume * @param storageViews the VPlexStorageViewInfo set the unmanaged volume is found in */ private void updateWwnAndHluInfo(UnManagedVolume unManagedVolume, String volumeName, Set<VPlexStorageViewInfo> storageViews) { if (null != storageViews) { String wwn = unManagedVolume.getWwn(); StringSet hluMappings = new StringSet(); for (VPlexStorageViewInfo storageView : storageViews) { if (wwn == null || wwn.isEmpty()) { // the wwn may have been found in the virtual volume, if this is a 5.4+ VPLEX // otherwise, we need to check in the storage view mappings for a WWN (5.3 and before) wwn = storageView.getWWNForStorageViewVolume(volumeName); s_logger.info("found wwn {} for unmanaged volume {}", wwn, volumeName); if (wwn != null) { unManagedVolume.setWwn(BlockObject.normalizeWWN(wwn)); } } Integer hlu = storageView.getHLUForStorageViewVolume(volumeName); if (hlu != null) { hluMappings.add(storageView.getName() + "=" + hlu.toString()); } } if (!hluMappings.isEmpty()) { s_logger.info("setting HLU_TO_EXPORT_MASK_NAME_MAP for unmanaged volume {} to " + hluMappings, volumeName); unManagedVolume.putVolumeInfo( SupportedVolumeInformation.HLU_TO_EXPORT_MASK_NAME_MAP.name(), hluMappings); } } } /** * Creates a new UnManagedVolume with the info from * the VPLEX virtual volume. * * @param info a VPlexVirtualVolumeInfo descriptor * @param vplex the VPLEX storage system managing the volume * @param volumesToCgs a Map of volume labels to consistency group names * @param allVpools cache of virtual pools to filter */ private UnManagedVolume createUnmanagedVolume(VPlexVirtualVolumeInfo info, StorageSystem vplex, Map<String, String> volumesToCgs, Map<String, String> clusterIdToNameMap, Map<String, String> varrayToClusterIdMap, Map<String, String> distributedDevicePathToClusterMap, Map<String, String> backendVolumeGuidToVvolGuidMap, Map<String, Set<VPlexStorageViewInfo>> volumeToStorageViewMap, Collection<VirtualPool> allVpools) { s_logger.info("Creating new UnManagedVolume from VPLEX volume {}", info.getName()); UnManagedVolume volume = new UnManagedVolume(); volume.setId(URIUtil.createId(UnManagedVolume.class)); updateUnmanagedVolume(info, vplex, volume, volumesToCgs, clusterIdToNameMap, varrayToClusterIdMap, distributedDevicePathToClusterMap, backendVolumeGuidToVvolGuidMap, volumeToStorageViewMap, allVpools); return volume; } /** * Used to determine the value of the IS_INGESTABLE flag. * * @param info a VPlexVirtualVolumeInfo descriptor * @return true if the virtual volume is ingestable by ViPR */ private void persistUnManagedVolumes(List<UnManagedVolume> unManagedVolumesToCreate, List<UnManagedVolume> unManagedVolumesToUpdate, boolean flush) { if (null != unManagedVolumesToCreate) { if (flush || (unManagedVolumesToCreate.size() >= BATCH_SIZE)) { _partitionManager.insertInBatches(unManagedVolumesToCreate, BATCH_SIZE, _dbClient, UNMANAGED_VOLUME); unManagedVolumesToCreate.clear(); } } if (null != unManagedVolumesToUpdate) { if (flush || (unManagedVolumesToUpdate.size() >= BATCH_SIZE)) { _partitionManager.updateAndReIndexInBatches(unManagedVolumesToUpdate, BATCH_SIZE, _dbClient, UNMANAGED_VOLUME); unManagedVolumesToUpdate.clear(); } } } /** * This method cleans up UnManaged Volumes in DB, which had been deleted manually from the Array * * 1. Get All UnManagedVolumes from DB for the given VPLEX device * 2. Store URIs of unmanaged volumes returned from the Provider * 3. If unmanaged volume is found only in DB, then set unmanaged volume to inactive. * * @param vplexUri the URI for loading the VPLEX device * @param allUnmanagedVolumes a list of URI for all the newly discovered unmanaged volumes * @throws IOException */ private void cleanUpOrphanedVolumes(URI vplexUri, Set<URI> allUnmanagedVolumes) { URIQueryResultList results = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getStorageSystemUnManagedVolumeConstraint(vplexUri), results); Set<URI> unManagedVolumesInDBSet = new HashSet<URI>(); while (results.iterator().hasNext()) { // why does getting stuff from the database have to be so painful? unManagedVolumesInDBSet.add(results.iterator().next()); } SetView<URI> onlyAvailableinDB = Sets.difference(unManagedVolumesInDBSet, allUnmanagedVolumes); if (onlyAvailableinDB != null && !onlyAvailableinDB.isEmpty()) { s_logger.info("UnManagedVolumes to be Removed : " + Joiner.on("\t").join(onlyAvailableinDB)); List<UnManagedVolume> volumesToBeDeleted = new ArrayList<UnManagedVolume>(); Iterator<UnManagedVolume> unManagedVolumes = _dbClient.queryIterativeObjects(UnManagedVolume.class, new ArrayList<URI>(onlyAvailableinDB)); while (unManagedVolumes.hasNext()) { UnManagedVolume volume = unManagedVolumes.next(); if (null == volume || volume.getInactive()) { continue; } s_logger.info("Setting UnManagedVolume {} inactive", volume.getId()); volume.setStorageSystemUri(NullColumnValueGetter.getNullURI()); volume.setInactive(true); volumesToBeDeleted.add(volume); } if (!volumesToBeDeleted.isEmpty()) { _partitionManager.updateAndReIndexInBatches(volumesToBeDeleted, BATCH_SIZE, _dbClient, UNMANAGED_VOLUME); } } } private UnManagedExportMask getUnManagedExportMaskFromDb(VPlexStorageViewInfo storageView) { URIQueryResultList result = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getUnManagedExportMaskPathConstraint(storageView.getPath()), result); UnManagedExportMask uem = null; Iterator<URI> it = result.iterator(); while (it.hasNext()) { s_logger.info("found an existing unmanaged export mask for storage view " + storageView.getName()); uem = _dbClient.queryObject(UnManagedExportMask.class, it.next()); if (uem != null) { break; } } return uem; } /** * Discovers storage views on the VPLEX and creates UnManagedExportMasks for any * that are not managed by ViPR. * * @param accessProfile providing context for this discovery session * @param client a reference to the VPLEX API client * @param vvolMap map of virtual volume names to virtual volume info objects * @param volumeToExportMasksMap map of volumes to a set of associated UnManagedExportMasks * @param volumeToStorageViewMap map of volumes to a set of associated VPlexStorageViewInfos * @param recoverpointExportMasks recoverpoint export mask uris * @throws BaseCollectionException */ private void discoverUnmanagedStorageViews(AccessProfile accessProfile, VPlexApiClient client, Map<String, VPlexVirtualVolumeInfo> vvolMap, Map<String, Set<UnManagedExportMask>> volumeToExportMasksMap, Map<String, Set<VPlexStorageViewInfo>> volumeToStorageViewMap, Set<String> recoverPointExportMasks) throws BaseCollectionException { String statusMessage = "Starting discovery of Unmanaged VPLEX Storage Views."; s_logger.info(statusMessage + " Access Profile Details : IpAddress : " + "PortNumber : {}, namespace : {}", accessProfile.getIpAddress() + accessProfile.getPortNumber(), accessProfile.getnamespace()); URI vplexUri = accessProfile.getSystemId(); StorageSystem vplex = _dbClient.queryObject(StorageSystem.class, vplexUri); if (null == vplex) { s_logger.error("No VPLEX Device was found in ViPR for URI: " + vplexUri); s_logger.error("Unmanaged VPLEX StorageView discovery cannot continue."); return; } try { // this is a map of cluster id (1 or 2) to the actual cluster name Map<String, String> clusterIdToNameMap = client.getClusterIdToNameMap(); // this is a map of the cluster names to a map of its target port names to wwpns Map<String, Map<String, String>> clusterPortMap = new HashMap<String, Map<String, String>>(); for (String clusterName : clusterIdToNameMap.values()) { Map<String, String> targetPortToPwwnMap = VPlexControllerUtils.getTargetPortToPwwnMap(client, clusterName); clusterPortMap.put(clusterName, targetPortToPwwnMap); } Set<URI> allCurrentUnManagedExportMaskUris = new HashSet<URI>(); List<UnManagedExportMask> unManagedExportMasksToCreate = new ArrayList<UnManagedExportMask>(); List<UnManagedExportMask> unManagedExportMasksToUpdate = new ArrayList<UnManagedExportMask>(); Set<URI> rpPortInitiators = RPHelper.getBackendPortInitiators(_dbClient); for (String clusterName : clusterIdToNameMap.values()) { List<VPlexStorageViewInfo> storageViews = client.getStorageViewsForCluster(clusterName); for (VPlexStorageViewInfo storageView : storageViews) { s_logger.info("discovering storage view: " + storageView.toString()); List<Initiator> knownInitiators = new ArrayList<Initiator>(); List<StoragePort> knownPorts = new ArrayList<StoragePort>(); UnManagedExportMask uem = getUnManagedExportMaskFromDb(storageView); if (uem != null) { s_logger.info("found an existing unmanaged export mask for storage view " + storageView.getName()); unManagedExportMasksToUpdate.add(uem); // clean up collections (we'll be refreshing them) uem.getKnownInitiatorUris().clear(); uem.getKnownInitiatorNetworkIds().clear(); uem.getKnownStoragePortUris().clear(); uem.getKnownVolumeUris().clear(); uem.getUnmanagedInitiatorNetworkIds().clear(); uem.getUnmanagedStoragePortNetworkIds().clear(); uem.getUnmanagedVolumeUris().clear(); } else { s_logger.info("creating a new unmanaged export mask for storage view " + storageView.getName()); uem = new UnManagedExportMask(); unManagedExportMasksToCreate.add(uem); } // set basic info uem.setNativeId(storageView.getPath()); uem.setMaskingViewPath(storageView.getPath()); uem.setMaskName(storageView.getName()); uem.setStorageSystemUri(accessProfile.getSystemId()); s_logger.info("now discovering host initiators in storage view " + storageView.getName()); for (String initiatorNetworkId : storageView.getInitiatorPwwns()) { s_logger.info("looking at initiator network id " + initiatorNetworkId); if (initiatorNetworkId != null && initiatorNetworkId.matches(ISCSI_PATTERN) && (iSCSIUtility.isValidIQNPortName(initiatorNetworkId) || iSCSIUtility.isValidEUIPortName(initiatorNetworkId))) { s_logger.info("\tiSCSI network id normalized to " + initiatorNetworkId); } else if (initiatorNetworkId != null && initiatorNetworkId.matches(REGISTERED_PATTERN)) { initiatorNetworkId = initiatorNetworkId.substring(REGISTERED_PORT_PREFIX.length()); initiatorNetworkId = WWNUtility.getWWNWithColons(initiatorNetworkId); s_logger.info("\tRegistered network id normalized to " + initiatorNetworkId); } else if (WWNUtility.isValidWWNAlias(initiatorNetworkId)) { initiatorNetworkId = WWNUtility.getWWNWithColons(initiatorNetworkId); s_logger.info("\twwn normalized to " + initiatorNetworkId); } else { s_logger.warn("\tthis is not a valid network id format, skipping"); continue; } // check if a host initiator exists for this id // if so, add to _knownInitiators // otherwise, add to _unmanagedInitiators Initiator knownInitiator = NetworkUtil.getInitiator(initiatorNetworkId, _dbClient); if (knownInitiator != null) { s_logger.info(" found an initiator in ViPR on host " + knownInitiator.getHostName()); uem.getKnownInitiatorUris().add(knownInitiator.getId().toString()); uem.getKnownInitiatorNetworkIds().add(knownInitiator.getInitiatorPort()); knownInitiators.add(knownInitiator); } else { s_logger.info(" no hosts in ViPR found configured for initiator " + initiatorNetworkId); uem.getUnmanagedInitiatorNetworkIds().add(initiatorNetworkId); } } s_logger.info("now discovering storage ports in storage view " + storageView.getName()); List<String> targetPortNames = storageView.getPorts(); if (targetPortNames.isEmpty()) { s_logger.info("no storage ports found in storage view " + storageView.getName()); // continue; ? } // target port has value like - P0000000046E01E80-A0-FC02 // PortWwn has value like - 0x50001442601e8002 List<String> portWwns = new ArrayList<String>(); for (String targetPortName : targetPortNames) { Map<String, String> portToWwpnMap = clusterPortMap.get(clusterName); if (portToWwpnMap.keySet().contains(targetPortName)) { portWwns.add(WwnUtils.convertWWN(portToWwpnMap.get(targetPortName), WwnUtils.FORMAT.COLON)); } } for (String portNetworkId : portWwns) { s_logger.info("looking at storage port network id " + portNetworkId); // check if a storage port exists for this id in ViPR // if so, add to _storagePorts StoragePort knownStoragePort = NetworkUtil.getStoragePort(portNetworkId, _dbClient); if (knownStoragePort != null) { s_logger.info(" found a matching storage port in ViPR " + knownStoragePort.getLabel()); uem.getKnownStoragePortUris().add(knownStoragePort.getId().toString()); knownPorts.add(knownStoragePort); } else { s_logger.info(" no storage port in ViPR found matching portNetworkId " + portNetworkId); uem.getUnmanagedStoragePortNetworkIds().add(portNetworkId); } } s_logger.info("now discovering storage volumes in storage view " + storageView.getName()); for (String volumeNameStr : storageView.getVirtualVolumes()) { s_logger.info("found volume " + volumeNameStr); // volumeNameStr contains value like // (161,dd_V000195701573-00D57_VAPM00140801303-00614_vol,VPD83T3:6000144000000010f07dc46a0717e61d,2G) String[] tokens = volumeNameStr.split(","); String volumeName = tokens[1]; VPlexVirtualVolumeInfo vvol = vvolMap.get(volumeName); Volume volume = findVirtualVolumeManagedByVipr(vvol); if (volume != null) { s_logger.info("this is a volume already managed by ViPR: " + volume.getLabel()); uem.getKnownVolumeUris().add(volume.getId().toString()); } // add to map of volume paths to export masks if (vvol != null) { String nativeGuid = vvol.getPath(); s_logger.info("nativeGuid UnManagedVolume key for locating UnManagedExportMasks is " + nativeGuid); Set<UnManagedExportMask> maskSet = volumeToExportMasksMap.get(nativeGuid); if (maskSet == null) { maskSet = new HashSet<UnManagedExportMask>(); s_logger.info(" creating new maskSet for nativeGuid " + nativeGuid); volumeToExportMasksMap.put(nativeGuid, maskSet); } maskSet.add(uem); } // add this storage view to the volume to storage view map for this volume Set<VPlexStorageViewInfo> storageViewSet = volumeToStorageViewMap.get(volumeName); if (storageViewSet == null) { storageViewSet = new HashSet<VPlexStorageViewInfo>(); } storageViewSet.add(storageView); volumeToStorageViewMap.put(volumeName, storageViewSet); } if (uem.getId() == null) { uem.setId(URIUtil.createId(UnManagedExportMask.class)); } if (checkRecoverPointExportMask(uem, knownInitiators, rpPortInitiators)) { recoverPointExportMasks.add(uem.getId().toString()); } updateZoningMap(uem, knownInitiators, knownPorts); persistUnManagedExportMasks(unManagedExportMasksToCreate, unManagedExportMasksToUpdate, false); allCurrentUnManagedExportMaskUris.add(uem.getId()); } } persistUnManagedExportMasks(unManagedExportMasksToCreate, unManagedExportMasksToUpdate, true); cleanUpOrphanedExportMasks(vplexUri, allCurrentUnManagedExportMaskUris); } catch (Exception ex) { s_logger.error(ex.getLocalizedMessage(), ex); String vplexLabel = vplexUri.toString(); if (null != vplex) { vplexLabel = vplex.getLabel(); } throw VPlexCollectionException.exceptions.vplexUnmanagedExportMaskDiscoveryFailed( vplexLabel, ex.getLocalizedMessage()); } finally { if (null != vplex) { try { vplex.setLastDiscoveryStatusMessage(statusMessage); _dbClient.updateObject(vplex); } catch (Exception ex) { s_logger.error("Error while saving VPLEX discovery status message: {} - Exception: {}", statusMessage, ex.getLocalizedMessage()); } } } } private void updateZoningMap(UnManagedExportMask mask, List<Initiator> initiators, List<StoragePort> storagePorts) { try { s_logger.info(" Updating zoning map for vplex mask " + mask.getMaskName()); if (mask.getZoningMap() != null) { mask.getZoningMap().replace(_networkDeviceController.getInitiatorsZoneInfoMap(initiators, storagePorts)); } else { mask.setZoningMap(_networkDeviceController.getInitiatorsZoneInfoMap(initiators, storagePorts)); } } catch (Exception ex) { mask.setZoningMap(null); s_logger.error("Failed to get the zoning map for vplex mask {}", mask.getMaskName()); } } /** * Checks if the unmanaged export mask is RP mask by looking at the initiators * and determining if any of them represent RPA front-end ports * * @param uem - the UnManagedExportMask * @param initiators - the initiators to test for RPA ports status * @param rpPortInitiators - the RP front-end ports */ private boolean checkRecoverPointExportMask(UnManagedExportMask mask, List<Initiator> initiators, Set<URI> rpPortInitiators) { StringBuilder nonRecoverPointInitiators = new StringBuilder(); int rpPortInitiatorCount = 0; for (Initiator init : initiators) { if (rpPortInitiators.contains(init.getId())) { s_logger.info("export mask {} contains RPA initiator {}", mask.getMaskName(), init.getInitiatorPort()); rpPortInitiatorCount++; } else { nonRecoverPointInitiators.append(init.getInitiatorPort()).append(" "); } } if (rpPortInitiatorCount > 0) { s_logger.info("export mask {} contains {} RPA initiators", mask.getMaskName(), rpPortInitiatorCount); if (rpPortInitiatorCount < initiators.size()) { s_logger.warn(" there are some ports in this mask that are not " + "RPA initiators: " + nonRecoverPointInitiators); } return true; } return false; } /** * Handles persisting UnManagedExportMasks in batches. * * @param unManagedExportMasksToCreate UnManagedExportMasks to be created * @param unManagedExportMasksToUpdate UnManagedExportMasks to be updated * @param flush if true, persistence with be forced */ private void persistUnManagedExportMasks(List<UnManagedExportMask> unManagedExportMasksToCreate, List<UnManagedExportMask> unManagedExportMasksToUpdate, boolean flush) { if (null != unManagedExportMasksToCreate) { if (flush || (unManagedExportMasksToCreate.size() >= BATCH_SIZE)) { _partitionManager.insertInBatches(unManagedExportMasksToCreate, BATCH_SIZE, _dbClient, UNMANAGED_EXPORT_MASK); unManagedExportMasksToCreate.clear(); } } if (null != unManagedExportMasksToUpdate) { if (flush || (unManagedExportMasksToUpdate.size() >= BATCH_SIZE)) { _partitionManager.updateInBatches(unManagedExportMasksToUpdate, BATCH_SIZE, _dbClient, UNMANAGED_EXPORT_MASK); unManagedExportMasksToUpdate.clear(); } } } /** * Cleans up any UnManagedExportMask objects that are present in the ViPR database, * but are no longer present on the VPLEX device. * * @param vplexUri device id of the VPLEX * @param allCurrentUnManagedExportMaskUris all the UnManagedExportMasks we found in this discovery run */ private void cleanUpOrphanedExportMasks(URI vplexUri, Set<URI> allCurrentUnManagedExportMaskUris) { URIQueryResultList result = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory .getStorageSystemUnManagedExportMaskConstraint(vplexUri), result); Set<URI> allMasksInDatabase = new HashSet<URI>(); Iterator<URI> it = result.iterator(); while (it.hasNext()) { allMasksInDatabase.add(it.next()); } SetView<URI> onlyAvailableinDB = Sets.difference(allMasksInDatabase, allCurrentUnManagedExportMaskUris); if (!onlyAvailableinDB.isEmpty()) { s_logger.info("these UnManagedExportMasks are orphaned and will be cleaned up:" + Joiner.on("\t").join(onlyAvailableinDB)); List<UnManagedExportMask> unManagedExportMasksToBeDeleted = new ArrayList<UnManagedExportMask>(); Iterator<UnManagedExportMask> unManagedExportMasks = _dbClient.queryIterativeObjects(UnManagedExportMask.class, new ArrayList<URI>(onlyAvailableinDB)); while (unManagedExportMasks.hasNext()) { UnManagedExportMask uem = unManagedExportMasks.next(); if (null == uem || uem.getInactive()) { continue; } s_logger.info("Setting UnManagedExportMask {} inactive", uem.getMaskingViewPath()); uem.setStorageSystemUri(NullColumnValueGetter.getNullURI()); uem.setInactive(true); unManagedExportMasksToBeDeleted.add(uem); } if (!unManagedExportMasksToBeDeleted.isEmpty()) { _partitionManager.updateAndReIndexInBatches(unManagedExportMasksToBeDeleted, BATCH_SIZE, _dbClient, UNMANAGED_EXPORT_MASK); } } } /** * Implementation for discovering everything in a VPLEX storage system. * * @param accessProfile providing context for this discovery session * * @throws BaseCollectionException */ private void discoverAll(AccessProfile accessProfile) throws BaseCollectionException { boolean discoverySuccess = true; StringBuffer errMsgBuilder = new StringBuffer(); URI storageSystemURI = null; StorageSystem vplexStorageSystem = null; String detailedStatusMessage = "Unknown Status"; VPlexApiClient client = null; try { s_logger.info("Access Profile Details : IpAddress : {}, PortNumber : {}", accessProfile.getIpAddress(), accessProfile.getPortNumber()); storageSystemURI = accessProfile.getSystemId(); // Get the VPlex storage system from the database. vplexStorageSystem = _dbClient.queryObject(StorageSystem.class, storageSystemURI); s_logger.info("Discover VPlex storage system {} at IP:{}, PORT:{}", new Object[] { storageSystemURI.toString(), accessProfile.getIpAddress(), accessProfile.getPortNumber() }); // Get the Http client for getting information about the VPlex // storage system. client = getVPlexAPIClient(accessProfile); s_logger.debug("Got handle to VPlex API client"); // clear cached discovery data in the VPlexApiClient client.clearCaches(); client.primeCaches(); // The version for the storage system is the version of its active provider // and since we are discovering it, the provider was compatible, so the // VPLEX must also be compatible. StorageProvider activeProvider = _dbClient.queryObject(StorageProvider.class, vplexStorageSystem.getActiveProviderURI()); String serialNumber = getSystemSerialNumber(client, activeProvider, null); if (!vplexStorageSystem.getSerialNumber().equals(serialNumber)) { s_logger.error(String.format("The VPLEX serial number unexpectedly changed from %s to %s.", vplexStorageSystem.getSerialNumber(), serialNumber)); throw VPlexApiException.exceptions.vplexSerialNumberChanged(vplexStorageSystem.getSerialNumber(), serialNumber); } vplexStorageSystem.setFirmwareVersion(activeProvider.getVersionString()); vplexStorageSystem.setCompatibilityStatus(CompatibilityStatus.COMPATIBLE.toString()); // Discover the cluster identification (serial number / cluster id ) mapping try { s_logger.info("Discovering cluster identification."); discoverClusterIdentification(vplexStorageSystem, client); _completer.statusPending(_dbClient, "Completed cluster identification discovery"); } catch (VPlexCollectionException vce) { discoverySuccess = false; String errMsg = String.format("Failed cluster identification discovery for VPlex %s", storageSystemURI.toString()); s_logger.error(errMsg, vce); if (errMsgBuilder.length() != 0) { errMsgBuilder.append(", "); } errMsgBuilder.append(errMsg); } List<StoragePort> allPorts = new ArrayList<StoragePort>(); // Discover the VPlex port information. try { // When we discover storage ports on the VPlex, we create // initiators, if they don't exist, for backend ports. // The backend storage ports serve as initiators for the // connected backend storage. s_logger.info("Discovering frontend and backend ports."); discoverPorts(client, vplexStorageSystem, allPorts, null); _dbClient.updateObject(vplexStorageSystem); _completer.statusPending(_dbClient, "Completed port discovery"); } catch (VPlexCollectionException vce) { discoverySuccess = false; String errMsg = String.format("Failed port discovery for VPlex %s", storageSystemURI.toString()); s_logger.error(errMsg, vce); if (errMsgBuilder.length() != 0) { errMsgBuilder.append(", "); } errMsgBuilder.append(errMsg); } // update host initiators with registered initiator names from VPLEX try { updateHostInitiators(client, vplexStorageSystem.getSerialNumber()); } catch (VPlexCollectionException vce) { discoverySuccess = false; String errMsg = String.format("Failed host initiator update for VPlex %s", storageSystemURI.toString()); s_logger.error(errMsg, vce); if (errMsgBuilder.length() != 0) { errMsgBuilder.append(", "); } errMsgBuilder.append(errMsg); } try { s_logger.info("Discovering connectivity."); discoverConnectivity(vplexStorageSystem); _dbClient.updateObject(vplexStorageSystem); _completer.statusPending(_dbClient, "Completed connectivity verification"); } catch (VPlexCollectionException vce) { discoverySuccess = false; String errMsg = String.format( "Failed connectivity discovery for VPlex %s", storageSystemURI.toString()); s_logger.error(errMsg, vce); if (errMsgBuilder.length() != 0) { errMsgBuilder.append(", "); } errMsgBuilder.append(errMsg); } if (discoverySuccess) { vplexStorageSystem.setReachableStatus(true); _dbClient.updateObject(vplexStorageSystem); } else { // If part of the discovery process failed, throw an exception. vplexStorageSystem.setReachableStatus(false); _dbClient.updateObject(vplexStorageSystem); throw new Exception(errMsgBuilder.toString()); } StoragePortAssociationHelper.runUpdatePortAssociationsProcess(allPorts, null, _dbClient, _coordinator, null); // discovery succeeds detailedStatusMessage = String.format("Discovery completed successfully for Storage System: %s", storageSystemURI.toString()); } catch (Exception e) { if (null != client) { // clear cached discovery data in the VPlexApiClient client.clearCaches(); } VPlexCollectionException vce = VPlexCollectionException.exceptions.failedDiscovery( storageSystemURI.toString(), e.getLocalizedMessage()); detailedStatusMessage = vce.getLocalizedMessage(); s_logger.error(detailedStatusMessage, e); throw vce; } finally { if (vplexStorageSystem != null) { try { // set detailed message vplexStorageSystem.setLastDiscoveryStatusMessage(detailedStatusMessage); _dbClient.updateObject(vplexStorageSystem); } catch (DatabaseException ex) { s_logger.error("Error persisting last discovery status for storage system {}", vplexStorageSystem.getId(), ex); } } } } /** * Update host Initiator names in ViPR database according to what is found * on each VPLEX cluster as a registered name for the Initiator's port. * * @param client the VPLEX api client * @param systemSerialNumber the VPLEX system serial number */ private void updateHostInitiators(VPlexApiClient client, String systemSerialNumber) { // get all the Initiators in vipr List<URI> initiatorUris = _dbClient.queryByType(Initiator.class, true); Iterator<Initiator> hostInitiators = _dbClient.queryIterativeObjects(Initiator.class, initiatorUris, true); List<Initiator> initiatorsToPersist = new ArrayList<Initiator>(); // assemble a small map of the cluster names to the initiator name key for that cluster. // for example: FNM00114300288:FNM00114600001|cluster-1 and FNM00114300288:FNM00114600001|cluster-2. // this is just for efficiency, so we don't have to assemble the key over and over again. List<String> vplexClusterNames = new ArrayList<String>(client.getClusterIdToNameMap().values()); Map<String, String> clusterNameToInitNameKey = new HashMap<String, String>(2); for (String vplexClusterName : vplexClusterNames) { String initiatorNameKey = systemSerialNumber + VPlexApiConstants.INITIATOR_CLUSTER_NAME_DELIM + vplexClusterName; clusterNameToInitNameKey.put(vplexClusterName, initiatorNameKey); } // iterate through all the host Initiators in vipr and // update the initiator names mappings if necessary Boolean[] doRefresh = new Boolean[] { new Boolean(true) }; while (hostInitiators.hasNext()) { Initiator hostInitiator = hostInitiators.next(); for (String vplexClusterName : vplexClusterNames) { // find the current name on this vplex cluster hardware for the Initiator portWwn, // and also get the current value found in the database for comparison. String portWwn = hostInitiator.getInitiatorPort(); String vplexInitiatorName = client.getInitiatorNameForWwn( vplexClusterName, WWNUtility.getUpperWWNWithNoColons(portWwn), doRefresh); String initiatorNameKey = clusterNameToInitNameKey.get(vplexClusterName); String viprInitiatorName = hostInitiator.getInitiatorNames().get(initiatorNameKey); // if a registered initiator name was found, and it hasn't already been mapped, update the Initiator. // otherwise, if it is mapped in the vipr database, but it's no longer on the vplex, unmap it. if (vplexInitiatorName != null && !vplexInitiatorName.startsWith(VPlexApiConstants.UNREGISTERED_INITIATOR_PREFIX)) { if (!vplexInitiatorName.equals(viprInitiatorName)) { // map hostInitiator.mapInitiatorName(initiatorNameKey, vplexInitiatorName); initiatorsToPersist.add(hostInitiator); } } else if (hostInitiator.getInitiatorNames().containsKey(initiatorNameKey)) { // unmap hostInitiator.unmapInitiatorName(initiatorNameKey); initiatorsToPersist.add(hostInitiator); } } } _dbClient.updateObject(initiatorsToPersist); } /** * Discover the connectivity for the passed VPLEX storage system. * * @param storageSystem The VPLEX storage system. */ private void discoverConnectivity(StorageSystem storageSystem) { StringSet newVarrays = StoragePoolAssociationHelper .getVplexSystemConnectedVarrays(storageSystem.getId(), _dbClient); if (storageSystem.getVirtualArrays() == null) { storageSystem.setVirtualArrays(newVarrays); } else { storageSystem.getVirtualArrays().replace(newVarrays); } _dbClient.updateAndReindexObject(storageSystem); } /** * Discovers and creates the ports for the passed VPlex virtual storage * system. * * @param client The VPlex API client. * @param vplexStorageSystem A reference to the VPlex storage system. * @param autoUpgradePortsMap a map of port wwns to StoragePorts from the * original cluster in a local to metro auto upgrade situation * * @throws VPlexCollectionException When an error occurs discovering the * VPlex ports. */ private void discoverPorts(VPlexApiClient client, StorageSystem vplexStorageSystem, List<StoragePort> allPorts, Map<String, StoragePort> autoUpgradePortsMap) throws VPlexCollectionException { List<StoragePort> newStoragePorts = new ArrayList<StoragePort>(); List<StoragePort> existingStoragePorts = new ArrayList<StoragePort>(); List<Initiator> newInitiatorPorts = new ArrayList<Initiator>(); try { // Get the port information from the VPlex. String initiatorHostName = null; List<VPlexPortInfo> portInfoList = client.getPortInfo(false); Map<String, VPlexTargetInfo> portTargetMap = client.getTargetInfoForPorts(portInfoList); for (VPlexPortInfo portInfo : portInfoList) { s_logger.debug("VPlex port info: {}", portInfo.toString()); if (null == portInfo.getPortWwn()) { s_logger.info("Not a FC port, skipping port {}", portInfo.getName()); continue; } // VPlex director port can have a variety of roles. They can // be front-end ports for exposing VPlex virtual volumes to // hosts. They can be back-end ports that serve as initiators // to the connected back-end storage systems and expose back-end // storage volumes to the VPlex. They can also be WAN ports that // connect VPlex directors in the cluster. For now we are only // concerned with front-end and back-end ports. We create // StoragePort instances for these ports. if ((!portInfo.isFrontendPort()) && (!portInfo.isBackendPort())) { s_logger.debug("Not a front/back-end port, skipping port {}", portInfo.getName()); continue; } String portWWN = WWNUtility.getWWNWithColons(portInfo.getPortWwn()); String portType = portInfo.isBackendPort() ? PortType.backend.name() : PortType.frontend.name(); s_logger.info("Found {} port {}", portType, portWWN); // Ports that are not online can have a WWN that is not set. // In this case, WWN would be 00:00:00:00:00:00:00:00. We // ignore these ports. Change created to address CQ 649101, where // the frontend ports on one of the clusters directors were all // offline, resulting in 4 ports in the DB with the same unset // WWN. if ((portWWN == null) || (portWWN.equals(OFFLINE_PORT_WWN))) { s_logger.info("Skipping port {} with WWN {}", portInfo.getName(), portWWN); continue; } // See if the port already exists in the DB. If not we need to // create it. StoragePort storagePort = findPortInDB(vplexStorageSystem, portInfo, autoUpgradePortsMap); if (storagePort == null) { s_logger.info("Creating new port {}", portWWN); storagePort = new StoragePort(); storagePort.setId(URIUtil.createId(StoragePort.class)); storagePort.setPortNetworkId(portWWN); storagePort.setPortName(portInfo.getName()); storagePort.setStorageDevice(vplexStorageSystem.getId()); String nativeGuid = NativeGUIDGenerator.generateNativeGuid(_dbClient, storagePort); storagePort.setNativeGuid(nativeGuid); storagePort.setLabel(nativeGuid); storagePort.setPortType(portType); storagePort.setTransportType(StorageProtocol.Block.FC.name()); // Always FC setHADomainForStoragePort(vplexStorageSystem, storagePort, portInfo); storagePort.setRegistrationStatus(RegistrationStatus.REGISTERED.toString()); newStoragePorts.add(storagePort); } else { existingStoragePorts.add(storagePort); } // CTRL-4701 - if we got to this point, the VPLEX firmware was validated as compatible, // so, the storage port should be marked compatible as well storagePort.setCompatibilityStatus(DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name()); storagePort.setDiscoveryStatus(DiscoveryStatus.VISIBLE.name()); // Port speed is current port speed and should be updated // for existing ports. storagePort.setPortSpeed(portInfo.getCurrentSpeed(SpeedUnits.GBITS_PER_SECOND)); // Set or update the port operational status. storagePort.setOperationalStatus(getPortOperationalStatus(portInfo, portTargetMap)); // If there is not an initiator in the database representing the // backend storage port, then create one and add it to the passed // list. if (portInfo.isBackendPort()) { Initiator initiatorPort = findInitiatorInDB(portInfo); if (initiatorPort == null) { s_logger.info("Creating initiator for backend port", portWWN); if (initiatorHostName == null) { initiatorHostName = getInitiatorHostName(vplexStorageSystem); } s_logger.info("Host name is {}", initiatorHostName); initiatorPort = new Initiator(StorageProtocol.Block.FC.name(), portWWN, WWNUtility.getWWNWithColons(portInfo.getNodeWwn()), initiatorHostName, false); initiatorPort.setId(URIUtil.createId(Initiator.class)); newInitiatorPorts.add(initiatorPort); } } } // Persist changes to new and exiting ports and initiators. _dbClient.createObject(newStoragePorts); _dbClient.updateObject(existingStoragePorts); _dbClient.createObject(newInitiatorPorts); allPorts.addAll(newStoragePorts); allPorts.addAll(existingStoragePorts); List<StoragePort> notVisiblePorts = DiscoveryUtils.checkStoragePortsNotVisible(allPorts, _dbClient, vplexStorageSystem.getId()); if (notVisiblePorts != null && !notVisiblePorts.isEmpty()) { allPorts.addAll(notVisiblePorts); } } catch (Exception e) { s_logger.error("Error discovering ports for the VPLEX storage system {}:", vplexStorageSystem.getIpAddress(), e); throw VPlexCollectionException.exceptions.failedPortsDiscovery( vplexStorageSystem.getId().toString(), e.getLocalizedMessage(), e); } } /** * Implementation for statistics collection for VPlex storage systems. * * @param accessProfile * * @throws BaseCollectionException */ @Override public void collectStatisticsInformation(AccessProfile accessProfile) throws BaseCollectionException { initializeContext(accessProfile); _statsCollector.collect(accessProfile, _keyMap); dumpStatRecords(); injectStats(); } /** * Get the HTTP client for making requests to the VPlex at the * endpoint specified in the passed profile. * * @param accessProfile A reference to the access profile. * * @return A reference to the VPlex API HTTP client. * @throws URISyntaxException */ private VPlexApiClient getVPlexAPIClient(AccessProfile accessProfile) throws URISyntaxException { // Create the URI to access the VPlex Management Station based // on the IP and port in the passed access profile. URI vplexEndpointURI = new URI("https", null, accessProfile.getIpAddress(), accessProfile.getPortNumber(), "/", null, null); s_logger.debug("VPlex base URI is {}", vplexEndpointURI.toString()); VPlexApiClient client = _apiFactory.getClient(vplexEndpointURI, accessProfile.getUserName(), accessProfile.getPassword()); return client; } /** * Find the port in the data base corresponding to the passed port * information. * * @param vplexStorageSystem A reference to the port's storage system. * @param portInfo The port information. * @param autoUpgradePortsMap a map of port wwns to StoragePorts from the * original cluster in a local to metro auto upgrade situation * * @return The found StoragePort instance, or null if not found. * * @throws IOException When an error occurs querying the database. */ private StoragePort findPortInDB(StorageSystem vplexStorageSystem, VPlexPortInfo portInfo, Map<String, StoragePort> autoUpgradePortsMap) throws IOException { StoragePort port = null; String portWWN = WWNUtility.getWWNWithColons(portInfo.getPortWwn()); String portNativeGuid = NativeGUIDGenerator.generateNativeGuid( vplexStorageSystem, portWWN, NativeGUIDGenerator.PORT); if (null != autoUpgradePortsMap && autoUpgradePortsMap.containsKey(portWWN)) { s_logger.info("Found port {} in the auto upgrade ports map", portWWN); return autoUpgradePortsMap.get(portWWN); } s_logger.info("Looking for port {} in database", portNativeGuid); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getStoragePortByNativeGuidConstraint(portNativeGuid), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); if (resultsIter.hasNext()) { s_logger.info("Found port {}", portNativeGuid); port = _dbClient.queryObject(StoragePort.class, resultsIter.next()); } return port; } /** * Find the initiator in the data base corresponding to the passed port * information. * * @param portInfo The port information. * * @return The found Initiator instance, or null if not found. * * @throws IOException When an error occurs querying the database. */ private Initiator findInitiatorInDB(VPlexPortInfo portInfo) throws IOException { Initiator initiator = null; String portWWN = WWNUtility.getWWNWithColons(portInfo.getPortWwn()); s_logger.debug("Looking for initiator {} in database", portWWN); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getInitiatorPortInitiatorConstraint(portWWN), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); if (resultsIter.hasNext()) { s_logger.debug("Found initiator {}", portWWN); initiator = _dbClient.queryObject(Initiator.class, resultsIter.next()); } return initiator; } private String getInitiatorHostName(StorageSystem vplexStorageSystem) { // By default, we use the IP address of the active VPLEX management // server used for discovery for the host name for initiators // associated with the VPLEX. URI activeMgmntSvrURI = vplexStorageSystem.getActiveProviderURI(); StorageProvider activeMgmntSvr = _dbClient.queryObject(StorageProvider.class, activeMgmntSvrURI); String hostName = activeMgmntSvr.getIPAddress(); if (IPAddressUtil.isIPv4LiteralAddress(hostName) || IPAddressUtil.isIPv6LiteralAddress(hostName)) { // The VNX cannot deal with literal IP addresses in the hostName // field of an initiator. Make it something more reasonable. hostName = VPLEX_INITIATOR_HOSTNAME_PREFIX + hostName; s_logger.info("New host name is {}", hostName); } // The default is to use the IP of the active provider when no initiators // are found for this VPLEX system. String dfltHostName = hostName; // Check to see if there are any existing initiators with this host name. // If there are, then return this host name. List<Initiator> initiatorsWithHostName = CustomQueryUtility .queryActiveResourcesByAltId(_dbClient, Initiator.class, "hostname", hostName); if (!initiatorsWithHostName.isEmpty()) { return hostName; } // Otherwise, see if there are any initiators with a host name based // on any other providers for this vplex system. It could be that the // active management server has changed, and since that happened new // backend ports were added to the VPLEX. We want to make sure that all // initiators have the same host name. So it could be that previously // discovered backend ports have a host name based on the IP of a now // inactive management server. StringSet mgmntSvrIds = vplexStorageSystem.getProviders(); for (String mgmntSvrId : mgmntSvrIds) { if (mgmntSvrId.equals(activeMgmntSvrURI.toString())) { continue; } StorageProvider mgmntSvr = _dbClient.queryObject(StorageProvider.class, URI.create(mgmntSvrId)); hostName = mgmntSvr.getIPAddress(); if (IPAddressUtil.isIPv4LiteralAddress(hostName) || IPAddressUtil.isIPv6LiteralAddress(hostName)) { hostName = VPLEX_INITIATOR_HOSTNAME_PREFIX + hostName; s_logger.info("New host name is {}", hostName); } // Check to see if there are any existing initiators with // this host name. If there are, then return this host name. initiatorsWithHostName = CustomQueryUtility .queryActiveResourcesByAltId(_dbClient, Initiator.class, "hostname", hostName); if (!initiatorsWithHostName.isEmpty()) { return hostName; } } // There are no initiators for the VPLEX. Likely this is the initial // discovery of the VPLEX. Just use the default. return dfltHostName; } /** * Sets the storage HA domain for the passed port, creating and persisting * the domain if necessary. * * @param vplexStorageSystem A reference to the VPlex virtual storage * system. * @param storagePort The storage port whose HA domain is to be set. * @param portInfo The port information from the VPlex. * * @throws IOException When an error occurs accessing the database. */ private void setHADomainForStoragePort(StorageSystem vplexStorageSystem, StoragePort storagePort, VPlexPortInfo portInfo) throws IOException { StorageHADomain haDomain = null; VPlexDirectorInfo directorInfo = portInfo.getDirectorInfo(); String directorSerialNumber = directorInfo.getSerialNumber(); String directorNativeGuid = NativeGUIDGenerator.generateNativeGuid( vplexStorageSystem, directorSerialNumber, NativeGUIDGenerator.ADAPTER); s_logger.debug("Looking for storage HA domain {} in the database", directorNativeGuid); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getStorageHADomainByNativeGuidConstraint(directorNativeGuid), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); if (resultsIter.hasNext()) { s_logger.debug("Found storage HA doamin {}", directorNativeGuid); haDomain = _dbClient.queryObject(StorageHADomain.class, resultsIter.next()); } else { s_logger.info("Creating storage HA domain {}", directorNativeGuid); haDomain = new StorageHADomain(); haDomain.setId(URIUtil.createId(StorageHADomain.class)); haDomain.setName(directorInfo.getName()); haDomain.setAdapterName(directorInfo.getName()); haDomain.setStorageDeviceURI(vplexStorageSystem.getId()); haDomain.setNativeGuid(directorNativeGuid); haDomain.setSerialNumber(directorSerialNumber); List<PortRole> portRoles = new ArrayList<PortRole>(); portRoles.add(PortRole.FRONTEND); portRoles.add(PortRole.BACKEND); haDomain.setNumberofPorts(String.valueOf(directorInfo .getNumberOfPortsOfType(portRoles))); haDomain.setProtocol(StorageProtocol.Block.FC.name()); haDomain.setSlotNumber(String.valueOf(directorInfo.getSlotNumber())); _dbClient.createObject(haDomain); } s_logger.info("Setting storage HA domain {} for port {}", directorNativeGuid, storagePort.getPortNetworkId()); storagePort.setStorageHADomain(haDomain.getId()); storagePort.setPortGroup(haDomain.getAdapterName()); } /** * Gets the operational status for the passed port based on whether it is * a frontend or backend port. * * @param portInfo Port info for the port. * @param portTargetMap The port target info for frontend ports. * * @return A String representing the ViPR port status. */ private String getPortOperationalStatus(VPlexPortInfo portInfo, Map<String, VPlexTargetInfo> portTargetMap) { s_logger.info("Port status for port {}", portInfo.getPath()); if (portInfo.isFrontendPort()) { s_logger.info("Port is front end"); // We use the export status of the frontend port from the // associated target info for the port to determine the // operational status for the port. VPlexTargetInfo portTargetInfo = portTargetMap.get(portInfo.getPortWwn()); if (null == portTargetInfo) { // CTRL-11698: return unknown if portTargetInfo is null, // which indicate no exports are present on this port return StoragePort.OperationalStatus.UNKNOWN.name(); } String portExportStatus = portTargetInfo.getExportStatus(); s_logger.info("Export status is {}", portExportStatus); if (VPlexTargetInfo.ExportStatus.ok.name().equals(portExportStatus)) { return StoragePort.OperationalStatus.OK.name(); } else { return StoragePort.OperationalStatus.NOT_OK.name(); } } else { // For backend ports we simply use the operation status // of the port. String portOperationalStatus = portInfo.getOperationalStatus(); s_logger.info("Operational status is {}", portOperationalStatus); if (VPlexPortInfo.OperationalStatus.ok.name().equals(portOperationalStatus)) { return StoragePort.OperationalStatus.OK.name(); } else { return StoragePort.OperationalStatus.NOT_OK.name(); } } } /** * Returns the system serial number for a VPlexApiClient instance. * * @param client the VPLEX API client to check * @param storageProvider the Storage Provider for the VPLEX * @param clusterAssemblyIds if non-null, the assembly ids will be added to this collection * @return the formatted system serial number * * @throws VPlexCollectionException */ private String getSystemSerialNumber(VPlexApiClient client, StorageProvider storageProvider, List<String> clusterAssemblyIds) throws VPlexCollectionException { // Get the cluster info. List<VPlexClusterInfo> clusterInfoList = client.getClusterInfoLite(); // Get the cluster assembly identifiers and form the // system serial number based on these identifiers. StringBuilder systemSerialNumber = new StringBuilder(); for (VPlexClusterInfo clusterInfo : clusterInfoList) { String assemblyId = clusterInfo.getTopLevelAssembly(); if (null == assemblyId || VPlexApiConstants.NULL_ATT_VAL.equals(assemblyId) || assemblyId.isEmpty()) { client.clearCaches(); throw VPlexCollectionException.exceptions .failedScanningManagedSystemsNullAssemblyId( storageProvider.getIPAddress(), clusterInfo.getName()); } if (null != clusterAssemblyIds) { clusterAssemblyIds.add(assemblyId); } if (systemSerialNumber.length() != 0) { systemSerialNumber.append(ASSEMBY_DELIM); } systemSerialNumber.append(assemblyId); } return systemSerialNumber.toString(); } /** * Private inner class to track and report on discovery performance. */ private class UnmanagedDiscoveryPerformanceTracker { public String discoveryMode = "Unknown"; public Map<String, Long> volumeTimeResults = new TreeMap<String, Long>(); public long startTime = new Date().getTime(); public long virtualVolumeFetch = 0; public long storageViewFetch = 0; public long consistencyGroupFetch = 0; public long unmanagedVolumeProcessing = 0; public long vpoolMatching = 0; public int totalVolumesFetched = 0; public int totalVolumesDiscovered = 0; /** * Returns a String containing details of the VPLEX UnManagedVolume discovery session. * * @return a String that is text report on discovery performance */ public String getPerformanceReport() { StringBuilder report = new StringBuilder("\n\nVolume Discovery Performance Report\n"); report.append("\tdiscovery mode: ").append(discoveryMode).append("\n"); long totalDiscoveryTime = System.currentTimeMillis() - startTime; report.append("\ttotal discovery time: ").append(totalDiscoveryTime); report.append(" (about ").append(totalDiscoveryTime / 1000 / 60).append(" minutes)\n"); report.append("\ttotal volumes fetched: ").append(totalVolumesFetched).append("\n"); report.append("\ttotal volumes discovered: ").append(totalVolumesDiscovered).append("\n"); long averageTime = 0; if (totalVolumesDiscovered != 0) { averageTime = (System.currentTimeMillis() - startTime) / totalVolumesDiscovered; } report.append("\taverage time per volume: ").append(averageTime).append("ms\n"); report.append("\tvirtual volume data fetch: ").append(virtualVolumeFetch).append("ms\n"); report.append("\tstorage view data fetch: ").append(storageViewFetch).append("ms\n"); report.append("\tconsistency group data fetch: ").append(consistencyGroupFetch).append("ms\n"); report.append("\tunmanaged volume processing time: ").append(unmanagedVolumeProcessing).append("ms\n"); report.append("\tvpool matching processing time: ").append(unmanagedVolumeProcessing).append("ms\n"); volumeTimeResults = sortByValue(volumeTimeResults); report.append("\nTop 20 Longest-Running Volumes...\n"); Object[] keys = volumeTimeResults.keySet().toArray(); for (int i = 0; i < 20; i++) { if (keys.length >= (1 + i)) { Object key = keys[keys.length - 1 - i]; report.append("\t").append(key).append(": ").append(volumeTimeResults.get(key)).append("ms\n"); } } return report.toString(); } /** * Returns an estimate of the time remaining for discovery based on the total * number of volumes to be discovered and the average single volume discovery * time to the point this method is called. * * @return an estimate of time remaining for discovery */ public String getDiscoveryTimeRemaining() { try { if (totalVolumesDiscovered != 0) { long averageTime = (System.currentTimeMillis() - startTime) / totalVolumesDiscovered; long timeRemaining = averageTime * (totalVolumesFetched - totalVolumesDiscovered); timeRemaining = (timeRemaining / 1000 / 60); if (timeRemaining < 1) { return "less than a minute"; } return "about " + timeRemaining + " minutes"; } } catch (Exception ex) { s_logger.warn("couldn't calculate discovery remaining time estimate: ", ex.getLocalizedMessage()); } return "Unknown"; } /** * Sorts the given map by values rather than keys. * * @param map the Map to sort * @return the Map returned sorted by values */ public <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) { List<Map.Entry<K, V>> listOfEntries = new LinkedList<Map.Entry<K, V>>(map.entrySet()); Collections.sort(listOfEntries, new Comparator<Map.Entry<K, V>>() { @Override public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) { return (o1.getValue()).compareTo(o2.getValue()); } }); Map<K, V> sortedMap = new LinkedHashMap<K, V>(); for (Map.Entry<K, V> entry : listOfEntries) { sortedMap.put(entry.getKey(), entry.getValue()); } return sortedMap; } } /** * Initializes the performance statistics collection context with required key mappings. * * @param accessProfile Profile providing context for this discovery session. */ private void initializeContext(AccessProfile accessProfile) { _keyMap.put(Constants._serialID, accessProfile.getserialID()); _keyMap.put(Constants.dbClient, _dbClient); if (_networkDeviceController != null) { _keyMap.put(Constants.networkDeviceController, _networkDeviceController); } _keyMap.put(Constants._nativeGUIDs, Sets.newHashSet()); _keyMap.put(Constants._Stats, new LinkedList<Stat>()); _keyMap.put(Constants.ACCESSPROFILE, accessProfile); _keyMap.put(Constants.PROPS, accessProfile.getProps()); _keyMap.put(Constants._TimeCollected, accessProfile.getCurrentSampleTime()); } }