/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource.fullcopy;
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 javax.ws.rs.core.UriInfo;
import com.emc.storageos.api.service.impl.resource.ArgValidator;
import com.emc.storageos.api.service.impl.resource.utils.BlockServiceUtils;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.Project;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.Volume.PersonalityTypes;
import com.emc.storageos.db.client.model.Volume.ReplicationState;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.util.VPlexUtil;
/**
* Utilities class for processing fully copy request
*/
public class BlockFullCopyUtils {
/**
* Returns the volume or block snapshot instance for the passed URI.
*
* @param fcResourceURI The URI for the Volume or BlockSnapshot instance.
* @param uriInfo A reference to the URI information.
* @param isSource true if the passed URI is for the full copy source, false otherwise.
* @param dbClient A reference to a database client.
*
* @return A reference to the block object.
*/
public static BlockObject queryFullCopyResource(URI fcResourceURI, UriInfo uriInfo,
boolean isSource, DbClient dbClient) {
ArgValidator.checkUri(fcResourceURI);
if (isSource) {
if ((!URIUtil.isType(fcResourceURI, Volume.class))
&& (!URIUtil.isType(fcResourceURI, BlockSnapshot.class))) {
throw APIException.badRequests.invalidFullCopySource(fcResourceURI.toString());
}
} else if (!URIUtil.isType(fcResourceURI, Volume.class)) {
throw APIException.badRequests.protectionVolumeNotFullCopy(fcResourceURI);
}
BlockObject blockObj = BlockObject.fetch(dbClient, fcResourceURI);
ArgValidator.checkEntity(blockObj, fcResourceURI,
BlockServiceUtils.isIdEmbeddedInURL(fcResourceURI, uriInfo), true);
return blockObj;
}
/**
* Returns the project for the full copy source.
*
* @param fcSourceObj A reference to the Volume or BlockSnapshot instance.
* @param dbClient A reference to a database client.
*
* @return A reference to the project for the full copy source.
*/
public static Project queryFullCopySourceProject(BlockObject fcSourceObj, DbClient dbClient) {
URI fcSourceURI = fcSourceObj.getId();
URI projectURI = null;
if (URIUtil.isType(fcSourceURI, Volume.class)) {
projectURI = ((Volume) fcSourceObj).getProject().getURI();
} else if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
projectURI = ((BlockSnapshot) fcSourceObj).getProject().getURI();
}
if (projectURI == null) {
throw APIException.badRequests.invalidFullCopySource(fcSourceURI.toString());
}
Project project = dbClient.queryObject(Project.class, projectURI);
return project;
}
/**
* Returns the vpool for the full copy source.
*
* @param fcSourceObj A reference to the Volume or BlockSnapshot instance.
* @param dbClient A reference to a database client.
*
* @return A reference to the vpool for the full copy source.
*/
public static VirtualPool queryFullCopySourceVPool(BlockObject fcSourceObj, DbClient dbClient) {
URI fcSourceURI = fcSourceObj.getId();
URI vpoolURI = null;
if (URIUtil.isType(fcSourceURI, Volume.class)) {
vpoolURI = ((Volume) fcSourceObj).getVirtualPool();
} else if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
URI parentVolURI = ((BlockSnapshot) fcSourceObj).getParent().getURI();
Volume parentVolume = dbClient.queryObject(Volume.class, parentVolURI);
vpoolURI = parentVolume.getVirtualPool();
}
if (vpoolURI == null) {
throw APIException.badRequests.invalidFullCopySource(fcSourceURI.toString());
}
VirtualPool vpool = dbClient.queryObject(VirtualPool.class, vpoolURI);
return vpool;
}
/**
* Returns the vpool for the full copy source.
*
* @param fcSourceObj A reference to the Volume or BlockSnapshot instance.
* @param dbClient A reference to a database client.
*
* @return A reference to the vpool for the full copy source.
*/
public static StoragePool queryFullCopySourceStoragePool(BlockObject fcSourceObj, DbClient dbClient) {
URI fcSourceURI = fcSourceObj.getId();
URI poolURI = null;
if (URIUtil.isType(fcSourceURI, Volume.class)) {
poolURI = ((Volume) fcSourceObj).getPool();
} else if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
URI parentVolURI = ((BlockSnapshot) fcSourceObj).getParent().getURI();
Volume parentVolume = dbClient.queryObject(Volume.class, parentVolURI);
poolURI = parentVolume.getPool();
} else {
throw APIException.badRequests.invalidFullCopySource(fcSourceURI.toString());
}
StoragePool storagePool = null;
if (!NullColumnValueGetter.isNullURI(poolURI)) {
storagePool = dbClient.queryObject(StoragePool.class, poolURI);
}
return storagePool;
}
/**
* Returns the capacity of the source object. The snap implementation used
* provisioned capacity, while the volume implementation used the user specified
* capacity.
*
* TBD: Evaluate the need for this. This was just the current implementation.
*
* @param fcSourceObj A reference to the Volume or BlockSnapshot instance.
* @param dbClient A reference to a database client.
*
* @return The capacity.
*/
public static long getCapacityForFullCopySource(BlockObject fcSourceObj, DbClient dbClient) {
URI fcSourceURI = fcSourceObj.getId();
if (URIUtil.isType(fcSourceURI, Volume.class)) {
return ((Volume) fcSourceObj).getCapacity();
} else if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
return ((BlockSnapshot) fcSourceObj).getProvisionedCapacity();
} else {
throw APIException.badRequests.invalidFullCopySource(fcSourceURI.toString());
}
}
/**
* Returns the capacity of the source object. The snap implementation used
* provisioned capacity, while the volume implementation used the user specified
* capacity.
*
* TBD: Evaluate the need for this. This was just the current implementation.
*
* @param fcSourceObj A reference to the Volume or BlockSnapshot instance.
* @param dbClient A reference to a database client.
*
* @return The capacity.
*/
public static long getAllocatedCapacityForFullCopySource(BlockObject fcSourceObj, DbClient dbClient) {
URI fcSourceURI = fcSourceObj.getId();
if (URIUtil.isType(fcSourceURI, Volume.class)) {
return ((Volume) fcSourceObj).getAllocatedCapacity();
} else if (URIUtil.isType(fcSourceURI, BlockSnapshot.class)) {
return ((BlockSnapshot) fcSourceObj).getAllocatedCapacity();
} else {
throw APIException.badRequests.invalidFullCopySource(fcSourceURI.toString());
}
}
/**
* Gets the SRDF copy mode of the passed volume.
*
* @param fcSourceVolume A reference to a volume.
* @param dbClient A reference to a database client.
*
* @return The SRDF copy mode of the passed volume.
*/
public static String getSRDFCopyMode(Volume volume, DbClient dbClient) {
if (Volume.isSRDFProtectedVolume(volume)) {
if (PersonalityTypes.SOURCE.toString().equalsIgnoreCase(volume.getPersonality())) {
StringSet targetIds = volume.getSrdfTargets();
if ((null != targetIds) && !targetIds.isEmpty()) {
for (String targetId : targetIds) {
Volume targetVolume = dbClient.queryObject(Volume.class, URI.create(targetId));
return targetVolume.getSrdfCopyMode();
}
}
}
return volume.getSrdfCopyMode();
}
return null;
}
/**
* Verifies the passed source and full copy URIs for a requested full
* copy operation.
*
* @param sourceURI The URI of the source volume or snapshot.
* @param fullCopyURI The URI of a full copy of the source.
* @param uriInfo A reference to the URI information.
* @param dbClient A reference to a database client.
*
* @return The map containing references to the source and full copy.
*/
public static Map<URI, BlockObject> verifySourceAndFullCopy(
URI sourceURI, URI fullCopyURI, UriInfo uriInfo, DbClient dbClient) {
// Verify passed URIs.
BlockObject fcSourceObj = queryFullCopyResource(sourceURI, uriInfo, true, dbClient);
Volume fullCopyVolume = (Volume) queryFullCopyResource(fullCopyURI, uriInfo, false, dbClient);
// Verify the full copy volume is actually a full copy.
verifyVolumeIsFullCopy(fullCopyVolume);
// Verify the copy is for the source.
verifyCopyIsForSource(fullCopyVolume, sourceURI);
// Add the volumes to the volume map.
Map<URI, BlockObject> resourceMap = new HashMap<URI, BlockObject>();
resourceMap.put(sourceURI, fcSourceObj);
resourceMap.put(fullCopyURI, fullCopyVolume);
return resourceMap;
}
/**
* Verifies that the passed volume is really a full copy volume.
*
* @param fullCopyVolume A reference to a volume.
*/
public static void verifyVolumeIsFullCopy(Volume fullCopyVolume) {
if (NullColumnValueGetter.isNullURI(fullCopyVolume.getAssociatedSourceVolume())) {
throw APIException.badRequests.protectionOnlyFullCopyVolumesCanBeActivated();
}
}
/**
* Verifies that the passed URI references the actual source for the passed
* full copy.
*
* @param fullCopyVolume A reference to the full copy volume.
* @param fcSourceURI The URI of the full copy source.
*/
public static void verifyCopyIsForSource(Volume fullCopyVolume, URI fcSourceURI) {
if (!fullCopyVolume.getAssociatedSourceVolume().toString()
.equals(fcSourceURI.toString())) {
throw APIException.badRequests.protectionVolumeNotFullCopyOfVolume(
fullCopyVolume.getId(), fcSourceURI);
}
}
/**
* Verify that a volume with full copies can be deleted.
*
* @param volume A reference to a volume that has full copies.
* @param dbClient A reference to a database client.
*
* @return true, if the volume has no full copies or is detached from all
* full copies, false otherwise.
*/
public static boolean volumeDetachedFromFullCopies(Volume volume, DbClient dbClient) {
boolean detached = true;
// The volume must be detached from all its full copies.
StringSet fullCopyIds = volume.getFullCopies();
if ((fullCopyIds != null) && (!fullCopyIds.isEmpty())) {
Iterator<String> fullCopyIdsIter = fullCopyIds.iterator();
while (fullCopyIdsIter.hasNext()) {
String fullCopyId = fullCopyIdsIter.next();
Volume fullCopyVolume = dbClient.queryObject(Volume.class,
URI.create(fullCopyId));
if (!isFullCopyDetached(fullCopyVolume, dbClient)) {
detached = false;
}
}
}
return detached;
}
/**
* Check if the full copy volume could be restored.
*
* @param volume A reference to a volume.
* @param dbClient A reference to database client.
*
* @return true if the full copy is restorable, false otherwise.
*/
public static boolean isFullCopyRestorable(Volume volume, DbClient dbClient) {
boolean result = false;
String replicaState = volume.getReplicaState();
if (isVolumeFullCopy(volume, dbClient) && replicaState != null && !replicaState.isEmpty()) {
ReplicationState state = ReplicationState.getEnumValue(replicaState);
if (state != null && state == ReplicationState.SYNCHRONIZED) {
result = true;
}
}
return result;
}
/**
* Check if the full copy volume could be resynchronized.
*
* @param volume A reference to a volume.
* @param dbClient A reference to database client.
*
* @return true if the full copy can be resynchronized, false otherwise.
*/
public static boolean isFullCopyResynchronizable(Volume volume, DbClient dbClient) {
boolean result = false;
String replicaState = volume.getReplicaState();
if (isVolumeFullCopy(volume, dbClient) && replicaState != null && !replicaState.isEmpty()) {
ReplicationState state = ReplicationState.getEnumValue(replicaState);
if (state != null && state == ReplicationState.SYNCHRONIZED) {
result = true;
}
}
return result;
}
/**
* Check if the full copy is detached.
*
* @param volume A reference to a volume.
* @param dbClient A reference to database client.
*
* @return true if the full copy is detached from the source, false otherwise.
*/
public static boolean isFullCopyDetached(Volume volume, DbClient dbClient) {
boolean result = false;
String replicaState = volume.getReplicaState();
// When the full copy is detached, it will not have reference to the source volume.
if (!isVolumeFullCopy(volume, dbClient) && replicaState != null && !replicaState.isEmpty()) {
ReplicationState state = ReplicationState.getEnumValue(replicaState);
if (state != null && state == ReplicationState.DETACHED) {
result = true;
}
}
return result;
}
/**
* Check if the full copy is inactive.
*
* @param volume A reference to a volume.
* @param dbClient A reference to database client.
*
* @return true if the full copy is inactive, false otherwise.
*/
public static boolean isFullCopyInactive(Volume volume, DbClient dbClient) {
boolean result = true;
String replicaState = volume.getReplicaState();
if (isVolumeFullCopy(volume, dbClient) && replicaState != null && !replicaState.isEmpty()) {
ReplicationState state = ReplicationState.getEnumValue(replicaState);
if (state != null && state != ReplicationState.INACTIVE) {
result = false;
}
}
return result;
}
/**
* Determines if the passed volume is a full copy.
*
* @param volume A reference to a volume.
* @param dbClient A reference to database client.
*
* @return true if the volume is a full copy, false otherwise.
*/
public static boolean isVolumeFullCopy(Volume volume, DbClient dbClient) {
boolean isFullCopy = false;
URI fcSourceObjURI = volume.getAssociatedSourceVolume();
if (!NullColumnValueGetter.isNullURI(fcSourceObjURI)) {
BlockObject fcSourceObj = BlockObject.fetch(dbClient, fcSourceObjURI);
if ((fcSourceObj != null) && (!fcSourceObj.getInactive())) {
// The volume has a valid source object, so it
// is a full copy volume. We check the source,
// because the full copy mat have been detached
// from the source and the source may have been
// deleted.
isFullCopy = true;
}
}
return isFullCopy;
}
/**
* Determine if the passed volume is a source volume
* for any full copies.
*
* @param volume A reference to a volume.
* @param dbClient A reference to a database client.
*
* @return true if the volume is a full copy source, false otherwise.
*/
public static boolean isVolumeFullCopySource(Volume volume, DbClient dbClient) {
boolean isFullCopySource = false;
StringSet fullCopyIds = volume.getFullCopies();
if ((fullCopyIds != null) && (!fullCopyIds.isEmpty())) {
Iterator<String> fullCopyIdsIter = fullCopyIds.iterator();
while (fullCopyIdsIter.hasNext()) {
URI fullCopyURI = URI.create(fullCopyIdsIter.next());
Volume fullCopyVolume = dbClient.queryObject(Volume.class, fullCopyURI);
if ((fullCopyVolume != null) && (!fullCopyVolume.getInactive())) {
isFullCopySource = true;
}
}
}
return isFullCopySource;
}
/**
* Determine if the passed volume is a source volume
* for any consistency group full copies.
*
* @param volume A reference to a volume.
* @param dbClient A reference to a database client.
*
* @return true if the volume is a CG full copy source, false otherwise.
*/
public static boolean isVolumeCGFullCopySource(Volume volume, DbClient dbClient) {
boolean isFullCopySource = false;
StringSet fullCopyIds = volume.getFullCopies();
if ((fullCopyIds != null) && (!fullCopyIds.isEmpty())) {
Iterator<String> fullCopyIdsIter = fullCopyIds.iterator();
while (fullCopyIdsIter.hasNext()) {
URI fullCopyURI = URI.create(fullCopyIdsIter.next());
Volume fullCopyVolume = dbClient.queryObject(Volume.class, fullCopyURI);
if ((fullCopyVolume != null) && (!fullCopyVolume.getInactive())) {
String groupName = fullCopyVolume.getReplicationGroupInstance();
if (NullColumnValueGetter.isNotNullValue(groupName) ||
VPlexUtil.isBackendFullCopyInReplicationGroup(fullCopyVolume, dbClient)) {
isFullCopySource = true;
break;
}
}
}
}
return isFullCopySource;
}
/**
* Determines if the passed volume has an active full copy session.
*
* @param volume A reference to the volume.
* @param dbClient A reference to database client.
*
* @return true if the volume has a full copy session, false otherwise.
*/
public static boolean volumeHasFullCopySession(Volume volume, DbClient dbClient) {
boolean hasFcSession = false;
if (((isVolumeFullCopy(volume, dbClient)) && (!isFullCopyDetached(volume, dbClient))) ||
((isVolumeFullCopySource(volume, dbClient)) && (!volumeDetachedFromFullCopies(volume, dbClient)))) {
// The volume is a full copy and it is not detached
// from its source or it is a full copy source volume
// and it is not detached from one or more of its full
// copies.
hasFcSession = true;
}
return hasFcSession;
}
/**
* Determines if the active full count is violated when a request
* is made for the passed number of full copies for the source
* volume with the passed URI. Throws and exception if this is
* the case.
*
* @param sourceVolumeURI The URI of the full copy source volume.
* @param numRequested The number of requested full copies.
* @param maxCount The maximum number of active full copy sessions.
* @param dbClient A reference to a database client.
*/
public static void validateActiveFullCopyCount(BlockObject fcSourceObj,
int numRequested, DbClient dbClient) {
validateActiveFullCopyCount(fcSourceObj, numRequested, 0, dbClient);
}
/**
* Determines if the active full count is violated when a request
* is made for the passed number of full copies for the source
* volume with the passed URI. Throws and exception if this is
* the case.
*
* @param sourceVolumeURI The URI of the full copy source volume.
* @param numRequested The number of requested full copies.
* @param otherCount Other additional count to be considered.
* @param maxCount The maximum number of active full copy sessions.
* @param dbClient A reference to a database client.
*/
public static void validateActiveFullCopyCount(BlockObject fcSourceObj,
int numRequested, int otherCount, DbClient dbClient) {
List<Volume> undetachedFullCopies = getUndetachedFullCopiesForSource(fcSourceObj,
dbClient);
int currentCount = undetachedFullCopies.size() + otherCount;
URI systemURI = fcSourceObj.getStorageController();
StorageSystem system = dbClient.queryObject(StorageSystem.class, systemURI);
int maxCount = Integer.MAX_VALUE;
if (system != null) {
maxCount = BlockFullCopyManager.getMaxFullCopiesForSystemType(system.getSystemType());
}
if ((numRequested + currentCount) > maxCount) {
throw APIException.badRequests.maxFullCopySessionLimitExceeded(
fcSourceObj.getId(), maxCount - currentCount);
}
}
/**
* Gets a list of the full copies for passed full copy source
* that are not detached from the source.
*
* @param fcSourceObj The full copy source.
* @param dbClient A reference to a database client.
*
* @return A list of the undetached full copies for the source.
*/
public static List<Volume> getUndetachedFullCopiesForSource(BlockObject fcSourceObj,
DbClient dbClient) {
ArrayList<Volume> undetachedFullCopies = new ArrayList<Volume>();
URI fcSourceURI = fcSourceObj.getId();
List<Volume> fullCopies = CustomQueryUtility.queryActiveResourcesByConstraint(
dbClient, Volume.class, ContainmentConstraint.Factory
.getAssociatedSourceVolumeConstraint(fcSourceURI));
Iterator<Volume> fullCopiesIter = fullCopies.iterator();
while (fullCopiesIter.hasNext()) {
Volume fullCopy = fullCopiesIter.next();
String fullCopyReplicaState = fullCopy.getReplicaState();
if ((!fullCopy.getInactive())
&& (!Volume.ReplicationState.DETACHED.name().equals(
fullCopyReplicaState))) {
undetachedFullCopies.add(fullCopy);
}
}
return undetachedFullCopies;
}
}