/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.blockorchestrationcontroller; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.Controller; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.Migration; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.locking.LockRetryException; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPDeviceController; import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPHelper; import com.emc.storageos.srdfcontroller.SRDFDeviceController; 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.ControllerLockingService; import com.emc.storageos.volumecontroller.TaskCompleter; import com.emc.storageos.volumecontroller.impl.ControllerUtils; import com.emc.storageos.volumecontroller.impl.block.BlockDeviceController; import com.emc.storageos.volumecontroller.impl.block.ReplicaDeviceController; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotRestoreCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionCreateWorkflowCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneCreateWorkflowCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneRestoreCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeCreateWorkflowCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeVarrayChangeTaskCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeVpoolChangeTaskCompleter; import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeWorkflowCompleter; import com.emc.storageos.volumecontroller.impl.utils.ConsistencyGroupUtils; import com.emc.storageos.volumecontroller.impl.validators.ValCk; import com.emc.storageos.volumecontroller.impl.validators.ValidatorFactory; import com.emc.storageos.vplexcontroller.VPlexDeviceController; import com.emc.storageos.workflow.Workflow; import com.emc.storageos.workflow.WorkflowException; import com.emc.storageos.workflow.WorkflowService; import com.emc.storageos.workflow.WorkflowState; public class BlockOrchestrationDeviceController implements BlockOrchestrationController, Controller { private static final Logger s_logger = LoggerFactory.getLogger(BlockOrchestrationDeviceController.class); private WorkflowService _workflowService; private static DbClient s_dbClient; private static BlockDeviceController _blockDeviceController; private static VPlexDeviceController _vplexDeviceController; private static RPDeviceController _rpDeviceController; private static SRDFDeviceController _srdfDeviceController; private static ReplicaDeviceController _replicaDeviceController; private static ValidatorFactory validator; private ControllerLockingService _locker; static final String CREATE_VOLUMES_WF_NAME = "CREATE_VOLUMES_WORKFLOW"; static final String DELETE_VOLUMES_WF_NAME = "DELETE_VOLUMES_WORKFLOW"; static final String EXPAND_VOLUMES_WF_NAME = "EXPAND_VOLUMES_WORKFLOW"; static final String RESTORE_VOLUME_FROM_SNAPSHOT_WF_NAME = "RESTORE_VOLUME_FROM_SNAPSHOT_WORKFLOW"; static final String CHANGE_VPOOL_WF_NAME = "CHANGE_VPOOL_WORKFLOW"; static final String CHANGE_VARRAY_WF_NAME = "CHANGE_VARRAY_WORKFLOW"; static final String RESTORE_FROM_FULLCOPY_WF_NAME = "RESTORE_FROM_FULLCOPY_WORKFLOW"; static final String CREATE_FULL_COPIES_WF_NAME = "CREATE_FULL_COPIES_WORKFLOW"; static final String CREATE_SNAPSHOT_SESSION_WF_NAME = "CREATE_SNAPSHOT_SESSION_WORKFLOW"; /* * (non-Javadoc) * * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#createVolumes(java.util.List, * java.lang.String) */ @Override public void createVolumes(List<VolumeDescriptor> volumes, String taskId) throws ControllerException { List<URI> volUris = VolumeDescriptor.getVolumeURIs(volumes); VolumeCreateWorkflowCompleter completer = new VolumeCreateWorkflowCompleter(volUris, taskId, volumes); Workflow workflow = null; try { // Generate the Workflow. workflow = _workflowService.getNewWorkflow(this, CREATE_VOLUMES_WF_NAME, true, taskId); String waitFor = null; // the wait for key returned by previous call s_logger.info("Generating steps for create Volume"); // First, call the BlockDeviceController to add its methods. waitFor = _blockDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); s_logger.info("Checking for SRDF steps"); // Call the SRDFDeviceController to add its methods if there are SRDF volumes. waitFor = _srdfDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); s_logger.info("Checking for VPLEX steps"); // Call the VPlexDeviceController to add its methods if there are VPLEX volumes. waitFor = _vplexDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); s_logger.info("Checking for RP steps"); // Call the RPDeviceController to add its methods if there are RP protections waitFor = _rpDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); s_logger.info("Checking for Replica steps"); // Call the ReplicaDeviceController to add its methods if volumes are added to CG, and the CG associated // with replication // group(s) waitFor = _replicaDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Create volumes successful for: " + volUris.toString(); Object[] callbackArgs = new Object[] { volUris }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (LockRetryException ex) { /** * Added this catch block to mark the current workflow as completed so that lock retry will not get exception while creating new * workflow using the same taskid. */ s_logger.info(String.format("Lock retry exception key: %s remaining time %d", ex.getLockIdentifier(), ex.getRemainingWaitTimeSeconds())); releaseWorkflowLocks(workflow); if (workflow != null && !NullColumnValueGetter.isNullURI(workflow.getWorkflowURI()) && workflow.getWorkflowState() == WorkflowState.CREATED) { com.emc.storageos.db.client.model.Workflow wf = s_dbClient.queryObject(com.emc.storageos.db.client.model.Workflow.class, workflow.getWorkflowURI()); if (!wf.getCompleted()) { s_logger.error("Marking the status to completed for the newly created workflow {}", wf.getId()); wf.setCompleted(true); s_dbClient.updateObject(wf); } } throw ex; } catch (Exception ex) { s_logger.error("Could not create volumes: " + volUris, ex); releaseWorkflowLocks(workflow); String opName = ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME.getName(); ServiceError serviceError = DeviceControllerException.errors.createVolumesFailed( volUris.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } @SuppressWarnings("serial") private static class WorkflowCallback implements Workflow.WorkflowCallbackHandler, Serializable { @SuppressWarnings("unchecked") @Override public void workflowComplete(Workflow workflow, Object[] args) throws WorkflowException { List<URI> volumes = (List<URI>) args[0]; String msg = BlockDeviceController.getVolumesMsg(s_dbClient, volumes); s_logger.info("Processed volumes:\n" + msg); } } /* * (non-Javadoc) * * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#deleteVolumes(java.util.List, * java.lang.String) */ @Override public void deleteVolumes(List<VolumeDescriptor> volumes, String taskId) throws ControllerException { List<URI> volUris = VolumeDescriptor.getVolumeURIs(volumes); VolumeWorkflowCompleter completer = new VolumeWorkflowCompleter(volUris, taskId); Workflow workflow = null; try { // Validate the volume identities before proceeding validator.volumeURIs(volUris, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. workflow = _workflowService.getNewWorkflow(this, DELETE_VOLUMES_WF_NAME, true, taskId); String waitFor = null; // the wait for key returned by previous call // Call the RPDeviceController to add its methods if there are RP protections. waitFor = _rpDeviceController.addStepsForDeleteVolumes( workflow, waitFor, volumes, taskId); // Call the ReplicaDeviceController to add its methods if volumes are removed from, // and the CG associated with replication group(s) waitFor = _replicaDeviceController.addStepsForDeleteVolumes( workflow, waitFor, volumes, taskId); // Call the VPlexDeviceController to add its methods if there are VPLEX volumes. waitFor = _vplexDeviceController.addStepsForDeleteVolumes( workflow, waitFor, volumes, taskId); // Call the RPDeviceController to add its post-delete methods. waitFor = _rpDeviceController.addStepsForPostDeleteVolumes( workflow, waitFor, volumes, taskId, completer, _blockDeviceController); // Call the SRDFDeviceController to add its methods if there are SRDF volumes. waitFor = _srdfDeviceController.addStepsForDeleteVolumes( workflow, waitFor, volumes, taskId); // Next, call the BlockDeviceController to add its methods. waitFor = _blockDeviceController.addStepsForDeleteVolumes( workflow, waitFor, volumes, taskId); // Next, call the BlockDeviceController to add post deletion methods. waitFor = _blockDeviceController.addStepsForPostDeleteVolumes( workflow, waitFor, volumes, taskId, completer); // Call the VPlexDeviceController to add its post-delete methods. waitFor = _vplexDeviceController.addStepsForPostDeleteVolumes( workflow, waitFor, volumes, taskId, completer); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Delete volumes successful for: " + volUris.toString(); Object[] callbackArgs = new Object[] { volUris }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not delete volumes: " + volUris, ex); releaseWorkflowLocks(workflow); String opName = ResourceOperationTypeEnum.DELETE_BLOCK_VOLUME.getName(); ServiceError serviceError = DeviceControllerException.errors.deleteVolumesFailed( volUris.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } /* * (non-Javadoc) * * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#expandVolume(java.net.URI, long, * java.lang.String) */ @Override public void expandVolume(List<VolumeDescriptor> volumes, String taskId) throws ControllerException { List<URI> volUris = VolumeDescriptor.getVolumeURIs(volumes); VolumeWorkflowCompleter completer = new VolumeWorkflowCompleter(volUris, taskId); try { // Validate the volume identities before proceeding validator.volumeURIs(volUris, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. Workflow workflow = _workflowService.getNewWorkflow(this, EXPAND_VOLUMES_WF_NAME, true, taskId); String waitFor = null; // the wait for key returned by previous call // First, call the RP controller to add methods for RP CG delete waitFor = _rpDeviceController.addPreVolumeExpandSteps( workflow, volumes, taskId); // Call the BlockDeviceController to add its methods if there are block or VPLEX backend volumes. waitFor = _blockDeviceController.addStepsForExpandVolume( workflow, waitFor, volumes, taskId); // Call the SRDFDeviceController to add its methods for SRDF Source / SRDF Target volumes. waitFor = _srdfDeviceController.addStepsForExpandVolume( workflow, waitFor, volumes, taskId); // Call the VPlexDeviceController to add its methods if there are VPLEX volumes. waitFor = _vplexDeviceController.addStepsForExpandVolume( workflow, waitFor, volumes, taskId); // Call the RPDeviceController to add its methods for post volume expand ie. recreate RPCG waitFor = _rpDeviceController.addPostVolumeExpandSteps( workflow, waitFor, volumes, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Expand volume successful for: " + volUris.toString(); Object[] callbackArgs = new Object[] { new ArrayList<URI>(volUris) }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not expand volume: " + volUris, toString(), ex); String opName = ResourceOperationTypeEnum.EXPAND_BLOCK_VOLUME.getName(); ServiceError serviceError = DeviceControllerException.errors.expandVolumeFailed(volUris.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } /* * (non-Javadoc) * * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#restoreVolume(java.net.URI, * java.net.URI, * java.net.URI, java.net.URI, java.lang.String) */ @Override public void restoreVolume(URI storage, URI pool, URI volume, URI snapshot, String syncDirection, String taskId) throws ControllerException { List<URI> volUris = Arrays.asList(volume); BlockSnapshotRestoreCompleter completer = new BlockSnapshotRestoreCompleter(snapshot, taskId); try { // Validate the volume identities before proceeding validator.volumeURIs(volUris, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. Workflow workflow = _workflowService.getNewWorkflow(this, RESTORE_VOLUME_FROM_SNAPSHOT_WF_NAME, true, taskId); String waitFor = null; // the wait for key returned by previous call // First, call the RP controller to add RP steps for volume restore from snapshot waitFor = _rpDeviceController.addPreRestoreVolumeSteps( workflow, storage, volume, snapshot, taskId); // Call the VplexDeviceController to add its steps for restore volume from snapshot waitFor = _vplexDeviceController.addStepsForRestoreVolume( workflow, waitFor, storage, pool, volume, snapshot, null, syncDirection, taskId, completer); // Call the BlockDeviceController to add its steps for restore volume from snapshot waitFor = _blockDeviceController.addStepsForRestoreVolume( workflow, waitFor, storage, pool, volume, snapshot, Boolean.TRUE, syncDirection, taskId, completer); // Call the RPDeviceController to add its steps for post restore volume from snapshot waitFor = _rpDeviceController.addStepsForRestoreVolume( workflow, waitFor, storage, pool, volume, snapshot, null, syncDirection, taskId, completer); // Call the RP controller to add RP post restore steps waitFor = _rpDeviceController.addPostRestoreVolumeSteps( workflow, waitFor, storage, volume, snapshot, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = String.format("Restore of volume %s from %s completed successfully", volume, snapshot); Object[] callbackArgs = new Object[] { new ArrayList<URI>(volUris) }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not restore volume: " + volUris.toString(), ex); String opName = ResourceOperationTypeEnum.RESTORE_VOLUME_SNAPSHOT.getName(); ServiceError serviceError = DeviceControllerException.errors.restoreVolumeFromSnapshotFailed(volUris.toString(), snapshot.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } /* * (non-Javadoc) * * @see * com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#changeVirtualPool(java.util.List, * java.lang.String) */ @Override public void changeVirtualPool(List<VolumeDescriptor> volumes, String taskId) throws ControllerException { Map<URI, URI> volumeToOldVpoolsMap = VolumeDescriptor.createVolumeToOldVpoolMap(volumes); Map<URI, URI> volumeToNewVpoolsMap = VolumeDescriptor.createVolumeToNewVpoolMap(volumes); List<URI> volURIs = VolumeDescriptor.getVolumeURIs(volumes); List<URI> cgIds = null; List<URI> migrationURIs = new ArrayList<URI>(); for (VolumeDescriptor desc : volumes) { URI migrationURI = desc.getMigrationId(); if (!NullColumnValueGetter.isNullURI(migrationURI)) { migrationURIs.add(migrationURI); } cgIds = Volume.fetchCgIds(s_dbClient, volURIs); } VolumeVpoolChangeTaskCompleter completer = new VolumeVpoolChangeTaskCompleter( volURIs, migrationURIs, volumeToOldVpoolsMap, volumeToNewVpoolsMap, taskId); try { // Validate the volume identities before proceeding validator.volumeURIs(volURIs, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. Workflow workflow = _workflowService.getNewWorkflow(this, CHANGE_VPOOL_WF_NAME, true, taskId, completer); String waitFor = null; // the wait for key returned by previous call // Mainly for RP+VPLEX as a change vpool would require new volumes (source-journal, target(s), // target-journal) to be created. waitFor = _blockDeviceController.addStepsForCreateVolumes( workflow, waitFor, volumes, taskId); // Call the VPlexDeviceController to add change virtual pool steps. waitFor = _vplexDeviceController.addStepsForChangeVirtualPool( workflow, waitFor, volumes, taskId); // Last, call the RPDeviceController to add change virtual pool steps. waitFor = _rpDeviceController.addStepsForChangeVirtualPool( workflow, waitFor, volumes, taskId); // This step is currently used to ensure that any existing resources get added to native // CGs. Mainly used for VPLEX->RP+VPLEX change vpool. The existing VPLEX volume would not be // in any CG and we now need its backing volume(s) to be added to their local array CG. waitFor = postRPChangeVpoolSteps(workflow, waitFor, volumes, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Change Virtual Pool suceeded for volumes: " + volURIs.toString(); Object[] callbackArgs = new Object[] { volURIs }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not change Virtual Pool for volumes: " + volURIs, ex); String opName = ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VPOOL.getName(); ServiceError serviceError = DeviceControllerException.errors.changeVirtualPoolFailed( volURIs.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } @Override public void changeVirtualArray(List<VolumeDescriptor> volumeDescriptors, String taskId) throws ControllerException { // The descriptors that contain descriptor parameters // specifying the new target varray are the volumes being // moved to the new virtual array. List<URI> changeVArrayVolURIList = new ArrayList<URI>(); List<URI> migrationURIs = new ArrayList<URI>(); for (VolumeDescriptor volumeDescriptor : volumeDescriptors) { Map<String, Object> descrParams = volumeDescriptor.getParameters(); if ((descrParams != null) && (!descrParams.isEmpty())) { changeVArrayVolURIList.add(volumeDescriptor.getVolumeURI()); } URI migrationURI = volumeDescriptor.getMigrationId(); if (!NullColumnValueGetter.isNullURI(migrationURI)) { migrationURIs.add(migrationURI); } } // Create a completer that will update the task status for these // volumes and associated migrations when the workflow completes. VolumeVarrayChangeTaskCompleter completer = new VolumeVarrayChangeTaskCompleter( VolumeDescriptor.getVolumeURIs(volumeDescriptors), migrationURIs, taskId); try { // Validate the volume identities before proceeding validator.volumeURIs(changeVArrayVolURIList, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. String waitFor = null; Workflow workflow = _workflowService.getNewWorkflow(this, CHANGE_VARRAY_WF_NAME, true, taskId); // First, call the BlockDeviceController to add its steps. // This will create the migration target volumes. waitFor = _blockDeviceController.addStepsForCreateVolumes(workflow, waitFor, volumeDescriptors, taskId); // Then call the VPlexDeviceController to add change virtual array steps. waitFor = _vplexDeviceController.addStepsForChangeVirtualArray(workflow, waitFor, volumeDescriptors, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = String.format( "Change virtual array suceeded for volumes: %s", changeVArrayVolURIList); Object[] callbackArgs = new Object[] { changeVArrayVolURIList }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not change virtual array for volumes: " + changeVArrayVolURIList, ex); String opName = ResourceOperationTypeEnum.CHANGE_BLOCK_VOLUME_VARRAY.getName(); ServiceError serviceError = DeviceControllerException.errors .changeVirtualArrayFailed(changeVArrayVolURIList.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } /** * Needed to perform post change vpool operations on RP volumes. * * @param workflow * The current workflow * @param waitFor * The previous operation to wait for * @param volumeDescriptors * All the volume descriptors * @param taskId * The current task id * @return The previous operation id */ private String postRPChangeVpoolSteps(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId) { // Get the list of descriptors needed for post change virtual pool operations on RP. List<VolumeDescriptor> rpVolumeDescriptors = VolumeDescriptor.filterByType(volumeDescriptors, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.RP_EXISTING_SOURCE, }, null); // If no volume descriptors match, just return if (rpVolumeDescriptors.isEmpty()) { return waitFor; } List<VolumeDescriptor> migratedBlockDataDescriptors = new ArrayList<VolumeDescriptor>(); // We could be performing a change vpool for RP+VPLEX / MetroPoint. This means // we could potentially have migrations that need to be done on the backend // volumes. If migration info exists we need to collect that ahead of time. List<URI> volumesWithMigration = new ArrayList<URI>(); if (volumeDescriptors != null) { List<VolumeDescriptor> migrateDescriptors = VolumeDescriptor.filterByType(volumeDescriptors, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.VPLEX_MIGRATE_VOLUME }, null); if (migrateDescriptors != null && !migrateDescriptors.isEmpty()) { s_logger.info("Data Migration detected, this is due to a change virtual pool operation on RP+VPLEX or MetroPoint."); // Load the migration objects for use later Iterator<VolumeDescriptor> migrationIter = migrateDescriptors.iterator(); while (migrationIter.hasNext()) { VolumeDescriptor migrationDesc = migrationIter.next(); Migration migration = s_dbClient.queryObject(Migration.class, migrationDesc.getMigrationId()); volumesWithMigration.add(migration.getSource()); Volume migratedVolume = s_dbClient.queryObject(Volume.class, migration.getVolume()); VolumeDescriptor migratedBlockDataDesc = new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA, migratedVolume.getStorageController(), migratedVolume.getId(), null, migratedVolume.getConsistencyGroup(), migrationDesc.getCapabilitiesValues()); migratedBlockDataDescriptors.add(migratedBlockDataDesc); } } } List<VolumeDescriptor> blockDataDescriptors = new ArrayList<VolumeDescriptor>(); for (VolumeDescriptor descr : rpVolumeDescriptors) { // If there are RP_EXISTING_SOURCE volume descriptors, we need to ensure the // existing volumes are added to their native CGs for the change vpool request. // Before any existing resource can be protected by RP they have to be removed // from their existing CGs but now will need to be added to the new CG needed // for RecoverPoint protection. // NOTE: Only relevant for RP+VPLEX and MetroPoint. Regular RP does not enforce local // array CGs. Volume rpExistingSource = s_dbClient.queryObject(Volume.class, descr.getVolumeURI()); // Check to see if the existing is not already protected by RP and that // there are associated volumes (meaning it's a VPLEX volume) if (RPHelper.isVPlexVolume(rpExistingSource, s_dbClient)) { s_logger.info(String.format("Adding post RP Change Vpool steps for existing VPLEX source volume [%s].", rpExistingSource.getLabel())); // VPLEX, use associated backing volumes // NOTE: If migrations exist for this volume the VPLEX Device Controller will clean these up // newly added CGs because we won't need them as the migration volumes will create their own CGs. // This is OK. if (null != rpExistingSource.getAssociatedVolumes()) { for (String assocVolumeId : rpExistingSource.getAssociatedVolumes()) { Volume assocVolume = s_dbClient.queryObject(Volume.class, URI.create(assocVolumeId)); // If there is a migration for this backing volume, we don't have to // do any extra steps for ensuring that this volume gets gets added to the backing array CG // because the migration volume will trump this volume. This volume will eventually be // deleted so let's skip it. if (volumesWithMigration.contains(assocVolume.getId())) { s_logger.info(String.format("Migration exists for [%s] so no need to add this volume to a backing array CG.", assocVolume.getLabel())); continue; } // Only add the change vpool volume's backend volumes to the backend CGs if the // getReplicationGroupInstance // field has been populated during the API prepare volume steps. if (NullColumnValueGetter.isNotNullValue(assocVolume.getReplicationGroupInstance())) { // Create the BLOCK_DATA descriptor with the correct info // for creating the CG and adding the backing volume to it. VolumeDescriptor blockDataDesc = new VolumeDescriptor(VolumeDescriptor.Type.BLOCK_DATA, assocVolume.getStorageController(), assocVolume.getId(), null, rpExistingSource.getConsistencyGroup(), descr.getCapabilitiesValues()); blockDataDescriptors.add(blockDataDesc); // Good time to update the backing volume with its new CG assocVolume.setConsistencyGroup(rpExistingSource.getConsistencyGroup()); s_dbClient.updateObject(assocVolume); s_logger.info( String.format("Backing volume [%s] needs to be added to CG [%s] on storage system [%s].", assocVolume.getLabel(), rpExistingSource.getConsistencyGroup(), assocVolume.getStorageController())); } } } } } if (!blockDataDescriptors.isEmpty()) { // Add a step to create the local array consistency group waitFor = _blockDeviceController.addStepsForCreateConsistencyGroup(workflow, waitFor, blockDataDescriptors, "postRPChangeVpoolCreateCG"); // Add a step to update the local array consistency group with the volumes to add waitFor = _blockDeviceController.addStepsForUpdateConsistencyGroup(workflow, waitFor, blockDataDescriptors, null); } // Consolidate all the block data descriptors to see if any replica steps are needed. blockDataDescriptors.addAll(migratedBlockDataDescriptors); s_logger.info("Checking for Replica steps"); // Call the ReplicaDeviceController to add its methods if volumes are added to CG, and the CG associated with // replication // group(s) waitFor = _replicaDeviceController.addStepsForCreateVolumes(workflow, waitFor, blockDataDescriptors, taskId); return waitFor; } public WorkflowService getWorkflowService() { return _workflowService; } public void setWorkflowService(WorkflowService workflowService) { this._workflowService = workflowService; } public DbClient getDbClient() { return s_dbClient; } public void setDbClient(DbClient dbClient) { this.s_dbClient = dbClient; } public void setLocker(ControllerLockingService locker) { this._locker = locker; } public BlockDeviceController getBlockDeviceController() { return _blockDeviceController; } public void setBlockDeviceController(BlockDeviceController blockDeviceController) { this._blockDeviceController = blockDeviceController; } public static VPlexDeviceController getVplexDeviceController() { return _vplexDeviceController; } public static void setVplexDeviceController(VPlexDeviceController vplexDeviceController) { BlockOrchestrationDeviceController._vplexDeviceController = vplexDeviceController; } public static RPDeviceController getRpDeviceController() { return _rpDeviceController; } public static void setRpDeviceController(RPDeviceController rpDeviceController) { BlockOrchestrationDeviceController._rpDeviceController = rpDeviceController; } public static SRDFDeviceController getSrdfDeviceController() { return _srdfDeviceController; } public static void setSrdfDeviceController(SRDFDeviceController srdfDeviceController) { BlockOrchestrationDeviceController._srdfDeviceController = srdfDeviceController; } public static ReplicaDeviceController getReplicaDeviceController() { return BlockOrchestrationDeviceController._replicaDeviceController; } public static void setReplicaDeviceController(ReplicaDeviceController replicaDeviceController) { BlockOrchestrationDeviceController._replicaDeviceController = replicaDeviceController; } private void releaseWorkflowLocks(Workflow workflow) { if (workflow == null) { return; } s_logger.info("Releasing all workflow locks with owner: {}", workflow.getWorkflowURI()); _workflowService.releaseAllWorkflowLocks(workflow); } @Override public void restoreFromFullCopy(URI storage, List<URI> fullCopyURIs, String taskId) throws InternalException { CloneRestoreCompleter completer = new CloneRestoreCompleter(fullCopyURIs, taskId); // add the CG to the completer if this is a CG restore Iterator<Volume> iter = getDbClient().queryIterativeObjects(Volume.class, fullCopyURIs); while (iter.hasNext()) { Volume fc = iter.next(); if (!NullColumnValueGetter.isNullURI(fc.getAssociatedSourceVolume())) { BlockObject firstSource = BlockObject.fetch(getDbClient(), fc.getAssociatedSourceVolume()); if (firstSource != null) { if (firstSource instanceof Volume && !NullColumnValueGetter.isNullURI(firstSource.getConsistencyGroup())) { completer.addConsistencyGroupId(firstSource.getConsistencyGroup()); } break; } } } s_logger.info("Creating steps for restore from full copy."); try { // Validate the volume identities before proceeding validator.volumeURIs(fullCopyURIs, true, true, ValCk.ID, ValCk.VPLEX); // Generate the Workflow. Workflow workflow = _workflowService.getNewWorkflow(this, RESTORE_FROM_FULLCOPY_WF_NAME, true, taskId); String waitFor = null; // the wait for key returned by previous call // First, call the RP controller to add RP steps for volume restore waitFor = _rpDeviceController.addPreRestoreFromFullcopySteps( workflow, waitFor, storage, fullCopyURIs, taskId); // Call the VplexDeviceController to add its steps for restore volume from full copy waitFor = _vplexDeviceController.addStepsForRestoreFromFullcopy( workflow, waitFor, storage, fullCopyURIs, taskId, completer); // Call the BlockDeviceController to add its steps for restore volume from full copy waitFor = _blockDeviceController.addStepsForRestoreFromFullcopy(workflow, waitFor, storage, fullCopyURIs, taskId, completer); // Call the RPDeviceController to add its steps for post restore volume from full copy waitFor = _rpDeviceController.addPostRestoreFromFullcopySteps(workflow, waitFor, storage, fullCopyURIs, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Restore from full copy completed successfully"; Object[] callbackArgs = new Object[] { new ArrayList<URI>(fullCopyURIs) }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not restore volume: ", ex); ServiceError serviceError = DeviceControllerException.errors.jobFailed(ex); completer.error(s_dbClient, _locker, serviceError); } } public static ValidatorFactory getValidator() { return validator; } public static void setValidator(ValidatorFactory validator) { BlockOrchestrationDeviceController.validator = validator; } /* * (non-Javadoc) * * @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#createFullCopy(java.util.List, * java.lang.String) */ @Override public void createFullCopy(List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { // The volume descriptors include the VPLEX source volume, which we do not want // to pass to the completer. In case of error constructing the WF, the completer // must mark all volumes prepared for this request inactive. However, we must not // mark the VPLEX source volume inactive! List<URI> volUris = new ArrayList<>(); URI vplexSourceURI = null; for (VolumeDescriptor descriptor : volumeDescriptors) { if (descriptor.getParameters().get(VolumeDescriptor.PARAM_IS_COPY_SOURCE_ID) == null) { volUris.add(descriptor.getVolumeURI()); } else { vplexSourceURI = descriptor.getVolumeURI(); } } TaskCompleter completer = new CloneCreateWorkflowCompleter(volUris, taskId); Workflow workflow = null; List<VolumeDescriptor> blockVolmeDescriptors = VolumeDescriptor.filterByType(volumeDescriptors, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA, VolumeDescriptor.Type.VPLEX_IMPORT_VOLUME }, new VolumeDescriptor.Type[] {}); List<URI> blockVolUris = VolumeDescriptor.getVolumeURIs(blockVolmeDescriptors); // add all consistency groups to the completer Set<URI> cgIds = new HashSet<URI>(); for (URI blockId : blockVolUris) { Volume fcVolume = getDbClient().queryObject(Volume.class, blockId); // need to check for a null associated source volume here because the list of full copy volume descriptors // includes // the HA side of a vplex distributed volume. By design, this volume is not a clone and so it won't have // associated // source volume set. The change was added here to reduce regression testing scope but really belongs in the // utility // method // Filed COP-23075 to move this check for null associated source volume to the utility method in x-wing if (fcVolume != null && !fcVolume.getInactive() && !NullColumnValueGetter.isNullURI(fcVolume.getAssociatedSourceVolume())) { BlockConsistencyGroup group = ConsistencyGroupUtils.getCloneConsistencyGroup(blockId, getDbClient()); if (group != null) { cgIds.add(group.getId()); } } } for (URI cgId : cgIds) { completer.addConsistencyGroupId(cgId); } for (URI appId : ControllerUtils.getApplicationsForFullCopies(blockVolUris, getDbClient())) { completer.addVolumeGroupId(appId); } try { // For VPLEX full copies, validate the VPLEX source volume. if (vplexSourceURI != null) { validator.volumeURIs(Arrays.asList(vplexSourceURI), true, true, ValCk.ID, ValCk.VPLEX); } // Generate the Workflow. workflow = _workflowService.getNewWorkflow(this, CREATE_FULL_COPIES_WF_NAME, false, taskId, completer); String waitFor = null; // the wait for key returned by previous call s_logger.info("Adding steps for RecoverPoint create full copy"); // Call the RPDeviceController to add its methods if there are RP protections waitFor = _rpDeviceController.addStepsForPreCreateReplica( workflow, waitFor, volumeDescriptors, taskId); s_logger.info("Adding steps for storage array create full copies"); // First, call the BlockDeviceController to add its methods. waitFor = _blockDeviceController.addStepsForCreateFullCopy( workflow, waitFor, volumeDescriptors, taskId); // post recoverpoint steps disables image access which should be done after the // create clone steps but before the vplex steps. s_logger.info("Adding steps for RecoverPoint post create full copy"); // Call the RPDeviceController to add its methods if there are RP protections waitFor = _rpDeviceController.addStepsForPostCreateReplica( workflow, waitFor, volumeDescriptors, taskId); s_logger.info("Checking for VPLEX steps"); // Call the VPlexDeviceController to add its methods if there are VPLEX volumes. waitFor = _vplexDeviceController.addStepsForCreateFullCopy( workflow, waitFor, volumeDescriptors, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Create volumes successful for: " + volUris.toString(); Object[] callbackArgs = new Object[] { volUris }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not create full copy volumes: " + volUris, ex); releaseWorkflowLocks(workflow); String opName = ResourceOperationTypeEnum.CREATE_BLOCK_VOLUME.getName(); ServiceError serviceError = DeviceControllerException.errors.createVolumesFailed( volUris.toString(), opName, ex); completer.error(s_dbClient, _locker, serviceError); } } /* * (non-Javadoc) * * @see * com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationController#createSnapshotSession(java.util.List, * java.lang.String) */ @Override public void createSnapshotSession(List<VolumeDescriptor> volumeDescriptors, String taskId) throws InternalException { Workflow workflow = null; List<VolumeDescriptor> snapshotSessionDescriptors = VolumeDescriptor.filterByType(volumeDescriptors, new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_SNAPSHOT_SESSION }, new VolumeDescriptor.Type[] {}); List<URI> snapshotSessionURIs = VolumeDescriptor.getVolumeURIs(snapshotSessionDescriptors); // we expect just one snapshot session volume descriptor per create snapshot session operation TaskCompleter completer = new BlockSnapshotSessionCreateWorkflowCompleter(snapshotSessionURIs.get(0), snapshotSessionDescriptors.get(0).getSnapSessionSnapshotURIs(), taskId); ControllerUtils.checkSnapshotSessionConsistencyGroup(snapshotSessionURIs.get(0), getDbClient(), completer); try { // Generate the Workflow. workflow = _workflowService.getNewWorkflow(this, CREATE_SNAPSHOT_SESSION_WF_NAME, false, taskId, completer); String waitFor = null; // the wait for key returned by previous call s_logger.info("Adding steps for RecoverPoint create snapshot session"); // Call the RPDeviceController to add its methods if there are RP protections waitFor = _rpDeviceController.addStepsForPreCreateReplica( workflow, waitFor, volumeDescriptors, taskId); s_logger.info("Adding steps for storage array create snapshot session"); // First, call the BlockDeviceController to add its methods. waitFor = _blockDeviceController.addStepsForCreateSnapshotSession( workflow, waitFor, volumeDescriptors, taskId); s_logger.info("Adding steps for RecoverPoint post create snapshot session"); // Call the RPDeviceController to add its methods if there are RP protections waitFor = _rpDeviceController.addStepsForPostCreateReplica( workflow, waitFor, volumeDescriptors, taskId); // Finish up and execute the plan. // The Workflow will handle the TaskCompleter String successMessage = "Create volumes successful for: " + snapshotSessionURIs.toString(); Object[] callbackArgs = new Object[] { snapshotSessionURIs }; workflow.executePlan(completer, successMessage, new WorkflowCallback(), callbackArgs, null, null); } catch (Exception ex) { s_logger.error("Could not create snapshot session: " + snapshotSessionURIs, ex); releaseWorkflowLocks(workflow); ServiceError serviceError = DeviceControllerException.errors.jobFailed(ex); completer.error(s_dbClient, _locker, serviceError); } } }