/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.client.model.util; import static com.emc.storageos.db.client.constraint.ContainmentConstraint.Factory.getVolumesByConsistencyGroup; import static com.google.common.base.Preconditions.checkNotNull; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.URIQueryResultList; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject.Type; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.ProtectionSystem; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.StringSetMap; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.Volume.PersonalityTypes; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.svcs.errorhandling.resources.APIException; public class BlockConsistencyGroupUtils { /** * Splitter character that divides the cluster name and consistency * group name. */ private static final String SPLITTER = ":"; public static final String CLUSTER_1 = "cluster-1"; public static final String CLUSTER_2 = "cluster-2"; /** * Parses out the cluster name from the combined cluster/cg name. * * @param clusterCgName The combined cluster/cg name from which to extract * the cluster name. * @return The cluster name. */ public static String fetchClusterName(String clusterCgName) { String clusterName = null; if (clusterCgName != null && !clusterCgName.isEmpty()) { String[] tmp = clusterCgName.split(SPLITTER); clusterName = tmp[0]; } return clusterName; } /** * Parses out the consistency group name from the combined cluster/cg name. * * @param clusterCgName The combined cluster/cg name from which to extract * the cg name. * @return The cg name. */ public static String fetchCgName(String clusterCgName) { String cgName = null; if (clusterCgName != null && !clusterCgName.isEmpty()) { String[] tmp = clusterCgName.split(SPLITTER); cgName = tmp[1]; } return cgName; } /** * Builds a concatenated name combining the cluster name and consistency * group name. This is used for mapping VPlex storage systems to their * corresponding cluster consistency groups. * * @param clusterName The cluster name. * @param cgName The consistency group name. * @return The cluster name concatenated with the consistency group. */ public static String buildClusterCgName(String clusterName, String cgName) { return String.format("%s" + SPLITTER + "%s", clusterName, cgName); } /** * Checks to see if the BlockConsistencyGroup references any VPlex * consistency groups. * * @return true if this BlockConsistencyGroup references any VPlex * consistency groups, false otherwise. */ public static boolean referencesVPlexCGs(BlockConsistencyGroup cg, DbClient dbClient) { if (cg.getTypes() != null && cg.getTypes().contains(Types.VPLEX.name())) { for (StorageSystem storageSystem : getVPlexStorageSystems(cg, dbClient)) { if (storageSystem.getSystemType().equals(DiscoveredDataObject.Type.vplex.name())) { StringSet cgNames = cg.getSystemConsistencyGroups().get(storageSystem.getId().toString()); if (cgNames != null && !cgNames.isEmpty()) { return true; } } } } return false; } public static List<StorageSystem> getVPlexStorageSystems(BlockConsistencyGroup cg, DbClient dbClient) { List<StorageSystem> vplexStorageSystems = new ArrayList<StorageSystem>(); if (cg.getSystemConsistencyGroups() != null && !cg.getSystemConsistencyGroups().isEmpty()) { for (String systemUri : cg.getSystemConsistencyGroups().keySet()) { // Only look at StorageSystem types for VPlex if (URIUtil.isType(URI.create(systemUri), StorageSystem.class)) { StorageSystem storageSystem = dbClient.queryObject(StorageSystem.class, URI.create(systemUri)); if (storageSystem.getSystemType().equals(DiscoveredDataObject.Type.vplex.name())) { vplexStorageSystems.add(storageSystem); } } } } return vplexStorageSystems; } /** * Gets the protection system(s) containing the CGs corresponding to the * passed RP CG. * * @param cg the BlockConsistencyGroup. * @param dbClient the DB client references. * @return A list of the the protection system(s) containing the CGs * corresponding to the passed RP CG. */ public static List<ProtectionSystem> getRPProtectionSystems(BlockConsistencyGroup cg, DbClient dbClient) { List<ProtectionSystem> rpSystems = new ArrayList<ProtectionSystem>(); if (cg.getSystemConsistencyGroups() != null && !cg.getSystemConsistencyGroups().isEmpty()) { for (String systemUri : cg.getSystemConsistencyGroups().keySet()) { if (URIUtil.isType(URI.create(systemUri), ProtectionSystem.class)) { ProtectionSystem rpSystem = dbClient.queryObject(ProtectionSystem.class, URI.create(systemUri)); rpSystems.add(rpSystem); } } } return rpSystems; } /** * Determines if the given BlockConsistencyGroup references any non-LOCAL storage system * consistency groups. * * @param cg the BlockConsistencyGroup. * @param dbClient the DB client references. * @return true if the CG references any non-local CGs, false otherwise. */ public static boolean referencesNonLocalCgs(BlockConsistencyGroup cg, DbClient dbClient) { List<StorageSystem> storageSystems = getVPlexStorageSystems(cg, dbClient); List<ProtectionSystem> protectionSystems = getRPProtectionSystems(cg, dbClient); boolean referencesVplexCgs = false; boolean referencesRpCgs = false; if (storageSystems != null && !storageSystems.isEmpty()) { for (StorageSystem storageSystem : storageSystems) { StringSet cgs = cg.getSystemConsistencyGroups().get(storageSystem.getId().toString()); if (cgs != null && !cgs.isEmpty()) { referencesVplexCgs = true; break; } } } if (protectionSystems != null && !protectionSystems.isEmpty()) { for (ProtectionSystem protectionSystem : protectionSystems) { StringSet cgs = cg.getSystemConsistencyGroups().get(protectionSystem.getId().toString()); if (cgs != null && !cgs.isEmpty()) { referencesRpCgs = true; break; } } } return referencesVplexCgs || referencesRpCgs; } /** * Checks to see if the VPlex CG for the VPlex system and cluster has been created. * * @param vplexSystem The VPlex storage system. * @param clusterName The VPlex cluster name. * @param cgName The consistency group name. * @param isDistributed True if the check is for a distributed CG. * @return true if the VPlex consistency group has been created, false otherwise. */ public static boolean isVplexCgCreated(BlockConsistencyGroup cg, String vplexSystem, String clusterName, String cgName, boolean isDistributed) { boolean vplexCgCreated = false; if (cg.getSystemConsistencyGroups() == null) { return false; } StringSet clusterCgNames = cg.getSystemConsistencyGroups().get(vplexSystem); if (clusterCgNames != null && !clusterCgNames.isEmpty()) { if (isDistributed) { String cluster1CgName = buildClusterCgName(CLUSTER_1, cgName); String cluster2CgName = buildClusterCgName(CLUSTER_2, cgName); vplexCgCreated = clusterCgNames.contains(cluster1CgName) || clusterCgNames.contains(cluster2CgName); } else { String clusterCgName = buildClusterCgName(clusterName, cgName); vplexCgCreated = clusterCgNames.contains(clusterCgName); } } return vplexCgCreated; } /** * Gets the storage system(s) containing the native CGs corresponding to the * passed VPLEX CG. * * @param cg A reference to a VPLEX CG * @param dbClient A reference to a database client * * @return A list of the the storage system(s) containing the native CGs * corresponding to the passed VPLEX CG. */ public static List<URI> getLocalSystems(BlockConsistencyGroup cg, DbClient dbClient) { List<URI> localSystemUris = new ArrayList<URI>(); StringSetMap systemCgMap = cg.getSystemConsistencyGroups(); if (systemCgMap != null) { Iterator<String> cgSystemIdsIter = cg.getSystemConsistencyGroups().keySet().iterator(); while (cgSystemIdsIter.hasNext()) { URI cgSystemUri = URI.create(cgSystemIdsIter.next()); if (URIUtil.isType(cgSystemUri, StorageSystem.class)) { StorageSystem cgSystem = dbClient.queryObject(StorageSystem.class, cgSystemUri); // TODO: If we support other systems for consistency groups, we may need // to add another type check here. if (!DiscoveredDataObject.Type.vplex.name().equals(cgSystem.getSystemType())) { localSystemUris.add(cgSystemUri); } } } } return localSystemUris; } /** * Gets the storage system(s) containing the native CGs corresponding to the * passed VPLEX CG. * * @param cg A reference to a VPLEX CG * @param dbClient A reference to a database client * * @return A list of the the storage system(s) containing the native CGs * corresponding to the passed VPLEX CG. */ public static List<URI> getLocalSystemsInCG(BlockConsistencyGroup cg, DbClient dbClient) { List<URI> localSystemUris = new ArrayList<URI>(); StringSetMap systemCgMap = cg.getSystemConsistencyGroups(); if (systemCgMap != null) { Iterator<String> cgSystemIdsIter = cg.getSystemConsistencyGroups().keySet().iterator(); while (cgSystemIdsIter.hasNext()) { URI cgSystemUri = URI.create(cgSystemIdsIter.next()); if (URIUtil.isType(cgSystemUri, StorageSystem.class)) { StorageSystem cgSystem = dbClient.queryObject(StorageSystem.class, cgSystemUri); // TODO: If we add support for new block systems, add the same in the // isBlockStorageSystem if (Type.isBlockStorageSystem(cgSystem.getSystemType())) { localSystemUris.add(cgSystemUri); } } } } return localSystemUris; } /** * Gets all the active volumes in the passed CG. * * @param cg A reference to a VPLEX CG * @param dbClient A reference to a database client * @param personalityType The RecoverPoint personality type. Used to isolate VPlex volumes * in the consistency group to those of that personality type. Optional field * left null if not desired. * * @return A list of the active VPLEX volumes in the passed VPLEX CG. */ static public List<Volume> getActiveVolumesInCG(BlockConsistencyGroup cg, DbClient dbClient, PersonalityTypes personalityType) { List<Volume> volumeList = new ArrayList<Volume>(); URIQueryResultList uriQueryResultList = new URIQueryResultList(); dbClient.queryByConstraint(getVolumesByConsistencyGroup(cg.getId()), uriQueryResultList); Iterator<Volume> volumeIterator = dbClient.queryIterativeObjects(Volume.class, uriQueryResultList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (!volume.getInactive()) { // If the personalityType is specified, we want to ensure we only consider // volumes with that personality type. if (personalityType == null || (volume.getPersonality() != null && volume.getPersonality().equals(personalityType.name()))) { volumeList.add(volume); } } } return volumeList; } /** * Gets the active VPLEX volumes in the passed VPLEX CG. * * @param cg A reference to a VPLEX CG * @param dbClient A reference to a database client * @param personalityType The RecoverPoint personality type. Used to isolate VPlex volumes * in the consistency group to those of that personality type. Optional field * left null if not desired. * * @return A list of the active VPLEX volumes in the passed VPLEX CG. */ static public List<Volume> getActiveVplexVolumesInCG(BlockConsistencyGroup cg, DbClient dbClient, PersonalityTypes personalityType) { List<Volume> volumeList = new ArrayList<Volume>(); URIQueryResultList uriQueryResultList = new URIQueryResultList(); dbClient.queryByConstraint(getVolumesByConsistencyGroup(cg.getId()), uriQueryResultList); if (uriQueryResultList != null) { Iterator<Volume> volumeIterator = dbClient.queryIterativeObjects(Volume.class, uriQueryResultList); if (volumeIterator != null) { while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (!volume.getInactive()) { // When determining the volumes to snap, we want the VPLEX volumes // referencing the consistency group. The backend volumes for the // VPLEX volume will also now reference the consistency group. // At this point we just want the VPLEX volumes. if (volume.isVPlexVolume(dbClient)) { // If the personalityType is specified, we want to ensure we only consider // volumes with that personality type. if (personalityType == null || (volume.getPersonality() != null && volume.getPersonality().equals(personalityType.name()))) { volumeList.add(volume); } } } } } } return volumeList; } /** * Gets the active non-VPlex volumes in the consistency group. * * @param cg A reference to a CG * @param dbClient A reference to a database client * @param personalityType The RecoverPoint personality type. Used to isolate volumes * in the consistency group to those of that personality type. Optional field * left null if not desired. * * @return A list of the active volumes in the passed VPLEX CG. */ static public List<Volume> getActiveNonVplexVolumesInCG(BlockConsistencyGroup cg, DbClient dbClient, PersonalityTypes personalityType) { List<Volume> volumeList = new ArrayList<Volume>(); URIQueryResultList uriQueryResultList = new URIQueryResultList(); dbClient.queryByConstraint(getVolumesByConsistencyGroup(cg.getId()), uriQueryResultList); Iterator<Volume> volumeIterator = dbClient.queryIterativeObjects(Volume.class, uriQueryResultList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (!volume.getInactive()) { // We want the non-VPlex volumes, which are those volumes that do not have associated volumes. if (volume.getAssociatedVolumes() == null || volume.getAssociatedVolumes().isEmpty()) { // If the personalityType is specified, we want to ensure we only consider // volumes with that personality type. if (personalityType == null || (volume.getPersonality() != null && volume.getPersonality().equals(personalityType.name()))) { volumeList.add(volume); } } } } return volumeList; } /** * Gets the active native, non-VPLEX, non-RP volumes in the consistency group. * * @param cg Consistency group. * @param dbClient Database client. * @return A list of native back-end volumes in the given consistency group. */ public static List<Volume> getActiveNativeVolumesInCG(BlockConsistencyGroup cg, DbClient dbClient) { List<Volume> volumeList = new ArrayList<>(); URIQueryResultList uriQueryResultList = new URIQueryResultList(); dbClient.queryByConstraint(getVolumesByConsistencyGroup(cg.getId()), uriQueryResultList); Iterator<Volume> volumeIterator = dbClient.queryIterativeObjects(Volume.class, uriQueryResultList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (!volume.getInactive()) { // We want the non-VPlex volumes, which are those volumes that do not have associated volumes. if (volume.getAssociatedVolumes() == null || volume.getAssociatedVolumes().isEmpty()) { String personality = volume.getPersonality(); if (Volume.isSRDFProtectedVolume(volume) || personality == null || PersonalityTypes.SOURCE.name().equalsIgnoreCase(personality)) { volumeList.add(volume); } } } } return volumeList; } /** * Verify that the project for the volume is the same as that for the * consistency group. Throws an APIException when the projects are not the * same. * * @param volume A reference to a volume * @param cg A reference to a consistency group * @param dbClient A reference to a database client */ public static void verifyProjectForVolumeToBeAddedToCG(Volume volume, BlockConsistencyGroup cg, DbClient dbClient) { // Object to add to consistency group must be in the same project URI cgProjectURI = cg.getProject().getURI(); URI volumeProjectURI = volume.getProject().getURI(); if (!volumeProjectURI.equals(cgProjectURI)) { List<Project> projects = dbClient.queryObjectField(Project.class, "label", Arrays.asList(cgProjectURI, volumeProjectURI)); throw APIException.badRequests .consistencyGroupAddVolumeThatIsInDifferentProject(volume.getLabel(), projects.get(0).getLabel(), projects.get(1).getLabel()); } } public static List<Volume> getAllCGVolumes(BlockConsistencyGroup cg, DbClient dbClient) { List<Volume> result = new ArrayList<>(); if (cg.checkForType(BlockConsistencyGroup.Types.VPLEX) && cg.checkForType(BlockConsistencyGroup.Types.RP)) { // VPLEX+RP - Right now application supports taking snap sessions on RP targets too. result.addAll(getActiveVplexVolumesInCG(cg, dbClient, null)); } else if (cg.checkForType(BlockConsistencyGroup.Types.VPLEX) && !cg.checkForType(BlockConsistencyGroup.Types.RP)) { // VPLEX result.addAll(getActiveVplexVolumesInCG(cg, dbClient, null)); } else if (cg.checkForType(BlockConsistencyGroup.Types.RP) && !cg.checkForType(BlockConsistencyGroup.Types.VPLEX)) { // RP Right now application supports taking snap sessions on RP targets too. result.addAll(getActiveNonVplexVolumesInCG(cg, dbClient, null)); } else { // Native (no protection) result.addAll(getActiveNativeVolumesInCG(cg, dbClient)); } return result; } /** * Given a list of volumes find all unique CG URIs. * * @param volumes List of volumes to parse over and find all CGs related to the volumes * @return Set of unique CG URIs */ public static Set<URI> getAllCGsFromVolumes(List<Volume> volumes) { // Using a Set to store all unique CG URIs collected from the // volumes passed in. Set<URI> cgURIs = new HashSet<URI>(); if (volumes != null) { for (Volume vol : volumes) { if (!NullColumnValueGetter.isNullURI(vol.getConsistencyGroup())) { cgURIs.add(vol.getConsistencyGroup()); } } } return cgURIs; } /** * Method to clean up a consistency group after an operation succeeds or fails. * * @param consistencyGroup * consistency group * @param storageId * storage system * @param replicationGroupName * replication group name * @param markInactive * whether you can mark it as inactive as part of cleanup (delete the CG itself) * @param dbClient * dbclient */ public static void cleanUpCG(BlockConsistencyGroup consistencyGroup, URI storageId, String replicationGroupName, Boolean markInactive, DbClient dbClient) { checkNotNull(consistencyGroup, "consistency group must be non-null"); // Remove the replication group name from the SystemConsistencyGroup field if (replicationGroupName != null) { consistencyGroup.removeSystemConsistencyGroup(URIUtil.asString(storageId), replicationGroupName); } /* * Verify if the BlockConsistencyGroup references any LOCAL arrays. * If we no longer have any references we can remove the 'LOCAL' type from the BlockConsistencyGroup. */ List<URI> referencedArrays = getLocalSystems(consistencyGroup, dbClient); boolean cgReferenced = false; for (URI storageSystemUri : referencedArrays) { StringSet cgs = consistencyGroup.getSystemConsistencyGroups().get(storageSystemUri.toString()); if (cgs != null && !cgs.isEmpty()) { cgReferenced = true; break; } } if (!cgReferenced) { // Remove the LOCAL type StringSet cgTypes = consistencyGroup.getTypes(); cgTypes.remove(BlockConsistencyGroup.Types.LOCAL.name()); consistencyGroup.setTypes(cgTypes); StringSet requestedTypes = consistencyGroup.getRequestedTypes(); requestedTypes.remove(BlockConsistencyGroup.Types.LOCAL.name()); consistencyGroup.setRequestedTypes(requestedTypes); // Remove the referenced storage system as well, but only if there are no other types // of storage systems associated with the CG. if (!BlockConsistencyGroupUtils.referencesNonLocalCgs(consistencyGroup, dbClient)) { consistencyGroup.setStorageController(NullColumnValueGetter.getNullURI()); // Clear any remaining types and requestedTypes consistencyGroup.getTypes().clear(); consistencyGroup.getRequestedTypes().clear(); // Update the consistency group model consistencyGroup.setInactive(markInactive); } } } /** * Method to clean up a consistency group after an operation succeeds or fails. Also persists any changes to the * database. * * @param consistencyGroup * consistency group * @param storageId * storage system * @param replicationGroupName * replication group name * @param markInactive * whether you can mark it as inactive as part of cleanup (delete the CG itself) * @param dbClient * dbclient */ public static void cleanUpCGAndUpdate(BlockConsistencyGroup consistencyGroup, URI storageId, String replicationGroupName, Boolean markInactive, DbClient dbClient) { if (consistencyGroup != null) { cleanUpCG(consistencyGroup, storageId, replicationGroupName, markInactive, dbClient); dbClient.updateObject(consistencyGroup); } } /** * Return a set of ReplicationGroup names for the given storage system and consistency group. * * @param consistencyGroup Consistency group * @param storageSystem Storage system * @return Set of group names or an empty set if none exist. */ public static Set<String> getGroupNamesForSystemCG(BlockConsistencyGroup consistencyGroup, StorageSystem storageSystem) { checkNotNull(consistencyGroup); checkNotNull(storageSystem); Set<String> result = new HashSet<>(); StringSetMap systemConsistencyGroups = consistencyGroup.getSystemConsistencyGroups(); if (systemConsistencyGroups != null) { StringSet cgsForSystem = systemConsistencyGroups.get(storageSystem.getId().toString()); if (cgsForSystem != null || !cgsForSystem.isEmpty()) { result.addAll(cgsForSystem); } } return result; } }