/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.api.service.impl.resource.utils; import static com.emc.storageos.db.client.model.SynchronizationState.FRACTURED; import static com.emc.storageos.db.client.util.CommonTransformerFunctions.FCTN_STRING_TO_URI; import static com.google.common.collect.Collections2.transform; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.UUID; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.api.mapper.TaskMapper; import com.emc.storageos.api.service.authorization.PermissionsHelper; import com.emc.storageos.api.service.impl.resource.ArgValidator; import com.emc.storageos.db.client.DbClient; 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.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockMirror; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType; import com.emc.storageos.db.client.model.BlockSnapshotSession; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.DataObject.Flag; import com.emc.storageos.db.client.model.DiscoveredDataObject.Type; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Project; import com.emc.storageos.db.client.model.ScopedLabel; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.Task; import com.emc.storageos.db.client.model.VirtualArray; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.VolumeGroup; import com.emc.storageos.db.client.model.VplexMirror; import com.emc.storageos.db.client.model.util.TagUtils; import com.emc.storageos.db.client.model.util.TaskUtils; import com.emc.storageos.db.client.util.CustomQueryUtility; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.db.client.util.ResourceOnlyNameGenerator; import com.emc.storageos.db.client.util.StringSetUtil; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.model.TaskResourceRep; import com.emc.storageos.security.authentication.StorageOSUser; import com.emc.storageos.security.authorization.ACL; import com.emc.storageos.security.authorization.Role; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.svcs.errorhandling.resources.APIException; import com.emc.storageos.util.VPlexUtil; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.smis.SmisConstants; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Sets; import com.google.common.collect.Table; /** * Utility class to hold generic, reusable block service methods */ public class BlockServiceUtils { private static Logger _log = LoggerFactory.getLogger(BlockServiceUtils.class); /** * Validate that the passed block object is not an internal block object, * such as a backend volume for a VPLEX volume. If so, throw a bad request * exception unless the SUPPORTS_FORCE flag is present AND force is true. * * @param blockObject A reference to a BlockObject * @param force true if an operation should be forced regardless of whether * or not the passed block object is an internal object, false * otherwise. */ public static void validateNotAnInternalBlockObject(BlockObject blockObject, boolean force) { if (blockObject != null) { if (blockObject.checkInternalFlags(Flag.INTERNAL_OBJECT) && !blockObject.checkInternalFlags(Flag.SUPPORTS_FORCE)) { throw APIException.badRequests.notSupportedForInternalVolumes(); } else if (blockObject.checkInternalFlags(Flag.INTERNAL_OBJECT) && blockObject.checkInternalFlags(Flag.SUPPORTS_FORCE) && !force) { throw APIException.badRequests.notSupportedForInternalVolumes(); } } } /** * Validate that the passed block object is not marked as a boot volume. * * @param blockObject A reference to a BlockObject * @param force true if an operation should be forced regardless of whether * or not the passed block object is an internal object, false * otherwise. */ public static void validateNotABootVolume(BlockObject blockObject, boolean force) { if (blockObject != null && blockObject.getTag() != null) { Iterator<ScopedLabel> slIter = blockObject.getTag().iterator(); boolean taggedAsBootVolume = false; while (slIter.hasNext()) { ScopedLabel sl = slIter.next(); if (sl.getLabel().startsWith(TagUtils.getBootVolumeTagName())) { taggedAsBootVolume = true; } } if (taggedAsBootVolume && !force) { throw APIException.badRequests.notSupportedForBootVolumes(); } } } /** * Gets and verifies that the VirtualArray passed in the request is * accessible to the tenant. * * @param project A reference to the project. * @param varrayURI The URI of the VirtualArray * * @return A reference to the VirtualArray. */ public static VirtualArray verifyVirtualArrayForRequest(Project project, URI varrayURI, UriInfo uriInfo, PermissionsHelper permissionsHelper, DbClient dbClient) { VirtualArray neighborhood = dbClient.queryObject(VirtualArray.class, varrayURI); ArgValidator.checkEntity(neighborhood, varrayURI, isIdEmbeddedInURL(varrayURI, uriInfo)); permissionsHelper.checkTenantHasAccessToVirtualArray(project.getTenantOrg() .getURI(), neighborhood); return neighborhood; } /** * Determine if the unique id for a resource is embedded in the passed * resource URI. * * @param resourceURI A resource URI. * @param uriInfo A reference to the URI info. * * @return true if the unique id for a resource is embedded in the passed * resource URI, false otherwise. */ public static boolean isIdEmbeddedInURL(final URI resourceURI, UriInfo uriInfo) { ArgValidator.checkUri(resourceURI); return isIdEmbeddedInURL(resourceURI.toString(), uriInfo); } /** * Determine if the unique id for a resource is embedded in the passed * resource id. * * @param resourceId A resource Id. * @param uriInfo A reference to the URI info. * * @return true if the unique id for a resource is embedded in the passed * resource Id, false otherwise. */ public static boolean isIdEmbeddedInURL(final String resourceId, UriInfo uriInfo) { try { final Set<Entry<String, List<String>>> pathParameters = uriInfo .getPathParameters().entrySet(); for (final Entry<String, List<String>> entry : pathParameters) { for (final String param : entry.getValue()) { if (param.equals(resourceId)) { return true; } } } } catch (Exception e) { // ignore any errors and return false } return false; } /** * Verify the user is authorized for a request. * * @param project A reference to the Project. */ public static void verifyUserIsAuthorizedForRequest(Project project, StorageOSUser user, PermissionsHelper permissionsHelper) { if (!(permissionsHelper.userHasGivenRole(user, project.getTenantOrg().getURI(), Role.TENANT_ADMIN) || permissionsHelper.userHasGivenACL(user, project.getId(), ACL.OWN, ACL.ALL))) { throw APIException.forbidden.insufficientPermissionsForUser(user.getName()); } } /** * Get StorageOSUser from the passed security context. * * @param securityContext A reference to the security context. * * @return A reference to the StorageOSUser. */ public static StorageOSUser getUserFromContext(SecurityContext securityContext) { if (!hasValidUserInContext(securityContext)) { throw APIException.forbidden.invalidSecurityContext(); } return (StorageOSUser) securityContext.getUserPrincipal(); } /** * Determine if the security context has a valid StorageOSUser object. * * @param securityContext A reference to the security context. * * @return true if the StorageOSUser is present. */ public static boolean hasValidUserInContext(SecurityContext securityContext) { if ((securityContext != null) && (securityContext.getUserPrincipal() instanceof StorageOSUser)) { return true; } else { return false; } } /** * For VMAX3, We can't create fullcopy/mirror when there are active snap sessions. * * @TODO remove this validation when provider add support for this. * @param sourceVolURI * @param dbClient */ public static void validateVMAX3ActiveSnapSessionsExists(URI sourceVolURI, DbClient dbClient, String replicaType) { URIQueryResultList queryResults = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(sourceVolURI), queryResults); Iterator<URI> queryResultsIter = queryResults.iterator(); while (queryResultsIter.hasNext()) { BlockSnapshot snapshot = dbClient.queryObject(BlockSnapshot.class, queryResultsIter.next()); if ((snapshot != null) && (!snapshot.getInactive()) && (snapshot.getIsSyncActive())) { throw APIException.badRequests.noFullCopiesForVMAX3VolumeWithActiveSnapshot(replicaType); } } // Also check for snapshot sessions. List<BlockSnapshotSession> snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(sourceVolURI)); if (!snapSessions.isEmpty()) { throw APIException.badRequests.noFullCopiesForVMAX3VolumeWithActiveSnapshot(replicaType); } } /** * For VMAX, creating/deleting volume in/from CG with existing group relationship is supported for SMI-S provider version 8.0.3 or * higher * * Fox XtremIO creating/deleting volume in/from CG with existing CG is supported. * * For VNX, creating/deleting volume in/from CG with existing group relationship is supported if volume is not part of an array * replication group * * For Application support, allow volumes to be added/removed to/from CG for VPLEX when the backend volume is VMAX/VNX/XtremIO * * @param cg BlockConsistencyGroup * @param volume Volume part of the CG * @dbClient DbClient * @return true if the operation is supported. */ public static boolean checkCGVolumeCanBeAddedOrRemoved(BlockConsistencyGroup cg, Volume volume, DbClient dbClient) { StorageSystem storage = dbClient.queryObject(StorageSystem.class, volume.getStorageController()); if (storage != null) { if (storage.deviceIsType(Type.vmax)) { if (storage.getUsingSmis80()) { return true; } } else if (storage.deviceIsType(Type.vnxblock)) { BlockConsistencyGroup consistencyGroup = cg; if (consistencyGroup == null) { consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, volume.getConsistencyGroup()); } if (consistencyGroup != null && !consistencyGroup.getInactive()) { return !consistencyGroup.getArrayConsistency(); } } else if (storage.deviceIsType(Type.xtremio)) { return true; } else if (storage.deviceIsType(Type.unity) && volume.checkForRp()) { return true; } if (storage.deviceIsType(Type.vplex)) { Set<Type> applicationSupported = Sets.newHashSet(Type.vmax, Type.vnxblock, Type.xtremio, Type.unity); Set<Type> backendSystemTypes = new HashSet<>(); if (volume.getAssociatedVolumes() != null && !volume.getAssociatedVolumes().isEmpty()) { for (String associatedVolumeId : volume.getAssociatedVolumes()) { Volume associatedVolume = dbClient.queryObject(Volume.class, URI.create(associatedVolumeId)); if (associatedVolume != null) { StorageSystem backendSystem = dbClient.queryObject(StorageSystem.class, associatedVolume.getStorageController()); if (backendSystem != null && !Strings.isNullOrEmpty(backendSystem.getSystemType())) { backendSystemTypes.add(Type.valueOf(backendSystem.getSystemType())); } } } } // Application support: Allow volumes to be added/removed to/from CG for VPLEX and RP // when the backend volume is VMAX/VNX/XtremIO if (volume.getApplication(dbClient) != null) { // Returns true, if any backendSystemTypes are in the supported set for applications return !Collections.disjoint(applicationSupported, backendSystemTypes); } else if (!Volume.checkForRP(dbClient, volume.getId())) { // Returns true, for VPLEX&VMAX scenarios return backendSystemTypes.contains(Type.vmax); } } } return false; } /** * Check if the storage system type is openstack, vnxblock, vmax or ibmxiv. * Snapshot full copy is supported only on these storage systems. * * @param blockSnapURI SnapshotURI for which storage system type needs to be checked * @param dbClient DBClient object * @return */ public static boolean isSnapshotFullCopySupported(URI blockSnapURI, DbClient dbClient) { BlockSnapshot blockObj = dbClient.queryObject(BlockSnapshot.class, blockSnapURI); StorageSystem storage = dbClient.queryObject(StorageSystem.class, blockObj.getStorageController()); return (storage != null && (storage.deviceIsType(Type.openstack) || storage.deviceIsType(Type.vnxblock) || storage.deviceIsType(Type.ibmxiv) || storage.deviceIsType(Type.vmax))); } /** * Return a list of active BlockMirror URI's that are known to be active * (in Synchronized state). * * @param volume Volume to check for mirrors against * @param dbClient A reference to a database client. * * @return List of active BlockMirror URI's */ public static List<URI> getActiveMirrorsForVolume(Volume volume, DbClient dbClient) { List<URI> activeMirrorURIs = new ArrayList<>(); if (hasMirrors(volume)) { Collection<URI> mirrorUris = transform(volume.getMirrors(), FCTN_STRING_TO_URI); List<BlockMirror> mirrors = dbClient.queryObject(BlockMirror.class, mirrorUris); for (BlockMirror mirror : mirrors) { if (!FRACTURED.toString().equalsIgnoreCase(mirror.getSyncState())) { activeMirrorURIs.add(mirror.getId()); } } } return activeMirrorURIs; } /** * Determines if the passed volume has attached mirrors. * * @param volume A reference to a Volume. * * @return true if passed volume has attached mirrors, false otherwise. */ public static boolean hasMirrors(Volume volume) { return volume.getMirrors() != null && !volume.getMirrors().isEmpty(); } /** * Return a list of active VplexMirror URI's that are known to be active. * * @param volume Volume to check for mirrors against. * @param dbClient A reference to a database client. * * @return List of active VplexMirror URI's. */ public static List<URI> getActiveMirrorsForVplexVolume(Volume volume, DbClient dbClient) { List<URI> activeMirrorURIs = new ArrayList<>(); if (BlockServiceUtils.hasMirrors(volume)) { List<VplexMirror> mirrors = dbClient.queryObject(VplexMirror.class, StringSetUtil.stringSetToUriList(volume.getMirrors())); for (VplexMirror mirror : mirrors) { if (!mirror.getInactive()) { activeMirrorURIs.add(mirror.getId()); } } } return activeMirrorURIs; } /** * Group volumes by array group. * * @param volumes the volumes * @return the map of array group to volumes */ public static Map<String, List<Volume>> groupVolumesByArrayGroup(List<Volume> volumes) { Map<String, List<Volume>> arrayGroupToVolumes = new HashMap<String, List<Volume>>(); for (Volume volume : volumes) { String repGroupName = volume.getReplicationGroupInstance(); if (arrayGroupToVolumes.get(repGroupName) == null) { arrayGroupToVolumes.put(repGroupName, new ArrayList<Volume>()); } arrayGroupToVolumes.get(repGroupName).add(volume); } return arrayGroupToVolumes; } /** * Checks if there are any native array snapshots with the requested name. * * @param requestedName A name requested for a new native array snapshot. * @param sourceURI The URI of the snapshot source. * @param dbClient A reference to a database client. */ public static void checkForDuplicateArraySnapshotName(String requestedName, URI sourceURI, DbClient dbClient) { // First ensure the requested snapshot name is no more than 63 characters in length. // If we remove special characters and truncate to 63 characters, this could lead // to invalid duplicate name exceptions (COP-14512). By restricting to 63 characters in total, this // won't happen. ArgValidator.checkFieldLengthMaximum(requestedName, SmisConstants.MAX_SNAPSHOT_NAME_LENGTH, "snapshotName"); // We need to check the BlockSnapshotSession instances created using // the new Create Snapshot Session service as it creates a native // array snapshot. String modifiedRequestedName = ResourceOnlyNameGenerator.removeSpecialCharsForName( requestedName, SmisConstants.MAX_SNAPSHOT_NAME_LENGTH); List<BlockSnapshotSession> snapSessions = null; Volume sourceVolume = null; if (URIUtil.isType(sourceURI, Volume.class)) { sourceVolume = dbClient.queryObject(Volume.class, sourceURI); } if (sourceVolume != null && NullColumnValueGetter.isNotNullValue(sourceVolume.getReplicationGroupInstance())) { snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getBlockSnapshotSessionByConsistencyGroup(sourceVolume.getConsistencyGroup())); } else { snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(sourceURI)); } for (BlockSnapshotSession snapSession : snapSessions) { if (modifiedRequestedName.equals(snapSession.getSessionLabel())) { throw APIException.badRequests.duplicateLabel(requestedName); } } // We also need to check BlockSnapshot instances created on the source // using the existing Create Snapshot service. List<BlockSnapshot> sourceSnapshots = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshot.class, ContainmentConstraint.Factory.getVolumeSnapshotConstraint(sourceURI)); for (BlockSnapshot snapshot : sourceSnapshots) { if (modifiedRequestedName.equals(snapshot.getSnapsetLabel())) { throw APIException.badRequests.duplicateLabel(requestedName); } } } /** * Gets the number of native array snapshots created for the source with * the passed URI. * * @param sourceURI The URI of the source. * @param dbClient A reference to a database client. * * @return The number of native array snapshots for the source. */ public static int getNumNativeSnapshots(URI sourceURI, DbClient dbClient) { // The number of native array snapshots is determined by the // number of BlockSnapshotSession instances created for the // source using new Create Snapshot Session service. List<BlockSnapshotSession> snapSessions = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(sourceURI)); int numSnapshots = snapSessions.size(); // Also, we must account for the native array snapshots associated // with the BlockSnapshot instances created using the existing Create // Block Snapshot service. These will be the BlockSnapshot instances // that are not a linked target for a BlockSnapshotSession instance. List<BlockSnapshot> sourceSnapshots = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshot.class, ContainmentConstraint.Factory.getVolumeSnapshotConstraint(sourceURI)); for (BlockSnapshot snapshot : sourceSnapshots) { URIQueryResultList queryResults = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getLinkedTargetSnapshotSessionConstraint( snapshot.getId()), queryResults); Iterator<URI> queryResultsIter = queryResults.iterator(); if ((!queryResultsIter.hasNext()) && (TechnologyType.NATIVE.toString().equalsIgnoreCase(snapshot.getTechnologyType()))) { numSnapshots++; } } return numSnapshots; } /** * Creates a Task on given Volume with Error state * * @param opr the opr * @param volume the volume * @param sc the sc * @return the failed task for volume */ public static TaskResourceRep createFailedTaskOnVolume(DbClient dbClient, Volume volume, ResourceOperationTypeEnum opr, ServiceCoded sc) { String taskId = UUID.randomUUID().toString(); Operation op = new Operation(); op.setResourceType(opr); dbClient.createTaskOpStatus(Volume.class, volume.getId(), taskId, op); volume = dbClient.queryObject(Volume.class, volume.getId()); op = volume.getOpStatus().get(taskId); op.error(sc); volume.getOpStatus().updateTaskStatus(taskId, op); dbClient.updateObject(volume); return TaskMapper.toTask(volume, taskId, op); } /** * Creates a Task on given CG with Error state * * @param opr the opr * @param cg the consistency group * @param sc the sc * @return the failed task for cg */ public static TaskResourceRep createFailedTaskOnCG(DbClient dbClient, BlockConsistencyGroup cg, ResourceOperationTypeEnum opr, ServiceCoded sc) { String taskId = UUID.randomUUID().toString(); Operation op = new Operation(); op.setResourceType(opr); dbClient.createTaskOpStatus(BlockConsistencyGroup.class, cg.getId(), taskId, op); cg = dbClient.queryObject(BlockConsistencyGroup.class, cg.getId()); op = cg.getOpStatus().get(taskId); op.error(sc); cg.getOpStatus().updateTaskStatus(taskId, op); dbClient.updateObject(cg); return TaskMapper.toTask(cg, taskId, op); } /** * Creates a Task on given snapshot session with Error state * * @param opr the opr * @param session the snap session * @param sc the sc * @return the failed task for snap session */ public static TaskResourceRep createFailedTaskOnSnapshotSession(DbClient dbClient, BlockSnapshotSession session, ResourceOperationTypeEnum opr, ServiceCoded sc) { String taskId = UUID.randomUUID().toString(); Operation op = new Operation(); op.setResourceType(opr); dbClient.createTaskOpStatus(BlockSnapshotSession.class, session.getId(), taskId, op); session = dbClient.queryObject(BlockSnapshotSession.class, session.getId()); op = session.getOpStatus().get(taskId); op.error(sc); session.getOpStatus().updateTaskStatus(taskId, op); dbClient.updateObject(session); return TaskMapper.toTask(session, taskId, op); } /** * Given a Tenant and DataObject references, check if any of the DataObjects have pending * Tasks against them. If so, generate an error that this cannot be deleted. * * @param tenant - [in] Tenant URI * @param dataObjects - [in] List of DataObjects to check * @param dbClient - Reference to a database client */ public static void checkForPendingTasks(URI tenant, Collection<? extends DataObject> dataObjects, DbClient dbClient) { // First, find tasks for the resources sent in. Set<URI> objectURIsThatHavePendingTasks = new HashSet<URI>(); // Get a unique list of Task objects associated with the data objects for (DataObject dataObject : dataObjects) { List<Task> newTasks = TaskUtils.findResourceTasks(dbClient, dataObject.getId()); for (Task newTask : newTasks) { if (newTask.isPending() && newTask.getTenant().equals(tenant)) { objectURIsThatHavePendingTasks.add(dataObject.getId()); } } } // Search through the list of Volumes to see if any are in the pending list List<String> pendingObjectLabels = new ArrayList<>(); for (DataObject dataObject : dataObjects) { if (dataObject.getInactive()) { continue; } String label = dataObject.getLabel(); if (label == null) { label = dataObject.getId().toString(); } if (objectURIsThatHavePendingTasks.contains(dataObject.getId())) { pendingObjectLabels.add(label); // Remove entry, since we already found it was matched. objectURIsThatHavePendingTasks.remove(dataObject.getId()); } } // If there are an pendingObjectLabels, then we found some objects that have // a pending task against them. Need to signal an error if (!pendingObjectLabels.isEmpty()) { String pendingListStr = Joiner.on(',').join(pendingObjectLabels); _log.warn(String.format( "Attempted to execute operation against these resources while there are tasks pending against them: %s", pendingListStr)); throw APIException.badRequests.cannotExecuteOperationWhilePendingTask(pendingListStr); } } /** * Group volumes by storage system and replication group * * @param volumeUris List of volumes (part or all) in a volume group * @param cgUri * @param dbClient * @return table with storage URI, replication group name, and volumes */ public static Table<URI, String, List<Volume>> getReplicationGroupVolumes(List<URI> volumeUris, URI cgUri, DbClient dbClient, UriInfo uriInfo) { // Group volumes by storage system and replication group Table<URI, String, List<Volume>> storageRgToVolumes = HashBasedTable.create(); for (URI volumeUri : volumeUris) { ArgValidator.checkFieldUriType(volumeUri, Volume.class, "volume"); Volume volume = dbClient.queryObject(Volume.class, volumeUri); ArgValidator.checkEntity(volume, volumeUri, isIdEmbeddedInURL(volumeUri, uriInfo)); if (!volume.isInCG() || !volume.getConsistencyGroup().equals(cgUri)) { throw APIException.badRequests.invalidParameterSourceVolumeNotInGivenConsistencyGroup(volumeUri, cgUri); } String label = volume.getLabel(); boolean isVPlex = volume.isVPlexVolume(dbClient); if (isVPlex) { // get backend source volume to get RG name volume = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient); if (volume == null || volume.getInactive()) { throw APIException.badRequests.noBackendVolume(label); } } String rgName = volume.getReplicationGroupInstance(); if (NullColumnValueGetter.isNullValue(rgName)) { throw APIException.badRequests.noRepGroupInstance(volume.getLabel()); } URI storage = volume.getStorageController(); if (!storageRgToVolumes.contains(storage, rgName)) { List<Volume> volumes = ControllerUtils.getVolumesPartOfRG(storage, rgName, dbClient); if (isVPlex) { List<Volume> vplexVolumes = new ArrayList<Volume>(); for (Volume backendVol : volumes) { Volume vplexVol = Volume.fetchVplexVolume(dbClient, backendVol); if (vplexVol == null || vplexVol.getInactive()) { throw APIException.badRequests.noVPLEXVolume(backendVol.getLabel()); } vplexVolumes.add(vplexVol); } volumes = vplexVolumes; } storageRgToVolumes.put(storage, rgName, volumes); } } return storageRgToVolumes; } /** * Group CG volumes by storage system and replication group * * @param srcVolumes List of all volumes in a CG * @param dbClient * @return table with storage URI, replication group name, and volumes */ public static Table<URI, String, List<Volume>> getReplicationGroupVolumes(List<Volume> srcVolumes, DbClient dbClient) { // Group volumes by storage system and replication group Table<URI, String, List<Volume>> storageRgToVolumes = HashBasedTable.create(); for (Volume volume : srcVolumes) { String rgName = null; URI storage = null; if (volume.isVPlexVolume(dbClient)) { // get backend source volume to get RG name Volume backedVol = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient); if (backedVol != null) { rgName = backedVol.getReplicationGroupInstance(); storage = backedVol.getStorageController(); } } else { rgName = volume.getReplicationGroupInstance(); storage = volume.getStorageController(); } if (NullColumnValueGetter.isNullValue(rgName)) { throw APIException.badRequests.noRepGroupInstance(volume.getLabel()); } List<Volume> volumes = storageRgToVolumes.get(storage, rgName); if (volumes == null) { volumes = new ArrayList<Volume>(); storageRgToVolumes.put(storage, rgName, volumes); } volumes.add(volume); } return storageRgToVolumes; } public static BlockSnapshot querySnapshotResource(URI snapshotURI, UriInfo uriInfo, DbClient dbClient) { ArgValidator.checkFieldUriType(snapshotURI, BlockSnapshot.class, "snapshots"); BlockSnapshot snapshot = dbClient.queryObject(BlockSnapshot.class, snapshotURI); ArgValidator.checkEntity(snapshot, snapshotURI, BlockServiceUtils.isIdEmbeddedInURL(snapshotURI, uriInfo), true); return snapshot; } /** * validate volume with no replica * * @param volume * @param application * @param dbClient */ public static void validateVolumeNoReplica(Volume volume, VolumeGroup application, DbClient dbClient) { // check if the volume has any replica // no need to check backing volumes for vplex virtual volumes because for full copies // there will be a virtual volume for the clone boolean hasReplica = volume.getFullCopies() != null && !volume.getFullCopies().isEmpty() || volume.getMirrors() != null && !volume.getMirrors().isEmpty(); // check for snaps only if no full copies if (!hasReplica) { Volume snapSource = volume; if (volume.isVPlexVolume(dbClient)) { snapSource = VPlexUtil.getVPLEXBackendVolume(volume, true, dbClient); if (snapSource == null || snapSource.getInactive()) { return; } } hasReplica = ControllerUtils.checkIfVolumeHasSnapshot(snapSource, dbClient); // check for VMAX3 individual session and group session if (!hasReplica && snapSource.isVmax3Volume(dbClient)) { hasReplica = ControllerUtils.checkIfVolumeHasSnapshotSession(snapSource.getId(), dbClient); String rgName = snapSource.getReplicationGroupInstance(); if (!hasReplica && NullColumnValueGetter.isNotNullValue(rgName)) { URI cgURI = snapSource.getConsistencyGroup(); List<BlockSnapshotSession> sessionsList = CustomQueryUtility.queryActiveResourcesByConstraint(dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getBlockSnapshotSessionByConsistencyGroup(cgURI)); for (BlockSnapshotSession session : sessionsList) { if (rgName.equals(session.getReplicationGroupInstance())) { hasReplica = true; break; } } } } } if (hasReplica) { throw APIException.badRequests.volumeGroupCantBeUpdated(application.getLabel(), String.format("the volume %s has replica. please remove all replicas from the volume", volume.getLabel())); } } /** * Check if a unity volume could be add or removed from unity consistency group. for Unity, if the unity CG has snapshot, volumes could * not be added or removed. * * @param rgName Unity consistency group name * @param volume Unity volume to be added or removed * @param dbClient * @param isAdd If the volume is for add * @return true if the volume could be added or removed */ public static boolean checkUnityVolumeCanBeAddedOrRemovedToCG(String rgName, Volume volume, DbClient dbClient, boolean isAdd) { StorageSystem storage = dbClient.queryObject(StorageSystem.class, volume.getStorageController()); if (storage != null) { if (storage.deviceIsType(Type.unity)) { if (isAdd && rgName != null) { List<Volume> volumesInRG = CustomQueryUtility.queryActiveResourcesByConstraint( dbClient, Volume.class, AlternateIdConstraint.Factory.getVolumeByReplicationGroupInstance(rgName)); if (volumesInRG != null && !volumesInRG.isEmpty()) { for (Volume vol : volumesInRG) { if (vol.getStorageController().equals(volume.getStorageController())) { // Check if the volume in RG has snapshot List<BlockSnapshot> snaps = getVolumeNativeSnapshots(vol.getId(), dbClient); if (!snaps.isEmpty()) { return false; } } } } } else if (!isAdd) { // for remove, Check if the volume has snapshot List<BlockSnapshot> snaps = getVolumeNativeSnapshots(volume.getId(), dbClient); if (!snaps.isEmpty()) { return false; } } } } return true; } /** * Get volume's block snapshots, whose technologyType attributes is NATIVE * * @param volumeUri The volume URI * @param dbClient * @return The list of block snapshot for the given volume. */ public static List<BlockSnapshot> getVolumeNativeSnapshots(URI volumeUri, DbClient dbClient) { List<BlockSnapshot> result = new ArrayList<BlockSnapshot>(); URIQueryResultList snapshotURIs = new URIQueryResultList(); dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint( volumeUri), snapshotURIs); Iterator<URI> it = snapshotURIs.iterator(); while (it.hasNext()) { URI snapUri = it.next(); BlockSnapshot snapshot = dbClient.queryObject(BlockSnapshot.class, snapUri); if (snapshot != null && !snapshot.getInactive() && BlockSnapshot.TechnologyType.NATIVE.name().equalsIgnoreCase(snapshot.getTechnologyType())) { result.add(snapshot); } } return result; } }