/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.block; import static com.emc.storageos.db.client.constraint.AlternateIdConstraint.Factory.getSnapshotSessionReplicationGroupInstanceConstraint; import static com.emc.storageos.db.client.constraint.AlternateIdConstraint.Factory.getVolumesByAssociatedId; import static com.emc.storageos.db.client.util.CommonTransformerFunctions.FCTN_URI_TO_STRING; import static com.emc.storageos.db.client.util.CommonTransformerFunctions.fctnDataObjectToID; import static com.google.common.collect.Collections2.filter; import static com.google.common.collect.Collections2.transform; import static com.google.common.collect.Lists.newArrayList; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.Controller; import com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface; import com.emc.storageos.blockorchestrationcontroller.VolumeDescriptor; 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.BlockSnapshotSession; import com.emc.storageos.db.client.model.DataObject.Flag; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.model.OpStatusMap; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.db.client.model.SynchronizationState; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils; 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.exceptions.DeviceControllerException; import com.emc.storageos.locking.LockTimeoutValue; import com.emc.storageos.locking.LockType; import com.emc.storageos.svcs.errorhandling.model.ServiceError; import com.emc.storageos.svcs.errorhandling.resources.InternalException; import com.emc.storageos.volumecontroller.ControllerException; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.impl.ControllerLockingUtil; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupUpdateCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotRestoreCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeWorkflowCompleter; import com.emc.storageos.volumecontroller.impl.smis.SmisConstants; import com.emc.storageos.volumecontroller.impl.smis.srdf.SRDFUtils; import com.emc.storageos.volumecontroller.impl.utils.labels.LabelFormat; import com.emc.storageos.volumecontroller.impl.utils.labels.LabelFormatFactory; import com.emc.storageos.workflow.Workflow; import com.emc.storageos.workflow.WorkflowException; import com.emc.storageos.workflow.WorkflowService; import com.emc.storageos.workflow.WorkflowStepCompleter; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; /** * Specific controller implementation to support block orchestration for handling replicas of volumes in a consistency group. */ public class ReplicaDeviceController implements Controller, BlockOrchestrationInterface { private static final Logger log = LoggerFactory.getLogger(ReplicaDeviceController.class); private static final String MARK_SNAP_SESSIONS_INACTIVE_OR_REMOVE_TARGET_ID = "markSnapSessionsInactiveOrRemoveTargetId"; private DbClient _dbClient; private BlockDeviceController _blockDeviceController; public void setDbClient(DbClient dbClient) { _dbClient = dbClient; } public void setBlockDeviceController(BlockDeviceController blockDeviceController) { this._blockDeviceController = blockDeviceController; } @Override public String addStepsForCreateVolumes(Workflow workflow, String waitFor, List<VolumeDescriptor> volumes, String taskId) throws InternalException { // Get the list of descriptors which represent source volumes that have // just been created and added to CG possibly List<VolumeDescriptor> volumeDescriptors = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA, VolumeDescriptor.Type.SRDF_SOURCE, VolumeDescriptor.Type.SRDF_EXISTING_SOURCE }, null); // If no source volumes, just return if (volumeDescriptors.isEmpty()) { log.info("No replica steps required"); return waitFor; } // Get the consistency group. If no consistency group for // any source volumes, just return. Get CG from any descriptor. URI cgURI = null; for (VolumeDescriptor descriptor : volumeDescriptors) { Volume volume = _dbClient.queryObject(Volume.class, descriptor.getVolumeURI()); if (volume == null || !volume.isInCG() || !(ControllerUtils.isVmaxVolumeUsing803SMIS(volume, _dbClient) || ControllerUtils.isNotInRealVNXRG(volume, _dbClient))) { log.info("No replica steps required for volume: " + descriptor.getVolumeURI()); continue; } log.info("CG URI:{}", volume.getConsistencyGroup()); cgURI = volume.getConsistencyGroup(); } // If array consistency in disabled in CG and VPLEX/RP provisioning, skip creating replicas. // Reason:Provisioning new volumes for VPLEX/RP CG in Application does not add backend volume to RG if (!NullColumnValueGetter.isNullURI(cgURI)) { BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgURI); if (!cg.getArrayConsistency() && isBackendVolumeForVplexOrRp(volumes)) { log.info("No replica steps required for CG {} as array consistency is disabled.", cg.getLabel()); return waitFor; } } else { log.info("No replica steps required because no volumes had CG"); return waitFor; } List<VolumeDescriptor> nonSrdfVolumeDescriptors = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA }, null); List<VolumeDescriptor> srdfSourceVolumeDescriptors = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.SRDF_SOURCE, VolumeDescriptor.Type.SRDF_EXISTING_SOURCE }, null); if (srdfSourceVolumeDescriptors.isEmpty()) { waitFor = createReplicaIfCGHasReplica(workflow, waitFor, nonSrdfVolumeDescriptors, cgURI); } else { // Create Replica for SRDF R1 and R2 if any replica available already log.debug("srdfSourceVolumeDescriptors :{}", srdfSourceVolumeDescriptors); List<VolumeDescriptor> srdfTargetVolumeDescriptors = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.SRDF_TARGET }, null); log.debug("srdfTargetVolumeDescriptors :{}", srdfTargetVolumeDescriptors); // Create replica for R1 waitFor = createReplicaIfCGHasReplica(workflow, waitFor, srdfSourceVolumeDescriptors, cgURI); // Create replica for R2 URI targetVolumeCG = SRDFUtils.getTargetVolumeCGFromSourceCG(_dbClient, cgURI); if (targetVolumeCG != null) { waitFor = createReplicaIfCGHasReplica(workflow, waitFor, srdfTargetVolumeDescriptors, targetVolumeCG); } } return waitFor; } /** * Checks if the requested volume descriptors has a mix of Block and VPLEX/RP volumes * * @param volumes all volume descriptors * @return true, if is backend volume for vplex or rp */ private boolean isBackendVolumeForVplexOrRp(List<VolumeDescriptor> volumes) { // Get only the block volumes from the descriptors List<VolumeDescriptor> blockVolumes = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA }, new VolumeDescriptor.Type[] {}); // Get only the VPlex volumes from the descriptors List<VolumeDescriptor> vplexVolumes = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.VPLEX_VIRT_VOLUME }, new VolumeDescriptor.Type[] {}); // Get only the RP volumes from the descriptors List<VolumeDescriptor> protectedVolumes = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.RP_TARGET, VolumeDescriptor.Type.RP_VPLEX_VIRT_TARGET, VolumeDescriptor.Type.RP_EXISTING_PROTECTED_SOURCE, VolumeDescriptor.Type.RP_JOURNAL, VolumeDescriptor.Type.RP_VPLEX_VIRT_JOURNAL }, new VolumeDescriptor.Type[] {}); if (!blockVolumes.isEmpty() && (!vplexVolumes.isEmpty() || !protectedVolumes.isEmpty())) { return true; } return false; } /** * Creates replica snap/clone/mirror for the newly created volume, if the existing CG Volume has any replica. * * @param workflow * @param waitFor * @param volumeDescriptors * @param cgURI * @return */ private String createReplicaIfCGHasReplica(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, URI cgURI) { log.info("CG URI {}", cgURI); if (volumeDescriptors != null && !volumeDescriptors.isEmpty()) { VolumeDescriptor firstVolumeDescriptor = volumeDescriptors.get(0); if (firstVolumeDescriptor != null && cgURI != null) { // find member volumes in the group BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgURI); List<Volume> existingVolumesInCG = BlockConsistencyGroupUtils.getActiveNativeVolumesInCG(cg, _dbClient); URI storage = existingVolumesInCG.get(0).getStorageController(); //We will not end up in more than 1 RG within a CG, hence taking System from CG is fine. StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); if (checkIfCGHasCloneReplica(existingVolumesInCG)) { log.info("Adding clone steps for create {} volumes", firstVolumeDescriptor.getType()); // create new clones for the newly created volumes // add the created clones to clone groups waitFor = createCloneSteps(workflow, waitFor, volumeDescriptors, existingVolumesInCG, cgURI); } if (checkIfCGHasMirrorReplica(existingVolumesInCG)) { log.info("Adding mirror steps for create {} volumes", firstVolumeDescriptor.getType()); // create new mirrors for the newly created volumes // add the created mirrors to mirror groups waitFor = createMirrorSteps(workflow, waitFor, volumeDescriptors, existingVolumesInCG, cgURI); } List<BlockSnapshotSession> sessions = getSnapSessionsForCGVolume(existingVolumesInCG.get(0)); boolean isExistingCGSnapShotAvailable = checkIfCGHasSnapshotReplica(existingVolumesInCG); boolean isExistingCGSnapSessionAvailable = sessions != null && !sessions.isEmpty(); boolean isVMAX3ExistingVolume = ControllerUtils.isVmaxVolumeUsing803SMIS(existingVolumesInCG.get(0), _dbClient); List<URI> volumeListtoAddURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors); List<Volume> volumeListToAdd = ControllerUtils.queryVolumesByIterativeQuery(_dbClient, volumeListtoAddURIs); if (isVMAX3ExistingVolume) { if (isVMAX3VolumeHasSessionOnly(isExistingCGSnapSessionAvailable, isExistingCGSnapShotAvailable)) { log.info("Existing CG only has Snap Session, adding snap session steps for adding volumes"); processSnapSessions(existingVolumesInCG, workflow, waitFor, volumeListToAdd); } else if (isVMAX3VolumeHasSnapshotOnly(isExistingCGSnapSessionAvailable, isExistingCGSnapShotAvailable)) { // create new snapshots for the newly added volumes // add the created snapshots to snapshot groups Set<String> snapGroupNames = ControllerUtils.getSnapshotReplicationGroupNames(existingVolumesInCG, _dbClient); for (String snapGroupName : snapGroupNames) { // we can use the same storage system as RG--> CG is 1:1 mapping log.info("Existing CG only has Snapshots, adding snapshot steps for existing snap group {} adding volumes", snapGroupName); waitFor = addSnapshotsToReplicationGroupStep(workflow, waitFor, storageSystem, volumeListToAdd, snapGroupName, cgURI); } } else if (isVMAX3VolumeHasSessionAndSnapshot(isExistingCGSnapSessionAvailable,isExistingCGSnapShotAvailable)) { log.info("Existing CG has both Sessions and linked targets, adding snapshot and session steps"); processSnapSessionsAndLinkedTargets(existingVolumesInCG, workflow, waitFor, volumeListToAdd, cgURI); } } else if (isExistingCGSnapShotAvailable) { // non VMAX3 volume log.info("Adding snapshot steps for adding volumes"); // create new snapshots for the newly added volumes // add the created snapshots to snapshot groups Set<String> snapGroupNames = ControllerUtils.getSnapshotReplicationGroupNames(existingVolumesInCG, _dbClient); for (String snapGroupName : snapGroupNames) { waitFor = addSnapshotsToReplicationGroupStep(workflow, waitFor, storageSystem, volumeListToAdd, snapGroupName, cgURI); } } } } return waitFor; } /* * 1. for each newly created volumes in a CG, create a clone * 2. add all clones to an existing replication group */ private String createCloneSteps(final Workflow workflow, String waitFor, final List<VolumeDescriptor> volumeDescriptors, List<Volume> volumeList, URI cgURI) { log.info("START create clone steps"); List<URI> sourceList = VolumeDescriptor.getVolumeURIs(volumeDescriptors); List<Volume> volumes = new ArrayList<Volume>(); Iterator<Volume> volumeIterator = _dbClient.queryIterativeObjects(Volume.class, sourceList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (volume != null && !volume.getInactive()) { volumes.add(volume); } } URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); Set<String> repGroupNames = ControllerUtils.getCloneReplicationGroupNames(volumeList, _dbClient); if (repGroupNames.isEmpty()) { return waitFor; } for (String repGroupName : repGroupNames) { waitFor = addClonesToReplicationGroupStep(workflow, waitFor, storageSystem, volumes, repGroupName, cgURI); } return waitFor; } private String addSnapshotsToReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, List<Volume> volumes, String repGroupName, URI cgURI) { log.info("START create snapshot step"); URI storage = storageSystem.getId(); List<URI> snapshotList = new ArrayList<>(); for (Volume volume : volumes) { BlockSnapshot snapshot = prepareSnapshot(volume, repGroupName); URI snapshotId = snapshot.getId(); snapshotList.add(snapshotId); } waitFor = _blockDeviceController.createListSnapshotStep(workflow, waitFor, storageSystem, snapshotList); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), addToReplicationGroupMethod(storage, cgURI, repGroupName, snapshotList), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, snapshotList), null); log.info(String.format("Step created for adding snapshot [%s] to group on device [%s]", Joiner.on("\t").join(snapshotList), storage)); return waitFor; } public BlockSnapshotSession prepareSnapshotSessionFromSource( BlockObject sourceObj, BlockSnapshotSession existingSession) { BlockSnapshotSession snapSession = new BlockSnapshotSession(); URI sourceProject = ControllerUtils .querySnapshotSessionSourceProject(sourceObj, _dbClient); snapSession.setId(URIUtil.createId(BlockSnapshotSession.class)); snapSession.setProject(new NamedURI(sourceProject, sourceObj .getLabel())); snapSession.setStorageController(sourceObj.getStorageController()); snapSession.setParent(new NamedURI(sourceObj.getId(), sourceObj .getLabel())); // session label should be same as group session's label String sessionLabel = existingSession.getSessionLabel(); // append source object name to session label to uniquely identify this session from RG session String instanceLabel = String.format("%s-%s", existingSession.getLabel(), sourceObj.getLabel()); snapSession.setLabel(instanceLabel); snapSession.setSessionLabel(ResourceOnlyNameGenerator .removeSpecialCharsForName(sessionLabel, SmisConstants.MAX_SNAPSHOT_NAME_LENGTH)); _dbClient.createObject(snapSession); return snapSession; } public String createSnapshotSessionsStep(final Workflow workflow, String waitFor, URI systemURI, List<Volume> volumes, String repGroupName, BlockSnapshotSession existingSession) { // create session for each volume (session's parent is volume. i.e as a non-CG session) for (Volume volume : volumes) { // delete the new session object at the end from DB BlockSnapshotSession session = prepareSnapshotSessionFromSource(volume, existingSession); log.info("adding snapshot session create step for volume {}", volume.getLabel()); waitFor = _blockDeviceController. addStepToCreateSnapshotSession(workflow, systemURI, session.getId(), repGroupName, waitFor); // add step to delete the newly created session object from DB waitFor = workflow.createStep(MARK_SNAP_SESSIONS_INACTIVE_OR_REMOVE_TARGET_ID, String.format("marking snap session %s inactive", session.getLabel()), waitFor, systemURI, _blockDeviceController.getDeviceType(systemURI), this.getClass(), markSnapSessionInactiveOrRemoveTargetIdsMethod(session.getId(), null), _blockDeviceController.rollbackMethodNullMethod(), null); } return waitFor; } public String createSnapshotSessionAndLinkSessionStep(final Workflow workflow, String waitFor, URI systemURI, List<Volume> existingVolumes, List<Volume> volumes, BlockSnapshotSession existingSession, URI cgURI) { log.info("START create snapshot session and link session to targets step"); Volume existingVolume = existingVolumes.get(0); // get existing snapshot groups Set<String> snapGroupNames = ControllerUtils.getSnapshotReplicationGroupNamesForSnapSession(existingVolumes, existingSession, _dbClient); for (Volume volume : volumes) { // delete the new session object at the end from DB BlockSnapshotSession session = prepareSnapshotSessionFromSource(volume, existingSession); log.info("adding snapshot session create step for volume {}", volume.getLabel()); waitFor = _blockDeviceController.addStepToCreateSnapshotSession(workflow, systemURI, session.getId(), existingSession.getReplicationGroupInstance(), waitFor); // Add step to remove volume from its Replication Group before linking its target // -volume was added to RG as part of create volume step // otherwise linking single target will fail when it sees the source in group if (!snapGroupNames.isEmpty()) { waitFor = _blockDeviceController.addStepToRemoveFromConsistencyGroup(workflow, systemURI, cgURI, Arrays.asList(volume.getId()), waitFor, false); } // snapshot targets Map<String, List<URI>> snapGroupToSnapshots = new HashMap<String, List<URI>>(); for (String snapGroupName : snapGroupNames) { String copyMode = ControllerUtils.getCopyModeFromSnapshotGroup(snapGroupName, systemURI, _dbClient); log.info("Existing snap group {}, copy mode {}", snapGroupName, copyMode); // prepare snapshot target BlockSnapshot blockSnapshot = prepareSnapshot(volume, snapGroupName); blockSnapshot.setCopyMode(copyMode); _dbClient.updateObject(blockSnapshot); // add this snapshot target to existing snap session StringSet linkedTargets = existingSession.getLinkedTargets(); if (linkedTargets == null) { linkedTargets = new StringSet(); } linkedTargets.add(blockSnapshot.getId().toString()); _dbClient.updateObject(existingSession); if (snapGroupToSnapshots.get(snapGroupName) == null) { snapGroupToSnapshots.put(snapGroupName, new ArrayList<URI>()); } snapGroupToSnapshots.get(snapGroupName).add(blockSnapshot.getId()); // Add steps to create new target and link them to the session waitFor = _blockDeviceController.addStepToLinkBlockSnapshotSessionTarget(workflow, systemURI, session, blockSnapshot.getId(), copyMode, waitFor); } // add step to delete the newly created session object from DB waitFor = workflow.createStep(MARK_SNAP_SESSIONS_INACTIVE_OR_REMOVE_TARGET_ID, String.format("marking snap session %s inactive", session.getLabel()), waitFor, systemURI, _blockDeviceController.getDeviceType(systemURI), this.getClass(), markSnapSessionInactiveOrRemoveTargetIdsMethod(session.getId(), null), _blockDeviceController.rollbackMethodNullMethod(), null); // Add step to add back the source volume to its group which was removed before linking target if (!snapGroupNames.isEmpty()) { waitFor = _blockDeviceController.addStepToAddToConsistencyGroup(workflow, systemURI, cgURI, existingVolume.getReplicationGroupInstance(), Arrays.asList(volume.getId()), waitFor); } // Add steps to add new targets to their snap groups for (Map.Entry<String, List<URI>> entry : snapGroupToSnapshots.entrySet()) { waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, systemURI, _blockDeviceController.getDeviceType(systemURI), this.getClass(), addToReplicationGroupMethod(systemURI, cgURI, entry.getKey(), entry.getValue()), _blockDeviceController.rollbackMethodNullMethod(), null); } } return waitFor; } public Workflow.Method markSnapSessionInactiveOrRemoveTargetIdsMethod(URI snapSession, List<URI> targetIds) { return new Workflow.Method(MARK_SNAP_SESSIONS_INACTIVE_OR_REMOVE_TARGET_ID, snapSession, targetIds); } /** * A workflow step that * removes the given target ids from snap session object * if no target ids provided or the linked target set is empty after removing given targets, it marks the snap session inactive. * * @param snapSessionURI the snap session uri * @param targetIds the target ids * @param stepId -- Workflow Step Id. */ public void markSnapSessionsInactiveOrRemoveTargetId(URI snapSessionURI, List<URI> targetIds, String stepId) { try { BlockSnapshotSession snapSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI); StringSet linkedTargets = null; if (snapSession != null && !snapSession.getInactive()) { if (targetIds != null) { log.info("Removing target ids {} from snap session {}", Joiner.on(", ").join(targetIds), snapSession.getLabel()); List<String> targets = newArrayList(transform(targetIds, FCTN_URI_TO_STRING)); linkedTargets = snapSession.getLinkedTargets(); if (linkedTargets != null) { log.info("target ids present: {}", Joiner.on(", ").join(linkedTargets)); linkedTargets.removeAll(targets); } } if (targetIds == null || (linkedTargets == null || linkedTargets.isEmpty())) { log.info("Marking snap session in-active: {}", snapSession.getLabel()); snapSession.setInactive(true); } _dbClient.updateObject(snapSession); } } finally { WorkflowStepCompleter.stepSucceded(stepId); } } private String addSnapshotSessionsToReplicationGroupStep(Workflow workflow, String waitFor, StorageSystem storageSystem, List<Volume> volumes, URI cgURI) { List<URI> volumeURIs = newArrayList(transform(volumes, fctnDataObjectToID())); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating SnapVx sessions for consistency group %s", cgURI), waitFor, storageSystem.getId(), storageSystem.getSystemType(), this.getClass(), addSnapshotSessionsToConsistencyGroupMethod(storageSystem.getId(), cgURI, volumeURIs), _blockDeviceController.rollbackMethodNullMethod(), null); return waitFor; } private static Workflow.Method addSnapshotSessionsToConsistencyGroupMethod(URI storage, URI consistencyGroup, List<URI> addVolumesList) { return new Workflow.Method("addSnapshotSessionsToConsistencyGroup", storage, consistencyGroup, addVolumesList); } public boolean addSnapshotSessionsToConsistencyGroup(URI storage, URI consistencyGroup, List<URI> volumes, String opId) throws ControllerException { TaskCompleter taskCompleter = null; WorkflowStepCompleter.stepExecuting(opId); try { StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); taskCompleter = new BlockConsistencyGroupUpdateCompleter(consistencyGroup, opId); _blockDeviceController.getDevice(storageSystem.getSystemType()).doAddSnapshotSessionsToConsistencyGroup( storageSystem, consistencyGroup, volumes, taskCompleter); } catch (Exception e) { ServiceError serviceError = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(_dbClient, serviceError); WorkflowStepCompleter.stepFailed(opId, serviceError); return false; } return true; } private String addClonesToReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, List<Volume> volumes, String repGroupName, URI cgURI) { log.info("START create clone step"); URI storage = storageSystem.getId(); List<URI> cloneList = new ArrayList<URI>(); // For clones of new volumes added to Application, get the clone set name and set it String cloneSetName = null; List<Volume> fullCopies = ControllerUtils.getFullCopiesPartOfReplicationGroup(repGroupName, _dbClient); if (!fullCopies.isEmpty()) { cloneSetName = fullCopies.get(0).getFullCopySetName(); if (cloneSetName == null || cloneSetName.isEmpty()) { Volume fullcopy = fullCopies.get(0); if(fullcopy.checkInternalFlags(Flag.INTERNAL_OBJECT)) { // Get vplex virtual volume final List<Volume> vplexVolumes = CustomQueryUtility .queryActiveResourcesByConstraint(_dbClient, Volume.class, getVolumesByAssociatedId(fullcopy.getId().toString())); if (vplexVolumes != null && !vplexVolumes.isEmpty()) { cloneSetName = vplexVolumes.get(0).getFullCopySetName(); } } } log.info(String.format("CloneSetName : %s", cloneSetName)); } for (Volume volume : volumes) { Volume clone = prepareClone(volume, repGroupName, cloneSetName); cloneList.add(clone.getId()); } // create clone waitFor = _blockDeviceController.createListCloneStep(workflow, storageSystem, cloneList, waitFor); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), addToReplicationGroupMethod(storage, cgURI, repGroupName, cloneList), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, cloneList), null); log.info(String.format("Step created for adding clone [%s] to group on device [%s]", Joiner.on("\t").join(cloneList), storage)); return waitFor; } private BlockSnapshot prepareSnapshot(Volume volume, String repGroupName) { BlockSnapshot snapshot = new BlockSnapshot(); snapshot.setId(URIUtil.createId(BlockSnapshot.class)); URI cgUri = volume.getConsistencyGroup(); if (cgUri != null) { snapshot.setConsistencyGroup(cgUri); } snapshot.setSourceNativeId(volume.getNativeId()); snapshot.setParent(new NamedURI(volume.getId(), volume.getLabel())); snapshot.setReplicationGroupInstance(repGroupName); snapshot.setStorageController(volume.getStorageController()); snapshot.setSystemType(volume.getSystemType()); snapshot.setVirtualArray(volume.getVirtualArray()); snapshot.setProtocol(new StringSet()); snapshot.getProtocol().addAll(volume.getProtocol()); NamedURI project = volume.getProject(); // if this volume is a backend volume for VPLEX virtual volume, // get the project from VPLEX volume as backend volume have different project set with internal flag. if (Volume.checkForVplexBackEndVolume(_dbClient, volume)) { Volume vplexVolume = Volume.fetchVplexVolume(_dbClient, volume); project = vplexVolume.getProject(); } snapshot.setProject(new NamedURI(project.getURI(), project.getName())); String existingSnapSnapSetLabel = ControllerUtils.getSnapSetLabelFromExistingSnaps(repGroupName, volume.getStorageController(), _dbClient); if (null == existingSnapSnapSetLabel) { log.warn("Not able to find any snapshots with group {}", repGroupName); existingSnapSnapSetLabel = repGroupName; } snapshot.setSnapsetLabel(existingSnapSnapSetLabel); Set<String> existingLabels = ControllerUtils.getSnapshotLabelsFromExistingSnaps(repGroupName, volume, _dbClient); LabelFormatFactory labelFormatFactory = new LabelFormatFactory(); LabelFormat labelFormat = labelFormatFactory.getLabelFormat(existingLabels); snapshot.setLabel(labelFormat.next()); snapshot.setTechnologyType(BlockSnapshot.TechnologyType.NATIVE.name()); _dbClient.createObject(snapshot); return snapshot; } private Volume prepareClone(Volume volume, String repGroupName, String cloneSetName) { // create clone for the source Volume clone = new Volume(); clone.setId(URIUtil.createId(Volume.class)); clone.setLabel(volume.getLabel() + "-" + repGroupName); clone.setPool(volume.getPool()); clone.setStorageController(volume.getStorageController()); clone.setSystemType(volume.getSystemType()); clone.setProject(new NamedURI(volume.getProject().getURI(), clone.getLabel())); clone.setTenant(new NamedURI(volume.getTenant().getURI(), clone.getLabel())); clone.setVirtualPool(volume.getVirtualPool()); clone.setVirtualArray(volume.getVirtualArray()); clone.setProtocol(new StringSet()); clone.getProtocol().addAll(volume.getProtocol()); clone.setThinlyProvisioned(volume.getThinlyProvisioned()); clone.setOpStatus(new OpStatusMap()); clone.setAssociatedSourceVolume(volume.getId()); clone.setReplicationGroupInstance(repGroupName); // For clones of new volumes added to Application, get the clone set name and set it if (cloneSetName != null) { clone.setFullCopySetName(cloneSetName); } StringSet fullCopies = volume.getFullCopies(); if (fullCopies == null) { fullCopies = new StringSet(); volume.setFullCopies(fullCopies); } fullCopies.add(clone.getId().toString()); _dbClient.createObject(clone); _dbClient.updateObject(volume); return clone; } /* * 1. for each newly created volumes in a CG, create a mirror * 2. add all mirrors to an existing replication group */ private String createMirrorSteps(final Workflow workflow, String waitFor, final List<VolumeDescriptor> volumeDescriptors, List<Volume> volumeList, URI cgURI) { log.info("START create mirror steps"); List<URI> sourceList = VolumeDescriptor.getVolumeURIs(volumeDescriptors); List<Volume> volumes = new ArrayList<Volume>(); Iterator<Volume> volumeIterator = _dbClient.queryIterativeObjects(Volume.class, sourceList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (volume != null && !volume.getInactive()) { volumes.add(volume); } } URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); Set<String> repGroupNames = ControllerUtils.getMirrorReplicationGroupNames(volumeList, _dbClient); if (repGroupNames.isEmpty()) { return waitFor; } for (String repGroupName : repGroupNames) { waitFor = addMirrorToReplicationGroupStep(workflow, waitFor, storageSystem, volumes, repGroupName, cgURI); } return waitFor; } private String addMirrorToReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, List<Volume> volumes, String repGroupName, URI cgURI) { log.info("START create mirror step"); URI storage = storageSystem.getId(); List<URI> mirrorList = new ArrayList<URI>(); for (Volume volume : volumes) { String mirrorLabel = volume.getLabel() + "-" + repGroupName; BlockMirror mirror = createMirror(volume, volume.getVirtualPool(), volume.getPool(), mirrorLabel, repGroupName); URI mirrorId = mirror.getId(); mirrorList.add(mirrorId); } waitFor = _blockDeviceController.createListMirrorStep(workflow, waitFor, storageSystem, mirrorList); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), addToReplicationGroupMethod(storage, cgURI, repGroupName, mirrorList), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, mirrorList), null); log.info(String.format("Step created for adding mirror [%s] to group on device [%s]", Joiner.on("\t").join(mirrorList), storage)); return waitFor; } /** * Adds a BlockMirror structure for a Volume. It also calls addMirrorToVolume to * link the mirror into the volume's mirror set. * * @param volume Volume * @param vPoolURI * @param recommendedPoolURI Pool that should be used to create the mirror * @param volumeLabel * @param repGroupName * @return BlockMirror (persisted) */ private BlockMirror createMirror(Volume volume, URI vPoolURI, URI recommendedPoolURI, String volumeLabel, String repGroupName) { BlockMirror createdMirror = new BlockMirror(); createdMirror.setSource(new NamedURI(volume.getId(), volume.getLabel())); createdMirror.setId(URIUtil.createId(BlockMirror.class)); URI cgUri = volume.getConsistencyGroup(); if (!NullColumnValueGetter.isNullURI(cgUri)) { createdMirror.setConsistencyGroup(cgUri); } createdMirror.setLabel(volumeLabel); createdMirror.setStorageController(volume.getStorageController()); createdMirror.setSystemType(volume.getSystemType()); createdMirror.setVirtualArray(volume.getVirtualArray()); createdMirror.setProtocol(new StringSet()); createdMirror.getProtocol().addAll(volume.getProtocol()); createdMirror.setCapacity(volume.getCapacity()); createdMirror.setProject(new NamedURI(volume.getProject().getURI(), createdMirror.getLabel())); createdMirror.setTenant(new NamedURI(volume.getTenant().getURI(), createdMirror.getLabel())); createdMirror.setPool(recommendedPoolURI); createdMirror.setVirtualPool(vPoolURI); createdMirror.setSyncState(SynchronizationState.UNKNOWN.toString()); createdMirror.setSyncType(BlockMirror.MIRROR_SYNC_TYPE); createdMirror.setThinlyProvisioned(volume.getThinlyProvisioned()); createdMirror.setReplicationGroupInstance(repGroupName); _dbClient.createObject(createdMirror); addMirrorToVolume(volume, createdMirror); return createdMirror; } /** * Adds a Mirror structure to a Volume's mirror set. * * @param volume * @param mirror */ private void addMirrorToVolume(Volume volume, BlockMirror mirror) { StringSet mirrors = volume.getMirrors(); if (mirrors == null) { mirrors = new StringSet(); } mirrors.add(mirror.getId().toString()); volume.setMirrors(mirrors); // Persist changes _dbClient.updateObject(volume); } public Workflow.Method addToReplicationGroupMethod(URI storage, URI consistencyGroup, String repGroupName, List<URI> addVolumesList) { return new Workflow.Method("addToReplicationGroup", storage, consistencyGroup, repGroupName, addVolumesList); } /** * Orchestration method for adding members to a replication group. * * @param storage * @param consistencyGroup * @param replicationGroupName * @param addVolumesList * @param opId * @return * @throws ControllerException */ public boolean addToReplicationGroup(URI storage, URI consistencyGroup, String replicationGroupName, List<URI> addVolumesList, String opId) throws ControllerException { WorkflowStepCompleter.stepExecuting(opId); TaskCompleter taskCompleter = null; try { List<String> lockKeys = new ArrayList<String>(); lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, replicationGroupName, storage)); WorkflowService workflowService = _blockDeviceController.getWorkflowService(); workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG)); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); taskCompleter = new BlockConsistencyGroupUpdateCompleter(consistencyGroup, opId); _blockDeviceController.getDevice(storageSystem.getSystemType()).doAddToReplicationGroup( storageSystem, consistencyGroup, replicationGroupName, addVolumesList, taskCompleter); WorkflowStepCompleter.stepSucceded(opId); } catch (Exception e) { ServiceError serviceError = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(_dbClient, serviceError); WorkflowStepCompleter.stepFailed(opId, serviceError); return false; } return true; } private boolean checkIfCGHasCloneReplica(List<Volume> volumes) { for (Volume volume : volumes) { StringSet fullCopies = volume.getFullCopies(); if (fullCopies != null && !fullCopies.isEmpty()) { return true; } } return false; } private boolean checkIfCGHasMirrorReplica(List<Volume> volumes) { for (Volume volume : volumes) { StringSet mirrors = volume.getMirrors(); if (mirrors != null && !mirrors.isEmpty()) { return true; } } return false; } private boolean checkIfCGHasSnapshotReplica(List<Volume> volumes) { for (Volume volume : volumes) { URIQueryResultList list = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(volume.getId()), list); Iterator<URI> it = list.iterator(); while (it.hasNext()) { URI snapshotID = it.next(); BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, snapshotID); if (snapshot != null) { log.debug("There are Snapshot(s) available for volume {}", volume.getId()); return true; } } } return false; } /** * Gets the snap sessions for a CG volume. * * @param volume the volume * @return the snap sessions for cg volume */ private List<BlockSnapshotSession> getSnapSessionsForCGVolume(Volume volume) { /** * Get all snap sessions for Volume's CG * filter the snap sessions which matches the Volume's Replication Group */ List<BlockSnapshotSession> cgVolumeSessions = new ArrayList<BlockSnapshotSession>(); String rgName = volume.getReplicationGroupInstance(); if (NullColumnValueGetter.isNotNullValue(rgName)) { URI cgURI = volume.getConsistencyGroup(); List<BlockSnapshotSession> sessionsList = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getBlockSnapshotSessionByConsistencyGroup(cgURI)); for (BlockSnapshotSession session : sessionsList) { if (rgName.equals(session.getReplicationGroupInstance())) { cgVolumeSessions.add(session); } } } return cgVolumeSessions; } private String processSnapSessions(List<Volume> existingVolumesInCG, Workflow workflow, String waitFor, List<Volume> volumesToAdd) { // Get # of existing sessions for RG volumes, and create session for new volumes for every existing session Volume existingVolume = existingVolumesInCG.get(0); URI system = existingVolume.getStorageController(); log.info("Processing RG {}", existingVolume.getReplicationGroupInstance()); List<BlockSnapshotSession> sessions = getSnapSessionsForCGVolume(existingVolumesInCG.get(0)); for (BlockSnapshotSession session : sessions) { log.info("Processing SnapSession {} for RG {}", session.getSessionLabel(), session.getReplicationGroupInstance()); waitFor = createSnapshotSessionsStep(workflow, waitFor, system, volumesToAdd, existingVolume.getReplicationGroupInstance(), session); } return waitFor; } private String processSnapSessionsAndLinkedTargets(List<Volume> existingVolumesInCG, Workflow workflow, String waitFor, List<Volume> volumesToAdd, URI cgUri) { /** * Get # of existing sessions for RG volumes * for every existing session: * -get # of existing snapshot groups * -for every new volume: * --create new session * --For every new session, create new linked targets as many as existing snap groups */ Volume existingVolume = existingVolumesInCG.get(0); log.info("Processing RG {}", existingVolume.getReplicationGroupInstance()); URI system = existingVolume.getStorageController(); List<BlockSnapshotSession> sessions = getSnapSessionsForCGVolume(existingVolume); for (BlockSnapshotSession session : sessions) { log.info("Processing SnapSession {} for RG {}", session.getSessionLabel(), session.getReplicationGroupInstance()); waitFor = createSnapshotSessionAndLinkSessionStep(workflow, waitFor, system, existingVolumesInCG, volumesToAdd, session, cgUri); } return waitFor; } @Override public String addStepsForDeleteVolumes(Workflow workflow, String waitFor, List<VolumeDescriptor> volumes, String taskId) throws InternalException { // Get the list of descriptors which represent source volumes to be deleted List<VolumeDescriptor> volumeDescriptors = VolumeDescriptor.filterByType(volumes, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA, VolumeDescriptor.Type.SRDF_SOURCE, VolumeDescriptor.Type.SRDF_EXISTING_SOURCE, VolumeDescriptor.Type.SRDF_TARGET }, null); // If no source volumes, just return if (volumeDescriptors.isEmpty()) { log.info("No replica steps required"); return waitFor; } List<URI> volumeDescriptorURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors); List<Volume> unfilteredVolumes = _dbClient.queryObject(Volume.class, volumeDescriptorURIs); // Filter Volume list, returning if no volumes passed. Collection<Volume> filteredVolumes = filter(unfilteredVolumes, deleteVolumeFilterPredicate()); if (filteredVolumes.isEmpty()) { return waitFor; } Map<String, Set<URI>> rgVolsMap = sortVolumesBySystemAndReplicationGroup(filteredVolumes); for (Set<URI> volumeURIs : rgVolsMap.values()) { // find member volumes in the group List<Volume> volumeList = getVolumes(filteredVolumes, volumeURIs); if (volumeList.isEmpty()) { continue; } Volume firstVol = volumeList.get(0); String rpName = firstVol.getReplicationGroupInstance(); URI storage = firstVol.getStorageController(); boolean isRemoveAllFromRG = ControllerUtils.replicationGroupHasNoOtherVolume(_dbClient, rpName, volumeURIs, storage); log.info("isRemoveAllFromRG {}", isRemoveAllFromRG); if (checkIfCGHasCloneReplica(volumeList)) { log.info("Adding clone steps for deleting volumes"); waitFor = detachCloneSteps(workflow, waitFor, volumeURIs, volumeList, isRemoveAllFromRG); } if (checkIfCGHasMirrorReplica(volumeList)) { log.info("Adding mirror steps for deleting volumes"); // delete mirrors for the to be deleted volumes waitFor = deleteMirrorSteps(workflow, waitFor, volumeURIs, volumeList, isRemoveAllFromRG); } if (checkIfCGHasSnapshotReplica(volumeList)) { log.info("Adding snapshot steps for deleting volumes"); // delete snapshots for the to be deleted volumes waitFor = deleteSnapshotSteps(workflow, waitFor, volumeURIs, volumeList, isRemoveAllFromRG); } if (checkIfCGHasSnapshotSessions(volumeList)) { log.info("Adding snapshot session steps for deleting volumes"); waitFor = deleteSnapshotSessionSteps(workflow, waitFor, volumeList, isRemoveAllFromRG); } } return waitFor; } /** * Remove all snapshot sessions from the volumes to be deleted. * * @param workflow * @param waitFor * @param volumes * @param isRemoveAllFromRG * @return */ private String deleteSnapshotSessionSteps(Workflow workflow, String waitFor, List<Volume> volumes, boolean isRemoveAllFromRG) { log.info("START delete snapshot session steps"); if (!isRemoveAllFromRG) { log.info("Nothing to do"); return waitFor; } URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); Set<String> replicationGroupInstances = new HashSet<>(); for (Volume volume : volumes) { replicationGroupInstances.add(volume.getReplicationGroupInstance()); } for (String replicationGroupInstance : replicationGroupInstances) { Collection<BlockSnapshotSession> sessions = getSessionsForReplicationGroup(replicationGroupInstance, storage); for (BlockSnapshotSession session : sessions) { Workflow.Method deleteMethod = BlockDeviceController.deleteBlockSnapshotSessionMethod(storage, session.getId(), replicationGroupInstance, true); waitFor = workflow.createStep("RemoveSnapshotSessions", "Remove Snapshot Session", waitFor, storage, storageSystem.getSystemType(), BlockDeviceController.class, deleteMethod, null, null); } } return waitFor; } /** * Remove all snapshots from the volumes to be deleted. * * @param workflow * @param waitFor * @param volumeURIs * @param volumes * @return */ private String deleteSnapshotSteps(Workflow workflow, String waitFor, Set<URI> volumeURIs, List<Volume> volumes, boolean isRemoveAll) { log.info("START delete snapshot steps"); URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); Set<String> repGroupNames = ControllerUtils.getSnapshotReplicationGroupNames(volumes, _dbClient); if (repGroupNames.isEmpty()) { return waitFor; } for (String repGroupName : repGroupNames) { List<URI> snapList = getSnapshotsToBeRemoved(volumeURIs, repGroupName); if (!isRemoveAll) { URI cgURI = volumes.get(0).getConsistencyGroup(); waitFor = removeSnapshotsFromReplicationGroupStep(workflow, waitFor, storageSystem, cgURI, snapList, repGroupName); } log.info("Adding delete snapshot steps"); waitFor = _blockDeviceController.deleteSnapshotStep(workflow, waitFor, storage, storageSystem, snapList, isRemoveAll); } return waitFor; } /* * Detach all clones of the to be deleted volumes in a CG */ private String detachCloneSteps(final Workflow workflow, String waitFor, Set<URI> volumeURIs, List<Volume> volumes, boolean isRemoveAll) { log.info("START detach clone steps"); Set<String> repGroupNames = ControllerUtils.getCloneReplicationGroupNames(volumes, _dbClient); if (repGroupNames.isEmpty()) { return waitFor; } URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); for (String repGroupName : repGroupNames) { List<URI> cloneList = getClonesToBeRemoved(volumeURIs, repGroupName); if (!isRemoveAll) { URI cgURI = volumes.get(0).getConsistencyGroup(); waitFor = removeClonesFromReplicationGroupStep(workflow, waitFor, storageSystem, cgURI, cloneList, repGroupName); } waitFor = _blockDeviceController.detachCloneStep(workflow, waitFor, storageSystem, cloneList, isRemoveAll); } return waitFor; } /* * Delete all mirrors of the to-be-deleted volumes in a CG. * * When removing all CG mirrors, we can use SMI-S group operations. * * When removing n-1 CG mirrors, we must first remove them from their ReplicationGroup and proceed * using SMI-S ListReplica operations. */ private String deleteMirrorSteps(final Workflow workflow, String waitFor, Set<URI> volumeURIs, List<Volume> volumes, boolean isRemoveAll) { log.info("START delete mirror steps"); Set<URI> allMirrors = getAllMirrors(volumes); log.info("Total mirrors for deletion: {}", Joiner.on(", ").join(allMirrors)); Set<String> repGroupNames = ControllerUtils.getMirrorReplicationGroupNames(volumes, _dbClient); List<URI> mirrorList = null; URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); for (String repGroupName : repGroupNames) { mirrorList = getMirrorsToBeRemoved(volumeURIs, repGroupName); log.info("ReplicationGroup {} has mirrors {}", repGroupName, Joiner.on(", ").join(mirrorList)); if (!isRemoveAll) { URI cgURI = volumes.get(0).getConsistencyGroup(); // After this step executes for a mirror, it will lose its CG and replicationGroupInstance field values. waitFor = removeMirrorsFromReplicationGroupStep(workflow, waitFor, storageSystem, cgURI, mirrorList, repGroupName); } allMirrors.removeAll(mirrorList); waitFor = _blockDeviceController.deleteListMirrorStep(workflow, waitFor, storage, storageSystem, mirrorList, isRemoveAll); } log.info("Remaining mirrors: {}", Joiner.on(", ").join(allMirrors)); /* * Any mirrors left at this point would have had no replicationGroupInstance set, so we must * attempt to delete one by one. */ for (URI danglingMirror : allMirrors) { waitFor = _blockDeviceController.deleteListMirrorStep(workflow, waitFor, storage, storageSystem, Lists.newArrayList(danglingMirror), isRemoveAll); } return waitFor; } private String removeSnapshotsFromReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, URI cgURI, List<URI> snapshots, String repGroupName) { log.info("START remove snapshot from CG steps"); URI storage = storageSystem.getId(); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, snapshots), _blockDeviceController.rollbackMethodNullMethod(), null); log.info(String.format("Step created for remove snapshots [%s] to group on device [%s]", Joiner.on("\t").join(snapshots), storage)); BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapshots.get(0)); Volume sourceVol = _dbClient.queryObject(Volume.class, snap.getParent()); // delete replication group from array if no more snapshots in the group. boolean rgHasNoOtherSnapshot = ControllerUtils.replicationGroupHasNoOtherSnapshot(_dbClient, repGroupName, snapshots, storage); if (rgHasNoOtherSnapshot) { log.info(String.format("Adding step to delete the replication group %s", repGroupName)); String sourceRepGroupName = sourceVol.getReplicationGroupInstance(); waitFor = workflow.createStep(BlockDeviceController.DELETE_GROUP_STEP_GROUP, String.format("Deleting replication group %s", repGroupName), waitFor, storage, storageSystem.getSystemType(), BlockDeviceController.class, _blockDeviceController.deleteReplicationGroupMethod(storage, cgURI, ControllerUtils.extractGroupName(repGroupName), true, false, sourceRepGroupName), _blockDeviceController.rollbackMethodNullMethod(), null); } // get snap session associated if any List<BlockSnapshotSession> sessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getLinkedTargetSnapshotSessionConstraint(snap.getId())); Iterator<BlockSnapshotSession> itr = sessions.iterator(); if (itr.hasNext()) { BlockSnapshotSession session = itr.next(); // add step to remove target ids from snap session object // snap session will be marked inactive when removing last target waitFor = workflow.createStep(MARK_SNAP_SESSIONS_INACTIVE_OR_REMOVE_TARGET_ID, String.format("marking snap session %s inactive or removing target ids", session.getLabel()), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), markSnapSessionInactiveOrRemoveTargetIdsMethod(session.getId(), snapshots), _blockDeviceController.rollbackMethodNullMethod(), null); } return waitFor; } private String removeClonesFromReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, URI cgURI, List<URI> cloneList, String repGroupName) { log.info("START remove clone from CG steps"); URI storage = storageSystem.getId(); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, cloneList), _blockDeviceController.rollbackMethodNullMethod(), null); log.info(String.format("Step created for remove clones [%s] from group on device [%s]", Joiner.on("\t").join(cloneList), storage)); // delete replication group from array if no more clones in the group. if (ControllerUtils.replicationGroupHasNoOtherVolume(_dbClient, repGroupName, cloneList, storage)) { log.info(String.format("Adding step to delete the replication group %s", repGroupName)); Volume clone = _dbClient.queryObject(Volume.class, cloneList.get(0)); URI sourceId = clone.getAssociatedSourceVolume(); Volume sourceVol = _dbClient.queryObject(Volume.class, sourceId); String sourceRepGroupName = sourceVol.getReplicationGroupInstance(); waitFor = workflow.createStep(BlockDeviceController.DELETE_GROUP_STEP_GROUP, String.format("Deleting replication group %s", repGroupName), waitFor, storage, storageSystem.getSystemType(), BlockDeviceController.class, _blockDeviceController.deleteReplicationGroupMethod(storage, cgURI, repGroupName, true, false, sourceRepGroupName), _blockDeviceController.rollbackMethodNullMethod(), null); } return waitFor; } private String removeMirrorsFromReplicationGroupStep(final Workflow workflow, String waitFor, StorageSystem storageSystem, URI cgURI, List<URI> mirrorList, String repGroupName) { log.info("START remove mirror from CG steps"); URI storage = storageSystem.getId(); waitFor = workflow.createStep(BlockDeviceController.UPDATE_CONSISTENCY_GROUP_STEP_GROUP, String.format("Updating consistency group %s", cgURI), waitFor, storage, _blockDeviceController.getDeviceType(storage), this.getClass(), removeFromReplicationGroupMethod(storage, cgURI, repGroupName, mirrorList), _blockDeviceController.rollbackMethodNullMethod(), null); log.info(String.format("Step created for remove mirrors [%s] from group on device [%s]", Joiner.on("\t").join(mirrorList), storage)); return waitFor; } private List<URI> getSnapshotsToBeRemoved(Set<URI> volumes, String repGroupName) { List<URI> replicas = new ArrayList<>(); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getSnapshotReplicationGroupInstanceConstraint(repGroupName), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); while (resultsIter.hasNext()) { BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, resultsIter.next()); if (volumes.contains(snapshot.getParent().getURI())) { replicas.add(snapshot.getId()); } } return replicas; } private List<URI> getClonesToBeRemoved(Set<URI> volumes, String repGroupName) { List<URI> replicas = new ArrayList<URI>(); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getVolumeReplicationGroupInstanceConstraint(repGroupName), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); while (resultsIter.hasNext()) { Volume clone = _dbClient.queryObject(Volume.class, resultsIter.next()); if (volumes.contains(clone.getAssociatedSourceVolume())) { replicas.add(clone.getId()); } } return replicas; } private List<URI> getMirrorsToBeRemoved(Set<URI> volumes, String repGroupName) { List<URI> replicas = new ArrayList<URI>(); URIQueryResultList queryResults = new URIQueryResultList(); _dbClient.queryByConstraint(AlternateIdConstraint.Factory .getMirrorReplicationGroupInstanceConstraint(repGroupName), queryResults); Iterator<URI> resultsIter = queryResults.iterator(); while (resultsIter.hasNext()) { BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, resultsIter.next()); if (volumes.contains(mirror.getSource().getURI())) { replicas.add(mirror.getId()); } } return replicas; } static Workflow.Method removeFromReplicationGroupMethod(URI storage, URI consistencyGroup, String repGroupName, List<URI> addVolumesList) { return new Workflow.Method("removeFromReplicationGroup", storage, consistencyGroup, repGroupName, addVolumesList); } /** * Orchestration method for removing members from a replication group. * * @param storage * @param consistencyGroup * @param repGroupName * @param addVolumesList * @param opId * @return * @throws ControllerException */ public boolean removeFromReplicationGroup(URI storage, URI consistencyGroup, String repGroupName, List<URI> addVolumesList, String opId) throws ControllerException { TaskCompleter taskCompleter = new BlockConsistencyGroupUpdateCompleter(consistencyGroup, opId); try { List<String> lockKeys = new ArrayList<>(); lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, repGroupName, storage)); WorkflowService workflowService = _blockDeviceController.getWorkflowService(); workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG)); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); _blockDeviceController.getDevice(storageSystem.getSystemType()).doRemoveFromReplicationGroup( storageSystem, consistencyGroup, repGroupName, addVolumesList, taskCompleter); } catch (Exception e) { ServiceError serviceError = DeviceControllerException.errors.jobFailed(e); taskCompleter.error(_dbClient, serviceError); WorkflowStepCompleter.stepFailed(opId, serviceError); return false; } return true; } @Override public String addStepsForPostDeleteVolumes(Workflow workflow, String waitFor, List<VolumeDescriptor> volumes, String taskId, VolumeWorkflowCompleter completer) { // Nothing to do, no steps to add return waitFor; } @Override public String addStepsForExpandVolume(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { // Nothing to do, no steps to add return waitFor; } @Override public String addStepsForChangeVirtualPool(Workflow workflow, String waitFor, List<VolumeDescriptor> volumes, String taskId) throws InternalException { // Nothing to do, no steps to add return waitFor; } @Override public String addStepsForChangeVirtualArray(Workflow workflow, String waitFor, List<VolumeDescriptor> volumes, String taskId) throws InternalException { // Nothing to do, no steps to add return waitFor; } @Override public String addStepsForRestoreVolume(Workflow workflow, String waitFor, URI storage, URI pool, URI volume, URI snapshot, Boolean updateOpStatus, String syncDirection, String taskId, BlockSnapshotRestoreCompleter completer) throws InternalException { // Nothing to do, no steps to add return waitFor; } public void rollbackMethodNull(String stepId) throws WorkflowException { WorkflowStepCompleter.stepSucceded(stepId); } /** * Add steps to create clones/snapshots when add volumes to a replication group. * @param workflow - The workflow that the steps would be added to * @param waitFor - a waitFor key that can be used by subsequent controllers to wait on * the Steps created by this controller. * @param cgURI - CG URI * @param volumeListToAdd - The volumes to be added. * @param replicationGroup - replication group name * @param taskId - top level operation's taskId * @return - a waitFor key that can be used by subsequent controllers to wait on * the Steps created by this controller. * @throws InternalException */ public String addStepsForAddingVolumesToRG(Workflow workflow, String waitFor, URI cgURI, List<URI> volumeListToAdd, String replicationGroup, String taskId) throws InternalException { log.info(String.format("addStepsForAddingVolumesToRG %s", replicationGroup)); List<Volume> volumesToAdd = ControllerUtils.queryVolumesByIterativeQuery(_dbClient, volumeListToAdd); if (!volumesToAdd.isEmpty()) { Volume firstVolume = volumesToAdd.get(0); if (!ControllerUtils.isVmaxVolumeUsing803SMIS(firstVolume, _dbClient) && !ControllerUtils.isVnxVolume(firstVolume, _dbClient)) { return waitFor; } URI storage = firstVolume.getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); // find member volumes in the group List<Volume> existingRGVolumes = ControllerUtils.getVolumesPartOfRG(storage, replicationGroup, _dbClient); if (existingRGVolumes.isEmpty()) { return waitFor; } if (checkIfCGHasCloneReplica(existingRGVolumes)) { log.info("Adding clone steps for adding volumes"); // create new clones for the newly added volumes // add the created clones to clone groups Set<String> repGroupNames = ControllerUtils.getCloneReplicationGroupNames(existingRGVolumes, _dbClient); for (String repGroupName : repGroupNames) { waitFor = addClonesToReplicationGroupStep(workflow, waitFor, storageSystem, volumesToAdd, repGroupName, cgURI); } } if (checkIfCGHasMirrorReplica(existingRGVolumes)) { log.info("Adding mirror steps for adding volumes"); // create new mirrors for the newly added volumes // add the created mirrors to mirror groups Set<String> repGroupNames = ControllerUtils.getMirrorReplicationGroupNames(existingRGVolumes, _dbClient); for (String repGroupName : repGroupNames) { waitFor = addMirrorToReplicationGroupStep(workflow, waitFor, storageSystem, volumesToAdd, repGroupName, cgURI); } } List<BlockSnapshotSession> sessions = getSnapSessionsForCGVolume(existingRGVolumes.get(0)); boolean isExistingCGSnapShotAvailable = checkIfCGHasSnapshotReplica(existingRGVolumes); boolean isExistingCGSnapSessionAvailable = sessions != null && !sessions.isEmpty(); boolean isVMAX3ExistingVolume = existingRGVolumes.get(0).isVmax3Volume(_dbClient); if (isVMAX3ExistingVolume) { if (isVMAX3VolumeHasSessionOnly(isExistingCGSnapSessionAvailable, isExistingCGSnapShotAvailable)) { log.info("Existing CG only has Snap Session, adding snap session steps for adding volumes"); processSnapSessions(existingRGVolumes, workflow, waitFor, volumesToAdd); } else if (isVMAX3VolumeHasSnapshotOnly(isExistingCGSnapSessionAvailable, isExistingCGSnapShotAvailable)) { // create new snapshots for the newly added volumes // add the created snapshots to snapshot groups Set<String> snapGroupNames = ControllerUtils.getSnapshotReplicationGroupNames(existingRGVolumes, _dbClient); for (String snapGroupName : snapGroupNames) { // This method is invoked per RG, so need to get storage system separately log.info("Existing CG only has Snapshots, adding snapshot steps for existing snap group {} adding volumes", snapGroupName); waitFor = addSnapshotsToReplicationGroupStep(workflow, waitFor, storageSystem, volumesToAdd, snapGroupName, cgURI); } } else if (isVMAX3VolumeHasSessionAndSnapshot(isExistingCGSnapSessionAvailable, isExistingCGSnapShotAvailable)) { log.info("Existing CG has both Sessions and linked targets, adding snapshot and session steps"); processSnapSessionsAndLinkedTargets(existingRGVolumes, workflow, waitFor, volumesToAdd, cgURI); } } else if (isExistingCGSnapShotAvailable) { // non VMAX3 volume log.info("Adding snapshot steps for adding volumes"); // create new snapshots for the newly added volumes // add the created snapshots to snapshot groups Set<String> snapGroupNames = ControllerUtils.getSnapshotReplicationGroupNames(existingRGVolumes, _dbClient); for (String snapGroupName : snapGroupNames) { waitFor = addSnapshotsToReplicationGroupStep(workflow, waitFor, storageSystem, volumesToAdd, snapGroupName, cgURI); } } } return waitFor; } /** * adding new snap sessions * * @param workflow * @param waitFor * @param cgURI * @param volumeListToAdd * @param replicationGroup * @param taskId * @return Workflow step id * @throws InternalException */ public String addStepsForAddingSessionsToCG(Workflow workflow, String waitFor, URI cgURI, List<URI> volumeListToAdd, String replicationGroup, String taskId) throws InternalException { log.info("addStepsForAddingSessionsToCG {}", cgURI); List<Volume> volumes = ControllerUtils.queryVolumesByIterativeQuery(_dbClient, volumeListToAdd); if (volumes.isEmpty() || !ControllerUtils.isVmaxVolumeUsing803SMIS(volumes.get(0), _dbClient) || !volumes.get(0).isVmax3Volume(_dbClient)) { return waitFor; } URI storage = volumes.get(0).getStorageController(); StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage); if (checkIfAnyVolumesHaveSnapshotSessions(volumes)) { log.info("Adding snapshot session steps for adding volumes"); // Consolidate multiple snapshot sessions into one CG-based snapshot // session waitFor = addSnapshotSessionsToReplicationGroupStep(workflow, waitFor, storageSystem, volumes, cgURI); } return waitFor; } /** * Check if any {@link Volume} in the given list have a {@link BlockSnapshotSession} referencing it as * a parent. * * @param volumes List of {@link Volume} * @return True if any {@link BlockSnapshotSession} instances are found, false otherwise. */ private boolean checkIfAnyVolumesHaveSnapshotSessions(List<Volume> volumes) { for (BlockObject volume : volumes) { List<BlockSnapshotSession> sessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getParentSnapshotSessionConstraint(volume.getId())); if (!sessions.isEmpty()) { return true; } } return false; } /** * For any {@link Volume} in the given list, check if any {@link BlockSnapshotSession} instances reference * their {@link BlockConsistencyGroup}. * * @param volumes List of {@link Volume} * @return True if any {@link BlockSnapshotSession} instances are found, false otherwise. */ private boolean checkIfCGHasSnapshotSessions(List<Volume> volumes) { for (BlockObject volume : volumes) { if (!NullColumnValueGetter.isNullURI(volume.getConsistencyGroup())) { List<BlockSnapshotSession> sessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, ContainmentConstraint.Factory.getBlockSnapshotSessionByConsistencyGroup(volume.getConsistencyGroup())); if (!sessions.isEmpty()) { return true; } } } return false; } private boolean isVMAX3VolumeHasSessionOnly(boolean isExistingCGSnapSessionAvailable, boolean isExistingCGSnapShotAvailable) { if (!isExistingCGSnapSessionAvailable) return false; if (isExistingCGSnapShotAvailable) return false; return true; } private boolean isVMAX3VolumeHasSnapshotOnly(boolean isExistingCGSnapSessionAvailable, boolean isExistingCGSnapShotAvailable) { if (!isExistingCGSnapShotAvailable) return false; if (isExistingCGSnapSessionAvailable) return false; return true; } private boolean isVMAX3VolumeHasSessionAndSnapshot(boolean isExistingCGSnapSessionAvailable, boolean isExistingCGSnapShotAvailable) { return isExistingCGSnapSessionAvailable && isExistingCGSnapShotAvailable; } /** * Adds the steps necessary for removing one or more volumes from replication groups to the given Workflow. * volume list could contain volumes from different storage systems and different replication groups * * @param workflow - a Workflow * @param waitFor - a String key that should be used in the Workflow.createStep * waitFor parameter in order to wait on the previous controller's actions to complete. * @param cgURI - URI list of consistency group * @param volumeList - URI list of volumes * @param taskId - top level operation's taskId * @return - a waitFor key that can be used by subsequent controllers to wait on * the Steps created by this controller. * @throws InternalException */ public String addStepsForRemovingVolumesFromCG(Workflow workflow, String waitFor, URI cgURI, List<URI> volumeList, String taskId) throws InternalException { Map<URI, List<URI>> storageToVolMap = new HashMap<URI, List<URI>>(); Iterator<Volume> volumes = _dbClient.queryIterativeObjects(Volume.class, volumeList); while (volumes.hasNext()) { Volume volume = volumes.next(); URI system = volume.getStorageController(); if (storageToVolMap.get(system) == null) { storageToVolMap.put(system, new ArrayList<URI>()); } storageToVolMap.get(system).add(volume.getId()); } for (Entry<URI, List<URI>> entry : storageToVolMap.entrySet()) { waitFor = addStepsForRemovingVolumesFromCG(workflow, waitFor, cgURI, entry.getKey(), entry.getValue(), taskId); } return waitFor; } /** * add steps to remove replicas from storage groups for a single storage system; could be multiple replication groups * @param workflow * @param waitFor * @param cgURI * @param storage * @param volumeList * @param taskId * @return * @throws InternalException */ private String addStepsForRemovingVolumesFromCG(Workflow workflow, String waitFor, URI cgURI, URI storage, List<URI> volumeList, String taskId) throws InternalException { Iterator<Volume> volumes = _dbClient.queryIterativeObjects(Volume.class, volumeList); Map<String, List<URI>> groupMap = new HashMap<String, List<URI>>(); while (volumes.hasNext()) { Volume volume = volumes.next(); String groupName = volume.getReplicationGroupInstance(); if (NullColumnValueGetter.isNotNullValue(groupName)) { if (groupMap.get(groupName) == null) { groupMap.put(groupName, new ArrayList<URI>()); } groupMap.get(groupName).add(volume.getId()); } } for (Entry<String, List<URI>> entry : groupMap.entrySet()) { waitFor = addStepsForRemovingVolumesFromCG(workflow, waitFor, cgURI, storage, entry.getKey(), entry.getValue(), taskId); } return waitFor; } /** * add steps to remove replicas from storage group for a single storage system and a single replication group * @param workflow * @param waitFor * @param cgURI * @param storage * @param groupName * @param volumeList * @param taskId * @return * @throws InternalException */ private String addStepsForRemovingVolumesFromCG(Workflow workflow, String waitFor, URI cgURI, URI storage, String groupName, List<URI> volumeList, String taskId) throws InternalException { log.info("addStepsForRemovingVolumesFromCG {}", cgURI); List<Volume> volumes = new ArrayList<Volume>(); Iterator<Volume> volumeIterator = _dbClient.queryIterativeObjects(Volume.class, volumeList); while (volumeIterator.hasNext()) { Volume volume = volumeIterator.next(); if (volume != null && !volume.getInactive()) { volumes.add(volume); } } StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage); if (!volumes.isEmpty()) { Volume firstVolume = volumes.get(0); if (!(firstVolume.isInCG() && ControllerUtils.isVmaxVolumeUsing803SMIS(firstVolume, _dbClient)) && !ControllerUtils.isNotInRealVNXRG(firstVolume, _dbClient)) { log.info(String.format("Remove from replication group not supported for volume %s", firstVolume.getLabel())); return waitFor; } boolean isRemoveAllFromCG = ControllerUtils.replicationGroupHasNoOtherVolume(_dbClient, groupName, volumeList, firstVolume.getStorageController()); log.info("isRemoveAllFromCG {}", isRemoveAllFromCG); if (checkIfCGHasCloneReplica(volumes)) { log.info("Adding steps to process clones for removing volumes"); // get clone volumes Map<String, List<URI>> cloneGroupCloneURIMap = new HashMap<String, List<URI>>(); for (Volume volume : volumes) { if (volume.getFullCopies() != null && !volume.getFullCopies().isEmpty()) { for (String cloneUri : volume.getFullCopies()) { Volume clone = _dbClient.queryObject(Volume.class, URI.create(cloneUri)); if (clone != null && !clone.getInactive() && NullColumnValueGetter.isNotNullValue(clone.getReplicationGroupInstance())) { if (cloneGroupCloneURIMap.get(clone.getReplicationGroupInstance()) == null) { cloneGroupCloneURIMap.put(clone.getReplicationGroupInstance(), new ArrayList<URI>()); } cloneGroupCloneURIMap.get(clone.getReplicationGroupInstance()).add(clone.getId()); } } } } // add steps to remove clones from the replication group for (Entry<String, List<URI>> entry : cloneGroupCloneURIMap.entrySet()) { waitFor = removeClonesFromReplicationGroupStep(workflow, waitFor, system, cgURI, entry.getValue(), entry.getKey()); } } if (checkIfCGHasMirrorReplica(volumes)) { log.info("Adding steps to process mirrors for removing volumes"); Map<String, List<URI>> mirrorGroupCloneURIMap = new HashMap<String, List<URI>>(); for (Volume volume : volumes) { StringSet mirrors = volume.getMirrors(); if (mirrors != null && !mirrors.isEmpty()) { for (String mirrorUri : mirrors) { BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, URI.create(mirrorUri)); if (mirror != null && !mirror.getInactive() && NullColumnValueGetter.isNotNullValue(mirror.getReplicationGroupInstance())) { if (mirrorGroupCloneURIMap.get(mirror.getReplicationGroupInstance()) == null) { mirrorGroupCloneURIMap.put(mirror.getReplicationGroupInstance(), new ArrayList<URI>()); } mirrorGroupCloneURIMap.get(mirror.getReplicationGroupInstance()).add(mirror.getId()); } } } } // add steps to remove mirrors from replication group for (Entry<String, List<URI>> entry : mirrorGroupCloneURIMap.entrySet()) { waitFor = removeMirrorsFromReplicationGroupStep(workflow, waitFor, system, cgURI, entry.getValue(), entry.getKey()); } } if (checkIfCGHasSnapshotReplica(volumes)) { log.info("Adding steps to process snapshots for removing volumes"); Map<String, List<URI>> snapGroupCloneURIMap = new HashMap<String, List<URI>>(); for (Volume volume : volumes) { URIQueryResultList list = new URIQueryResultList(); _dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeSnapshotConstraint(volume.getId()), list); Iterator<URI> it = list.iterator(); while (it.hasNext()) { BlockSnapshot snapshot = _dbClient.queryObject(BlockSnapshot.class, it.next()); String snapGroupName = null; if (NullColumnValueGetter.isNotNullValue(snapshot.getReplicationGroupInstance())) { snapGroupName = snapshot.getReplicationGroupInstance(); } else if (NullColumnValueGetter.isNotNullValue(snapshot.getSnapsetLabel())) { snapGroupName = snapshot.getSnapsetLabel(); } if (snapGroupName != null) { if (snapGroupCloneURIMap.get(snapGroupName) == null) { snapGroupCloneURIMap.put(snapGroupName, new ArrayList<URI>()); } snapGroupCloneURIMap.get(snapGroupName).add(snapshot.getId()); } } } // add steps to remove snapshots from the replication group for (Entry<String, List<URI>> entry : snapGroupCloneURIMap.entrySet()) { waitFor = removeSnapshotsFromReplicationGroupStep(workflow, waitFor, system, cgURI, entry.getValue(), entry.getKey()); } } } return waitFor; } /* (non-Javadoc) * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface#addStepsForCreateFullCopy(com.emc.storageos.workflow.Workflow, java.lang.String, java.util.List, java.lang.String) */ @Override public String addStepsForPreCreateReplica(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { return waitFor; } /* (non-Javadoc) * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface#addStepsForPostCreateReplica(com.emc.storageos.workflow.Workflow, java.lang.String, java.util.List, java.lang.String) */ @Override public String addStepsForPostCreateReplica(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { return waitFor; } /* (non-Javadoc) * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface#addStepsForCreateFullCopy(com.emc.storageos.workflow.Workflow, java.lang.String, java.util.List, java.lang.String) */ @Override public String addStepsForCreateFullCopy(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { return waitFor; } private List<Volume> getVolumes(Collection<Volume> volumes, final Collection<URI> withURIs) { return ImmutableList.copyOf(filter(volumes, new Predicate<Volume>() { @Override public boolean apply(Volume volume) { return withURIs.contains(volume.getId()); } })); } private Map<String, Set<URI>> sortVolumesBySystemAndReplicationGroup(Collection<Volume> volumes) { Map<String, Set<URI>> rgVolsMap = new HashMap<>(); for (Volume filteredVolume : volumes) { String replicationGroup = filteredVolume.getReplicationGroupInstance(); if (NullColumnValueGetter.isNotNullValue(replicationGroup)) { URI storage = filteredVolume.getStorageController(); String key = storage.toString() + replicationGroup; Set<URI> rgVolumeList = rgVolsMap.get(key); if (rgVolumeList == null) { rgVolumeList = new HashSet<>(); rgVolsMap.put(key, rgVolumeList); } rgVolumeList.add(filteredVolume.getId()); } } return rgVolsMap; } private Predicate<Volume> deleteVolumeFilterPredicate() { return new Predicate<Volume>() { @Override public boolean apply(Volume volume) { return volume != null && !volume.getInactive() && volume.isInCG() && NullColumnValueGetter.isNotNullValue(volume.getReplicationGroupInstance()) && (ControllerUtils.isVmaxVolumeUsing803SMIS(volume, _dbClient) || ControllerUtils.isNotInRealVNXRG(volume, _dbClient)); } }; } /** * Get instances of BlockSnapshotSession that contain the given replication group instance and storage system. * * @param replicationGroupInstance ReplicationGroup instance * @param storage StorageSystem URI * @return Collection of BlockSnapshotSession */ private Collection<BlockSnapshotSession> getSessionsForReplicationGroup(String replicationGroupInstance, final URI storage) { List<BlockSnapshotSession> sessions = CustomQueryUtility.queryActiveResourcesByConstraint(_dbClient, BlockSnapshotSession.class, getSnapshotSessionReplicationGroupInstanceConstraint(replicationGroupInstance)); return filter(sessions, new Predicate<BlockSnapshotSession>() { @Override public boolean apply(BlockSnapshotSession session) { return session.getStorageController().equals(storage); } }); } private Set<URI> getAllMirrors(Collection<Volume> volumes) { Set<URI> allMirrors = new HashSet<>(); for (Volume volume : volumes) { for (String mirror : volume.getMirrors()) { allMirrors.add(URI.create(mirror)); } } return allMirrors; } }