/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.utils;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObject;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedFileSystem;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedFileSystem.SupportedFileSystemCharacterstics;
import com.emc.storageos.db.client.model.UnManagedDiscoveredObjects.UnManagedFileSystem.SupportedFileSystemInformation;
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.exceptions.DatabaseException;
import com.emc.storageos.db.joiner.Joiner;
import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper;
import com.emc.storageos.util.ConnectivityUtil;
import com.emc.storageos.volumecontroller.impl.NativeGUIDGenerator;
import com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.processor.detailedDiscovery.RemoteMirrorObject;
import com.emc.storageos.volumecontroller.impl.smis.srdf.SRDFUtils;
import com.emc.storageos.vplex.api.VPlexApiClient;
import com.emc.storageos.vplex.api.VPlexApiFactory;
import com.emc.storageos.vplexcontroller.VPlexControllerUtils;
import com.emc.storageos.vplexcontroller.VplexBackendIngestionContext;
public class ImplicitUnManagedObjectsMatcher {
private static final String LOCAL = "LOCAL";
private static final Logger _log = LoggerFactory
.getLogger(ImplicitUnManagedObjectsMatcher.class);
private static final String INVALID = "Invalid";
private static final String MATCHED = "Matched";
private static final int VOLUME_BATCH_SIZE = 200;
private static final int FILESHARE_BATCH_SIZE = 200;
private static final String TRUE = "TRUE";
/**
* run implicit unmanaged matcher during rediscovery
*
* @param dbClient
*/
public static void runImplicitUnManagedObjectsMatcher(DbClient dbClient) {
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) {
matchVirtualPoolsWithUnManagedVolumes(vpool, srdfEnabledTargetVPools, rpEnabledTargetVPools, dbClient, false);
}
}
public static void matchVirtualPoolsWithUnManagedVolumes(VirtualPool virtualPool, Set<URI> srdfEnabledTargetVPools,
Set<URI> rpEnabledTargetVPools, DbClient dbClient, boolean recalcVplexVolumes) {
List<UnManagedVolume> modifiedUnManagedVolumes = new ArrayList<UnManagedVolume>();
Map<String, StringSet> poolMapping = new HashMap<String, StringSet>();
StringSet invalidPools = null;
// if a virtual pool has assigned pools, use them for matching.
// Otherwise use matched pools
if (virtualPool.getUseMatchedPools() && null != virtualPool.getMatchedStoragePools()) {
poolMapping.put(MATCHED, virtualPool.getMatchedStoragePools());
} else if (null != virtualPool.getAssignedStoragePools()) {
poolMapping.put(MATCHED, virtualPool.getAssignedStoragePools());
if (null != virtualPool.getMatchedStoragePools()) {
// Find out the storage pools which are in matched pools but not in assigned pools.
// These pools should not be in the supported vpool list
invalidPools = (StringSet) virtualPool.getMatchedStoragePools().clone();
invalidPools.removeAll(virtualPool.getAssignedStoragePools());
}
}
if (null != virtualPool.getInvalidMatchedPools()) {
if (invalidPools == null) {
invalidPools = virtualPool.getInvalidMatchedPools();
} else {
invalidPools.addAll(virtualPool.getInvalidMatchedPools());
}
}
if (invalidPools != null) {
poolMapping.put(INVALID, invalidPools);
}
// too many loops, as I am trying to encapsulate both invalid and
// matched pools within the same logic.T
for (Entry<String, StringSet> entry : poolMapping.entrySet()) {
String key = entry.getKey();
// loop through matchedPools and later on invalid pools
for (String pool : entry.getValue()) {
StorageSystem system = null;
@SuppressWarnings("deprecation")
List<URI> unManagedVolumeUris = dbClient
.queryByConstraint(ContainmentConstraint.Factory
.getPoolUnManagedVolumeConstraint(URI.create(pool)));
Iterator<UnManagedVolume> unManagedVolumes = dbClient
.queryIterativeObjects(UnManagedVolume.class, unManagedVolumeUris);
while (unManagedVolumes.hasNext()) {
UnManagedVolume unManagedVolume = unManagedVolumes.next();
StringSetMap unManagedVolumeInfo = unManagedVolume
.getVolumeInformation();
if (null == unManagedVolumeInfo) {
continue;
}
if (system == null) {
system = dbClient.queryObject(StorageSystem.class, unManagedVolume.getStorageSystemUri());
}
String unManagedVolumeProvisioningType = UnManagedVolume.SupportedProvisioningType
.getProvisioningType(unManagedVolume.getVolumeCharacterstics().get(
SupportedVolumeCharacterstics.IS_THINLY_PROVISIONED.toString()));
// remove the vpool from supported Vpool List if present
if (INVALID.equalsIgnoreCase(key) ||
!unManagedVolumeProvisioningType.equalsIgnoreCase(virtualPool.getSupportedProvisioningType())) {
if (removeVPoolFromUnManagedVolumeObjectVPools(virtualPool, unManagedVolume)) {
modifiedUnManagedVolumes.add(unManagedVolume);
}
} else if (addVPoolToUnManagedObjectSupportedVPools(virtualPool, unManagedVolumeInfo,
unManagedVolume, system, srdfEnabledTargetVPools, rpEnabledTargetVPools)) {
modifiedUnManagedVolumes.add(unManagedVolume);
}
if (modifiedUnManagedVolumes.size() > VOLUME_BATCH_SIZE) {
insertInBatches(modifiedUnManagedVolumes, dbClient, "UnManagedVolumes");
modifiedUnManagedVolumes.clear();
}
}
}
}
if (recalcVplexVolumes) {
// VPLEX unmanaged volumes need to be matched by different rules.
matchVirtualPoolWithUnManagedVolumeVPLEX(modifiedUnManagedVolumes, virtualPool, dbClient);
}
insertInBatches(modifiedUnManagedVolumes, dbClient, "UnManagedVolumes");
}
/**
* Match virtual pool with unmanaged VPLEX volumes. Uses a different criteria than straight block matchers.
* Currently this method will only add virtual pools to an unmanaged volume. It will not remove them.
* This code is loosely based on VPlexCommunicationInterface.updateUnmanagedVolume() content, but is changed
* to suit this specific case where a single virtual pool is getting added/updated.
*
* @param modifiedUnManagedVolumes list of volumes to add to
* @param vpool virtual pool (new or updated)
* @param dbClient dbclient
*/
private static void matchVirtualPoolWithUnManagedVolumeVPLEX(List<UnManagedVolume> modifiedUnManagedVolumes, VirtualPool vpool,
DbClient dbClient) {
// This method only applies to VPLEX vpools
if (!VirtualPool.vPoolSpecifiesHighAvailability(vpool)) {
return;
}
_log.info("START: matching virtual pool with unmanaged volume for VPLEX");
// Get all UnManagedVolumes where storageDevice is a StorageSystem where type = VPLEX
Joiner j = new Joiner(dbClient).join(StorageSystem.class, "ss").match("systemType", "vplex")
.join("ss", UnManagedVolume.class, "umv", "storageDevice").go();
// From the joiner, get the StorageSystems (which is a small amount of objects) and the UMVs (which is large, so get URIs and use iter)
Map<StorageSystem, List<URI>> ssToUmvMap = j.pushList("ss").pushUris("umv").map();
for (Entry<StorageSystem, List<URI>> ssToUmvEntry : ssToUmvMap.entrySet()) {
StorageSystem vplex = ssToUmvEntry.getKey();
// fetch the current mapping of VPLEX cluster ids to cluster names (e.g., "1"=>"cluster-1";"2"=>"cluster-2")
// the cluster names can be changed by the VPLEX admin, so we cannot rely on the default cluster-1 or cluster-2
Map<String, String> clusterIdToNameMap = null;
try {
VPlexApiClient client = VPlexControllerUtils.getVPlexAPIClient(VPlexApiFactory.getInstance(), vplex, dbClient);
clusterIdToNameMap = client.getClusterIdToNameMap();
} catch (Exception ex) {
_log.warn("Exception caught while getting cluster name info from VPLEX {}", vplex.forDisplay());
}
if (null == clusterIdToNameMap || clusterIdToNameMap.isEmpty()) {
_log.warn("Could not update virtual pool matches for VPLEX {} because cluster name info couldn't be retrieved",
vplex.forDisplay());
continue;
}
// Create a map of virtual arrays to their respective VPLEX cluster (a varray is not allowed to have both VPLEX clusters)
Map<String, String> varrayToClusterIdMap = new HashMap<String, String>();
// Since there may be a lot of unmanaged volumes to process, we use the iterative query
Iterator<UnManagedVolume> volumeIter = dbClient.queryIterativeObjects(UnManagedVolume.class, ssToUmvEntry.getValue());
while (volumeIter.hasNext()) {
UnManagedVolume volume = volumeIter.next();
String highAvailability = null;
if (volume.getVolumeInformation().get(SupportedVolumeInformation.VPLEX_LOCALITY.toString()) != null) {
String haFound = volume.getVolumeInformation().get(SupportedVolumeInformation.VPLEX_LOCALITY.toString()).iterator().next();
if (haFound.equalsIgnoreCase(LOCAL)) {
highAvailability = VirtualPool.HighAvailabilityType.vplex_local.name();
} else {
highAvailability = VirtualPool.HighAvailabilityType.vplex_distributed.name();
}
}
_log.debug("finding valid virtual pools for UnManagedVolume {}", volume.getLabel());
// 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()))) {
_log.info(String.format(" virtual pool %s is not valid because "
+ "its high availability setting does not match the unmanaged volume %s",
vpool.getLabel(), volume.forDisplay()));
continue;
}
// If the volume is in a CG, the vpool must specify multi-volume consistency.
Boolean mvConsistency = vpool.getMultivolumeConsistency();
if ((TRUE.equals(volume.getVolumeCharacterstics().get(
SupportedVolumeCharacterstics.IS_VOLUME_ADDED_TO_CONSISTENCYGROUP.toString()))) &&
((mvConsistency == null) || (mvConsistency == Boolean.FALSE))) {
_log.info(String.format(" virtual pool %s is not valid because it does not have the "
+ "multi-volume consistency flag set, and the unmanaged volume %s is in a consistency group",
vpool.getLabel(), volume.forDisplay()));
continue;
}
StringSet volumeClusters = new StringSet();
if (volume.getVolumeInformation().get(SupportedVolumeInformation.VPLEX_CLUSTER_IDS.toString()) != null) {
volumeClusters.addAll(volume.getVolumeInformation().get(SupportedVolumeInformation.VPLEX_CLUSTER_IDS.toString()));
}
// VPool must be assigned to a varray corresponding to volume's 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)) {
if (volume.getSupportedVpoolUris() == null) {
volume.setSupportedVpoolUris(new StringSet());
}
volume.getSupportedVpoolUris().add(vpool.getId().toString());
List<UnManagedVolume> backendVols =
VplexBackendIngestionContext.findBackendUnManagedVolumes(volume, dbClient);
if (backendVols != null) {
for (UnManagedVolume backendVol : backendVols) {
if (backendVol.getSupportedVpoolUris() == null) {
backendVol.setSupportedVpoolUris(new StringSet());
}
backendVol.getSupportedVpoolUris().add(vpool.getId().toString());
modifiedUnManagedVolumes.add(backendVol);
}
}
modifiedUnManagedVolumes.add(volume);
break;
}
}
}
if (!modifiedUnManagedVolumes.contains(volume)) {
_log.info(String.format(" virtual pool %s is not valid because "
+ "volume %s resides on a cluster that does not match the varray(s) associated with the vpool",
vpool.getLabel(), volume.forDisplay()));
}
}
}
_log.info("END: matching virtual pool with unmanaged volume for VPLEX");
}
/**
* remove VPool from Supported VPool List of UnManaged Objects
*
* @param supportedVPoolsList
* @param virtualPool
* @param unManagedVolume
* @return
*/
private static boolean removeVPoolFromUnManagedVolumeObjectVPools(VirtualPool virtualPool,
UnManagedDiscoveredObject unManagedObject) {
if (unManagedObject.getSupportedVpoolUris().contains(virtualPool.getId().toString())) {
_log.info("Removing Invalid VPool {}", virtualPool.getId().toString());
unManagedObject.getSupportedVpoolUris().remove(virtualPool.getId().toString());
return true;
}
return false;
}
/**
* add VPool to Supported VPool List of UnManaged Objects.
* @param virtualPool the virtual pool
* @param unManagedObjectInfo the un managed object info
* @param system the system (for Block systems, to verify policy matching)
* @param srdfEnabledTargetVPools SRDF enabled target vpools
* @param rpEnabledTargetVPools RP enabled target vpools
* @param supportedVPoolsList the supported v pools list
* @param unManagedObjectURI the un managed object uri
*
* @return true, if successful
*/
private static boolean addVPoolToUnManagedObjectSupportedVPools(VirtualPool virtualPool,
StringSetMap unManagedObjectInfo, UnManagedDiscoveredObject unManagedObject, StorageSystem system,
Set<URI> srdfEnabledTargetVPools, Set<URI> rpEnabledTargetVPools) {
// if virtual pool is already part of supported vpool
// List, then continue;
StringSet supportedVPoolsList = unManagedObject.getSupportedVpoolUris();
if (null != supportedVPoolsList
&& supportedVPoolsList.contains(virtualPool.getId().toString())) {
_log.debug("Matched VPool already there {}", virtualPool.getId().toString());
return false;
}
if (VirtualPool.Type.block.name().equals(virtualPool.getType())) {
// Before adding this vPool to supportedVPoolList, check if Tiering policy matches
if (system != null && system.getAutoTieringEnabled()) {
String autoTierPolicyId = null;
if (unManagedObjectInfo.containsKey(SupportedVolumeInformation.AUTO_TIERING_POLICIES.toString())) {
for (String policyName : unManagedObjectInfo.get(SupportedVolumeInformation.AUTO_TIERING_POLICIES
.toString())) {
autoTierPolicyId = NativeGUIDGenerator
.generateAutoTierPolicyNativeGuid(
system.getNativeGuid(), policyName, NativeGUIDGenerator.getTieringPolicyKeyForSystem(system));
break;
}
}
if (!DiscoveryUtils.checkVPoolValidForUnManagedVolumeAutoTieringPolicy(virtualPool, autoTierPolicyId, system)) {
String msg = "VPool %s is not added to UnManaged Volume's (%s) supported vPool list "
+ "since Auto-tiering Policy %s in UnManaged Volume does not match with vPool's (%s)";
_log.debug(String.format(msg, new Object[] { virtualPool.getId(), unManagedObject.getId(),
autoTierPolicyId, virtualPool.getAutoTierPolicyName() }));
return false;
}
}
// Verify whether unmanaged volume SRDF properties with the Vpool
boolean srdfSourceVpool = (null != virtualPool.getProtectionRemoteCopySettings() && !virtualPool
.getProtectionRemoteCopySettings().isEmpty());
boolean srdfTargetVpool = srdfEnabledTargetVPools == null ? false : (srdfEnabledTargetVPools.contains(virtualPool.getId()));
StringSet remoteVolType = unManagedObjectInfo.get(SupportedVolumeInformation.REMOTE_VOLUME_TYPE.toString());
boolean isRegularVolume = (null == remoteVolType);
boolean isSRDFSourceVolume = (null != remoteVolType && remoteVolType.contains(RemoteMirrorObject.Types.SOURCE.toString()));
boolean isSRDFTargetVolume = (null != remoteVolType && remoteVolType.contains(RemoteMirrorObject.Types.TARGET.toString()));
if (isRegularVolume && (srdfSourceVpool || srdfTargetVpool)) {
_log.debug("Found a regular volume with SRDF Protection Virtual Pool. No need to update.");
return false;
} else if (isSRDFSourceVolume && !(srdfSourceVpool || srdfTargetVpool)) {
_log.debug("Found a SRDF unmanaged volume with non-srdf virtualpool. No need to update.");
return false;
} else if (isSRDFSourceVolume && srdfTargetVpool) {
_log.debug("Found a SRDF source volume & target srdf vpool. No need to update.");
return false;
} else if (isSRDFTargetVolume && srdfSourceVpool) {
_log.debug("Found a SRDFTarget volume & source srdf source vpool No need to update.");
return false;
}
// Verify whether unmanaged volume RP properties with the Vpool
boolean isRPSourceVpool = (null != virtualPool.getProtectionVarraySettings() && !virtualPool
.getProtectionVarraySettings().isEmpty());
boolean isRPTargetVpool = rpEnabledTargetVPools == null ? false : (rpEnabledTargetVPools.contains(virtualPool.getId()));
remoteVolType = unManagedObjectInfo.get(SupportedVolumeInformation.RP_PERSONALITY.toString());
isRegularVolume = (null == remoteVolType);
boolean isRPSourceVolume = (null != remoteVolType && remoteVolType.contains(Volume.PersonalityTypes.SOURCE.toString()));
if (isRegularVolume && (isRPSourceVpool || isRPTargetVpool)) {
_log.debug("Found a regular volume with RP Protection Virtual Pool. No need to update.");
return false;
} else if (isRPSourceVolume && !isRPSourceVpool) {
_log.debug("Found a RP unmanaged volume with non-rp virtualpool. No need to update.");
return false;
} else if (isRPSourceVolume && isRPTargetVpool) {
_log.debug("Found a RP source volume & target rp vpool. No need to update.");
return false;
}
}
// Adding a fresh new VPool, if supportedVPoolList is
// empty
if (null == supportedVPoolsList) {
_log.debug("Adding a new Supported VPool List {}", virtualPool.getId().toString());
supportedVPoolsList = new StringSet();
} // updating the vpool list with new vpool
supportedVPoolsList.add(virtualPool.getId().toString());
return true;
}
public static void matchVirtualPoolsWithUnManagedFileSystems(VirtualPool virtualPool,
DbClient dbClient) {
List<UnManagedFileSystem> modifiedUnManagedFileSystems = new ArrayList<UnManagedFileSystem>();
Map<String, StringSet> poolMapping = new HashMap<String, StringSet>();
StringSet invalidPools = null;
// if a virtual pool has assigned pools, use them for matching.
// Otherwise use matched pools
if (virtualPool.getUseMatchedPools() && null != virtualPool.getMatchedStoragePools()) {
poolMapping.put(MATCHED, virtualPool.getMatchedStoragePools());
} else if (null != virtualPool.getAssignedStoragePools()) {
poolMapping.put(MATCHED, virtualPool.getAssignedStoragePools());
// Find out the storage pools which are in matched pools but not in assigned pools.
// These pools should not be in the supported vpool list
invalidPools = (StringSet) virtualPool.getMatchedStoragePools().clone();
invalidPools.removeAll(virtualPool.getAssignedStoragePools());
}
if (null != virtualPool.getInvalidMatchedPools()) {
if (invalidPools == null) {
invalidPools = virtualPool.getInvalidMatchedPools();
} else {
invalidPools.addAll(virtualPool.getInvalidMatchedPools());
}
}
if (invalidPools != null) {
poolMapping.put(INVALID, invalidPools);
}
for (Entry<String, StringSet> entry : poolMapping.entrySet()) {
String key = entry.getKey();
for (String pool : entry.getValue()) {
List<URI> unManagedFileSystemUris = dbClient
.queryByConstraint(ContainmentConstraint.Factory
.getPoolUnManagedFileSystemConstraint(URI.create(pool)));
Iterator<UnManagedFileSystem> unManagedFileSystems = dbClient
.queryIterativeObjects(UnManagedFileSystem.class,
unManagedFileSystemUris);
while (unManagedFileSystems.hasNext()) {
UnManagedFileSystem unManagedFileSystem = unManagedFileSystems.next();
StringSetMap unManagedFileSystemInfo = unManagedFileSystem
.getFileSystemInformation();
if (null == unManagedFileSystemInfo) {
continue;
}
String unManagedFileSystemProvisioningType = UnManagedFileSystem.SupportedProvisioningType
.getProvisioningType(unManagedFileSystem.getFileSystemCharacterstics().get(
SupportedFileSystemCharacterstics.IS_THINLY_PROVISIONED.toString()));
boolean isNetApp = unManagedFileSystemInfo.get(SupportedFileSystemInformation.SYSTEM_TYPE.toString()).
contains(DiscoveredDataObject.Type.netapp.name());
// Since, Provisioning Type for NetApp FileSystem is set as Thick by default,
// Ignoring the provisioning type check for NetApp.
if (INVALID.equalsIgnoreCase(key)
|| ((!isNetApp) && !unManagedFileSystemProvisioningType
.equalsIgnoreCase(virtualPool.getSupportedProvisioningType()))) {
if (removeVPoolFromUnManagedVolumeObjectVPools(virtualPool, unManagedFileSystem)) {
modifiedUnManagedFileSystems.add(unManagedFileSystem);
}
} else if (addVPoolToUnManagedObjectSupportedVPools(virtualPool, unManagedFileSystemInfo,
unManagedFileSystem, null, null, null)) {
modifiedUnManagedFileSystems.add(unManagedFileSystem);
}
if (modifiedUnManagedFileSystems.size() > FILESHARE_BATCH_SIZE) {
insertInBatches(modifiedUnManagedFileSystems, dbClient, "UnManagedFileSystems");
modifiedUnManagedFileSystems.clear();
}
}
}
}
insertInBatches(modifiedUnManagedFileSystems, dbClient,
"UnManagedFileSystems");
}
private static <T extends DataObject> void insertInBatches(List<T> records,
DbClient dbClient, String type) {
try {
dbClient.updateAndReindexObject(records);
} catch (DatabaseException e) {
_log.error("Error inserting {} records into the database", type, e);
}
}
}