/*
* Copyright (c) 2008-2011 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.block;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.FCTN_MIRROR_TO_URI;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.fctnBlockObjectToNativeID;
import static com.emc.storageos.db.client.util.CommonTransformerFunctions.fctnDataObjectToID;
import static com.emc.storageos.volumecontroller.impl.ControllerUtils.checkCloneConsistencyGroup;
import static com.emc.storageos.volumecontroller.impl.ControllerUtils.checkSnapshotSessionConsistencyGroup;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Collections2.transform;
import static java.lang.String.format;
import static java.util.Arrays.asList;
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.Set;
import java.util.UUID;
import javax.xml.bind.DataBindingException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ContainmentConstraint;
import com.emc.storageos.db.client.constraint.PrefixConstraint;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types;
import com.emc.storageos.db.client.model.BlockMirror;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.BlockSnapshot;
import com.emc.storageos.db.client.model.BlockSnapshot.TechnologyType;
import com.emc.storageos.db.client.model.BlockSnapshotSession;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.DataObject.Flag;
import com.emc.storageos.db.client.model.DecommissionedResource;
import com.emc.storageos.db.client.model.DiscoveredDataObject.Type;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.Migration;
import com.emc.storageos.db.client.model.NamedURI;
import com.emc.storageos.db.client.model.OpStatusMap;
import com.emc.storageos.db.client.model.Operation;
import com.emc.storageos.db.client.model.StoragePool;
import com.emc.storageos.db.client.model.StorageProvider;
import com.emc.storageos.db.client.model.StorageProvider.InterfaceType;
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.VirtualArray;
import com.emc.storageos.db.client.model.VirtualPool;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.Volume.ReplicationState;
import com.emc.storageos.db.client.model.VolumeGroup;
import com.emc.storageos.db.client.model.factories.VolumeFactory;
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.exceptions.DatabaseException;
import com.emc.storageos.exceptions.DeviceControllerErrors;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.locking.LockTimeoutValue;
import com.emc.storageos.locking.LockType;
import com.emc.storageos.model.ResourceOperationTypeEnum;
import com.emc.storageos.plugins.BaseCollectionException;
import com.emc.storageos.plugins.StorageSystemViewObject;
import com.emc.storageos.plugins.common.Constants;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.services.util.StorageDriverManager;
import com.emc.storageos.srdfcontroller.SRDFDeviceController;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.svcs.errorhandling.resources.InternalException;
import com.emc.storageos.util.ExportUtils;
import com.emc.storageos.util.InvokeTestFailure;
import com.emc.storageos.volumecontroller.ApplicationAddVolumeList;
import com.emc.storageos.volumecontroller.AsyncTask;
import com.emc.storageos.volumecontroller.BlockController;
import com.emc.storageos.volumecontroller.BlockStorageDevice;
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.ControllerServiceImpl;
import com.emc.storageos.volumecontroller.impl.ControllerServiceImpl.Lock;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.block.rollback.ReplicaCleanupContext;
import com.emc.storageos.volumecontroller.impl.block.rollback.ReplicaCleanupFactory;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ApplicationTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupAddVolumeCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupDeleteCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupRemoveVolumeCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupUpdateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorDeactivateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorDeleteCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorDetachCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorFractureCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorResumeCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockMirrorTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotActivateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotDeleteCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotEstablishGroupTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotRestoreCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotResyncCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionCreateWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionDeleteCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionDeleteWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionLinkTargetCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionLinkTargetsWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionRelinkTargetCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionRelinkTargetsWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionRestoreCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionRestoreWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionUnlinkTargetCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockSnapshotSessionUnlinkTargetsWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockWaitForSynchronizedCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CleanupMetaVolumeMembersCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneActivateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneCreateWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneFractureCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneRestoreCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneResyncCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.CloneWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.MultiVolumeTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.SimpleTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeCreateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeDeleteCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeDetachCloneCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeExpandCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeUpdateCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.VolumeWorkflowCompleter;
import com.emc.storageos.volumecontroller.impl.hds.prov.utils.HDSUtils;
import com.emc.storageos.volumecontroller.impl.monitoring.RecordableBourneEvent;
import com.emc.storageos.volumecontroller.impl.monitoring.RecordableEventManager;
import com.emc.storageos.volumecontroller.impl.monitoring.cim.enums.RecordType;
import com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.DiscoverTaskCompleter;
import com.emc.storageos.volumecontroller.impl.plugins.discovery.smis.ScanTaskCompleter;
import com.emc.storageos.volumecontroller.impl.smis.MetaVolumeRecommendation;
import com.emc.storageos.volumecontroller.impl.smis.SRDFOperations.Mode;
import com.emc.storageos.volumecontroller.impl.smis.srdf.SRDFUtils;
import com.emc.storageos.volumecontroller.impl.utils.ConsistencyGroupUtils;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.emc.storageos.volumecontroller.impl.utils.MetaVolumeUtils;
import com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper;
import com.emc.storageos.volumecontroller.placement.BlockStorageScheduler;
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.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Table;
import com.google.common.collect.Table.Cell;
/**
* Generic Block Controller Implementation that does all of the database
* operations and calls methods on the array specific implementations
*
*
*
*/
public class BlockDeviceController implements BlockController, BlockOrchestrationInterface {
// Constants for Event Types
private static final String EVENT_SERVICE_TYPE = "block";
private static final String EVENT_SERVICE_SOURCE = "BlockController";
private DbClient _dbClient;
private static final Logger _log = LoggerFactory.getLogger(BlockDeviceController.class);
private static final int SCAN_LOCK_TIMEOUT = 60; // wait at most 60 seconds for scan lock
private Map<String, BlockStorageDevice> _devices;
private RecordableEventManager _eventManager;
private BlockStorageScheduler _blockScheduler;
private WorkflowService _workflowService;
private SRDFDeviceController srdfDeviceController;
private ReplicaDeviceController _replicaDeviceController;
private StorageDriverManager driverManager = null;
private static final String ATTACH_MIRRORS_WF_NAME = "ATTACH_MIRRORS_WORKFLOW";
private static final String DETACH_MIRRORS_WF_NAME = "DETACH_MIRRORS_WORKFLOW";
private static final String RESUME_MIRRORS_WF_NAME = "RESUME_MIRRORS_WORKFLOW";
private static final String ESTABLISH_VOLUME_MIRROR_GROUP_WF_NAME = "ESTABLISH_VOLUME_MIRROR_GROUP_WORKFLOW";
private static final String ESTABLISH_VOLUME_SNAPSHOT_GROUP_WF_NAME = "ESTABLISH_VOLUME_SNAPSHOT_GROUP_WORKFLOW";
private static final String ESTABLISH_VOLUME_FULL_COPY_GROUP_WF_NAME = "ESTABLISH_VOLUME_FULL_COPY_GROUP_WORKFLOW";
private static final String PAUSE_MIRRORS_WF_NAME = "PAUSE_MIRRORS_WORKFLOW";
private static final String RESTORE_VOLUME_WF_NAME = "RESTORE_VOLUME_WORKFLOW";
private static final String EXPAND_VOLUME_WF_NAME = "expandVolume";
private static final String ROLLBACK_METHOD_NULL = "rollbackMethodNull";
private static final String TERMINATE_RESTORE_SESSIONS_METHOD = "terminateRestoreSessions";
private static final String FRACTURE_CLONE_METHOD = "fractureClone";
private static final String UPDATE_CONSISTENCY_GROUP_WF_NAME = "UPDATE_CONSISTENCY_GROUP_WORKFLOW";
private static final String UNTAG_VOLUME_STEP_GROUP = "UNTAG_VOLUME_WORKFLOW";
static final String CREATE_LIST_SNAPSHOT_METHOD = "createListSnapshot";
private static final String CREATE_SAPSHOT_SESSION_WF_NAME = "createSnapshotSessionWf";
private static final String LINK_SNAPSHOT_SESSION_TARGETS_WF_NAME = "linkSnapshotSessionTargetsWF";
private static final String RELINK_SNAPSHOT_SESSION_TARGETS_WF_NAME = "relinkSnapshotSessionTargetsWF";
private static final String UNLINK_SNAPSHOT_SESSION_TARGETS_WF_NAME = "unlinkSnapshotSessionTargetsWF";
private static final String RESTORE_SNAPSHOT_SESSION_WF_NAME = "restoreSnapshotSessionWF";
private static final String DELETE_SNAPSHOT_SESSION_WF_NAME = "deleteSnapshotSessionWF";
public static final String CREATE_SNAPSHOT_SESSION_STEP_GROUP = "createSnapshotSession";
private static final String CREATE_SNAPSHOT_SESSION_METHOD = "createBlockSnapshotSession";
public static final String LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP = "LinkSnapshotSessionTarget";
private static final String LINK_SNAPSHOT_SESSION_TARGET_METHOD = "linkBlockSnapshotSessionTarget";
private static final String LINK_SNAPSHOT_SESSION_TARGET_GROUP_METHOD = "linkBlockSnapshotSessionTargetGroup";
private static final String RB_LINK_SNAPSHOT_SESSION_TARGET_METHOD = "rollbackLinkBlockSnapshotSessionTarget";
private static final String RELINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP = "RelinkSnapshotSessionTarget";
private static final String RELINK_SNAPSHOT_SESSION_TARGET_METHOD = "relinkBlockSnapshotSessionTarget";
private static final String UNLINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP = "UnlinkSnapshotSessionTarget";
private static final String UNLINK_SNAPSHOT_SESSION_TARGET_METHOD = "unlinkBlockSnapshotSessionTarget";
private static final String RESTORE_SNAPSHOT_SESSION_STEP_GROUP = "RestoreSnapshotSession";
private static final String RESTORE_SNAPSHOT_SESSION_METHOD = "restoreBlockSnapshotSession";
private static final String DELETE_SNAPSHOT_SESSION_STEP_GROUP = "DeleteSnapshotSession";
private static final String DELETE_SNAPSHOT_SESSION_METHOD = "deleteBlockSnapshotSession";
private static final String RESTORE_FROM_FULLCOPY_METHOD_NAME = "restoreFromFullCopy";
private static final String ROLLBACK_CLEANUP_REPLICAS_STEP_GROUP = "RollbackReplicaCleanUp";
private static final String ROLLBACK_CLEANUP_REPLICAS_METHOD_NAME = "rollbackCleanupReplicas";
private static final String ROLLBACK_CLEANUP_REPLICAS_STEP_DESC = "Null provisioning step; clean up replicas on rollback";
private static final String METHOD_CREATE_FULLCOPY_ORCHESTRATE_ROLLBACK_STEP = "createFullCopyOrchestrationRollbackSteps";
public static final String BLOCK_VOLUME_EXPAND_GROUP = "BlockDeviceExpandVolume";
public static final String RESTORE_VOLUME_STEP = "restoreVolume";
private static final String RESTORE_VOLUME_METHOD_NAME = "restoreVolume";
private static final String ADD_VOLUMES_TO_CG_KEY = "ADD";
private static final String REMOVE_VOLUMES_FROM_CG_KEY = "REMOVE";
public static final String UPDATE_VOLUMES_FOR_APPLICATION_WS_NAME = "UPDATE_VOLUMES_FOR_APPLICATION_WS";
private static final String REMOVE_VOLUMES_FROM_CG_STEP_GROUP = "REMOVE_VOLUMES_FROM_CG";
private static final String UPDATE_VOLUMES_STEP_GROUP = "UPDATE_VOLUMES";
public static final String DELETE_GROUP_STEP_GROUP = "DELETE_GROUP";
private static final String RESTORE_FROM_FULLCOPY_STEP = "restoreFromFullCopy";
private static final String METHOD_CREATE_FULL_COPY_STEP = "createFullCopy";
private static final String METHOD_CREATE_SNAPSHOT_SESSION_STEP = "createSnapshotSession";
public void setDbClient(DbClient dbc) {
_dbClient = dbc;
}
public void setBlockScheduler(BlockStorageScheduler blockScheduler) {
_blockScheduler = blockScheduler;
}
public void setDevices(Map<String, BlockStorageDevice> deviceInterfaces) {
_devices = deviceInterfaces;
}
public void setEventManager(RecordableEventManager eventManager) {
_eventManager = eventManager;
}
public void setWorkflowService(WorkflowService workflowService) {
_workflowService = workflowService;
}
public WorkflowService getWorkflowService() {
return _workflowService;
}
public void setReplicaDeviceController(ReplicaDeviceController replicaDeviceController) {
_replicaDeviceController = replicaDeviceController;
}
public synchronized StorageDriverManager getDriverManager() {
if (driverManager == null) {
driverManager = (StorageDriverManager) ControllerServiceImpl.getBean(StorageDriverManager.STORAGE_DRIVER_MANAGER);
}
return driverManager;
}
public BlockStorageDevice getDevice(String deviceType) {
BlockStorageDevice storageDevice = _devices.get(deviceType);
if (storageDevice == null) {
// we will use external device
storageDevice = _devices.get(Constants.EXTERNALDEVICE);
if (storageDevice == null) {
throw DeviceControllerException.exceptions.invalidSystemType(deviceType);
}
}
return storageDevice;
}
/**
* Creates a rollback workflow method that does nothing, but allows rollback
* to continue to prior steps back up the workflow chain.
*
* @return A workflow method
*/
Workflow.Method rollbackMethodNullMethod() {
return new Workflow.Method(ROLLBACK_METHOD_NULL);
}
/**
* A rollback workflow method that does nothing, but allows rollback
* to continue to prior steps back up the workflow chain. Can be and is
* used in workflows in other controllers that invoke operations on this
* block controller. If the block operation happens to fail, this no-op
* rollback method is invoked. It says the rollback step succeeded,
* which will then allow other rollback operations to execute for other
* workflow steps executed by the other controller.
*
* See the VPlexDeviceController restoreVolume method which creates a
* workflow step that invokes the BlockDeviceController restoreVolume
* method. The rollback method for this step is this no-op. If the
* BlockDeviceController restoreVolume step fails, this rollback
* method is invoked, which simply says the rollback for the step
* was successful. This in turn allows the other steps in the workflow
* rollback.
*
* @param stepId
* The id of the step being rolled back.
*
* @throws WorkflowException
*/
public void rollbackMethodNull(String stepId) throws WorkflowException {
WorkflowStepCompleter.stepSucceded(stepId);
}
/**
* Fail the task
*
* @param clazz
* @param id
* @param opId
* @param msg
*/
@Deprecated
private void doFailTask(Class<? extends DataObject> clazz, URI id, String opId, String msg) {
try {
_dbClient.updateTaskOpStatus(clazz, id, opId, new Operation(Operation.Status.error.name(), msg));
} catch (DatabaseException ioe) {
_log.error(ioe.getMessage());
}
}
/**
* Set the status of operation to 'ready'
*
* @param clazz
* The data object class.
* @param ids
* The ids of the data objects for which the task completed.
* @param opId
* The task id.
*/
private void doSuccessTask(
Class<? extends DataObject> clazz, List<URI> ids, String opId) {
try {
for (URI id : ids) {
_dbClient.ready(clazz, id, opId);
}
} catch (DatabaseException ioe) {
_log.error(ioe.getMessage());
}
}
/**
* Fail the task. Called when an exception occurs attempting to
* execute a task on multiple data objects.
*
* @param clazz
* The data object class.
* @param ids
* The ids of the data objects for which the task failed.
* @param opId
* The task id.
* @param serviceCoded
* Original exception.
*/
private void doFailTask(
Class<? extends DataObject> clazz, List<URI> ids, String opId, ServiceCoded serviceCoded) {
try {
for (URI id : ids) {
_dbClient.error(clazz, id, opId, serviceCoded);
}
} catch (DatabaseException ioe) {
_log.error(ioe.getMessage());
}
}
/**
* Fail the task. Called when an exception occurs attempting to
* execute a task.
*
* @param clazz
* The data object class.
* @param id
* The id of the data object for which the task failed.
* @param opId
* The task id.
* @param serviceCoded
* Original exception.
*/
private void doFailTask(
Class<? extends DataObject> clazz, URI id, String opId, ServiceCoded serviceCoded) {
List<URI> ids = new ArrayList<URI>();
ids.add(id);
doFailTask(clazz, ids, opId, serviceCoded);
}
static final String CREATE_VOLUMES_STEP_GROUP = "BlockDeviceCreateVolumes";
static final String MODIFY_VOLUMES_STEP_GROUP = "BlockDeviceModifyVolumes";
static final String CREATE_MIRRORS_STEP_GROUP = "BlockDeviceCreateMirrors";
static final String CREATE_CONSISTENCY_GROUP_STEP_GROUP = "BlockDeviceCreateGroup";
static final String UPDATE_CONSISTENCY_GROUP_STEP_GROUP = "BlockDeviceUpdateGroup";
static final String CREATE_SNAPSHOTS_STEP_GROUP = "BlockDeviceCreateSnapshots";
/**
* {@inheritDoc}
*/
@Override
public String addStepsForCreateVolumes(Workflow workflow, String waitFor,
List<VolumeDescriptor> origVolumes, String taskId) throws ControllerException {
// Get the list of descriptors the BlockDeviceController needs to create volumes for.
List<VolumeDescriptor> volumeDescriptors = VolumeDescriptor.filterByType(origVolumes,
new VolumeDescriptor.Type[] {
VolumeDescriptor.Type.BLOCK_DATA,
VolumeDescriptor.Type.RP_SOURCE,
VolumeDescriptor.Type.RP_JOURNAL,
VolumeDescriptor.Type.RP_TARGET,
VolumeDescriptor.Type.SRDF_SOURCE,
VolumeDescriptor.Type.SRDF_TARGET
}, null);
// If no volumes to create, just return
if (volumeDescriptors.isEmpty()) {
return waitFor;
}
// Segregate by pool to list of volumes.
Map<URI, Map<Long, List<VolumeDescriptor>>> poolMap = VolumeDescriptor.getPoolSizeMap(volumeDescriptors);
// Add a Step to create the consistency group if needed
waitFor = addStepsForCreateConsistencyGroup(workflow, waitFor, volumeDescriptors, CREATE_CONSISTENCY_GROUP_STEP_GROUP);
waitFor = addStepsForReplicaRollbackCleanup(workflow, waitFor, volumeDescriptors);
// Add a Step for each Pool in each Device.
// For meta volumes add Step for each meta volume, except vmax thin meta volumes.
for (URI poolURI : poolMap.keySet()) {
for (Long volumeSize : poolMap.get(poolURI).keySet()) {
List<VolumeDescriptor> descriptors = poolMap.get(poolURI).get(volumeSize);
List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(descriptors);
VolumeDescriptor first = descriptors.get(0);
URI deviceURI = first.getDeviceURI();
VirtualPoolCapabilityValuesWrapper capabilities = first.getCapabilitiesValues();
// Check if volumes have to be created as meta volumes
_log.debug(String.format("Capabilities : isMeta: %s, Meta Type: %s, Member size: %s, Count: %s",
capabilities.getIsMetaVolume(), capabilities.getMetaVolumeType(), capabilities.getMetaVolumeMemberSize(),
capabilities.getMetaVolumeMemberCount()));
Volume volume = _dbClient.queryObject(Volume.class, first.getVolumeURI());
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, volume.getStorageController());
boolean createAsMetaVolume = capabilities.getIsMetaVolume()
|| MetaVolumeUtils.createAsMetaVolume(first.getVolumeURI(), _dbClient, capabilities);
if (storageSystem.checkIfVmax3()) {
createAsMetaVolume = false; // VMAX3 does not support META and we will get here due to change VPool
// scenario
}
if (createAsMetaVolume) {
// For vmax thin meta volumes we can create multiple meta volumes in one smis request
if (volume.getThinlyProvisioned() && storageSystem.getSystemType().equals(StorageSystem.Type.vmax.toString())) {
workflow.createStep(CREATE_VOLUMES_STEP_GROUP,
String.format("Creating meta volumes:%n%s", getVolumesMsg(_dbClient, volumeURIs)),
waitFor, deviceURI, getDeviceType(deviceURI),
this.getClass(),
createMetaVolumesMethod(deviceURI, poolURI, volumeURIs, capabilities),
rollbackCreateMetaVolumesMethod(deviceURI, volumeURIs), null);
} else {
// Add workflow step for each meta volume
for (URI metaVolumeURI : volumeURIs) {
List<URI> metaVolumeURIs = new ArrayList<URI>();
metaVolumeURIs.add(metaVolumeURI);
String stepId = workflow.createStepId();
workflow.createStep(CREATE_VOLUMES_STEP_GROUP,
String.format("Creating meta volume:%n%s", getVolumesMsg(_dbClient, metaVolumeURIs)),
waitFor, deviceURI, getDeviceType(deviceURI),
this.getClass(),
createMetaVolumeMethod(deviceURI, poolURI, metaVolumeURI, capabilities),
rollbackCreateMetaVolumeMethod(deviceURI, metaVolumeURI, stepId), stepId);
}
}
} else {
workflow.createStep(CREATE_VOLUMES_STEP_GROUP,
String.format("Creating volumes:%n%s", getVolumesMsg(_dbClient, volumeURIs)),
waitFor, deviceURI, getDeviceType(deviceURI),
this.getClass(),
createVolumesMethod(deviceURI, poolURI, volumeURIs, capabilities),
rollbackCreateVolumesMethod(deviceURI, volumeURIs), null);
}
// Following workflow step is only applicable to HDS Thin Volume modification.
if (getDeviceType(deviceURI).equalsIgnoreCase(Type.hds.name())) {
boolean modifyHitachiVolumeToApplyTieringPolicy = HDSUtils.isVolumeModifyApplicable(
first.getVolumeURI(), _dbClient);
if (modifyHitachiVolumeToApplyTieringPolicy) {
workflow.createStep(MODIFY_VOLUMES_STEP_GROUP,
String.format("Modifying volumes:%n%s", getVolumesMsg(_dbClient, volumeURIs)),
CREATE_VOLUMES_STEP_GROUP, deviceURI, getDeviceType(deviceURI), this.getClass(),
moidfyVolumesMethod(deviceURI, poolURI, volumeURIs),
rollbackCreateVolumesMethod(deviceURI, volumeURIs), null);
}
}
}
}
waitFor = CREATE_VOLUMES_STEP_GROUP;
return waitFor;
}
private String addStepsForReplicaRollbackCleanup(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors) {
List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(volumeDescriptors);
VolumeDescriptor volumeDescriptor = volumeDescriptors.get(0);
URI deviceURI = volumeDescriptor.getDeviceURI();
Workflow.Method cleanupReplicasMethod = new Workflow.Method(ROLLBACK_CLEANUP_REPLICAS_METHOD_NAME, deviceURI, volumeURIs);
waitFor = workflow.createStep(ROLLBACK_CLEANUP_REPLICAS_STEP_GROUP, ROLLBACK_CLEANUP_REPLICAS_STEP_DESC,
waitFor, deviceURI, getDeviceType(deviceURI), this.getClass(),
rollbackMethodNullMethod(), cleanupReplicasMethod, null);
return waitFor;
}
public boolean rollbackCleanupReplicas(URI systemURI, List<URI> volumeURIs, String opId) {
WorkflowStepCompleter.stepExecuting(opId);
try {
_log.info("Cleaning up replicas for {} volumes", volumeURIs.size());
Set<URI> uniqueVolumes = new HashSet<>(volumeURIs);
ReplicaCleanupContext replicaCleanupContext = ReplicaCleanupFactory.getContext(_dbClient);
replicaCleanupContext.execute(uniqueVolumes);
} catch (Exception e) {
_log.warn("Caught exception whilst rolling back replica cleanup.", e);
} finally {
WorkflowStepCompleter.stepSucceded(opId);
}
return true;
}
/**
* Add Steps to create any BLOCK_MIRRORs specified in the VolumeDescriptor list.
*
* @param workflow
* -- The Workflow being built
* @param waitFor
* -- Previous steps to waitFor
* @param volumes
* -- List<VolumeDescriptors> -- volumes of all types to be processed
* @return last step added to waitFor
* @throws ControllerException
*/
public String addStepsForCreateMirrors(Workflow workflow, String waitFor,
URI storage, URI sourceVolume, List<URI> mirrorList, boolean isCG) throws ControllerException {
String stepId = waitFor;
if (!isCG) {
for (URI mirror : mirrorList) {
stepId = workflow.createStep(CREATE_MIRRORS_STEP_GROUP,
String.format("Creating mirror for %s", sourceVolume), waitFor,
storage, getDeviceType(storage),
this.getClass(),
createMirrorMethod(storage, asList(mirror), isCG, false),
rollbackMirrorMethod(storage, asList(mirror)), null);
}
} else {
stepId = workflow.createStep(CREATE_MIRRORS_STEP_GROUP,
String.format("Creating mirror for %s", sourceVolume), waitFor,
storage, getDeviceType(storage),
this.getClass(),
createMirrorMethod(storage, mirrorList, isCG, false),
rollbackMirrorMethod(storage, mirrorList), null);
}
return stepId;
}
/**
* Add Steps to create the required consistency group
*
* @param workflow
* -- The Workflow being built
* @param waitFor
* -- Previous steps to waitFor
* @param volumesDescriptors
* -- List<VolumeDescriptors> -- volumes of all types to be processed
* @return last step added to waitFor
* @throws ControllerException
*/
public String addStepsForCreateConsistencyGroup(Workflow workflow, String waitFor,
List<VolumeDescriptor> volumesDescriptors, String stepGroup) throws ControllerException {
// Filter any BLOCK_DATAs that need to be created.
List<VolumeDescriptor> volumes = VolumeDescriptor.filterByType(volumesDescriptors,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA },
new VolumeDescriptor.Type[] {});
// If no volumes to be created, just return.
if (volumes.isEmpty()) {
return waitFor;
}
// Get the consistency group. If no consistency group to be created,
// just return. Get CG from any descriptor.
final VolumeDescriptor firstVolume = volumes.get(0);
if (firstVolume == null || NullColumnValueGetter.isNullURI(firstVolume.getConsistencyGroupURI())) {
return waitFor;
}
final URI consistencyGroupURI = firstVolume.getConsistencyGroupURI();
final BlockConsistencyGroup consistencyGroup = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroupURI);
if (firstVolume.getType() != null) {
if (VolumeDescriptor.Type.SRDF_SOURCE.toString().equalsIgnoreCase(firstVolume.getType().toString())
|| VolumeDescriptor.Type.SRDF_TARGET.toString().equalsIgnoreCase(firstVolume.getType().toString())
|| VolumeDescriptor.Type.SRDF_EXISTING_SOURCE.toString().equalsIgnoreCase(firstVolume.getType().toString())) {
return waitFor;
}
}
// Create the CG on each system it has yet to be created on.
Map<URI, Set<String>> deviceURIs = new HashMap<URI, Set<String>>();
for (VolumeDescriptor descr : volumes) {
// If the descriptor's associated volume is the backing volume for a RP+VPlex
// journal/target volume, we want to ignore its storage system. We do not want to
// create backing array consistency groups for RP+VPlex target volumes. Only
// source volume.
// We would create backend CG only if the volume's replicationGroupInstance is set when it is prepared in
// apisvc level.
Volume volume = _dbClient.queryObject(Volume.class, descr.getVolumeURI());
String rgName = volume.getReplicationGroupInstance();
if (NullColumnValueGetter.isNotNullValue(rgName)) {
URI deviceURI = descr.getDeviceURI();
_log.info(String.format("If it doesn't already exist, creating backend CG [%s] on device (%s) for volume [%s](%s).",
rgName, deviceURI, volume.getLabel(), volume.getId()));
Set<String> rgNames = deviceURIs.get(deviceURI);
if (rgNames == null) {
rgNames = new HashSet<String>();
}
rgNames.add(rgName);
deviceURIs.put(deviceURI, rgNames);
}
}
boolean createdCg = false;
for (Map.Entry<URI, Set<String>> entry : deviceURIs.entrySet()) {
URI deviceURI = entry.getKey();
Set<String> rgNames = entry.getValue();
for (String rgName : rgNames) {
// If the consistency group has already been created in the array, just return
if (!consistencyGroup.created(deviceURI, rgName)) {
// Create step to create consistency group
waitFor = workflow.createStep(stepGroup,
String.format("Creating consistency group %s", consistencyGroupURI), waitFor,
deviceURI, getDeviceType(deviceURI),
this.getClass(),
createConsistencyGroupMethod(deviceURI, consistencyGroupURI, rgName),
deleteConsistencyGroupMethod(deviceURI, consistencyGroupURI, rgName, false, false, true), null);
createdCg = true;
_log.info(String.format("Step created for creating CG [%s] on device [%s]", consistencyGroup.getLabel(), deviceURI));
}
}
}
if (createdCg) {
waitFor = stepGroup;
}
return waitFor;
}
/**
* Add Steps to add or remove volumes to the required consistency group
*
* @param workflow
* -- The Workflow being built
* @param waitFor
* -- Previous steps to waitFor
* @param volumesDescriptorsToAdd
* -- List<VolumeDescriptors> -- volumes of all types to be processed for adding to CG
* @param volumesDescriptorsToRemove
* -- List<VolumeDescriptors> -- volumes of all types to be processed for removing from CG
* @return last step added to waitFor
* @throws ControllerException
*/
public String addStepsForUpdateConsistencyGroup(Workflow workflow, String waitFor,
List<VolumeDescriptor> volumesDescriptorsToAdd, List<VolumeDescriptor> volumesDescriptorsToRemove) throws ControllerException {
// Filter any BLOCK_DATAs that need to be added to CG.
List<VolumeDescriptor> addDescriptors = VolumeDescriptor.filterByType(volumesDescriptorsToAdd,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA },
new VolumeDescriptor.Type[] {});
// Filter any BLOCK_DATAs that need to be removed from CG.
List<VolumeDescriptor> removeDescriptors = VolumeDescriptor.filterByType(volumesDescriptorsToRemove,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA },
new VolumeDescriptor.Type[] {});
// We need at least one volume to check, so either get it from
// the add descriptors or the delete descriptors.
VolumeDescriptor firstVolume = null;
if (!addDescriptors.isEmpty()) {
firstVolume = addDescriptors.get(0);
} else if (!removeDescriptors.isEmpty()) {
firstVolume = removeDescriptors.get(0);
} else {
_log.warn("No volumes to add or remove from CG, skip step.");
// No volumes to be added or removed, just return.
return waitFor;
}
if (NullColumnValueGetter.isNullURI(firstVolume.getConsistencyGroupURI())) {
_log.warn(String.format("Volume (%s) has a null CG reference, skip step.", firstVolume.getVolumeURI()));
return waitFor;
}
// Check for SRDF
if (firstVolume.getType() != null) {
if (VolumeDescriptor.Type.SRDF_SOURCE.toString().equalsIgnoreCase(firstVolume.getType().toString())
|| VolumeDescriptor.Type.SRDF_TARGET.toString().equalsIgnoreCase(firstVolume.getType().toString())
|| VolumeDescriptor.Type.SRDF_EXISTING_SOURCE.toString().equalsIgnoreCase(firstVolume.getType().toString())) {
_log.warn(String.format("Volume (%s) is of type SRDF, skip step.", firstVolume.getVolumeURI()));
return waitFor;
}
}
// We want the map to contain both the volumes to ADD and REMOVE segregated by device and also by CG.
// The map will look like the below:
// Device URI
// --> CG URI
// ----> ADD -> List of Volumes to Add from this CG for this device
// ----> REMOVE -> List of Volumes to Remove from this CG for this device
Map<URI, Map<URI, Map<String, List<URI>>>> deviceToCGMap = createDeviceToCGMapFromDescriptors(addDescriptors, removeDescriptors);
// Distill the steps down to Device -> CG -> ADD and REMOVE volumes
for (Map.Entry<URI, Map<URI, Map<String, List<URI>>>> deviceEntry : deviceToCGMap.entrySet()) {
URI deviceURI = deviceEntry.getKey();
Map<URI, Map<String, List<URI>>> volumesToUpdateByCG = deviceEntry.getValue();
for (Map.Entry<URI, Map<String, List<URI>>> cgEntry : volumesToUpdateByCG.entrySet()) {
URI consistencyGroupURI = cgEntry.getKey();
List<URI> volumesToAdd = cgEntry.getValue().get(ADD_VOLUMES_TO_CG_KEY);
List<URI> volumesToRemove = cgEntry.getValue().get(REMOVE_VOLUMES_FROM_CG_KEY);
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Updating consistency group %s", consistencyGroupURI), waitFor,
deviceURI, getDeviceType(deviceURI),
this.getClass(),
new Workflow.Method("updateConsistencyGroup", deviceURI, consistencyGroupURI, volumesToAdd, volumesToRemove),
rollbackMethodNullMethod(), null);
if (volumesToAdd != null) {
_log.info(String.format("Step created for adding volumes [%s] to CG [%s] on device [%s]",
Joiner.on("\t").join(volumesToAdd),
consistencyGroupURI,
deviceURI));
}
if (volumesToRemove != null) {
_log.info(String.format("Step created for removing volumes [%s] from CG [%s] on device [%s]",
Joiner.on("\t").join(volumesToRemove),
consistencyGroupURI,
deviceURI));
}
}
}
return waitFor;
}
/**
* Convenience method to create a map Device to CG to Volume to ADD and REMOVE.
*
* We want the map to contain both the volumes to ADD and REMOVE segregated by device and also by CG.
* The map will look like the below:
* Device URI
* --> CG URI
* ----> ADD -> List of Volumes to Add from this CG for this device
* ----> REMOVE -> List of Volumes to Remove from this CG for this device
*
* @param addDescriptors
* BLOCK_DATA descriptors of volumes to add to CG
* @param removeDescriptors
* BLOCK_DATA descriptors of volumes to remove from CG
* @return populated map
*/
private Map<URI, Map<URI, Map<String, List<URI>>>> createDeviceToCGMapFromDescriptors(List<VolumeDescriptor> addDescriptors,
List<VolumeDescriptor> removeDescriptors) {
Map<URI, Map<URI, Map<String, List<URI>>>> deviceToCGMap = new HashMap<URI, Map<URI, Map<String, List<URI>>>>();
// Volumes to add
for (VolumeDescriptor descr : addDescriptors) {
// Segregated by device
URI deviceURI = descr.getDeviceURI();
Map<URI, Map<String, List<URI>>> volumesToUpdateByCG = deviceToCGMap.get(deviceURI);
if (volumesToUpdateByCG == null) {
volumesToUpdateByCG = new HashMap<URI, Map<String, List<URI>>>();
deviceToCGMap.put(deviceURI, volumesToUpdateByCG);
}
// Segregated by CG
URI consistencyGroupURI = descr.getConsistencyGroupURI();
Map<String, List<URI>> volumesToUpdate = volumesToUpdateByCG.get(consistencyGroupURI);
if (volumesToUpdate == null) {
volumesToUpdate = new HashMap<String, List<URI>>();
volumesToUpdateByCG.put(consistencyGroupURI, volumesToUpdate);
}
// Segregated by volumes to ADD
List<URI> volumesToAdd = volumesToUpdate.get(ADD_VOLUMES_TO_CG_KEY);
if (volumesToAdd == null) {
volumesToAdd = new ArrayList<URI>();
volumesToUpdate.put(ADD_VOLUMES_TO_CG_KEY, volumesToAdd);
}
volumesToAdd.add(descr.getVolumeURI());
}
// Volumes to remove
for (VolumeDescriptor descr : removeDescriptors) {
// Segregated by device
URI deviceURI = descr.getDeviceURI();
Map<URI, Map<String, List<URI>>> volumesToUpdateByCG = deviceToCGMap.get(deviceURI);
if (volumesToUpdateByCG == null) {
volumesToUpdateByCG = new HashMap<URI, Map<String, List<URI>>>();
deviceToCGMap.put(deviceURI, volumesToUpdateByCG);
}
// Segregated by CG
URI consistencyGroupURI = descr.getConsistencyGroupURI();
Map<String, List<URI>> volumesToUpdate = volumesToUpdateByCG.get(consistencyGroupURI);
if (volumesToUpdate == null) {
volumesToUpdate = new HashMap<String, List<URI>>();
volumesToUpdateByCG.put(consistencyGroupURI, volumesToUpdate);
}
// Segregated by volumes to REMOVE
List<URI> volumesToRemove = volumesToUpdate.get(REMOVE_VOLUMES_FROM_CG_KEY);
if (volumesToRemove == null) {
volumesToRemove = new ArrayList<URI>();
volumesToUpdate.put(REMOVE_VOLUMES_FROM_CG_KEY, volumesToRemove);
}
volumesToRemove.add(descr.getVolumeURI());
}
return deviceToCGMap;
}
/**
* Returns a message containing information about each volume.
*
* @param volumeURIs
* @return
*/
static public String getVolumesMsg(DbClient dbClient, List<URI> volumeURIs) {
StringBuilder builder = new StringBuilder();
for (URI uri : volumeURIs) {
BlockObject obj = BlockObject.fetch(dbClient, uri);
if (obj == null) {
continue;
}
builder.append("Volume: " + obj.getLabel() + " (" + obj.getId() + ")");
if (obj.getWWN() != null) {
builder.append(" wwn: " + obj.getWWN());
}
if (obj.getNativeId() != null) {
builder.append(" native id: " + obj.getNativeId());
}
builder.append("\n");
}
return builder.toString();
}
/**
* Return a Workflow.Method for createVolumes.
*
* @param systemURI
* @param poolURI
* @param volumeURIs
* @param capabilities
* @return Workflow.Method
*/
private Workflow.Method moidfyVolumesMethod(URI systemURI, URI poolURI, List<URI> volumeURIs) {
return new Workflow.Method("modifyVolumes", systemURI, poolURI, volumeURIs);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method modifyVolumesMethod just above (except
* opId).
* Currently this workflow step is used only for Hitachi Thin Volumes modification to update volume tieringPolicy.
* Hitachi allows setting of tieringpolicy at LDEV level, hence We should have a LDEV id of a LogicalUnit.
* But LDEV is only created after we LogicalUnit is created. Hence createVolumes workflow includes creation of LU
* (i.e. LDEV)
* And LDEV modification (to set tieringPolicy.)
*
*/
@Override
public void modifyVolumes(URI systemURI, URI poolURI, List<URI> volumeURIs, String opId) throws ControllerException {
List<Volume> volumes = new ArrayList<Volume>();
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
systemURI);
List<VolumeTaskCompleter> volumeCompleters = new ArrayList<VolumeTaskCompleter>();
Iterator<URI> volumeURIsIter = volumeURIs.iterator();
StringBuilder logMsgBuilder = new StringBuilder(String.format(
"moidfyVolumes start - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
while (volumeURIsIter.hasNext()) {
URI volumeURI = volumeURIsIter.next();
logMsgBuilder.append(String.format("%nVolume:%s", volumeURI.toString()));
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
volumes.add(volume);
VolumeUpdateCompleter volumeCompleter = new VolumeUpdateCompleter(
volumeURI, opId);
volumeCompleters.add(volumeCompleter);
}
_log.info(logMsgBuilder.toString());
StoragePool storagePool = _dbClient.queryObject(StoragePool.class, poolURI);
MultiVolumeTaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs, volumeCompleters, opId);
Volume volume = volumes.get(0);
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
WorkflowStepCompleter.stepExecuting(completer.getOpId());
getDevice(storageSystem.getSystemType()).doModifyVolumes(storageSystem,
storagePool, opId, volumes, completer);
logMsgBuilder = new StringBuilder(String.format(
"modifyVolumes end - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
volumeURIsIter = volumeURIs.iterator();
while (volumeURIsIter.hasNext()) {
logMsgBuilder.append(String.format("%nVolume:%s", volumeURIsIter.next()
.toString()));
}
_log.info(logMsgBuilder.toString());
} catch (InternalException e) {
_log.error(String.format("modifyVolumes Failed - Array: %s Pool:%s Volume:%s",
systemURI.toString(), poolURI.toString(), Joiner.on("\t").join(volumeURIs)));
doFailTask(Volume.class, volumeURIs, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
} catch (Exception e) {
_log.error(String.format("modifyVolumes Failed - Array: %s Pool:%s Volume:%s",
systemURI.toString(), poolURI.toString(), Joiner.on("\t").join(volumeURIs)));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURIs, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
/**
* Return a Workflow.Method for createVolumes.
*
* @param systemURI
* @param poolURI
* @param volumeURIs
* @param capabilities
* @return Workflow.Method
*/
private Workflow.Method createVolumesMethod(URI systemURI, URI poolURI, List<URI> volumeURIs,
VirtualPoolCapabilityValuesWrapper capabilities) {
return new Workflow.Method("createVolumes", systemURI, poolURI, volumeURIs, capabilities);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method createVolumesMethod just above (except
* opId).
*/
@Override
public void createVolumes(URI systemURI, URI poolURI, List<URI> volumeURIs,
VirtualPoolCapabilityValuesWrapper capabilities, String opId) throws ControllerException {
List<Volume> volumes = new ArrayList<Volume>();
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
systemURI);
List<VolumeTaskCompleter> volumeCompleters = new ArrayList<VolumeTaskCompleter>();
Iterator<URI> volumeURIsIter = volumeURIs.iterator();
StringBuilder logMsgBuilder = new StringBuilder(String.format(
"createVolumes start - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
while (volumeURIsIter.hasNext()) {
URI volumeURI = volumeURIsIter.next();
logMsgBuilder.append(String.format("%nVolume:%s", volumeURI.toString()));
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
volumes.add(volume);
VolumeCreateCompleter volumeCompleter = new VolumeCreateCompleter(
volumeURI, opId);
volumeCompleters.add(volumeCompleter);
}
_log.info(logMsgBuilder.toString());
StoragePool storagePool = _dbClient.queryObject(StoragePool.class, poolURI);
MultiVolumeTaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs, volumeCompleters, opId);
Volume volume = volumes.get(0);
WorkflowStepCompleter.stepExecuting(completer.getOpId());
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_005);
getDevice(storageSystem.getSystemType()).doCreateVolumes(storageSystem,
storagePool, opId, volumes, capabilities, completer);
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_006);
logMsgBuilder = new StringBuilder(String.format(
"createVolumes end - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
volumeURIsIter = volumeURIs.iterator();
while (volumeURIsIter.hasNext()) {
logMsgBuilder.append(String.format("%nVolume:%s", volumeURIsIter.next()
.toString()));
}
_log.info(logMsgBuilder.toString());
} catch (Exception e) {
_log.error(String.format("createVolume Failed - Array: %s Pool:%s Volume:%s",
systemURI.toString(), poolURI.toString(), Joiner.on("\t").join(volumeURIs)), e);
Workflow workflow = WorkflowService.getInstance().getWorkflowFromStepId(opId);
if (workflow != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURIs, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
_dbClient.markForDeletion(volumes);
} else {
_log.info("Workflow is null which means that the workflow has already completed. Not performing any error handling");
}
}
}
/**
* Return a Workflow.Method for rollbackCreateVolumes
*
* @param systemURI
* @param volumeURIs
* @return Workflow.Method
*/
public static Workflow.Method rollbackCreateVolumesMethod(URI systemURI, List<URI> volumeURIs) {
return new Workflow.Method("rollBackCreateVolumes", systemURI, volumeURIs);
}
/**
* {@inheritDoc} NOTE: The signature here MUST match the Workflow.Method rollbackCreateVolumesMethod just above
* (except opId).
*/
@Override
public void rollBackCreateVolumes(URI systemURI, List<URI> volumeURIs, String opId) throws ControllerException {
MultiVolumeTaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs, opId);
List<Volume> volumes = new ArrayList<>(volumeURIs.size());
completer.setRollingBack(true);
try {
String logMsg = String.format(
"rollbackCreateVolume start - Array:%s, Volume:%s", systemURI.toString(), Joiner.on(',').join(volumeURIs));
_log.info(logMsg.toString());
WorkflowStepCompleter.stepExecuting(opId);
volumes.addAll(_dbClient.queryObject(Volume.class, volumeURIs));
for (Volume volume : volumes) {
// CTRL-5597 clean volumes which have failed only in a multi-volume request
if (null != volume.getNativeGuid()) {
StorageSystem system = _dbClient.queryObject(StorageSystem.class,
volume.getStorageController());
if (Type.xtremio.toString().equalsIgnoreCase(system.getSystemType())) {
continue;
}
}
// clearing targets explicitly, during vpool change if target volume creation failed for same reason,
// then we need to clear srdfTargets field for source
if (null != volume.getSrdfTargets()) {
_log.info("Clearing targets for existing source");
volume.getSrdfTargets().clear();
_dbClient.updateObject(volume);
}
// for change Virtual Pool, if failed, clear targets for source
if (!NullColumnValueGetter.isNullNamedURI(volume.getSrdfParent())) {
URI sourceUri = volume.getSrdfParent().getURI();
Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceUri);
if (null != sourceVolume.getSrdfTargets()) {
sourceVolume.getSrdfTargets().clear();
_dbClient.updateObject(sourceVolume);
}
// Clearing target CG
URI cgUri = volume.getConsistencyGroup();
if (null != cgUri) {
BlockConsistencyGroup targetCG = _dbClient.queryObject(
BlockConsistencyGroup.class, cgUri);
if (null != targetCG && (null == targetCG.getTypes()
|| NullColumnValueGetter.isNullURI(targetCG.getStorageController()))) {
_log.info("Set target CG {} inactive", targetCG.getLabel());
targetCG.setInactive(true);
_dbClient.updateObject(targetCG);
}
// clear association between target volume and target cg
volume.setConsistencyGroup(NullColumnValueGetter.getNullURI());
_dbClient.updateAndReindexObject(volume);
}
}
// Check for loose export groups associated with this rolled-back volume
URIQueryResultList exportGroupURIs = new URIQueryResultList();
_dbClient.queryByConstraint(ContainmentConstraint.Factory.getVolumeExportGroupConstraint(volume.getId()), exportGroupURIs);
while (exportGroupURIs.iterator().hasNext()) {
URI exportGroupURI = exportGroupURIs.iterator().next();
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupURI);
if (!exportGroup.getInactive()) {
exportGroup.removeVolume(volume.getId());
boolean canRemoveGroup = false;
List<ExportMask> exportMasks = ExportMaskUtils.getExportMasks(_dbClient, exportGroup);
// Make sure the volume is not in an export mask
for (ExportMask exportMask : exportMasks) {
exportMask.removeVolume(volume.getId());
exportMask.removeFromUserCreatedVolumes(volume);
exportMask.removeFromExistingVolumes(volume);
if (!exportMask.getCreatedBySystem() && !exportMask.hasAnyVolumes() && exportMask.emptyVolumes()) {
canRemoveGroup = true;
_dbClient.removeObject(exportMask);
} else {
_dbClient.updateObject(exportMask);
}
}
// If we didn't find that volume in a mask, it's OK to remove it.
if (canRemoveGroup && exportMasks.size() == 1 && exportGroup.getVolumes().isEmpty()) {
_dbClient.removeObject(exportGroup);
} else {
_dbClient.updateObject(exportGroup);
}
}
}
}
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_013);
deleteVolumesWithCompleter(systemURI, volumeURIs, completer);
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_014);
logMsg = String.format(
"rollbackCreateVolume end - Array:%s, Volume:%s", systemURI.toString(), Joiner.on(',').join(volumeURIs));
_log.info(logMsg.toString());
} catch (Exception e) {
_log.error(String.format("rollbackCreateVolume Failed - Array:%s, Volume:%s", systemURI.toString(),
Joiner.on(',').join(volumeURIs)));
handleException(e, completer);
}
}
/**
* Private exception handling method to reduce code repetition.
*
* @param e
* exception
* @param taskCompleter
* completer to notify
*/
private void handleException(Exception e, TaskCompleter taskCompleter) {
_log.error("Handling exception with task completer: {}", taskCompleter, e);
if (taskCompleter != null && (taskCompleter.isCompleted() || taskCompleter.isAsynchronous())) {
_log.warn("Task has been marked as either asynchronous or completed. Not performing any error handling.");
return;
}
if (e instanceof InternalException) {
InternalException ie = (InternalException) e;
doFailTask(Volume.class, taskCompleter.getIds(), taskCompleter.getOpId(), ie);
WorkflowStepCompleter.stepFailed(taskCompleter.getOpId(), ie);
} else {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, taskCompleter.getIds(), taskCompleter.getOpId(), serviceError);
WorkflowStepCompleter.stepFailed(taskCompleter.getOpId(),
DeviceControllerException.exceptions.unexpectedCondition(e.getMessage()));
}
List<Volume> volumes = _dbClient.queryObject(Volume.class, taskCompleter.getIds());
_dbClient.markForDeletion(volumes);
}
/**
* Return a Workflow.Method for createMetaVolumes.
*
* @param systemURI
* @param poolURI
* @param volumeURIs
* @param capabilities
* @return Workflow.Method
*/
private Workflow.Method createMetaVolumesMethod(URI systemURI, URI poolURI, List<URI> volumeURIs,
VirtualPoolCapabilityValuesWrapper capabilities) {
return new Workflow.Method("createMetaVolumes", systemURI, poolURI, volumeURIs, capabilities);
}
/**
* Return a Workflow.Method for rollbackCreateMetaVolumes.
*
* @param systemURI
* @param volumeURIs
* @return Workflow.Method
*/
public static Workflow.Method rollbackCreateMetaVolumesMethod(URI systemURI, List<URI> volumeURIs) {
return rollbackCreateVolumesMethod(systemURI, volumeURIs);
}
/**
* Return a Workflow.Method for createVolumes.
*
* @param systemURI
* @param poolURI
* @param volumeURI
* @param capabilities
* @return Workflow.Method
*/
private Workflow.Method createMetaVolumeMethod(URI systemURI, URI poolURI, URI volumeURI,
VirtualPoolCapabilityValuesWrapper capabilities) {
return new Workflow.Method("createMetaVolume", systemURI, poolURI, volumeURI, capabilities);
}
/**
* Return a Workflow.Method for rollbackCreateMetaVolume.
*
* @param systemURI
* @param volumeURI
* @return Workflow.Method
*/
public static Workflow.Method rollbackCreateMetaVolumeMethod(URI systemURI, URI volumeURI, String createMetaVolumeStepId) {
return new Workflow.Method("rollBackCreateMetaVolume", systemURI, volumeURI, createMetaVolumeStepId);
}
/**
* {@inheritDoc} NOTE: The signature here MUST match the Workflow.Method rollbackCreateMetaVolumeMethod just above
* (except opId).
*/
@Override
public void rollBackCreateMetaVolume(URI systemURI, URI volumeURI, String createStepId, String opId) throws ControllerException {
try {
String logMsg = String.format(
"rollbackCreateMetaVolume start - Array:%s, Volume:%s", systemURI.toString(), volumeURI.toString());
_log.info(logMsg.toString());
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, systemURI);
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
CleanupMetaVolumeMembersCompleter cleanupCompleter = null;
WorkflowStepCompleter.stepExecuting(opId);
// Check if we need to cleanup dangling meta members volumes on array.
// Meta members are temporary array volumes. They only exist until they are added to a meta volume.
// We store these volumes in WF create step data.
List<String> metaMembers = (ArrayList<String>) _workflowService.loadStepData(createStepId);
if (metaMembers != null && !metaMembers.isEmpty()) {
boolean isWFStep = false;
cleanupCompleter = new CleanupMetaVolumeMembersCompleter(volumeURI, isWFStep, createStepId, opId);
getDevice(storageSystem.getSystemType()).doCleanupMetaMembers(storageSystem, volume, cleanupCompleter);
}
// TEMPER Used for negative testing.
// Comment out call to doCleanupMetaMembers above
// cleanupCompleter.setSuccess(false);
// // TEMPER
// Delete meta volume.
// Delete only if meta members cleanup was successful (in case it was executed).
if (cleanupCompleter == null || cleanupCompleter.isSuccess()) {
List<URI> volumeURIs = new ArrayList<URI>();
volumeURIs.add(volumeURI);
deleteVolumeStep(systemURI, volumeURIs, opId);
} else {
ServiceError serviceError;
if (cleanupCompleter.getError() != null) {
serviceError = cleanupCompleter.getError();
} else {
serviceError = DeviceControllerException.errors.jobFailedOp("CleanupMetaVolumeMembers");
}
doFailTask(Volume.class, volumeURI, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
logMsg = String.format(
"rollbackCreateMetaVolume end - Array:%s, Volume:%s", systemURI.toString(), volumeURI.toString());
_log.info(logMsg.toString());
} catch (InternalException e) {
_log.error(String.format("rollbackCreateMetaVolume Failed - Array:%s, Volume:%s", systemURI.toString(),
volumeURI.toString()));
doFailTask(Volume.class, volumeURI, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
} catch (Exception e) {
_log.error(String.format("rollbackCreateMetaVolume Failed - Array:%s, Volume:%s", systemURI.toString(),
volumeURI.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURI, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
/**
* Return a Workflow.Method for rollbackExpandVolume.
*
* @param systemURI
* @param volumeURI
* @return Workflow.Method
*/
public static Workflow.Method rollbackExpandVolumeMethod(URI systemURI, URI volumeURI, String expandStepId) {
return new Workflow.Method("rollBackExpandVolume", systemURI, volumeURI, expandStepId);
}
/**
* {@inheritDoc} NOTE: The signature here MUST match the Workflow.Method rollbackExpandVolume just above (except
* opId).
*/
@Override
public void rollBackExpandVolume(URI systemURI, URI volumeURI, String expandStepId, String opId) throws ControllerException {
try {
StringBuilder logMsgBuilder = new StringBuilder(String.format(
"rollbackExpandVolume start - Array:%s, Volume:%s", systemURI.toString(), volumeURI.toString()));
_log.info(logMsgBuilder.toString());
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, systemURI);
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
WorkflowStepCompleter.stepExecuting(opId);
// Check if we need to cleanup dangling meta members volumes on array.
// Meta members are temporary array volumes. They only exist until they are added to a meta volume.
// We store these volumes in WF expand step data.
List<String> metaMembers = (ArrayList<String>) _workflowService.loadStepData(expandStepId);
if (metaMembers != null && !metaMembers.isEmpty()) {
CleanupMetaVolumeMembersCompleter cleanupCompleter = null;
boolean isWFStep = true;
cleanupCompleter = new CleanupMetaVolumeMembersCompleter(volumeURI, isWFStep, expandStepId, opId);
getDevice(storageSystem.getSystemType()).doCleanupMetaMembers(storageSystem, volume, cleanupCompleter);
// TEMPER Used for negative testing.
// Comment out call to doCleanupMetaMembers above
// cleanupCompleter.setSuccess(false);
// // TEMPER
if (!cleanupCompleter.isSuccess()) {
ServiceError serviceError;
if (cleanupCompleter.getError() != null) {
serviceError = cleanupCompleter.getError();
} else {
serviceError = DeviceControllerException.errors.jobFailedOp("CleanupMetaVolumeMembers");
}
doFailTask(Volume.class, volumeURI, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
} else {
// We came here if: a. Volume was expanded as a regular volume. or b. Volume was expanded as a meta
// volume,
// but there are no dangling meta members left.
_log.info("rollbackExpandVolume: nothing to cleanup in rollback.");
WorkflowStepCompleter.stepSucceded(opId);
}
logMsgBuilder = new StringBuilder(String.format(
"rollbackExpandVolume end - Array:%s, Volume:%s", systemURI.toString(), volumeURI.toString()));
_log.info(logMsgBuilder.toString());
} catch (InternalException e) {
_log.error(String.format("rollbackExpandVolume Failed - Array:%s, Volume:%s", systemURI.toString(),
volumeURI.toString()));
doFailTask(Volume.class, volumeURI, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
} catch (Exception e) {
_log.error(String.format("rollbackExpandVolume Failed - Array:%s, Volume:%s", systemURI.toString(),
volumeURI.toString()), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURI, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method createMetaVolumesMethod just above
* (except opId).
*/
@Override
public void createMetaVolumes(URI systemURI, URI poolURI, List<URI> volumeURIs,
VirtualPoolCapabilityValuesWrapper capabilities, String opId) throws ControllerException {
boolean opCreateFailed = false;
List<Volume> volumes = new ArrayList<Volume>();
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
systemURI);
List<VolumeTaskCompleter> volumeCompleters = new ArrayList<VolumeTaskCompleter>();
Iterator<URI> volumeURIsIter = volumeURIs.iterator();
StringBuilder logMsgBuilder = new StringBuilder(String.format(
"createMetaVolumes start - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
while (volumeURIsIter.hasNext()) {
URI volumeURI = volumeURIsIter.next();
logMsgBuilder.append(String.format("%nVolume:%s", volumeURI.toString()));
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
volumes.add(volume);
VolumeCreateCompleter volumeCompleter = new VolumeCreateCompleter(
volumeURI, opId);
volumeCompleters.add(volumeCompleter);
}
_log.info(logMsgBuilder.toString());
StoragePool storagePool = _dbClient.queryObject(StoragePool.class, poolURI);
MultiVolumeTaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs, volumeCompleters, opId);
Volume volume = volumes.get(0);
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
// All volumes are in the same storage pool with the same capacity. Get recommendation for the first volume.
MetaVolumeRecommendation recommendation = MetaVolumeUtils.getCreateRecommendation(storageSystem, storagePool,
volume.getCapacity(), volume.getThinlyProvisioned(), vpool.getFastExpansion(), capabilities);
for (Volume metaVolume : volumes) {
MetaVolumeUtils.prepareMetaVolume(metaVolume, recommendation.getMetaMemberSize(), recommendation.getMetaMemberCount(),
recommendation.getMetaVolumeType().toString(), _dbClient);
}
WorkflowStepCompleter.stepExecuting(completer.getOpId());
getDevice(storageSystem.getSystemType()).doCreateMetaVolumes(storageSystem,
storagePool, volumes, capabilities, recommendation, completer);
logMsgBuilder = new StringBuilder(String.format(
"createMetaVolumes end - Array:%s Pool:%s", systemURI.toString(),
poolURI.toString()));
volumeURIsIter = volumeURIs.iterator();
while (volumeURIsIter.hasNext()) {
logMsgBuilder.append(String.format("%nVolume:%s", volumeURIsIter.next()
.toString()));
}
_log.info(logMsgBuilder.toString());
} catch (InternalException e) {
_log.error(String.format("createMetaVolumes Failed - Array: %s Pool:%s Volume:%s",
systemURI.toString(), poolURI.toString(), Joiner.on("\t").join(volumeURIs)));
doFailTask(Volume.class, volumeURIs, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
opCreateFailed = true;
} catch (Exception e) {
_log.error(String.format("createMetaVolumes Failed - Array: %s Pool:%s Volume:%s",
systemURI.toString(), poolURI.toString(), Joiner.on("\t").join(volumeURIs)));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURIs, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
opCreateFailed = true;
}
if (opCreateFailed) {
for (Volume volume : volumes) {
volume.setInactive(true);
_dbClient.updateObject(volume);
}
}
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method createMetaVolumeMethod just above
* (except opId).
*/
@Override
public void createMetaVolume(URI systemURI, URI poolURI, URI volumeURI,
VirtualPoolCapabilityValuesWrapper capabilities, String opId) throws ControllerException {
try {
StringBuilder logMsgBuilder = new StringBuilder(String.format(
"createMetaVolume start - Array:%s Pool:%s, Volume:%s", systemURI.toString(),
poolURI.toString(), volumeURI.toString()));
_log.info(logMsgBuilder.toString());
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, systemURI);
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
StoragePool storagePool = _dbClient.queryObject(StoragePool.class, poolURI);
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, volume.getVirtualPool());
MetaVolumeRecommendation recommendation = MetaVolumeUtils.getCreateRecommendation(storageSystem, storagePool,
volume.getCapacity(), volume.getThinlyProvisioned(), vpool.getFastExpansion(), capabilities);
MetaVolumeUtils.prepareMetaVolume(volume, recommendation.getMetaMemberSize(), recommendation.getMetaMemberCount(),
recommendation.getMetaVolumeType().toString(), _dbClient);
VolumeCreateCompleter completer = new VolumeCreateCompleter(volumeURI, opId);
WorkflowStepCompleter.stepExecuting(completer.getOpId());
getDevice(storageSystem.getSystemType()).doCreateMetaVolume(storageSystem,
storagePool, volume, capabilities, recommendation, completer);
logMsgBuilder = new StringBuilder(String.format(
"createMetaVolume end - Array:%s Pool:%s, Volume:%s", systemURI.toString(),
poolURI.toString(), volumeURI.toString()));
_log.info(logMsgBuilder.toString());
} catch (InternalException e) {
_log.error(String.format("createMetaVolume Failed - Array:%s Pool:%s, Volume:%s", systemURI.toString(),
poolURI.toString(), volumeURI.toString()));
doFailTask(Volume.class, volumeURI, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
} catch (Exception e) {
_log.error(String.format("createMetaVolume Failed - Array:%s Pool:%s, Volume:%s", systemURI.toString(),
poolURI.toString(), volumeURI.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURI, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
/**
* Return a Workflow.Method for expandVolume.
*
* @param storage
* storage system
* @param pool
* storage pool
* @param volume
* volume to expand
* @param size
* size to expand to
* @return Workflow.Method
*/
public static Workflow.Method expandVolumesMethod(URI storage, URI pool, URI volume, Long size) {
return new Workflow.Method("expandVolume", storage, pool, volume, size);
}
/*
* {@inheritDoc}
* <p>
* Single step workflow to expand volume with rollback.
*/
@Override
public void expandBlockVolume(URI storage, URI pool, URI volume, Long size, String opId)
throws ControllerException {
SimpleTaskCompleter completer = new SimpleTaskCompleter(Volume.class, volume, opId);
try {
WorkflowStepCompleter.stepExecuting(opId);;
final String workflowKey = "expandBlockVolume";
if (!WorkflowService.getInstance().hasWorkflowBeenCreated(opId, workflowKey)) {
// Get a new workflow to execute volume expand
Workflow workflow = _workflowService.getNewWorkflow(this,
EXPAND_VOLUME_WF_NAME, false, opId);
_log.info("Created new expansion workflow with operation id {}", opId);
String stepId = workflow.createStepId();
workflow.createStep(BLOCK_VOLUME_EXPAND_GROUP, String.format(
"Expand volume %s", volume), null,
storage, getDeviceType(storage),
BlockDeviceController.class,
expandVolumesMethod(storage, pool, volume, size),
rollbackExpandVolumeMethod(storage, volume, stepId),
stepId);
_log.info("Executing workflow plan {}", BLOCK_VOLUME_EXPAND_GROUP);
workflow.executePlan(completer, String.format(
"Expansion of volume %s completed successfully", volume));
// Mark this workflow as created/executed so we don't do it again on retry/resume
WorkflowService.getInstance().markWorkflowBeenCreated(opId, workflowKey);
}
} catch (Exception ex) {
_log.error("Could not expand volume: " + volume, ex);
String opName = ResourceOperationTypeEnum.EXPAND_BLOCK_VOLUME.getName();
ServiceError serviceError = DeviceControllerException.errors.expandVolumeFailed(
volume.toString(), opName, ex);
completer.error(_dbClient, serviceError);
}
}
/*
* Add workflow steps for volume expand.
*/
@Override
public String addStepsForExpandVolume(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors, String taskId)
throws InternalException {
// The the list of Volumes that the BlockDeviceController needs to process.
volumeDescriptors = VolumeDescriptor.filterByType(volumeDescriptors,
new VolumeDescriptor.Type[] {
VolumeDescriptor.Type.BLOCK_DATA,
VolumeDescriptor.Type.RP_SOURCE,
VolumeDescriptor.Type.RP_TARGET,
VolumeDescriptor.Type.RP_EXISTING_SOURCE,
VolumeDescriptor.Type.RP_VPLEX_VIRT_SOURCE,
VolumeDescriptor.Type.RP_VPLEX_VIRT_TARGET
}, null);
if (volumeDescriptors == null || volumeDescriptors.isEmpty()) {
return waitFor;
}
Map<URI, Long> volumesToExpand = new HashMap<URI, Long>();
// Check to see if there are any migrations
List<Migration> migrations = null;
if (volumeDescriptors != null) {
List<VolumeDescriptor> migrateDescriptors = VolumeDescriptor.filterByType(volumeDescriptors,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.VPLEX_MIGRATE_VOLUME }, null);
if (migrateDescriptors != null && !migrateDescriptors.isEmpty()) {
// Load the migration objects for use later
migrations = new ArrayList<Migration>();
Iterator<VolumeDescriptor> migrationIter = migrateDescriptors.iterator();
while (migrationIter.hasNext()) {
Migration migration = _dbClient.queryObject(Migration.class, migrationIter.next().getMigrationId());
migrations.add(migration);
}
}
}
for (VolumeDescriptor descriptor : volumeDescriptors) {
// Grab the volume, let's see if an expand is really needed
Volume volume = _dbClient.queryObject(Volume.class, descriptor.getVolumeURI());
// If this volume is a VPLEX volume, check to see if we need to expand its backend volume.
if (volume.getAssociatedVolumes() != null && !volume.getAssociatedVolumes().isEmpty()) {
for (String volStr : volume.getAssociatedVolumes()) {
URI volStrURI = URI.create(volStr);
Volume associatedVolume = _dbClient.queryObject(Volume.class, volStrURI);
boolean migrationExists = false;
// If there are any volumes that are tagged for migration, ignore them.
if (migrations != null && !migrations.isEmpty()) {
for (Migration migration : migrations) {
if (migration.getTarget().equals(volume.getId())) {
_log.info("Volume [{}] has a migration, ignore this volume for expand.", volume.getLabel());
migrationExists = true;
break;
}
}
}
// Only expand backend volume if there is no existing migration and
// the new size > existing backend volume's provisioned capacity, otherwise we can ignore.
if (!migrationExists
&& associatedVolume.getProvisionedCapacity() != null
&& descriptor.getVolumeSize() > associatedVolume.getProvisionedCapacity().longValue()) {
volumesToExpand.put(volStrURI, descriptor.getVolumeSize());
}
}
} else {
// Only expand the volume if it's an existing volume (provisoned capacity is not null and not 0) and
// new size > existing volume's provisioned capacity, otherwise we can ignore.
if (volume.getProvisionedCapacity() != null
&& volume.getProvisionedCapacity().longValue() != 0
&& descriptor.getVolumeSize() > volume.getProvisionedCapacity().longValue()) {
volumesToExpand.put(volume.getId(), descriptor.getVolumeSize());
}
}
}
String nextStep = (volumesToExpand.size() > 0) ? BLOCK_VOLUME_EXPAND_GROUP : waitFor;
for (Map.Entry<URI, Long> entry : volumesToExpand.entrySet()) {
_log.info("Creating WF step for Expand Volume for {}", entry.getKey().toString());
Volume volumeToExpand = _dbClient.queryObject(Volume.class, entry.getKey());
StorageSystem storage = _dbClient.queryObject(StorageSystem.class, volumeToExpand.getStorageController());
String stepId = workflow.createStepId();
workflow.createStep(
BLOCK_VOLUME_EXPAND_GROUP,
String.format(
"Expand Block volume %s", volumeToExpand),
waitFor,
storage.getId(),
getDeviceType(storage.getId()),
BlockDeviceController.class,
expandVolumesMethod(volumeToExpand.getStorageController(), volumeToExpand.getPool(), volumeToExpand.getId(),
entry.getValue()),
rollbackExpandVolumeMethod(volumeToExpand.getStorageController(), volumeToExpand.getId(), stepId),
stepId);
_log.info("Creating workflow step {}", BLOCK_VOLUME_EXPAND_GROUP);
}
return nextStep;
}
@Override
public void expandVolume(URI storage, URI pool, URI volume, Long size, String opId)
throws ControllerException {
try {
StorageSystem storageObj = _dbClient
.queryObject(StorageSystem.class, storage);
Volume volumeObj = _dbClient.queryObject(Volume.class, volume);
_log.info(String.format(
"expandVolume start - Array: %s Pool:%s Volume:%s, IsMetaVolume: %s, OldSize: %s, NewSize: %s",
storage.toString(), pool.toString(), volume.toString(), volumeObj.getIsComposite(), volumeObj.getCapacity(), size));
StoragePool poolObj = _dbClient.queryObject(StoragePool.class, pool);
VolumeExpandCompleter completer = new VolumeExpandCompleter(volume, size, opId);
long metaMemberSize = volumeObj.getIsComposite() ? volumeObj.getMetaMemberSize() : volumeObj.getCapacity();
long metaCapacity = volumeObj.getIsComposite() ? volumeObj.getTotalMetaMemberCapacity() : volumeObj.getCapacity();
VirtualPool vpool = _dbClient.queryObject(VirtualPool.class, volumeObj.getVirtualPool());
boolean isThinlyProvisioned = volumeObj.getThinlyProvisioned();
MetaVolumeRecommendation recommendation = MetaVolumeUtils.getExpandRecommendation(storageObj, poolObj,
metaCapacity, size, metaMemberSize, isThinlyProvisioned, vpool.getFastExpansion());
if (recommendation.isCreateMetaVolumes()) {
// check if we are required to create any members.
// When expansion size fits into total meta member size, no new members should be created.
// Also check that this is not recovery to clean dangling meta volumes with zero-capacity expansion.
if (recommendation.getMetaMemberCount() == 0 && (volumeObj.getMetaVolumeMembers() == null ||
volumeObj.getMetaVolumeMembers().isEmpty())) {
volumeObj.setCapacity(size);
_dbClient.updateObject(volumeObj);
_log.info(String
.format(
"Expanded volume within its total meta volume capacity (simple case) - Array: %s Pool:%s Volume:%s, IsMetaVolume: %s, Total meta volume capacity: %s, NewSize: %s",
storage.toString(), pool.toString(), volume.toString(), volumeObj.getIsComposite(),
volumeObj.getTotalMetaMemberCapacity(), volumeObj.getCapacity()));
completer.ready(_dbClient);
} else {
// set meta related data in task completer
long metaMemberCount = volumeObj.getIsComposite() ? recommendation.getMetaMemberCount()
+ volumeObj.getMetaMemberCount() : recommendation.getMetaMemberCount() + 1;
completer.setMetaMemberSize(recommendation.getMetaMemberSize());
completer.setMetaMemberCount((int) metaMemberCount);
completer.setTotalMetaMembersSize(metaMemberCount * recommendation.getMetaMemberSize());
completer.setComposite(true);
completer.setMetaVolumeType(recommendation.getMetaVolumeType().toString());
getDevice(storageObj.getSystemType()).doExpandAsMetaVolume(storageObj, poolObj,
volumeObj, size, recommendation, completer);
}
} else {
// expand as regular volume
InvokeTestFailure.internalOnlyInvokeTestFailure(InvokeTestFailure.ARTIFICIAL_FAILURE_080);
getDevice(storageObj.getSystemType()).doExpandVolume(storageObj, poolObj,
volumeObj, size, completer);
}
_log.info(String.format("expandVolume end - Array: %s Pool:%s Volume:%s",
storage.toString(), pool.toString(), volume.toString()));
} catch (Exception e) {
_log.error(String.format("expandVolume Failed - Array: %s Pool:%s Volume:%s",
storage.toString(), pool.toString(), volume.toString()), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
List<URI> volumes = Arrays.asList(volume);
doFailTask(Volume.class, volumes, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
static final String DELETE_VOLUMES_STEP_GROUP = "BlockDeviceDeleteVolumes";
/**
* {@inheritDoc}
*/
@Override
public String addStepsForDeleteVolumes(Workflow workflow, String waitFor,
List<VolumeDescriptor> volumes, String taskId) throws ControllerException {
// The the list of Volumes that the BlockDeviceController needs to process.
volumes = VolumeDescriptor.filterByType(volumes,
new VolumeDescriptor.Type[] {
VolumeDescriptor.Type.BLOCK_DATA,
VolumeDescriptor.Type.RP_JOURNAL,
VolumeDescriptor.Type.RP_TARGET,
VolumeDescriptor.Type.RP_VPLEX_VIRT_JOURNAL,
VolumeDescriptor.Type.RP_VPLEX_VIRT_TARGET
}, null);
// Check to see if there are any volumes flagged to not be fully deleted.
// Any flagged volumes will be removed from the list of volumes to delete.
List<VolumeDescriptor> doNotDeleteDescriptors = VolumeDescriptor.getDoNotDeleteDescriptors(volumes);
if (doNotDeleteDescriptors != null
&& !doNotDeleteDescriptors.isEmpty()) {
// If there are volumes we do not want fully deleted, remove
// those volumes here.
volumes.removeAll(doNotDeleteDescriptors);
}
// If there are no volumes, just return
if (volumes.isEmpty()) {
return waitFor;
}
// Segregate by device.
Map<URI, List<VolumeDescriptor>> deviceMap = VolumeDescriptor.getDeviceMap(volumes);
// Add a step to delete the volumes in each device.
for (URI deviceURI : deviceMap.keySet()) {
volumes = deviceMap.get(deviceURI);
List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(volumes);
workflow.createStep(DELETE_VOLUMES_STEP_GROUP,
String.format("Deleting volumes:%n%s", getVolumesMsg(_dbClient, volumeURIs)),
waitFor, deviceURI, getDeviceType(deviceURI),
this.getClass(),
deleteVolumesMethod(deviceURI, volumeURIs),
null, null);
}
return DELETE_VOLUMES_STEP_GROUP;
}
/**
* Return a Workflow.Method for deleteVolumes.
*
* @param systemURI
* @param volumeURIs
* @return
*/
private Workflow.Method deleteVolumesMethod(URI systemURI, List<URI> volumeURIs) {
return new Workflow.Method("deleteVolumes", systemURI, volumeURIs);
}
/**
* {@inheritDoc} NOTE NOTE: The arguments here must match deleteVolumesMethod defined above (except opId).
*/
@Override
public void deleteVolumes(URI systemURI, List<URI> volumeURIs, String opId) throws ControllerException {
MultiVolumeTaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs, opId);
deleteVolumesWithCompleter(systemURI, volumeURIs, completer);
}
/**
* Deletes the given volumes with an existing task completer.
*
* @param systemURI Storage system URI
* @param volumeURIs List of Volume URI
* @param completer Task completer
* @throws ControllerException
*/
public void deleteVolumesWithCompleter(URI systemURI, List<URI> volumeURIs, MultiVolumeTaskCompleter completer)
throws ControllerException {
String opId = completer.getOpId();
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
systemURI);
List<Volume> volumes = new ArrayList<>();
Iterator<URI> volumeURIsIter = volumeURIs.iterator();
String arrayName = systemURI.toString();
StringBuilder entryLogMsgBuilder = new StringBuilder(String.format(
"deleteVolume start - Array:%s", arrayName));
StringBuilder exitLogMsgBuilder = new StringBuilder(String.format(
"deleteVolume end - Array:%s", arrayName));
while (volumeURIsIter.hasNext()) {
URI volumeURI = volumeURIsIter.next();
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
String poolId = NullColumnValueGetter.isNullURI(volume.getPool()) ? "null" : volume.getPool().toString();
entryLogMsgBuilder.append(String.format("%nPool:%s Volume:%s", poolId, volumeURI.toString()));
exitLogMsgBuilder.append(String.format("%nPool:%s Volume:%s", poolId, volumeURI.toString()));
VolumeDeleteCompleter volumeCompleter = new VolumeDeleteCompleter(volumeURI, opId);
if (volume.getInactive() == false) {
// It is possible that there is a BlockSnaphot instance that references the
// same device Volume if a VPLEX virtual volume has been created from the
// snapshot. If this is the case then the VPLEX volume is being deleted and
// volume is represents the source side backend volume for that VPLEX volume.
// In this case, we won't delete the backend volume because the volume is
// still a block snapshot target and the deletion would fail. The volume will
// be deleted when the BlockSnapshot instance is deleted. All we want to do is
// mark the Volume instance inactive.
// COP-20875: Native Guid will not be set when there is error during create volume operation
if (!NullColumnValueGetter.isNullValue(volume.getNativeGuid())) {
List<BlockSnapshot> snapshots = CustomQueryUtility
.getActiveBlockSnapshotByNativeGuid(_dbClient, volume.getNativeGuid());
if (!snapshots.isEmpty()) {
volume.setInactive(true);
_dbClient.updateObject(volume);
continue;
}
}
// Add the volume to the list to delete
volumes.add(volume);
} else {
// Add the proper status, since we won't be deleting this volume
String opName = ResourceOperationTypeEnum.DELETE_BLOCK_VOLUME.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
serviceError.setMessage("Volume does not exist or is already deleted");
_log.info("Volume does not exist or is already deleted");
volumeCompleter.error(_dbClient, serviceError);
}
volumeCompleter.setRollingBack(completer.isRollingBack());
completer.addVolumeCompleter(volumeCompleter);
}
_log.info(entryLogMsgBuilder.toString());
if (!volumes.isEmpty()) {
WorkflowStepCompleter.stepExecuting(opId);
getDevice(storageSystem.getSystemType()).doDeleteVolumes(storageSystem, opId,
volumes, completer);
} else {
doSuccessTask(Volume.class, volumeURIs, opId);
WorkflowStepCompleter.stepSucceded(opId);
}
_log.info(exitLogMsgBuilder.toString());
} catch (Exception e) {
handleException(e, completer);
}
}
/**
* Add Steps to perform untag operations on underlying array for the volumes passed in.
*
* @param workflow
* -- The Workflow being built
* @param waitFor
* -- Previous steps to waitFor
* @param volumes
* -- List<Volume> -- volumes of all types to be processed
* @return last step added to waitFor
* @throws ControllerException
*/
public String addStepsForUntagVolumes(Workflow workflow, String waitFor,
List<VolumeDescriptor> volumes, String taskId) throws ControllerException {
// The the list of Volumes that the BlockDeviceController needs to process.
List<VolumeDescriptor> untagVolumeDescriptors = VolumeDescriptor.filterByType(volumes,
new VolumeDescriptor.Type[] {
VolumeDescriptor.Type.BLOCK_DATA },
null);
// If there are no volumes, just return
if (untagVolumeDescriptors.isEmpty()) {
return waitFor;
}
Map<URI, List<VolumeDescriptor>> untagVolumeDeviceMap = VolumeDescriptor.getDeviceMap(untagVolumeDescriptors);
// Add a step to perform an untag operation for all volumes in each device.
for (URI deviceURI : untagVolumeDeviceMap.keySet()) {
if (deviceURI != null) {
untagVolumeDescriptors = untagVolumeDeviceMap.get(deviceURI);
List<URI> volumeURIs = VolumeDescriptor.getVolumeURIs(untagVolumeDescriptors);
workflow.createStep(UNTAG_VOLUME_STEP_GROUP,
String.format("Untagging volumes:%n%s", getVolumesMsg(_dbClient, volumeURIs)),
waitFor, deviceURI, getDeviceType(deviceURI),
this.getClass(),
untagVolumesMethod(deviceURI, volumeURIs),
rollbackMethodNullMethod(), null);
_log.info(String.format("Adding step to untag volumes (%s)", Joiner.on(",").join(volumeURIs)));
}
}
return UNTAG_VOLUME_STEP_GROUP;
}
/**
* Return a Workflow.Method for untagVolumes.
*
* @param systemURI
* The system to perform the action on
* @param volumeURIs
* The volumes to perform the action on
* @return the new WF
*/
private Workflow.Method untagVolumesMethod(URI systemURI, List<URI> volumeURIs) {
return new Workflow.Method("untagVolumes", systemURI, volumeURIs);
}
/**
* Performs an untag operation on all volumes.
*
* @param systemURI
* Underlying system to perform the untag operation on
* @param volumeURIs
* Volumes to untag
* @param opId
* The opId
* @throws ControllerException
*/
public void untagVolumes(URI systemURI, List<URI> volumeURIs, String opId)
throws ControllerException {
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class,
systemURI);
List<Volume> volumes = new ArrayList<Volume>();
List<VolumeTaskCompleter> volumeCompleters = new ArrayList<VolumeTaskCompleter>();
Iterator<URI> volumeURIsIter = volumeURIs.iterator();
String arrayName = systemURI.toString();
StringBuilder entryLogMsgBuilder = new StringBuilder(String.format(
"untagVolume start - Array:%s", arrayName));
StringBuilder exitLogMsgBuilder = new StringBuilder(String.format(
"untagVolume end - Array:%s", arrayName));
while (volumeURIsIter.hasNext()) {
URI volumeURI = volumeURIsIter.next();
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
if (volume != null) {
entryLogMsgBuilder.append(String.format("%nUntag operation: Volume: [%s](%s)",
volume.getLabel(), volumeURI.toString()));
exitLogMsgBuilder.append(String.format("%nUntag operation: Volume: [%s](%s)",
volume.getLabel(), volumeURI.toString()));
if (!volume.getInactive()) {
volumes.add(volume);
} else {
// Nothing to do for an inactive volume
continue;
}
// Generic completer is fine here
VolumeWorkflowCompleter volumeCompleter = new VolumeWorkflowCompleter(volumeURI, opId);
volumeCompleters.add(volumeCompleter);
}
}
_log.info(entryLogMsgBuilder.toString());
if (!volumes.isEmpty()) {
WorkflowStepCompleter.stepExecuting(opId);
TaskCompleter completer = new MultiVolumeTaskCompleter(volumeURIs,
volumeCompleters, opId);
getDevice(storageSystem.getSystemType()).doUntagVolumes(storageSystem, opId,
volumes, completer);
}
doSuccessTask(Volume.class, volumeURIs, opId);
WorkflowStepCompleter.stepSucceded(opId);
_log.info(exitLogMsgBuilder.toString());
} catch (InternalException e) {
doFailTask(Volume.class, volumeURIs, opId, e);
WorkflowStepCompleter.stepFailed(opId, e);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, volumeURIs, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, DeviceControllerException.exceptions.unexpectedCondition(e.getMessage()));
}
}
/**
* Workflow step to delete a volume
*
* @param storageURI
* the storage system ID
* @param volumes
* the volume IDs
* @param token
* the task ID from the workflow
* @return true if the step was fired off properly.
* @throws WorkflowException
*/
public boolean deleteVolumeStep(URI storageURI, List<URI> volumes, String token) throws WorkflowException {
boolean status = true;
String volumeList = Joiner.on(',').join(volumes);
try {
WorkflowStepCompleter.stepExecuting(token);
_log.info("Delete Volume Step Started. " + volumeList);
deleteVolumes(storageURI, volumes, token);
_log.info("Delete Volume Step Dispatched: " + volumeList);
} catch (Exception ex) {
_log.error("Delete Volume Step Failed." + volumeList);
String opName = ResourceOperationTypeEnum.DELETE_VOLUME_WORKFLOW_STEP.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
WorkflowStepCompleter.stepFailed(token, serviceError);
status = false;
}
return status;
}
@Override
public void createSingleSnapshot(URI storage, List<URI> snapshotList, Boolean createInactive, Boolean readOnly, String opId)
throws ControllerException {
WorkflowStepCompleter.stepExecuting(opId);
TaskCompleter completer = null;
try {
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockSnapshotCreateCompleter(snapshotList, opId);
getDevice(storageObj.getSystemType()).doCreateSingleSnapshot(storageObj, snapshotList, createInactive, readOnly, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.createVolumeSnapshotFailed(e);
}
}
}
@Override
public void createSnapshot(URI storage, List<URI> snapshotList, Boolean createInactive, Boolean readOnly, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
boolean isListReplicaFlow = false;
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapshotObj = _dbClient.queryObject(BlockSnapshot.class, snapshotList.get(0));
Volume sourceVolumeObj = _dbClient.queryObject(Volume.class, snapshotObj.getParent().getURI());
URI cgURI = null;
completer = new BlockSnapshotCreateCompleter(snapshotList, opId);
/**
* VPLEX/RP CG volumes may not be having back end Array Group.
* In this case we should create element replica using createListReplica.
* We should not use createGroup replica as backend cg will not be available in this case.
*/
boolean isVnxVolume = ControllerUtils.isVnxVolume(sourceVolumeObj, _dbClient);
// VNX doesn't support list replica for snapshot
isListReplicaFlow = !isVnxVolume && isListReplicaFlow(sourceVolumeObj);
if (!isListReplicaFlow) {
getDevice(storageObj.getSystemType()).doCreateSnapshot(storageObj, snapshotList, createInactive, readOnly, completer);
} else {
// List Replica
completer.addConsistencyGroupId(cgURI);
getDevice(storageObj.getSystemType()).doCreateListReplica(storageObj, snapshotList, createInactive, completer);
}
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.createVolumeSnapshotFailed(e);
}
}
}
@Override
public void activateSnapshot(URI storage, List<URI> snapshotList, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockSnapshotActivateCompleter(snapshotList, opId);
getDevice(storageObj.getSystemType()).doActivateSnapshot(storageObj, snapshotList, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.activateVolumeSnapshotFailed(e);
}
}
}
@Override
public void deleteSnapshot(URI storage, URI snapshot, String opId) throws ControllerException {
_log.info("START deleteSnapshot");
TaskCompleter completer = null;
WorkflowStepCompleter.stepExecuting(opId);
try {
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);
completer = BlockSnapshotDeleteCompleter.createCompleter(_dbClient, snapObj, opId);
getDevice(storageObj.getSystemType()).doDeleteSnapshot(storageObj, snapshot, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.deleteVolumeSnapshotFailed(e);
}
}
}
@Override
public void establishVolumeAndSnapshotGroupRelation(URI storage, URI sourceVolume, URI snapshot, String opId)
throws ControllerException {
_log.info("START establishVolumeAndSnapshotGroupRelation workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, ESTABLISH_VOLUME_SNAPSHOT_GROUP_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
try {
workflow.createStep("establishStep", "create group relation between Volume group and Snapshot group", null, storage,
storageObj.getSystemType(),
this.getClass(), establishVolumeAndSnapshotGroupRelationMethod(storage, sourceVolume, snapshot), null, null);
taskCompleter = new BlockSnapshotEstablishGroupTaskCompleter(snapshot, opId);
workflow.executePlan(taskCompleter, "Successfully created group relation between Volume group and Snapshot group");
} catch (Exception e) {
String msg = String.format("Failed to create group relation between Volume group and Snapshot group."
+ "Source volume: %s, Snapshot: %s",
sourceVolume, snapshot);
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
private Workflow.Method establishVolumeAndSnapshotGroupRelationMethod(URI storage, URI sourceVolume, URI snapshot) {
return new Workflow.Method("establishVolumeSnapshotGroupRelation", storage, sourceVolume, snapshot);
}
public void establishVolumeSnapshotGroupRelation(
URI storage, URI sourceVolume, URI snapshot, String opId)
throws ControllerException {
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter completer = new BlockSnapshotEstablishGroupTaskCompleter(snapshot, opId);
getDevice(storageObj.getSystemType())
.doEstablishVolumeSnapshotGroupRelation(
storageObj, sourceVolume, snapshot, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
@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 ControllerException {
BlockSnapshot snap = _dbClient.queryObject(BlockSnapshot.class, snapshot);
URI parentVolumeURI = snap.getParent().getURI();
Volume parentVolume = _dbClient.queryObject(Volume.class, parentVolumeURI);
Volume associatedVPlexVolume = Volume.fetchVplexVolume(_dbClient, parentVolume);
// Do nothing if this is not a native snapshot
// Do not add block restore steps if this is a snapshot of a VPlex volume. The
// VPlex controller will add the required block restore steps.
if (NullColumnValueGetter.isNotNullValue(snap.getTechnologyType()) &&
!snap.getTechnologyType().equals(TechnologyType.NATIVE.toString()) ||
associatedVPlexVolume != null) {
return waitFor;
}
Workflow.Method restoreVolumeMethod = new Workflow.Method(
RESTORE_VOLUME_METHOD_NAME, storage, pool,
volume, snapshot, Boolean.TRUE, syncDirection);
workflow.createStep(RESTORE_VOLUME_STEP, String.format(
"Restore volume %s from snapshot %s",
volume, snapshot), waitFor,
storage, getDeviceType(storage),
BlockDeviceController.class, restoreVolumeMethod, rollbackMethodNullMethod(), null);
_log.info(
"Created workflow step to restore block volume {} from snapshot {}",
volume, snapshot);
return RESTORE_VOLUME_STEP;
}
private static final String BLOCK_VOLUME_RESTORE_GROUP = "BlockDeviceRestoreVolumeGroup";
private static final String POST_BLOCK_VOLUME_RESTORE_GROUP = "PostBlockDeviceRestoreVolumeGroup";
@Override
public void restoreVolume(URI storage, URI pool, URI volumeURI, URI snapshot, Boolean updateOpStatus, String syncDirection, String opId)
throws ControllerException {
SimpleTaskCompleter completer = new SimpleTaskCompleter(BlockSnapshot.class, snapshot, opId);
try {
Workflow workflow = _workflowService.getNewWorkflow(this, RESTORE_VOLUME_WF_NAME, false, opId);
_log.info("Created new restore workflow with operation id {}", opId);
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
BlockSnapshot blockSnapshot = _dbClient.queryObject(BlockSnapshot.class, snapshot);
StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage);
String description = String.format("Restore volume %s from snapshot %s", volumeURI, snapshot);
String waitFor = null;
URI srdfSourceStorageSystemURI = null;
Volume srdfSourceVolume = null;
Volume srdfTargetVolume = null;
boolean active = false;
/**
* We need to split the SRDF link for R2 snap restore if it is not paused already.
* Refer OPT#476788
*/
if (isNonSplitSRDFTargetVolume(volume)) {
URI srdfSourceVolumeURI = volume.getSrdfParent().getURI();
srdfSourceVolume = _dbClient.queryObject(Volume.class, srdfSourceVolumeURI);
srdfTargetVolume = volume;
srdfSourceStorageSystemURI = srdfSourceVolume.getStorageController();
if (Mode.ACTIVE.equals(Mode.valueOf(volume.getSrdfCopyMode()))) {
active = true;
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
srdfSourceVolumeURI, volumeURI, workflow);
} else {
// split all members the group
Workflow.Method splitMethod = srdfDeviceController.splitSRDFLinkMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, volumeURI, false);
Workflow.Method splitRollbackMethod = srdfDeviceController.resumeSyncPairMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, volumeURI);
waitFor = workflow.createStep(SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_DESC, waitFor, srdfSourceStorageSystemURI,
getDeviceType(srdfSourceStorageSystemURI), SRDFDeviceController.class, splitMethod,
splitRollbackMethod, null);
}
} else if (isNonSplitSRDFSourceVolume(volume)) {
srdfSourceVolume = volume;
srdfSourceStorageSystemURI = volume.getStorageController();
StringSet targets = volume.getSrdfTargets();
if (null != targets) {
for (String target : targets) {
if (NullColumnValueGetter.isNotNullValue(target)) {
srdfTargetVolume = _dbClient.queryObject(Volume.class, URI.create(target));
if (null != srdfTargetVolume && Mode.ACTIVE.equals(Mode.valueOf(srdfTargetVolume.getSrdfCopyMode()))) {
active = true;
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
volume.getId(), srdfTargetVolume.getId(), workflow);
}
break;
}
}
}
}
if (system.checkIfVmax3()) {
_log.info("Creating workflow for restore VMAX3 snapshot {}", blockSnapshot.getId());
// To restore the source from a linked target volume for VMAX3 SnapVX, we must
// do the following:
//
// 1. Terminate any stale restore sessions on the source.
// 2. Create a temporary snapvx snapshot session of the linked target volume or target group.
// 3. Link the source volume(s) of the BlockSnapshot(s) to the temporary snapshot session in copy mode.
// 4. Wait for the data from the session to be copied to the source volume(s)
// 5. Unlink the source volume(s) from the temporary snapvx snapshot session.
// 6. Delete the temporary session.
//
// This is essentially restoring by creating a cascaded snapshot session or group
// snapshot session on the linked target volume associated with the passed block
// snapshot or associated linked target group in the case of a group operation.
// Create a workflow step to terminate stale restore sessions.
waitFor = workflow.createStep(BLOCK_VOLUME_RESTORE_GROUP,
String.format("Terminating VMAX restore session from %s to %s", blockSnapshot.getId(), volume.getId()),
waitFor, system.getId(), system.getSystemType(), BlockDeviceController.class,
terminateRestoreSessionsMethod(system.getId(), volume.getId(), blockSnapshot.getId()),
rollbackMethodNullMethod(), null);
// Get all snapshots if this is a group snapshot.
String replicationGroupName = null;
List<BlockSnapshot> allSnapshots = new ArrayList<>();
String replicationGroupId = blockSnapshot.getReplicationGroupInstance();
if (!NullColumnValueGetter.isNullValue(replicationGroupId)) {
allSnapshots.addAll(ControllerUtils.getSnapshotsPartOfReplicationGroup(blockSnapshot, _dbClient));
int nameStartIndex = replicationGroupId.indexOf("+") + 1;
replicationGroupName = replicationGroupId.substring(nameStartIndex);
} else {
allSnapshots.add(blockSnapshot);
}
// Create a temporary BlockSnapshot instance to represent the parent source volumes
// for each block snapshot. Linking to a session required BlockSnapshot instances so
// we need to create some to represent the source volume(s).
StringSet linkedTargets = new StringSet();
List<BlockSnapshot> sourceSnapshots = new ArrayList<>();
List<URI> sourceSnapshotURIs = new ArrayList<>();
URI cgURI = blockSnapshot.getConsistencyGroup();
for (BlockSnapshot aSnapshot : allSnapshots) {
BlockObject aSourceObj = BlockObject.fetch(_dbClient, aSnapshot.getParent().getURI());
BlockSnapshot sourceSnapshot = new BlockSnapshot();
URI sourceSnapshotURI = URIUtil.createId(BlockSnapshot.class);
sourceSnapshot.setId(sourceSnapshotURI);
sourceSnapshot.setNativeId(aSourceObj.getNativeId());
sourceSnapshot.setParent(new NamedURI(aSnapshot.getId(), aSnapshot.getLabel()));
sourceSnapshot.setSourceNativeId(aSnapshot.getNativeId());
sourceSnapshot.setStorageController(storage);
sourceSnapshot.setSystemType(system.getSystemType());
if (!NullColumnValueGetter.isNullURI(cgURI)) {
sourceSnapshot.setConsistencyGroup(cgURI);
}
sourceSnapshot.addInternalFlags(Flag.INTERNAL_OBJECT);
sourceSnapshots.add(sourceSnapshot);
sourceSnapshotURIs.add(sourceSnapshotURI);
linkedTargets.add(sourceSnapshotURI.toString());
}
_dbClient.createObject(sourceSnapshots);
// Create a BlockSnapshotSession instance to represent the temporary snapshot session.
BlockSnapshotSession snapSession = new BlockSnapshotSession();
URI snapSessionURI = URIUtil.createId(BlockSnapshotSession.class);
snapSession.setId(snapSessionURI);
snapSession.setLabel(blockSnapshot.getLabel() + System.currentTimeMillis());
snapSession.setSessionLabel(snapSession.getLabel());
snapSession.setProject(blockSnapshot.getProject());
snapSession.setStorageController(storage);
snapSession.addInternalFlags(Flag.INTERNAL_OBJECT);
if (!NullColumnValueGetter.isNullURI(cgURI) && NullColumnValueGetter.isNotNullValue(replicationGroupName)) {
snapSession.setConsistencyGroup(cgURI);
snapSession.setReplicationGroupInstance(replicationGroupName);
snapSession.setSessionSetName(replicationGroupName);
} else {
snapSession.setParent(new NamedURI(blockSnapshot.getId(), blockSnapshot.getLabel()));
}
snapSession.setLinkedTargets(linkedTargets);
_dbClient.createObject(snapSession);
// Now create a workflow step that will create the snapshot session.
// This will create a group session in the case of a group operation.
waitFor = workflow.createStep(CREATE_SNAPSHOT_SESSION_STEP_GROUP,
String.format("Create snapshot session %s for snapshot target volume %s", snapSessionURI, snapshot),
waitFor, storage, getDeviceType(storage), BlockDeviceController.class,
createBlockSnapshotSessionMethod(storage, snapSessionURI, replicationGroupName),
deleteBlockSnapshotSessionMethod(storage, snapSessionURI, replicationGroupName, Boolean.TRUE), null);
// Create a workflow step to link the source volume for the passed snapshot
// to the snapshot session create by the previous step. We link the source
// volume in copy mode so that that the point-in-time copy of the snapshot
// target volume represented by the snapshot session is copied to the source
// volume. This is essentially the restore step so that the source will now
// reflect the data on the snapshot target volume. This step will not complete
// until the data is copied and the link has achieved the copied state. If this
// is group operation the source target group will be linked to the created
// group session.
Workflow.Method linkMethod;
if (!NullColumnValueGetter.isNullURI(cgURI) && NullColumnValueGetter.isNotNullValue(replicationGroupName)) {
linkMethod = linkBlockSnapshotSessionTargetGroupMethod(storage, snapSessionURI, sourceSnapshotURIs,
BlockSnapshotSession.CopyMode.copy.name(), Boolean.TRUE);
} else {
linkMethod = linkBlockSnapshotSessionTargetMethod(storage, snapSessionURI, sourceSnapshotURIs.get(0),
BlockSnapshotSession.CopyMode.copy.name(), Boolean.TRUE);
}
waitFor = workflow.createStep(
LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Link source volume %s to snapshot session for snapshot target volume %s", volume, snapshot),
waitFor, storage, getDeviceType(storage), BlockDeviceController.class, linkMethod,
unlinkBlockSnapshotSessionTargetMethod(storage, snapSessionURI, sourceSnapshotURIs.get(0), Boolean.FALSE), null);
// Once the data is fully copied to the source, we can unlink the source from the session.
// Again, for a group operation, this will unlink the source group from the group session.
waitFor = workflow.createStep(
UNLINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Unlink source volume %s from snapshot session for snapshot target volume %s", volumeURI, snapshot),
waitFor, storage, getDeviceType(storage), BlockDeviceController.class,
unlinkBlockSnapshotSessionTargetMethod(storage, snapSessionURI, sourceSnapshotURIs.get(0), Boolean.FALSE),
rollbackMethodNullMethod(), null);
// Finally create a step to delete the snapshot session we created on the snapshot
// target volume.
waitFor = workflow.createStep(
DELETE_SNAPSHOT_SESSION_STEP_GROUP,
String.format("Delete snapshot session %s for snapshot target volume %s", snapSessionURI, snapshot),
waitFor, storage, getDeviceType(storage), BlockDeviceController.class,
deleteBlockSnapshotSessionMethod(storage, snapSessionURI, replicationGroupName, Boolean.TRUE),
rollbackMethodNullMethod(), null);
/*
* If Active mode then create a step to resume srdf group or restore R2 To R1 or do nothing.
* If syncdirection is not specified means its null then after R1 snapshot restore, resume.
* If syncdirection is not specified means its null then after R2 snapshot restore, restore R2 to R1.
* If syncdirection is SOURCE_TO_TARGET then after R1 or R2 snapshot restore, resume.
* If syncdirection is TARGET_TO_SOURCE then after R1 or R2 snapshot restore, restore R2 to R1.
* If syncdirection is NONE then do nothing, RDF group will stay in suspend state.
*/
if (active) {
if (null == syncDirection) {
if (null != srdfSourceVolume && volumeURI.equals(srdfSourceVolume.getId())) {
resumeSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI, srdfSourceVolume.getId(),
srdfTargetVolume.getId(), workflow);
} else if (null != srdfTargetVolume && volumeURI.equals(srdfTargetVolume.getId())) {
restoreWorkflowStep(waitFor, srdfTargetVolume.getStorageController(), srdfSourceVolume.getId(),
srdfTargetVolume.getId(), workflow);
}
} else if (null != syncDirection) {
if (SRDFUtils.SyncDirection.SOURCE_TO_TARGET.toString().equals(syncDirection)) {
resumeSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI, srdfSourceVolume.getId(),
srdfTargetVolume.getId(), workflow);
} else if (SRDFUtils.SyncDirection.TARGET_TO_SOURCE.toString().equals(syncDirection)) {
restoreWorkflowStep(waitFor, srdfTargetVolume.getStorageController(), srdfSourceVolume.getId(),
srdfTargetVolume.getId(), workflow);
} else if (SRDFUtils.SyncDirection.NONE.toString().equals(syncDirection)) {
_log.info("Sync direction is specified as {} hence no action will be done after retsore snapshot which"
+ " means the RDF group for volume {} will be in a suspended state.",
syncDirection, volume.getLabel());
}
}
}
} else {
waitFor = workflow.createStep(BLOCK_VOLUME_RESTORE_GROUP, description, waitFor,
storage, getDeviceType(storage), BlockDeviceController.class,
restoreVolumeMethod(storage, pool, volumeURI, snapshot, updateOpStatus),
rollbackMethodNullMethod(), null);
// Skip the step for VMAX3, as restore operation may still be in progress (OPT#476325)
// Regardless, termination of restore session should be call before restore
// Note this is not needed for VNX
addPostRestoreVolumeSteps(workflow, system, volume, blockSnapshot, waitFor);
}
_log.info("Executing workflow {}", BLOCK_VOLUME_RESTORE_GROUP);
String msg = String.format("Restore of volume %s from %s completed successfully", volumeURI, snapshot);
workflow.executePlan(completer, msg);
} catch (Exception e) {
String msg = String.format("Could not restore volume %s from snapshot %s", volumeURI, snapshot);
_log.error(msg, e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
}
private boolean isNonSplitSRDFTargetVolume(Volume volume) {
_log.info("volume.getPersonality() : {} volume.getLinkStatus() : {} ", volume.getPersonality(), volume.getLinkStatus());
return (volume != null && !NullColumnValueGetter.isNullNamedURI(volume.getSrdfParent()) &&
Volume.PersonalityTypes.TARGET.toString().equalsIgnoreCase(volume.getPersonality())
&& !(Volume.LinkStatus.FAILED_OVER.name().equalsIgnoreCase(volume.getLinkStatus())
|| Volume.LinkStatus.SUSPENDED.name().equalsIgnoreCase(volume.getLinkStatus())
|| Volume.LinkStatus.SPLIT.name().equalsIgnoreCase(volume.getLinkStatus())));
}
private boolean isNonSplitSRDFSourceVolume(Volume volume) {
_log.info("volume.getPersonality() : {} volume.getLinkStatus() : {} ", volume.getPersonality(), volume.getLinkStatus());
return (volume != null && (volume.getSrdfTargets() != null && !volume.getSrdfTargets().isEmpty()) &&
Volume.PersonalityTypes.SOURCE.toString().equalsIgnoreCase(volume.getPersonality())
&& !(Volume.LinkStatus.FAILED_OVER.name().equalsIgnoreCase(volume.getLinkStatus())
|| Volume.LinkStatus.SUSPENDED.name().equalsIgnoreCase(volume.getLinkStatus())
|| Volume.LinkStatus.SPLIT.name().equalsIgnoreCase(volume.getLinkStatus())));
}
private String suspendSRDFLinkWorkflowStep(String waitFor, URI srdfSourceStorageSystemURI,
URI sourceURI, URI targetURI, Workflow workflow) {
Workflow.Method suspendMethod = srdfDeviceController.suspendSRDFLinkMethod(srdfSourceStorageSystemURI, sourceURI, targetURI, false);
Workflow.Method resumeMethod = srdfDeviceController.resumeSyncPairMethod(srdfSourceStorageSystemURI, sourceURI, targetURI);
return workflow.createStep(SRDFDeviceController.SUSPEND_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.SUSPEND_SRDF_MIRRORS_STEP_DESC, waitFor, srdfSourceStorageSystemURI,
getDeviceType(srdfSourceStorageSystemURI), SRDFDeviceController.class, suspendMethod,
resumeMethod, null);
}
private String resumeSRDFLinkWorkflowStep(String waitFor, URI srdfSourceStorageSystemURI,
URI sourceURI, URI targetURI, Workflow workflow) {
Workflow.Method resumeMethod = srdfDeviceController.resumeSyncPairMethod(srdfSourceStorageSystemURI, sourceURI, targetURI);
return workflow.createStep(SRDFDeviceController.RESUME_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.RESUME_SRDF_MIRRORS_STEP_DESC, waitFor, srdfSourceStorageSystemURI,
getDeviceType(srdfSourceStorageSystemURI), SRDFDeviceController.class, resumeMethod,
null, null);
}
private String restoreWorkflowStep(String waitFor, URI srdfTargetStorageSystemURI,
URI sourceURI, URI targetURI, Workflow workflow) {
Workflow.Method restoreMethod = srdfDeviceController.restoreMethod(srdfTargetStorageSystemURI, sourceURI, targetURI);
return workflow.createStep(SRDFDeviceController.RESTORE_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.RESTORE_SRDF_MIRRORS_STEP_DESC, waitFor, srdfTargetStorageSystemURI,
getDeviceType(srdfTargetStorageSystemURI), SRDFDeviceController.class, restoreMethod,
null, null);
}
/**
* Return a Workflow.Method for restoreVolume
*
* @param storage
* storage system
* @param pool
* storage pool
* @param volume
* target of restore operation
* @param snapshot
* snapshot to restore from
* @param updateOpStatus
* update operation status flag
* @return Workflow.Method
*/
public static Workflow.Method restoreVolumeMethod(URI storage, URI pool, URI volume, URI snapshot,
Boolean updateOpStatus) {
return new Workflow.Method("restoreVolumeStep", storage, pool, volume, snapshot, updateOpStatus);
}
public boolean restoreVolumeStep(URI storage, URI pool, URI volume, URI snapshot, Boolean updateOpStatus,
String opId) throws ControllerException {
TaskCompleter completer = null;
try {
StorageSystem storageDevice = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);
completer = new BlockSnapshotRestoreCompleter(snapObj, opId, updateOpStatus);
getDevice(storageDevice.getSystemType()).doRestoreFromSnapshot(storageDevice, volume, snapshot, completer);
} catch (Exception e) {
_log.error(String.format("restoreVolume failed - storage: %s, pool: %s, volume: %s, snapshot: %s",
storage.toString(), pool.toString(), volume.toString(), snapshot.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
doFailTask(BlockSnapshot.class, snapshot, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
private void addPostRestoreVolumeSteps(Workflow workflow, StorageSystem system, Volume sourceVolume,
BlockSnapshot blockSnapshot, String waitFor) {
_log.info("Creating post restore volume steps");
if (Type.vmax.toString().equalsIgnoreCase(system.getSystemType())) {
_log.info("Adding terminate restore session post-step for VMAX snapshot {}", blockSnapshot.getId());
String description = String.format("Terminating VMAX restore session from %s to %s", blockSnapshot.getId(),
sourceVolume.getId());
workflow.createStep(POST_BLOCK_VOLUME_RESTORE_GROUP, description, waitFor,
system.getId(), system.getSystemType(), BlockDeviceController.class,
terminateRestoreSessionsMethod(system.getId(), sourceVolume.getId(), blockSnapshot.getId()),
rollbackMethodNullMethod(), null);
}
}
public static Workflow.Method terminateRestoreSessionsMethod(URI storage, URI source, URI snapshot) {
return new Workflow.Method(TERMINATE_RESTORE_SESSIONS_METHOD, storage, source, snapshot);
}
public boolean terminateRestoreSessions(URI storage, URI source, URI snapshot, String opId) {
_log.info("Terminating restore sessions for snapshot: {}", snapshot);
TaskCompleter completer = null;
try {
StorageSystem storageDevice = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);
completer = new SimpleTaskCompleter(BlockSnapshot.class, snapshot, opId);
WorkflowStepCompleter.stepExecuting(opId);
// Synchronous operation
getDevice(storageDevice.getSystemType()).doTerminateAnyRestoreSessions(storageDevice, source, snapObj,
completer);
completer.ready(_dbClient);
} catch (Exception e) {
_log.error(
String.format("Terminate restore sessions step failed - storage: %s, volume: %s, snapshot: %s",
storage.toString(), source.toString(), snapshot.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
doFailTask(BlockSnapshot.class, snapshot, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
public Workflow.Method createMirrorMethod(URI storage, List<URI> mirrorList, Boolean isCG, Boolean createInactive) {
return new Workflow.Method("createMirror", storage, mirrorList, isCG, createInactive);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method createMirrorMethod just above (except
* opId).
*/
@Override
public void createMirror(URI storage, List<URI> mirrorList, Boolean isCG, Boolean createInactive, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
if (!isCG && mirrorList.size() == 1) {
completer = new BlockMirrorCreateCompleter(mirrorList.get(0), opId);
getDevice(storageObj.getSystemType()).doCreateMirror(storageObj, mirrorList.get(0), createInactive, completer);
} else {
boolean isListReplicaFlow = false;
BlockMirror mirrorObj = _dbClient.queryObject(BlockMirror.class, mirrorList.get(0));
Volume sourceVolume = _dbClient.queryObject(Volume.class, mirrorObj.getSource().getURI());
/**
* VPLEX/RP CG volumes may not be having back end Array Group.
* In this case we should create element replica using createListReplica.
* We should not use createGroup replica as backend cg will not be available in this case.
*/
isListReplicaFlow = isListReplicaFlow(sourceVolume);
completer = new BlockMirrorCreateCompleter(mirrorList, opId);
if (!isListReplicaFlow) {
getDevice(storageObj.getSystemType()).doCreateGroupMirrors(storageObj, mirrorList, createInactive, completer);
} else {
// List Replica
getDevice(storageObj.getSystemType()).doCreateListReplica(storageObj, mirrorList, createInactive, completer);
}
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method rollbackMirrorMethod(URI storage, List<URI> mirrorList) {
return new Workflow.Method("rollbackMirror", storage, mirrorList);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method rollbackMirrorMethod just above
* (except opId).
*/
public void rollbackMirror(URI storage, List<URI> mirrorList, String taskId) {
WorkflowStepCompleter.stepExecuting(taskId);
try {
List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
boolean isCG = isCGMirror(mirrorList.get(0), _dbClient);
List<BlockMirror> mirrorsNoRollback = new ArrayList<BlockMirror>();
for (BlockMirror mirror : mirrors) {
// for CG mirror, filter out mirrors with invalid replication group. It is necessary as the
// fracture/detach/delete will
// expect mirrors with valid replication group
// for non CG mirror, filter out mirror with no native Id
if ((isCG && NullColumnValueGetter.isNullValue(mirror.getReplicationGroupInstance()) || (!isCG && isNullOrEmpty(mirror
.getNativeId())))) {
mirror.setInactive(true);
mirrorsNoRollback.add(mirror);
}
}
if (!mirrorsNoRollback.isEmpty()) {
_dbClient.updateObject(mirrorsNoRollback);
mirrors.removeAll(mirrorsNoRollback);
}
if (!mirrors.isEmpty()) {
List<URI> mirrorURIsToRollback = new ArrayList<URI>(transform(mirrors, FCTN_MIRROR_TO_URI));
String mirrorNativeIds = Joiner.on(", ").join(transform(mirrors, fctnBlockObjectToNativeID()));
if (mirrorIsPausable(mirrors)) {
_log.info("Attempting to fracture {} for rollback", mirrorNativeIds);
fractureMirror(storage, mirrorURIsToRollback, isCG, false, taskId);
}
_log.info("Attempting to detach {} for rollback", mirrorNativeIds);
detachMirror(storage, mirrorURIsToRollback, isCG, false, taskId);
_log.info("Attempting to delete {} for rollback", mirrorNativeIds);
deleteMirror(storage, mirrorURIsToRollback, isCG, taskId);
}
WorkflowStepCompleter.stepSucceded(taskId);
} catch (InternalException ie) {
_log.error(String.format("rollbackMirror Failed - Array:%s, Mirror:%s", storage, Joiner.on("\t").join(mirrorList)));
doFailTask(Volume.class, mirrorList, taskId, ie);
WorkflowStepCompleter.stepFailed(taskId, ie);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, mirrorList, taskId, serviceError);
}
}
@Override
public void attachNativeContinuousCopies(URI storage, URI sourceVolume, List<URI> mirrorList, String opId) throws ControllerException {
_log.info("START attach continuous copies workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, ATTACH_MIRRORS_WF_NAME, true, opId);
TaskCompleter taskCompleter = null;
Volume sourceVolumeObj = _dbClient.queryObject(Volume.class, sourceVolume);
boolean isCG = sourceVolumeObj.isInCG();
try {
addStepsForCreateMirrors(workflow, null, storage, sourceVolume, mirrorList, isCG);
taskCompleter = new BlockMirrorTaskCompleter(BlockMirror.class, mirrorList, opId);
workflow.executePlan(taskCompleter, "Successfully attached continuous copies");
} catch (Exception e) {
String msg = String.format("Failed to execute attach continuous copies workflow for volume %s",
sourceVolume);
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
@Override
public void detachNativeContinuousCopies(URI storage, List<URI> mirrors, List<URI> promotees,
String opId) throws ControllerException {
_log.info("START detach continuous copies workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, DETACH_MIRRORS_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
try {
addStepsForPromoteMirrors(workflow, null, mirrors, promotees);
// There is a task for the source volume, as well as for each newly promoted volume
List<URI> volumesWithTasks = new ArrayList<URI>(promotees);
volumesWithTasks.addAll(getSourceVolumesFromURIs(mirrors));
taskCompleter = new BlockMirrorTaskCompleter(Volume.class, volumesWithTasks, opId);
ControllerUtils.checkMirrorConsistencyGroup(mirrors, _dbClient, taskCompleter);
workflow.executePlan(taskCompleter, "Successfully detached continuous copies");
} catch (Exception e) {
List<Volume> promotedVolumes = _dbClient.queryObject(Volume.class, promotees);
for (Volume promotedVolume : promotedVolumes) {
promotedVolume.setInactive(true);
}
_dbClient.updateObject(promotedVolumes);
String msg = String.format("Failed to execute detach continuous copies workflow for mirrors: %s", mirrors);
_log.error(msg, e);
}
}
private List<URI> getSourceVolumesFromURIs(List<URI> mirrorList) {
List<URI> sourceVolumes = new ArrayList<URI>();
for (URI mirror : mirrorList) {
BlockMirror mirrorObj = _dbClient.queryObject(BlockMirror.class, mirror);
sourceVolumes.add(mirrorObj.getSource().getURI());
}
return sourceVolumes;
}
private List<URI> getSourceVolumes(List<BlockMirror> mirrorList) {
List<URI> sourceVolumes = new ArrayList<URI>();
for (BlockMirror mirror : mirrorList) {
sourceVolumes.add(mirror.getSource().getURI());
}
return sourceVolumes;
}
public Workflow.Method removeMirrorFromGroupMethod(URI storage, List<URI> mirrorList) {
return new Workflow.Method("removeMirrorFromGroup", storage, mirrorList);
}
public void removeMirrorFromGroup(URI storage, List<URI> mirrorList, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorTaskCompleter(BlockMirror.class, mirrorList, opId);
getDevice(storageObj.getSystemType()).doRemoveMirrorFromDeviceMaskingGroup(storageObj,
mirrorList, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method fractureMirrorMethod(URI storage, List<URI> mirrorList, Boolean isCG, Boolean sync) {
return new Workflow.Method("fractureMirror", storage, mirrorList, isCG, sync);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method fractureMirrorMethod just above
* (except opId).
*/
public void fractureMirror(URI storage, List<URI> mirrorList, Boolean isCG, Boolean sync, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorFractureCompleter(mirrorList, opId);
if (!isCG) {
getDevice(storageObj.getSystemType()).doFractureMirror(storageObj, mirrorList.get(0), sync, completer);
} else {
completer.addConsistencyGroupId(ConsistencyGroupUtils.getMirrorsConsistencyGroup(mirrorList, _dbClient).getId());
getDevice(storageObj.getSystemType()).doFractureGroupMirrors(storageObj, mirrorList, sync, completer);
}
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
private boolean isCGMirror(URI mirrorURI, DbClient dbClient) {
BlockMirror mirror = dbClient.queryObject(BlockMirror.class, mirrorURI);
Volume sourceVolume = dbClient.queryObject(Volume.class, mirror.getSource());
return sourceVolume.isInCG();
}
@Override
public void pauseNativeContinuousCopies(URI storage, List<URI> mirrors, Boolean sync,
String opId) throws ControllerException {
_log.info("START pause continuous copies workflow");
boolean isCG = isCGMirror(mirrors.get(0), _dbClient);
if (mirrors.size() == 1 || isCG) {
fractureMirror(storage, mirrors, isCG, sync, opId);
return;
}
Workflow workflow = _workflowService.getNewWorkflow(this, PAUSE_MIRRORS_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, mirrors.get(0));
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
try {
for (URI mirrorUri : mirrors) {
BlockMirror blockMirror = _dbClient.queryObject(BlockMirror.class, mirrorUri);
if (!mirrorIsPausable(asList(blockMirror))) {
String errorMsg = format("Can not pause continuous copy %s with synchronization state %s for volume %s",
blockMirror.getId(), blockMirror.getSyncState(), blockMirror.getSource().getURI());
_log.error(errorMsg);
String opName = ResourceOperationTypeEnum.PAUSE_NATIVE_CONTINUOUS_COPIES.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
WorkflowStepCompleter.stepFailed(opId, serviceError);
throw new IllegalStateException(errorMsg);
}
workflow.createStep("pauseMirror", "pause mirror", null, storage, storageObj.getSystemType(),
this.getClass(), fractureMirrorMethod(storage, asList(mirrorUri), isCG, sync), null, null);
}
taskCompleter = new BlockMirrorTaskCompleter(Volume.class, asList(mirror.getSource().getURI()), opId);
workflow.executePlan(taskCompleter, "Successfully paused continuous copies");
} catch (Exception e) {
String msg = String.format("Failed to execute pause continuous copies workflow for volume %s",
mirror.getSource().getURI());
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
@Override
public void resumeNativeContinuousCopies(URI storage, List<URI> mirrors, String opId) throws ControllerException {
_log.info("START resume continuous copies workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, RESUME_MIRRORS_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
List<BlockMirror> mirrorList = _dbClient.queryObject(BlockMirror.class, mirrors);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
List<URI> sourceVolumes = getSourceVolumes(mirrorList);
try {
taskCompleter = new BlockMirrorTaskCompleter(Volume.class, sourceVolumes, opId);
boolean isCG = ControllerUtils.checkMirrorConsistencyGroup(mirrors, _dbClient, taskCompleter);
if (!isCG) {
for (BlockMirror blockMirror : mirrorList) {
if (SynchronizationState.FRACTURED.toString().equals(blockMirror.getSyncState())) {
workflow.createStep("resumeStep", "resume", null, storage, storageObj.getSystemType(),
this.getClass(), resumeNativeContinuousCopyMethod(storage, asList(blockMirror.getId()), isCG), null, null);
}
}
} else {
if (hasFracturedState(mirrorList)) {
workflow.createStep("resumeStep", "resume", null, storage, storageObj.getSystemType(),
this.getClass(), resumeNativeContinuousCopyMethod(storage, mirrors, isCG), null, null);
}
}
workflow.executePlan(taskCompleter, "Successfully resumed continuous copies");
} catch (Exception e) {
String msg = String.format("Failed to execute resume continuous copies workflow for volume %s",
Joiner.on("\t").join(sourceVolumes));
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
// Any of mirrors in Fractured state will return true
private boolean hasFracturedState(List<BlockMirror> mirrorList) {
for (BlockMirror mirror : mirrorList) {
if (SynchronizationState.FRACTURED.toString().equals(mirror.getSyncState())) {
return true;
}
}
return false;
}
private Workflow.Method resumeNativeContinuousCopyMethod(URI storage, List<URI> mirrorList, Boolean isCG) {
return new Workflow.Method("resumeNativeContinuousCopy", storage, mirrorList, isCG);
}
public void resumeNativeContinuousCopy(URI storage, List<URI> mirrorList, Boolean isCG, String opId) throws ControllerException {
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter completer = new BlockMirrorResumeCompleter(mirrorList, opId);
if (!isCG) {
getDevice(storageObj.getSystemType()).doResumeNativeContinuousCopy(storageObj, mirrorList.get(0), completer);
} else {
completer.addConsistencyGroupId(ConsistencyGroupUtils.getMirrorsConsistencyGroup(mirrorList, _dbClient).getId());
getDevice(storageObj.getSystemType()).doResumeGroupNativeContinuousCopies(storageObj, mirrorList, completer);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method detachMirrorMethod(URI storage, List<URI> mirrorList, Boolean isCG) {
return new Workflow.Method("detachMirror", storage, mirrorList, isCG, true);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method detachMirrorMethod just above (except
* opId).
*/
@Override
public void detachMirror(URI storage, List<URI> mirrorList, Boolean isCG,
Boolean deleteGroup, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
_log.info("Start detach Mirror for mirror {}, isCG {}", mirrorList, isCG);
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorDetachCompleter(mirrorList, opId);
if (!isCG) {
getDevice(storageObj.getSystemType()).doDetachMirror(storageObj, mirrorList.get(0), completer);
} else {
completer.addConsistencyGroupId(ConsistencyGroupUtils.getMirrorsConsistencyGroup(mirrorList, _dbClient).getId());
getDevice(storageObj.getSystemType()).doDetachGroupMirrors(storageObj, mirrorList, deleteGroup, completer);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public String addStepsForDetachMirror(Workflow workflow, String waitFor,
String stepGroup, List<URI> mirrorList, Boolean isCG) throws ControllerException {
List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
URI controller = mirrors.get(0).getStorageController();
String stepId = null;
// Optionally create a step to pause (fracture) the mirror
if (mirrorIsPausable(mirrors)) {
stepId = workflow.createStep(stepGroup,
String.format("Fracture mirror: %s", mirrorList.get(0)),
waitFor, controller, getDeviceType(controller),
this.getClass(),
fractureMirrorMethod(controller, mirrorList, isCG, false),
null, null);
}
// Create a step to detach the mirror
stepId = workflow.createStep(stepGroup,
String.format("Detach mirror: %s", mirrorList.get(0)),
stepId == null ? waitFor : stepId,
controller, getDeviceType(controller),
this.getClass(),
detachMirrorMethod(controller, mirrorList, isCG),
null, null);
return stepId;
}
private String addStepsToRemoveMirrorFromGroup(Workflow workflow,
String waitFor, String stepGroup, List<URI> mirrorList) {
List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
URI controller = mirrors.get(0).getStorageController();
String stepId = workflow.createStep(stepGroup,
String.format("Remove mirror from DeviceMaskingGroup: %s", mirrorList.get(0)),
waitFor, controller, getDeviceType(controller),
this.getClass(),
removeMirrorFromGroupMethod(controller, mirrorList),
null, null);
return stepId;
}
public static final String PROMOTE_MIRROR_STEP_GROUP = "BlockDevicePromoteMirror";
/**
* Adds the additional steps necessary to promote mirrors to regular block volumes
*
* @param workflow
* @param waitFor
* @param descriptors
* @param promotees
* @return
* @throws ControllerException
*/
public String addStepsForPromoteMirrors(Workflow workflow, String waitFor,
List<URI> mirrorList, List<URI> promotees)
throws ControllerException {
boolean isCG = isCGMirror(mirrorList.get(0), _dbClient);
List<Volume> promotedVolumes = _dbClient.queryObject(Volume.class, promotees);
if (!isCG) {
List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
for (BlockMirror mirror : mirrors) {
URI controller = mirror.getStorageController();
// Add steps for detaching the mirror
String stepId = addStepsForDetachMirror(workflow, waitFor, PROMOTE_MIRROR_STEP_GROUP, mirrorList, isCG);
// Find the volume this mirror will be promoted to
URI promotedVolumeForMirror = findPromotedVolumeForMirror(mirror.getId(), promotedVolumes);
// Create a step for promoting the mirror.
stepId = workflow.createStep(PROMOTE_MIRROR_STEP_GROUP,
String.format("Promote mirror: %s", mirror.getId()),
stepId, controller, getDeviceType(controller),
this.getClass(),
promoteMirrorMethod(asList(mirror.getId()), asList(promotedVolumeForMirror), isCG),
null, null);
}
} else {
BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, mirrorList.get(0));
URI controller = mirror.getStorageController();
// Add steps for detaching the mirror
String stepId = addStepsForDetachMirror(workflow, waitFor, PROMOTE_MIRROR_STEP_GROUP, mirrorList, isCG);
// Find the volumes this set of mirrors will be promoted to
List<URI> promotedVolumesForMirrors = findPromotedVolumesForMirrors(mirrorList, promotedVolumes);
// Create a step for promoting the mirrors.
stepId = workflow.createStep(PROMOTE_MIRROR_STEP_GROUP,
String.format("Promote mirrors: %s", Joiner.on("\t").join(mirrorList)),
stepId, controller, getDeviceType(controller),
this.getClass(),
promoteMirrorMethod(mirrorList, promotedVolumesForMirrors, isCG),
null, null);
}
return PROMOTE_MIRROR_STEP_GROUP;
}
private URI findPromotedVolumeForMirror(URI mirror, List<Volume> promotedVolumes) {
for (Volume promotee : promotedVolumes) {
OpStatusMap statusMap = promotee.getOpStatus();
for (Map.Entry<String, Operation> entry : statusMap.entrySet()) {
Operation operation = entry.getValue();
if (operation.getAssociatedResourcesField().contains(mirror.toString())) {
return promotee.getId();
}
}
}
throw new IllegalStateException("No volume available for the promotion of mirror " + mirror);
}
private List<URI> findPromotedVolumesForMirrors(List<URI> mirrorList, List<Volume> promotedVolumes) {
List<URI> orderedPromotedVolumes = new ArrayList<URI>(mirrorList.size());
for (URI mirror : mirrorList) {
orderedPromotedVolumes.add(findPromotedVolumeForMirror(mirror, promotedVolumes));
}
return orderedPromotedVolumes;
}
public Workflow.Method promoteMirrorMethod(List<URI> mirrorList, List<URI> promotedVolumeList, Boolean isCG) {
return new Workflow.Method("promoteMirror", mirrorList, promotedVolumeList, isCG);
}
public void promoteMirror(List<URI> mirrorList, List<URI> promotedVolumeList, Boolean isCG, String opId) {
_log.info("START promoteMirror");
Volume promoted = null;
try {
List<BlockMirror> mirrors = new ArrayList<BlockMirror>(mirrorList.size());
List<Volume> promotedVolumes = new ArrayList<Volume>(mirrorList.size());
int count = 0;
for (URI id : mirrorList) {
BlockMirror mirror = _dbClient.queryObject(BlockMirror.class, id);
Volume source = _dbClient.queryObject(Volume.class, mirror.getSource().getURI());
String promotedLabel = ControllerUtils.getMirrorLabel(source.getLabel(), mirror.getLabel());
if (isCG) {
promotedLabel = mirror.getLabel();
}
promoted = VolumeFactory.newInstance(mirror);
promoted.setId(promotedVolumeList.get(count++));
promoted.setLabel(promotedLabel);
promotedVolumes.add(promoted);
_log.info("Promoted mirror {} to volume {}", mirror.getId(), promoted.getId());
// If there are exports masks/export groups associated, then
// remove the mirror from them and add the promoted volume.
ExportUtils.updatePromotedMirrorExports(mirror, promoted, _dbClient);
mirror.setInactive(true);
mirrors.add(mirror);
}
_dbClient.updateObject(mirrors);
_dbClient.updateObject(promotedVolumes);
WorkflowStepCompleter.stepSucceded(opId);
} catch (Exception e) {
String msg = String.format("Failed to promote mirror %s", Joiner.on("\t").join(mirrorList));
_log.error(msg, e);
WorkflowStepCompleter.stepFailed(opId, DeviceControllerException.exceptions.stopVolumeMirrorFailed(mirrorList.get(0)));
}
}
public Workflow.Method deleteMirrorMethod(URI storage, List<URI> mirrorList, Boolean isCG) {
return new Workflow.Method("deleteMirror", storage, mirrorList, isCG);
}
/**
* {@inheritDoc} NOTE NOTE: The signature here MUST match the Workflow.Method deleteMirrorMethod just above (except
* opId).
*/
@Override
public void deleteMirror(URI storage, List<URI> mirrorList, Boolean isCG, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorDeleteCompleter(mirrorList, opId);
if (!isCG) {
getDevice(storageObj.getSystemType()).doDeleteMirror(storageObj, mirrorList.get(0), completer);
} else {
getDevice(storageObj.getSystemType()).doDeleteGroupMirrors(storageObj, mirrorList, completer);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
@Override
public void createConsistencyGroup(URI storage, URI consistencyGroup, String opId) throws ControllerException {
TaskCompleter completer = new BlockConsistencyGroupCreateCompleter(consistencyGroup, opId);
try {
WorkflowStepCompleter.stepExecuting(opId);
// Lock the CG for the step duration.
List<String> lockKeys = new ArrayList<String>();
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(_dbClient, consistencyGroup, storage));
_workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
// Check if already created, if not create, if so just complete.
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroup);
String groupName = ControllerUtils.generateReplicationGroupName(storageObj, cg, null, _dbClient);
if (!cg.created(storage, groupName)) {
getDevice(storageObj.getSystemType()).doCreateConsistencyGroup(storageObj, consistencyGroup, groupName, completer);
} else {
_log.info(String.format("Consistency group %s (%s) already created", cg.getLabel(), cg.getId()));
completer.ready(_dbClient);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
throw DeviceControllerException.exceptions.createConsistencyGroupFailed(e);
}
}
public Workflow.Method deleteConsistencyGroupMethod(URI storage, URI consistencyGroup, String groupName, Boolean keepRGName,
Boolean markInactive, Boolean throwErrorIfNotDeleted) {
return new Workflow.Method("deleteReplicationGroupInConsistencyGroup", storage, consistencyGroup, groupName, keepRGName,
markInactive, throwErrorIfNotDeleted);
}
@Override
public void deleteConsistencyGroup(URI storage, URI consistencyGroup, Boolean markInactive, String opId) throws ControllerException {
_log.info("START delete consistency group");
TaskCompleter wfCompleter = null;
try {
Workflow workflow = _workflowService.getNewWorkflow(this, "deleteReplicationGroupInConsistencyGroup", true, opId);
wfCompleter = new SimpleTaskCompleter(BlockConsistencyGroup.class, consistencyGroup, opId);
StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage);
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroup);
Set<String> groupNames = BlockConsistencyGroupUtils.getGroupNamesForSystemCG(cg, system);
String stepId = null;
for (String groupName : groupNames) {
Workflow.Method deleteStep = new Workflow.Method("deleteReplicationGroupInConsistencyGroup",
storage, consistencyGroup, groupName, false, markInactive, true);
stepId = workflow.createStep("DeleteReplicationGroup", "Deleting replication group", stepId, storage,
system.getSystemType(), this.getClass(), deleteStep, rollbackMethodNullMethod(), null);
}
String successMsg = String.format("Successfully deleted replication groups %s", Joiner.on(',').join(groupNames));
workflow.executePlan(wfCompleter, successMsg);
} catch (Exception e) {
if (wfCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
wfCompleter.error(_dbClient, serviceError);
}
throw DeviceControllerException.exceptions.deleteConsistencyGroupFailed(e);
}
}
public void deleteReplicationGroupInConsistencyGroup(URI storage, URI consistencyGroup, String groupName, Boolean keepRGName,
Boolean markInactive, Boolean throwErrorIfNotDeleted, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
completer = new BlockConsistencyGroupDeleteCompleter(consistencyGroup, storage, groupName, keepRGName, markInactive, opId);
List<String> lockKeys = new ArrayList<String>();
if (groupName != null && !groupName.isEmpty()) {
lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, groupName, storage));
} else if (!NullColumnValueGetter.isNullURI(consistencyGroup)) {
// Lock the CG for the step duration.
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(_dbClient, consistencyGroup, storage));
}
if (!lockKeys.isEmpty()) {
_workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
}
// Check if there is any members in the replication group before delete it.
if (groupName != null && !groupName.isEmpty()) {
List<Volume> groupVolumes = ControllerUtils.getVolumesPartOfRG(storage, groupName, _dbClient);
if (groupVolumes != null && !groupVolumes.isEmpty()) {
String msg = String.format("The replication group %s still have volumes, will not delete the replication group",
groupName);
_log.warn(msg);
if (throwErrorIfNotDeleted) {
completer.error(_dbClient, DeviceControllerException.exceptions.couldNotDeleteReplicationGroup(msg));
} else {
completer.ready(_dbClient);
}
return;
}
}
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
getDevice(storageObj.getSystemType()).doDeleteConsistencyGroup(storageObj, consistencyGroup, groupName, keepRGName,
markInactive, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
throw DeviceControllerException.exceptions.deleteConsistencyGroupFailed(e);
}
}
/**
* An orchestration controller method for detaching and deleting a mirror
*
* @param storage
* URI of storage controller.
* @param mirrorList
* List of URIs of block mirrors
* @param promotees
* List of URIs of promoted volumes
* @param isCG
* CG mirror or not
* @param opId
* Operation ID
* @throws ControllerException
*/
@Override
public void deactivateMirror(URI storage, List<URI> mirrorList, List<URI> promotees, Boolean isCG, String opId)
throws ControllerException {
_log.info("deactivateMirror: START");
TaskCompleter taskCompleter = null;
String mirrorStr = Joiner.on("\t").join(mirrorList);
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
Workflow workflow = _workflowService.getNewWorkflow(this, "deactivateMirror", true, opId);
taskCompleter = new BlockMirrorDeactivateCompleter(mirrorList, promotees, opId);
ControllerUtils.checkMirrorConsistencyGroup(mirrorList, _dbClient, taskCompleter);
String detachStep = workflow.createStepId();
Workflow.Method detach = detachMirrorMethod(storage, mirrorList, isCG);
workflow.createStep("deactivate", "detaching mirror volume: " + mirrorStr, null, storage,
storageSystem.getSystemType(), getClass(), detach, null, detachStep);
// for single volume mirror, the mirror will be deleted
List<URI> mirrorsToDelete = mirrorList;
// for group mirror, find mirrors to be deleted and mirrors to be promoted, and do the promotion
if (isCG) {
mirrorsToDelete = new ArrayList<URI>();
List<Volume> promotedVolumes = _dbClient.queryObject(Volume.class, promotees);
List<URI> orderedMirrorsToPromote = new ArrayList<URI>();
List<URI> orderedPromotedVolumes = new ArrayList<URI>();
for (URI mirror : mirrorList) {
URI promotedVolume = null;
for (Volume promotee : promotedVolumes) {
OpStatusMap statusMap = promotee.getOpStatus();
for (Map.Entry<String, Operation> entry : statusMap.entrySet()) {
Operation operation = entry.getValue();
if (operation.getAssociatedResourcesField().contains(mirror.toString())) {
promotedVolume = promotee.getId();
}
}
}
if (promotedVolume != null) {
orderedMirrorsToPromote.add(mirror);
orderedPromotedVolumes.add(promotedVolume);
} else {
mirrorsToDelete.add(mirror);
}
}
if (!orderedMirrorsToPromote.isEmpty()) {
// Create a step for promoting the mirrors.
String stepId = workflow.createStep(PROMOTE_MIRROR_STEP_GROUP,
String.format("Promote mirrors : %s", Joiner.on("\t").join(orderedMirrorsToPromote)),
detachStep, storage, storageSystem.getSystemType(),
this.getClass(),
promoteMirrorMethod(orderedMirrorsToPromote, orderedPromotedVolumes, isCG),
null, null);
}
}
String deleteStep = workflow.createStepId();
Workflow.Method delete = deleteMirrorMethod(storage, mirrorsToDelete, isCG);
workflow.createStep("deactivate", "deleting mirror volume: " + Joiner.on("\t").join(mirrorsToDelete), detachStep, storage,
storageSystem.getSystemType(), getClass(), delete, null, deleteStep);
String successMessage = String.format("Successfully deactivated mirror %s on StorageArray %s",
mirrorStr, storage);
workflow.executePlan(taskCompleter, successMessage);
} catch (Exception e) {
if (_log.isErrorEnabled()) {
String msg = String.format("Deactivate mirror failed for mirror %s", mirrorStr);
_log.error(msg);
}
if (taskCompleter != null) {
String opName = ResourceOperationTypeEnum.DEACTIVATE_VOLUME_MIRROR.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
taskCompleter.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.deactivateMirrorFailed(e);
}
}
}
@Override
public void establishVolumeAndNativeContinuousCopyGroupRelation(URI storage, URI sourceVolume, URI mirror, String opId)
throws ControllerException {
_log.info("START establishVolumeAndNativeContinuousCopyGroupRelation workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, ESTABLISH_VOLUME_MIRROR_GROUP_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
try {
workflow.createStep("establishStep", "create group relation between Volume group and Mirror group", null, storage,
storageObj.getSystemType(),
this.getClass(), establishVolumeAndNativeContinuousCopyGroupRelationMethod(storage, sourceVolume, mirror), null, null);
taskCompleter = new BlockMirrorTaskCompleter(Volume.class, asList(sourceVolume), opId);
workflow.executePlan(taskCompleter, "Successfully created group relation between Volume group and Mirror group");
} catch (Exception e) {
String msg = String.format("Failed to create group relation between Volume group and Mirror group."
+ "Source volume: %s, Mirror: %s",
sourceVolume, mirror);
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
private Workflow.Method establishVolumeAndNativeContinuousCopyGroupRelationMethod(URI storage, URI sourceVolume, URI mirror) {
return new Workflow.Method("establishVolumeNativeContinuousCopyGroupRelation", storage, sourceVolume, mirror);
}
public void establishVolumeNativeContinuousCopyGroupRelation(
URI storage, URI sourceVolume, URI mirror, String opId)
throws ControllerException {
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter completer = new BlockMirrorTaskCompleter(BlockMirror.class, mirror, opId);
getDevice(storageObj.getSystemType())
.doEstablishVolumeNativeContinuousCopyGroupRelation(
storageObj, sourceVolume, mirror, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
static final String FULL_COPY_WORKFLOW = "fullCopyVolumes";
static final String FULL_COPY_CREATE_STEP_GROUP = "createFullCopiesStepGroup";
static final String SNAPSHOT_SESSION_CREATE_STEP_GROUP = "createSnapshotSessionStepGroup";
static final String FULL_COPY_WFS_STEP_GROUP = "waitForSyncStepGroup";
static final String FULL_COPY_DETACH_STEP_GROUP = "detachFullCopyStepGroup";
static final String FULL_COPY_FRACTURE_STEP_GROUP = "fractureFullCopyStepGroup";
static final String SNAPSHOT_DELETE_STEP_GROUP = "deleteSnapshotStepGroup";
static final String MIRROR_FRACTURE_STEP_GROUP = "fractureMirrorStepGroup";
static final String MIRROR_DETACH_STEP_GROUP = "detachMirrorStepGroup";
static final String FULL_COPY_CREATE_ORCHESTRATION_STEP = "createFullCopiesOrchestrationStep";
static final String SNAPSHOT_SESSION_CREATE_ORCHESTRATION_STEP = "createSnapshotSessionOrchestrationStep";
@Override
public void createFullCopy(URI storage, List<URI> fullCopyVolumes, Boolean createInactive,
String taskId)
throws ControllerException {
_log.info("START fullCopyVolumes");
TaskCompleter taskCompleter = new CloneCreateWorkflowCompleter(fullCopyVolumes, taskId);
Volume clone = _dbClient.queryObject(Volume.class, fullCopyVolumes.get(0));
URI sourceVolume = clone.getAssociatedSourceVolume();
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
Workflow workflow = _workflowService.getNewWorkflow(this, FULL_COPY_WORKFLOW, true, taskId);
boolean isCG = false;
Volume source = URIUtil.isType(sourceVolume, Volume.class) ? _dbClient.queryObject(Volume.class, sourceVolume) : null;
VolumeGroup volumeGroup = (source != null)
? source.getApplication(_dbClient) : null;
if (volumeGroup != null) {
/**
* If a Volume is in Volume Group (COPY type),
* Query all volumes belonging to that Volume Group,
* Group full-copies by Array Replication Group and create workflow step for each Array Group,
* these steps runs in parallel
*/
_log.info("Creating full copy for Application {}", volumeGroup.getLabel());
createFullCopyForApplicationCGs(workflow, volumeGroup, fullCopyVolumes, createInactive, taskCompleter);
} else if (checkCloneConsistencyGroup(fullCopyVolumes.get(0), _dbClient, taskCompleter)) {
// check if the clone is in a CG
isCG = true;
_log.info("Creating group full copy");
createCGFullCopy(storage, sourceVolume, fullCopyVolumes, storageSystem, workflow, createInactive, isCG);
} else {
for (URI uri : fullCopyVolumes) {
Workflow.Method createMethod = createFullCopyVolumeMethod(storage, sourceVolume, Arrays.asList(uri), createInactive,
isCG);
Workflow.Method rollbackMethod = rollbackFullCopyVolumeMethod(storage, asList(uri));
workflow.createStep(FULL_COPY_CREATE_STEP_GROUP, "Creating full copy", null, storage,
storageSystem.getSystemType(), getClass(), createMethod,
rollbackMethod, null);
// For driver managed arrays, we rely on drivers to complete synchronization if needed and to set
// clone state.
if (!createInactive && !getDriverManager().isDriverManaged(storageSystem.getSystemType())) {
// After all full copies have been created, wait for synchronization to complete
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, Arrays.asList(uri), isCG);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", FULL_COPY_CREATE_STEP_GROUP, storage,
storageSystem.getSystemType(), getClass(), waitForSyncMethod, rollbackMethodNullMethod(), null);
Volume cloneVol = _dbClient.queryObject(Volume.class, uri);
BlockObject sourceObj = BlockObject.fetch(_dbClient, cloneVol.getAssociatedSourceVolume());
// detach if source is snapshot, or storage system is not vmax/vnx/hds
if (storageSystem.deviceIsType(Type.openstack)) {
setCloneReplicaStateStep(workflow, storageSystem, asList(uri), waitForSyncStep, ReplicationState.SYNCHRONIZED);
} else if (sourceObj instanceof BlockSnapshot
|| !(storageSystem.deviceIsType(Type.vmax) || storageSystem.deviceIsType(Type.hds)
|| storageSystem.deviceIsType(Type.vnxblock))) {
Workflow.Method detachMethod = detachFullCopyMethod(storage, asList(uri));
workflow.createStep(FULL_COPY_DETACH_STEP_GROUP, "Detaching full copy", waitForSyncStep,
storage, storageSystem.getSystemType(), getClass(), detachMethod, rollbackMethodNullMethod(), null);
} else if (storageSystem.deviceIsType(Type.vnxblock)) {
workflow.createStep(FULL_COPY_FRACTURE_STEP_GROUP, "fracture full copy", waitForSyncStep,
storage, storageSystem.getSystemType(), BlockDeviceController.class,
fractureCloneMethod(storage, Arrays.asList(uri), isCG), rollbackMethodNullMethod(), null);
} else {
setCloneReplicaStateStep(workflow, storageSystem, asList(uri), waitForSyncStep, ReplicationState.SYNCHRONIZED);
}
}
}
}
String successMsg = String.format("Full copy of %s to %s successful", sourceVolume, fullCopyVolumes);
workflow.executePlan(taskCompleter, successMsg);
} catch (InternalException e) {
_log.error("Failed to create full copy of volume", e);
doFailTask(Volume.class, sourceVolume, taskId, e);
WorkflowStepCompleter.stepFailed(taskId, e);
} catch (Exception e) {
_log.error("Failed to create full copy of volume", e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, sourceVolume, taskId, serviceError);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
}
}
/**
* Create Full Copies for volumes in Volume Group (COPY type),
* Query all volumes belonging to that Volume Group,
* Group full-copies by Array Replication Group and create workflow step for each Array Group,
* these steps runs in parallel
*/
private void createFullCopyForApplicationCGs(Workflow workflow, VolumeGroup volumeGroup,
List<URI> fullCopyVolumes, Boolean createInactive, TaskCompleter taskCompleter) {
boolean isCG = true; // VolumeGroup Volumes will be in CG
// add VolumeGroup to taskCompleter
taskCompleter.addVolumeGroupId(volumeGroup.getId());
List<Volume> allVolumes = ControllerUtils.getVolumeGroupVolumes(_dbClient, volumeGroup);
Map<String, List<Volume>> arrayGroupToVolumes = ControllerUtils.groupVolumesByArrayGroup(allVolumes, _dbClient);
for (String arrayGroupName : arrayGroupToVolumes.keySet()) { // AG - Array Group
List<Volume> arrayGroupVolumes = arrayGroupToVolumes.get(arrayGroupName);
List<URI> fullCopyVolumesAG = getFullCopiesForVolumes(fullCopyVolumes, arrayGroupVolumes);
if (fullCopyVolumesAG.isEmpty()) {
_log.debug("Looks Full copy not requested for array group {}", arrayGroupName);
// This is to support future case where there may be a request for subset of array groups
continue;
}
Volume sourceVolumeAG = arrayGroupVolumes.iterator().next();
// add CG to taskCompleter
if (!NullColumnValueGetter.isNullURI(sourceVolumeAG.getConsistencyGroup())) {
taskCompleter.addConsistencyGroupId(sourceVolumeAG.getConsistencyGroup());
}
URI storage = sourceVolumeAG.getStorageController();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
_log.info("Creating full copy for group {}", arrayGroupName);
createCGFullCopy(storage, sourceVolumeAG.getId(), fullCopyVolumesAG, storageSystem, workflow, createInactive,
isCG);
}
}
private void createCGFullCopy(URI storage, URI sourceVolume, List<URI> fullCopyVolumes, StorageSystem storageSystem,
Workflow workflow, Boolean createInactive, boolean isCG) {
Workflow.Method createMethod = createFullCopyVolumeMethod(storage, sourceVolume, fullCopyVolumes, createInactive, isCG);
Workflow.Method rollbackMethod = rollbackFullCopyVolumeMethod(storage, fullCopyVolumes);
String createFullCopyStep = workflow.createStep(FULL_COPY_CREATE_STEP_GROUP, "Creating full copy", null, storage,
storageSystem.getSystemType(), getClass(), createMethod,
rollbackMethod, null);
if (!createInactive) {
// After all full copies have been created, wait for synchronization to complete
if (!storageSystem.deviceIsType(Type.vnxblock)) {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, fullCopyVolumes, isCG);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", createFullCopyStep, storage,
storageSystem.getSystemType(), getClass(), waitForSyncMethod, rollbackMethodNullMethod(), null);
setCloneReplicaStateStep(workflow, storageSystem, fullCopyVolumes, waitForSyncStep, ReplicationState.SYNCHRONIZED);
} else {
String previousStep = createFullCopyStep;
for (URI cloneUri : fullCopyVolumes) {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, Arrays.asList(cloneUri),
false);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
storageSystem.getSystemType(), getClass(), waitForSyncMethod, rollbackMethodNullMethod(), null);
previousStep = waitForSyncStep;
}
workflow.createStep(FULL_COPY_FRACTURE_STEP_GROUP, "fracture full copy", previousStep,
storage, storageSystem.getSystemType(), BlockDeviceController.class,
fractureCloneMethod(storage, fullCopyVolumes, isCG), rollbackMethodNullMethod(), null);
}
}
}
public Workflow.Method createFullCopyVolumeMethod(URI storage, URI sourceVolume, List<URI> fullCopyVolumes,
Boolean createInactive, boolean isCG) {
return new Workflow.Method("createFullCopyVolume", storage, sourceVolume, fullCopyVolumes, createInactive, isCG);
}
public void createFullCopyVolume(URI storage, URI sourceVolume, List<URI> fullCopyVolumes, Boolean createInactive, boolean isCG,
String taskId) {
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter taskCompleter = new CloneCreateCompleter(fullCopyVolumes, taskId);
WorkflowStepCompleter.stepExecuting(taskId);
// if number of clones more than 1 we need to use createListReplica
if (isCG || fullCopyVolumes.size() > 1) {
boolean isListReplicaFlow = false;
Volume sourceVolumeObj = _dbClient.queryObject(Volume.class, sourceVolume);
// take a lock on the group and storage system to prevent two clones of the same group from being
// created at the same time
if (NullColumnValueGetter.isNotNullValue(sourceVolumeObj.getReplicationGroupInstance())) {
List<String> lockKeys = new ArrayList<>();
lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient,
sourceVolumeObj.getReplicationGroupInstance(), storage));
_workflowService.acquireWorkflowStepLocks(taskId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
}
/**
* VPLEX/RP CG volumes may not be having back end Array Group.
* In this case we should create element replica using createListReplica.
* We should not use createGroup replica as backend cg will not be available in this case.
*/
isListReplicaFlow = isListReplicaFlow(sourceVolumeObj);
if (!isListReplicaFlow) {
getDevice(storageSystem.getSystemType()).doCreateGroupClone(storageSystem, fullCopyVolumes,
createInactive, taskCompleter);
} else {
// List Replica
createListClone(storage, fullCopyVolumes, createInactive, taskId);
}
} else {
getDevice(storageSystem.getSystemType()).doCreateClone(storageSystem, sourceVolume, fullCopyVolumes.get(0),
createInactive, taskCompleter);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, fullCopyVolumes, taskId, serviceError);
}
}
private boolean isListReplicaFlow(Volume sourceVolumeObj) {
boolean isListReplicaFlow = false;
if (sourceVolumeObj != null && NullColumnValueGetter.isNotNullValue(sourceVolumeObj.getReplicationGroupInstance())
&& !ControllerUtils.checkCGCreatedOnBackEndArray(sourceVolumeObj)) {
isListReplicaFlow = true;
}
_log.info("isListReplicaFlow:{}", isListReplicaFlow);
return isListReplicaFlow;
}
public Workflow.Method rollbackFullCopyVolumeMethod(URI storage, List<URI> fullCopy) {
return new Workflow.Method("rollbackFullCopyVolume", storage, fullCopy);
}
public void rollbackFullCopyVolume(URI storage, List<URI> fullCopy, String taskId) {
WorkflowStepCompleter.stepExecuting(taskId);
List<Volume> volumes = _dbClient.queryObject(Volume.class, fullCopy);
try {
if (!isNullOrEmpty(volumes.get(0).getNativeId()) && !volumes.get(0).getInactive()) {
_log.info("Attempting to detach for rollback");
detachFullCopies(storage, fullCopy, taskId);
_log.info("Attempting to delete for rollback");
deleteVolumes(storage, fullCopy, taskId);
} else {
for (Volume volume : volumes) {
volume.setInactive(true);
_dbClient.updateObject(volume);
}
WorkflowStepCompleter.stepSucceded(taskId);
}
} catch (InternalException ie) {
_log.error(String.format("rollbackFullCopyVolume Failed - Array:%s, Volume:%s", storage, fullCopy));
doFailTask(Volume.class, fullCopy, taskId, ie);
WorkflowStepCompleter.stepFailed(taskId, ie);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, fullCopy, taskId, serviceError);
}
}
private static final String ACTIVATE_CLONE_WF_NAME = "ACTIVATE_CLONE_WORKFLOW";
private static final String ACTIVATE_CLONE_GROUP = "BlockDeviceActivateClone";
@Override
public void activateFullCopy(URI storage, List<URI> fullCopy, String opId) {
TaskCompleter completer = new CloneWorkflowCompleter(fullCopy, opId);
try {
// need to create a workflow to wait sync finish, then do fracture/activate
Workflow workflow = _workflowService.getNewWorkflow(this, ACTIVATE_CLONE_WF_NAME, false, opId);
_log.info("Created new activate workflow with operation id {}", opId);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
// add CG to taskCompleter
boolean isCG = checkCloneConsistencyGroup(fullCopy.get(0), _dbClient, completer);
if (storageSystem.deviceIsType(Type.vnxblock)) {
String previousStep = null;
if (isCG) {
for (URI cloneUri : fullCopy) {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, Arrays.asList(cloneUri),
false);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
storageSystem.getSystemType(), getClass(), waitForSyncMethod, null, null);
previousStep = waitForSyncStep;
}
} else {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, fullCopy, isCG);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
storageSystem.getSystemType(), getClass(), waitForSyncMethod, null, null);
previousStep = waitForSyncStep;
}
workflow.createStep(ACTIVATE_CLONE_GROUP, "Activating clone", previousStep,
storage, getDeviceType(storage), BlockDeviceController.class,
activateCloneMethod(storage, fullCopy), rollbackMethodNullMethod(), null);
} else {
workflow.createStep(ACTIVATE_CLONE_GROUP, "Activating clone", null,
storage, getDeviceType(storage), BlockDeviceController.class,
activateCloneMethod(storage, fullCopy), rollbackMethodNullMethod(), null);
}
_log.info("Executing Activate workflow");
String msg = String.format("Actitvate %s completed successfully", fullCopy.get(0));
workflow.executePlan(completer, msg);
} catch (Exception e) {
String msg = String.format("Could not activate the clone %s", fullCopy.get(0));
_log.error(msg, e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
}
public static Workflow.Method activateCloneMethod(URI storage, List<URI> clone) {
return new Workflow.Method("activateFullCopyStep", storage, clone);
}
public void activateFullCopyStep(URI storage, List<URI> fullCopy, String opId) {
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter taskCompleter = new CloneActivateCompleter(fullCopy, opId);
if (checkCloneConsistencyGroup(fullCopy.get(0), _dbClient, taskCompleter)) {
getDevice(storageSystem.getSystemType()).doActivateGroupFullCopy(storageSystem, fullCopy, taskCompleter);
} else {
getDevice(storageSystem.getSystemType()).doActivateFullCopy(storageSystem, fullCopy.get(0), taskCompleter);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
doFailTask(Volume.class, fullCopy, opId, serviceError);
}
}
public Workflow.Method detachFullCopyMethod(URI storage, List<URI> fullCopyVolume) {
return new Workflow.Method("detachFullCopies", storage, fullCopyVolume);
}
public void detachFullCopies(URI storage, List<URI> fullCopyVolumes, String taskId)
throws ControllerException {
_log.info("detach FullCopy: {}", fullCopyVolumes);
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter taskCompleter = new VolumeDetachCloneCompleter(fullCopyVolumes, taskId);
if (checkCloneConsistencyGroup(fullCopyVolumes.get(0), _dbClient, taskCompleter)) {
_log.info("detach group full copy");
getDevice(storageSystem.getSystemType()).doDetachGroupClone(storageSystem, fullCopyVolumes, taskCompleter);
} else {
getDevice(storageSystem.getSystemType()).doDetachClone(storageSystem, fullCopyVolumes.get(0),
taskCompleter);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, fullCopyVolumes, taskId, serviceError);
}
}
private static final String DETACH_CLONE_WF_NAME = "DETACH_CLONE_WORKFLOW";
@Override
public void detachFullCopy(URI storage, List<URI> fullCopyVolumes, String taskId)
throws ControllerException {
_log.info("START detachFullCopy: {}", fullCopyVolumes);
TaskCompleter taskCompleter = new CloneWorkflowCompleter(fullCopyVolumes, taskId);
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
Workflow workflow = _workflowService.getNewWorkflow(this, DETACH_CLONE_WF_NAME, true, taskId);
_log.info("Created new detach workflow with operation id {}", taskId);
// add CG to taskCompleter
checkCloneConsistencyGroup(fullCopyVolumes.get(0), _dbClient, taskCompleter);
Workflow.Method detachMethod = detachFullCopyMethod(storage, fullCopyVolumes);
workflow.createStep(FULL_COPY_DETACH_STEP_GROUP, "Detaching full copy", null,
storage, storageSystem.getSystemType(), getClass(), detachMethod, rollbackMethodNullMethod(), null);
String msg = String.format("Detach %s completed successfully", fullCopyVolumes.get(0));
workflow.executePlan(taskCompleter, msg);
} catch (Exception e) {
String msg = String.format("Could not detach the clone %s", fullCopyVolumes.get(0));
_log.error(msg, e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
@Override
public void establishVolumeAndFullCopyGroupRelation(URI storage, URI sourceVolume, URI fullCopy, String opId)
throws ControllerException {
_log.info("START establishVolumeAndFullCopyGroupRelation workflow");
Workflow workflow = _workflowService.getNewWorkflow(this, ESTABLISH_VOLUME_FULL_COPY_GROUP_WF_NAME, false, opId);
TaskCompleter taskCompleter = null;
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
try {
workflow.createStep("establishStep", "create group relation between Volume group and Full copy group", null, storage,
storageObj.getSystemType(),
this.getClass(), establishVolumeAndFullCopyGroupRelationMethod(storage, sourceVolume, fullCopy), null, null);
taskCompleter = new CloneTaskCompleter(fullCopy, opId);
workflow.executePlan(taskCompleter, "Successfully created group relation between Volume group and Full copy group");
} catch (Exception e) {
String msg = String.format("Failed to create group relation between Volume group and Full copy group."
+ "Source volume: %s, Full copy: %s",
sourceVolume, fullCopy);
_log.error(msg, e);
if (taskCompleter != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
}
}
}
private Workflow.Method establishVolumeAndFullCopyGroupRelationMethod(URI storage, URI sourceVolume, URI fullCopy) {
return new Workflow.Method("establishVolumeFullCopyGroupRelation", storage, sourceVolume, fullCopy);
}
public void establishVolumeFullCopyGroupRelation(
URI storage, URI sourceVolume, URI fullCopy, String opId)
throws ControllerException {
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter completer = new CloneTaskCompleter(fullCopy, opId);
getDevice(storageObj.getSystemType())
.doEstablishVolumeFullCopyGroupRelation(
storageObj, sourceVolume, fullCopy, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
@Override
public Integer checkSyncProgress(URI storage, URI source, URI target, String task) {
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
return getDevice(storageSystem.getSystemType()).checkSyncProgress(storage, source, target);
} catch (Exception e) {
String msg = String.format("Failed to check synchronization progress for %s", target);
_log.error(msg, e);
}
return null;
}
/**
* Creates a connection to monitor events generated by the storage
* identified by the passed URI.
*
* @param storage
* A database client URI that identifies the storage to be
* monitored.
*
* @throws ControllerException
* When errors occur connecting the storage for
* event monitoring.
*/
@Override
public void connectStorage(URI storage) throws ControllerException {
// Retrieve the storage device info from the database.
StorageSystem storageObj = null;
try {
storageObj = _dbClient.queryObject(StorageSystem.class, storage);
} catch (Exception e) {
throw DeviceControllerException.exceptions.connectStorageFailedDb(e);
}
// Verify non-null storage device returned from the database client.
if (storageObj == null) {
throw DeviceControllerException.exceptions.connectStorageFailedNull();
}
// Get the block device reference for the type of block device managed
// by the controller.
BlockStorageDevice storageDevice = getDevice(storageObj.getSystemType());
storageDevice.doConnect(storageObj);
_log.info("Adding to storage device to work pool: {}", storageObj.getId());
}
/**
* Removes a connection that was previously established for monitoring
* events from the storage identified by the passed URI.
*
* @param storage
* A database client URI that identifies the storage to be
* disconnected.
*
* @throws ControllerException
* When errors occur disconnecting the storage
* for event monitoring.
*/
@Override
public void disconnectStorage(URI storage) throws ControllerException {
// Retrieve the storage device info from the database.
StorageSystem storageObj = null;
try {
storageObj = _dbClient.queryObject(StorageSystem.class, storage);
} catch (Exception e) {
throw DeviceControllerException.exceptions.disconnectStorageFailedDb(e);
}
// Verify non-null storage device returned from the database client.
if (storageObj == null) {
throw DeviceControllerException.exceptions.disconnectStorageFailedNull();
}
// Get the block device reference for the type of block device managed
// by the controller.
BlockStorageDevice storageDevice = getDevice(storageObj.getSystemType());
if (storageDevice == null) {
throw DeviceControllerException.exceptions.disconnectStorageFailedNull();
}
storageDevice.doDisconnect(storageObj);
_log.info("Removing storage device from work pool: {}", storageObj.getId());
}
/**
* {@inheritDoc}
*/
@Override
public void discoverStorageSystem(AsyncTask[] tasks)
throws ControllerException {
throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
}
/**
* {@inheritDoc}
*/
@Override
public void scanStorageProviders(AsyncTask[] tasks)
throws ControllerException {
throw DeviceControllerException.exceptions.blockDeviceOperationNotSupported();
}
private String addStorageToSMIS(StorageSystem storageSystem, StorageProvider provider)
throws DataBindingException, ControllerException {
String system = "";
if (provider != null) {
// Populate provider info. This information normally corresponds to the active provider.
// Do not persist system information in the
storageSystem.setSmisPassword(provider.getPassword());
storageSystem.setSmisUserName(provider.getUserName());
storageSystem.setSmisPortNumber(provider.getPortNumber());
storageSystem.setSmisProviderIP(provider.getIPAddress());
storageSystem.setSmisUseSSL(provider.getUseSSL());
system = getDevice(storageSystem.getSystemType()).doAddStorageSystem(storageSystem);
_log.info("Storage is added to the SMI-S provider : " + provider.getProviderID());
}
return system;
}
private boolean scanProvider(StorageProvider provider, StorageSystem storageSystem,
boolean activeProvider, String opId) throws DatabaseException,
BaseCollectionException,
ControllerException {
Map<String, StorageSystemViewObject> storageCache = new HashMap<String, StorageSystemViewObject>();
_dbClient.createTaskOpStatus(StorageProvider.class, provider.getId(), opId,
ResourceOperationTypeEnum.SCAN_SMISPROVIDER);
ScanTaskCompleter scanCompleter = new ScanTaskCompleter(StorageProvider.class, provider.getId(), opId);
try {
scanCompleter.statusPending(_dbClient, "Scan for storage system is Initiated");
provider.setLastScanStatusMessage("");
_dbClient.updateObject(provider);
ControllerServiceImpl.performScan(provider.getId(), scanCompleter, storageCache);
scanCompleter.statusReady(_dbClient, "Scan for storage system has completed");
} catch (Exception ex) {
_log.error("Scan failed for {}--->", provider, ex);
scanCompleter.statusError(_dbClient, DeviceControllerErrors.dataCollectionErrors.scanFailed(ex.getLocalizedMessage(), ex));
throw DeviceControllerException.exceptions.scanProviderFailed(storageSystem.getNativeGuid(),
provider.getId().toString());
}
if (!storageCache.containsKey(storageSystem.getNativeGuid())) {
return false;
} else {
StorageSystemViewObject vo = storageCache.get(storageSystem.getNativeGuid());
String model = vo.getProperty(StorageSystemViewObject.MODEL);
if (StringUtils.isNotBlank(model)) {
storageSystem.setModel(model);
}
String serialNo = vo.getProperty(StorageSystemViewObject.SERIAL_NUMBER);
if (StringUtils.isNotBlank(serialNo)) {
storageSystem.setSerialNumber(serialNo);
}
String version = vo.getProperty(StorageSystemViewObject.VERSION);
if (StringUtils.isNotBlank(version)) {
storageSystem.setMajorVersion(version);
}
String name = vo.getProperty(StorageSystemViewObject.STORAGE_NAME);
if (StringUtils.isNotBlank(name)) {
storageSystem.setLabel(name);
}
provider.addStorageSystem(_dbClient, storageSystem, activeProvider);
return true;
}
}
/**
* {@inheritDoc}
*
* @throws WorkflowException
*/
@Override
public void addStorageSystem(URI storage, URI[] providers, boolean activeProvider, String opId) throws ControllerException {
if (providers == null) {
return;
}
String allProviders = Joiner.on("\t").join(providers);
DiscoverTaskCompleter completer = new DiscoverTaskCompleter(StorageSystem.class, storage, opId, ControllerServiceImpl.DISCOVERY);
StringBuilder failedProviders = new StringBuilder();
boolean exceptionIntercepted = false;
boolean needDiscovery = false;
boolean acquiredLock = false;
try {
acquiredLock = ControllerServiceImpl.Lock.SCAN_COLLECTION_LOCK.acquire(SCAN_LOCK_TIMEOUT);
} catch (Exception ex) {
_log.error("Exception while acquiring a lock ", ex);
acquiredLock = false;
}
if (acquiredLock) {
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
for (int ii = 0; ii < providers.length; ii++) {
try {
StorageProvider providerSMIS = _dbClient.queryObject(StorageProvider.class, providers[ii]);
if (providerSMIS == null) {
throw DeviceControllerException.exceptions.entityNullOrEmpty(null);
}
if (providerSMIS.getInactive()) {
throw DeviceControllerException.exceptions.entityInactive(providerSMIS.getId());
}
boolean found = scanProvider(providerSMIS, storageSystem, activeProvider && ii == 0, opId);
if (!found) {
if (storageSystem.getSystemType().equals(Type.vnxblock.toString()) &&
StringUtils.isNotBlank(storageSystem.getIpAddress())) {
String system = addStorageToSMIS(storageSystem, providerSMIS);
if (!system.equalsIgnoreCase(storageSystem.getNativeGuid())) {
throw DeviceControllerException.exceptions.addStorageSystemFailed(storageSystem.getNativeGuid(),
providerSMIS.getId().toString());
}
providerSMIS.addStorageSystem(_dbClient, storageSystem, activeProvider && ii == 0);
if (activeProvider && ii == 0) {
providerSMIS.removeDecommissionedSystem(_dbClient, storageSystem.getNativeGuid());
}
storageSystem.setReachableStatus(true);
_dbClient.updateObject(storageSystem);
} else {
throw DeviceControllerException.exceptions.scanFailedToFindSystem(providerSMIS.getId().toString(),
storageSystem.getNativeGuid());
}
} else {
storageSystem.setReachableStatus(true);
_dbClient.updateObject(storageSystem);
}
if (providers.length > 1) {
completer.statusPending(_dbClient,
"Adding storage to SMIS Providers : completed " + (ii + 1) + " providers out of " + providers.length);
}
} catch (Exception ex) {// any type of exceptions for a particular provider
_log.error("Failed to add storage from the following provider: " + providers[ii], ex);
failedProviders.append(providers[ii]).append(' ');
exceptionIntercepted = true;
}
}
if (activeProvider) {
updateActiveProvider(storageSystem);
_dbClient.updateObject(storageSystem);
}
DecommissionedResource.removeDecommissionedFlag(_dbClient, storageSystem.getNativeGuid(), StorageSystem.class);
if (exceptionIntercepted) {
String opName = ResourceOperationTypeEnum.ADD_STORAGE_SYSTEM.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
completer.error(_dbClient, serviceError);
} else {
recordBourneStorageEvent(RecordableEventManager.EventType.StorageDeviceAdded,
storageSystem, "Added SMI-S Storage");
_log.info("Storage is added to the SMI-S providers: ");
if (activeProvider) {
needDiscovery = true;
storageSystem.setLastDiscoveryRunTime(new Long(0));
completer.statusPending(_dbClient,
"Storage is added to the specified SMI-S providers : " + allProviders);
// We need to set timer back to 0 to indicate that it is a new system ready for discovery.
_dbClient.updateObject(storageSystem);
} else {
completer.ready(_dbClient);
}
}
} catch (Exception outEx) {
exceptionIntercepted = true;
_log.error("Failed to add SMIS providers", outEx);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(outEx);
completer.error(_dbClient, serviceError);
} finally {
try {
ControllerServiceImpl.Lock.SCAN_COLLECTION_LOCK.release();
} catch (Exception ex) {
_log.error("Failed to release SCAN lock; scanning might become disabled", ex);
}
if (needDiscovery && !exceptionIntercepted) {
try {
ControllerServiceImpl.scheduleDiscoverJobs(
new AsyncTask[] { new AsyncTask(StorageSystem.class, storage, opId) },
Lock.DISCOVER_COLLECTION_LOCK, ControllerServiceImpl.DISCOVERY);
} catch (Exception ex) {
_log.error("Failed to start discovery : " + storage, ex);
}
}
}
} else {
String opName = ResourceOperationTypeEnum.ADD_STORAGE_SYSTEM.getName();
ServiceError serviceError = DeviceControllerException.errors.jobFailedOp(opName);
completer.error(_dbClient, serviceError);
_log.debug("Not able to Acquire Scanning lock-->{}", Thread.currentThread().getId());
}
}
private void recordBourneStorageEvent(RecordableEventManager.EventType evtType, StorageSystem storage, String desc) {
RecordableBourneEvent event = ControllerUtils
.convertToRecordableBourneEvent(storage, evtType.toString(),
desc, "", _dbClient,
ControllerUtils.BLOCK_EVENT_SERVICE,
RecordType.Event.name(),
ControllerUtils.BLOCK_EVENT_SOURCE);
try {
_eventManager.recordEvents(event);
_log.info("Bourne {} event recorded", evtType.name());
} catch (Exception ex) {
_log.error(
"Failed to record event. Event description: {}. Error: ",
evtType.name(), ex);
}
}
private void updateActiveProvider(StorageSystem storage) throws DatabaseException {
if (storage.getActiveProviderURI() == null) {
return;
}
StorageProvider mainProvider = _dbClient.queryObject(StorageProvider.class, storage.getActiveProviderURI());
if (mainProvider != null) {
storage.setSmisPassword(mainProvider.getPassword());
storage.setSmisUserName(mainProvider.getUserName());
storage.setSmisPortNumber(mainProvider.getPortNumber());
storage.setSmisProviderIP(mainProvider.getIPAddress());
storage.setSmisUseSSL(mainProvider.getUseSSL());
_dbClient.updateObject(storage);
}
}
@Override
public void startMonitoring(AsyncTask task, Type deviceType)
throws ControllerException {
// TODO Auto-generated method stub
}
public <T extends BlockObject> Workflow.Method waitForSynchronizedMethod(Class clazz, URI storage, List<URI> target, boolean isCG) {
return new Workflow.Method("waitForSynchronized", clazz, storage, target, isCG);
}
public void waitForSynchronized(Class<? extends BlockObject> clazz, URI storage, List<URI> target, boolean isCG, String opId)
throws ControllerException {
_log.info("START waitForSynchronized for {}", target);
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockWaitForSynchronizedCompleter(clazz, target, opId);
if (!isCG) {
getDevice(storageObj.getSystemType()).doWaitForSynchronized(clazz, storageObj, target.get(0), completer);
} else {
Volume cloneVolume = _dbClient.queryObject(Volume.class, target.get(0));
Volume sourceVolumeObj = _dbClient.queryObject(Volume.class, cloneVolume.getAssociatedSourceVolume());
boolean canIgnoreThisStep = false;
/**
* VPLEX/RP CG volumes may not be having back end Array Group.
* In this case we should create element replica using createListReplica.
* We can ignore the wait for sync step in this case.
*/
canIgnoreThisStep = isListReplicaFlow(sourceVolumeObj);
if (!canIgnoreThisStep) {
getDevice(storageObj.getSystemType()).doWaitForGroupSynchronized(storageObj, target, completer);
} else {
completer.ready(_dbClient); // Workaround
}
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
/**
* Get the deviceType for a StorageSystem.
*
* @param deviceURI
* -- StorageSystem URI
* @return deviceType String
*/
String getDeviceType(URI deviceURI) throws ControllerException {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, deviceURI);
if (storageSystem == null) {
throw DeviceControllerException.exceptions.getDeviceTypeFailed(deviceURI.toString());
}
return storageSystem.getSystemType();
}
/**
* Checks if given BlockMirrors are applicable for pausing.
* A mirror is valid for pausing if it is
* 1) Not null
* 2) Has valid sync state integer value
* 3) Sync state is not already fractured (paused) and not resynchronizing
*
* @param mirrorList
* The BlockMirrors to test
* @return true, if at least one mirror is applicable for pause operation
*/
private boolean mirrorIsPausable(List<BlockMirror> mirrorList) {
for (BlockMirror mirror : mirrorList) {
try {
boolean hasPausableMirror = mirror != null &&
mirror.getInactive() == false &&
!SynchronizationState.FRACTURED.toString().equals(mirror.getSyncState()) &&
!SynchronizationState.RESYNCHRONIZING.toString().equals(mirror.getSyncState());
if (hasPausableMirror) { // will continue to look up if it is false
return hasPausableMirror;
}
} catch (NumberFormatException nfe) {
_log.warn("Failed to parse sync state ({}) for mirror {}", mirror.getSyncState(), mirror.getId());
}
}
return false;
}
/**
* Check if a mirror exists in ViPR as an active model and is pending creation on the
* storage array.
*
* @param mirror
* @return true if the mirror is pending creation
*/
private boolean isPending(BlockMirror mirror) {
return !isInactive(mirror) && isNullOrEmpty(mirror.getSynchronizedInstance());
}
private boolean isInactive(BlockMirror mirror) {
return mirror == null || (mirror.getInactive() != null && mirror.getInactive());
}
@Override
public void noActionRollBackStep(URI deviceURI, String opID) {
_log.info("Running empty Roll back step for storage system {}", deviceURI);
WorkflowStepCompleter.stepSucceded(opID);
}
@Override
public void updateConsistencyGroup(URI storage, URI consistencyGroup,
List<URI> addVolumesList,
List<URI> removeVolumesList, String task) {
TaskCompleter completer = new BlockConsistencyGroupUpdateCompleter(consistencyGroup, task);
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroup);
boolean srdfCG = false;
// check if SRDF
if (addVolumesList != null && !addVolumesList.isEmpty()) {
URI volumeURI = addVolumesList.get(0);
if (URIUtil.isType(volumeURI, Volume.class)) {
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
if (volume.isSRDFSource()) {
srdfCG = true;
cg.getRequestedTypes().add(Types.SRDF.name());
_dbClient.updateObject(cg);
}
}
}
// Generate the Workflow.
Workflow workflow = _workflowService.getNewWorkflow(this,
UPDATE_CONSISTENCY_GROUP_WF_NAME, false, task);
String waitFor = null;
// check if cg is created, if not create it
if (!cg.created()) {
_log.info("Consistency group not created. Creating it");
String groupName = ControllerUtils.generateReplicationGroupName(storageSystem, cg, null, _dbClient);
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Creating consistency group %s", consistencyGroup),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
createConsistencyGroupMethod(storage, consistencyGroup, groupName),
deleteConsistencyGroupMethod(storage, consistencyGroup, groupName, false, false, true), null);
}
if (addVolumesList != null && !addVolumesList.isEmpty()) {
String groupName = ControllerUtils.generateReplicationGroupName(storageSystem, cg, null, _dbClient);
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Adding volumes to consistency group %s", consistencyGroup),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
addToConsistencyGroupMethod(storage, consistencyGroup, groupName, addVolumesList),
removeFromConsistencyGroupMethod(storage, consistencyGroup, addVolumesList, false), null);
// call ReplicaDeviceController
waitFor = _replicaDeviceController.addStepsForAddingSessionsToCG(workflow, waitFor, consistencyGroup, addVolumesList,
groupName, task);
}
if (removeVolumesList != null && !removeVolumesList.isEmpty()) {
Volume volume = _dbClient.queryObject(Volume.class, removeVolumesList.get(0));
if (volume != null && !volume.getInactive()) {
String groupName = volume.getReplicationGroupInstance();
// call ReplicaDeviceController
waitFor = _replicaDeviceController.addStepsForRemovingVolumesFromCG(workflow, waitFor, consistencyGroup,
removeVolumesList, task);
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Removing volumes from consistency group %s", consistencyGroup),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
removeFromConsistencyGroupMethod(storage, consistencyGroup, removeVolumesList, false),
addToConsistencyGroupMethod(storage, consistencyGroup, groupName, removeVolumesList), null);
// remove replication group if the CG will become empty
if ((addVolumesList == null || addVolumesList.isEmpty()) &&
ControllerUtils.cgHasNoOtherVolume(_dbClient, consistencyGroup, removeVolumesList)) {
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Deleting replication group for consistency group %s", consistencyGroup),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
deleteConsistencyGroupMethod(storage, consistencyGroup, groupName, false, false, true),
createConsistencyGroupMethod(storage, consistencyGroup, groupName), null);
}
}
}
// For SRDF, we need to create target consistency group and
// add target volumes to that target consistency group.
if (srdfCG) {
createTargetConsistencyGroup(cg, addVolumesList, workflow, waitFor, task);
}
// Finish up and execute the plan.
_log.info("Executing workflow plan {}", UPDATE_CONSISTENCY_GROUP_STEP_GROUP);
String successMessage = String.format(
"Update consistency group successful for %s", consistencyGroup);
workflow.executePlan(completer, successMessage);
} catch (Exception e) {
_log.error("Error updating consistency group: {}", consistencyGroup, e);
completer.error(_dbClient, DeviceControllerException.exceptions.failedToUpdateConsistencyGroup(e.getMessage()));
}
}
private static Workflow.Method createConsistencyGroupMethod(URI storage, URI consistencyGroup, String replicationGroupName) {
return new Workflow.Method("createConsistencyGroupStep", storage, consistencyGroup, replicationGroupName);
}
public boolean createConsistencyGroupStep(URI storage, URI consistencyGroup, String replicationGroupName, String opId)
throws ControllerException {
TaskCompleter taskCompleter = null;
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
taskCompleter = new BlockConsistencyGroupCreateCompleter(consistencyGroup, opId);
String groupName = ControllerUtils.generateReplicationGroupName(storageSystem, consistencyGroup, replicationGroupName,
_dbClient);
String lockKey = groupName;
boolean isVNX = storageSystem.deviceIsType(Type.vnxblock);
if (isVNX && lockKey == null) {
lockKey = replicationGroupName;
}
if (lockKey == null) {
BlockConsistencyGroup cgObj = _dbClient.queryObject(BlockConsistencyGroup.class, consistencyGroup);
lockKey = cgObj.getAlternateLabel() != null ? cgObj.getAlternateLabel() : cgObj.getLabel();
}
// Lock the CG for the step duration.
List<String> lockKeys = new ArrayList<>();
lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, lockKey, storage));
_workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
if (isVNX) {
// replication group may have been just created by another thread, in that case,
// group name for VNX will be array generated name (if arrayConsistency is true), or
// replicationGroupName if arrayConsistency is false
// so get the group name again here to be used in ControllerUtils.replicationGroupExists call
groupName = ControllerUtils.generateReplicationGroupName(storageSystem, consistencyGroup, replicationGroupName,
_dbClient);
}
// make sure this array consistency group was not just created by another thread that held the lock
if (groupName != null && ControllerUtils.replicationGroupExists(storage, groupName, _dbClient)) {
taskCompleter.ready(_dbClient);
return true;
}
getDevice(storageSystem.getSystemType()).doCreateConsistencyGroup(
storageSystem, consistencyGroup, groupName, taskCompleter);
} catch (Exception e) {
_log.error("create consistency group job failed:", e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
public String addStepToAddToConsistencyGroup(Workflow workflow, URI storage, URI consistencyGroup, String replicationGroupName,
List<URI> addVolumesList, String waitFor) {
String stepId = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Adding volumes to consistency group %s", consistencyGroup),
waitFor, storage, getDeviceType(storage),
this.getClass(),
addToConsistencyGroupMethod(storage, consistencyGroup, replicationGroupName, addVolumesList),
rollbackMethodNullMethod(), null);
return stepId;
}
private static Workflow.Method addToConsistencyGroupMethod(URI storage, URI consistencyGroup, String replicationGroupName,
List<URI> addVolumesList) {
return new Workflow.Method("addToConsistencyGroup", storage, consistencyGroup, replicationGroupName, addVolumesList);
}
public boolean addToConsistencyGroup(URI storage, URI consistencyGroup, String replicationGroupName, List<URI> addVolumesList,
String opId)
throws ControllerException {
TaskCompleter taskCompleter = null;
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
taskCompleter = new BlockConsistencyGroupAddVolumeCompleter(consistencyGroup, addVolumesList, replicationGroupName, opId);
// Lock the CG for the step duration.
List<String> lockKeys = new ArrayList<String>();
if (replicationGroupName == null) {
replicationGroupName = ControllerUtils.generateReplicationGroupName(storageSystem, consistencyGroup, null, _dbClient);
}
lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, replicationGroupName, storage));
_workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
getDevice(storageSystem.getSystemType()).doAddToConsistencyGroup(
storageSystem, consistencyGroup, replicationGroupName, addVolumesList, taskCompleter);
} catch (Exception e) {
_log.error("Error whilst adding to consistency group", e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
public String addStepToRemoveFromConsistencyGroup(Workflow workflow, URI storage, URI consistencyGroup, List<URI> removeVolumesList,
String waitFor, boolean keepRGReference) {
String stepId = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Removing volumes from consistency group %s", consistencyGroup),
waitFor, storage, getDeviceType(storage),
this.getClass(),
removeFromConsistencyGroupMethod(storage, consistencyGroup, removeVolumesList, keepRGReference),
rollbackMethodNullMethod(), null);
return stepId;
}
private static Workflow.Method removeFromConsistencyGroupMethod(URI storage, URI consistencyGroup, List<URI> removeVolumesList,
boolean keepRGReference) {
return new Workflow.Method("removeFromConsistencyGroup", storage, consistencyGroup, removeVolumesList, keepRGReference);
}
public boolean removeFromConsistencyGroup(URI storage, URI consistencyGroup, List<URI> removeVolumesList, boolean keepRGReference,
String opId)
throws ControllerException {
TaskCompleter taskCompleter = null;
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
taskCompleter = new BlockConsistencyGroupRemoveVolumeCompleter(consistencyGroup, removeVolumesList, keepRGReference, opId);
// Lock the CG for the step duration.
if (removeVolumesList != null && !removeVolumesList.isEmpty()) {
List<String> lockKeys = new ArrayList<String>();
URI volumeUri = removeVolumesList.get(0);
Volume volume = _dbClient.queryObject(Volume.class, volumeUri);
String repGroup = volume.getReplicationGroupInstance();
if (NullColumnValueGetter.isNotNullValue(repGroup)) {
lockKeys.add(ControllerLockingUtil.getReplicationGroupStorageKey(_dbClient, repGroup, storage));
} else {
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(_dbClient, consistencyGroup, storage));
}
_workflowService.acquireWorkflowStepLocks(opId, lockKeys, LockTimeoutValue.get(LockType.ARRAY_CG));
}
getDevice(storageSystem.getSystemType()).doRemoveFromConsistencyGroup(
storageSystem, consistencyGroup, removeVolumesList, taskCompleter);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
/**
* This method creates SRDF target consistency group for the SRDF source consistency group 1. Creates target
* consistency group in ViPR
* DB & Array 2. Get the target volumes for the SRDF source volumes and adds them to the target consistency group
* Note: Target CG will
* be created using source system provider
*
* @param sourceCG
* the source cg
* @param addVolumesList
* the add volumes list
* @param workflow
* the workflow
* @param waitFor
* the wait for
*/
private void createTargetConsistencyGroup(BlockConsistencyGroup sourceCG,
List<URI> addVolumesList, Workflow workflow, String waitFor, String task) {
Volume sourceVolume = _dbClient.queryObject(Volume.class, addVolumesList.get(0));
VirtualPool vPool = _dbClient.queryObject(VirtualPool.class, sourceVolume.getVirtualPool());
VirtualArray protectionVirtualArray = null;
if (vPool.getProtectionRemoteCopySettings() != null) {
for (String protectionVarray : vPool.getProtectionRemoteCopySettings().keySet()) {
protectionVirtualArray = _dbClient.queryObject(VirtualArray.class, URI.create(protectionVarray));
}
}
String vArrayName = null;
if (protectionVirtualArray != null) {
vArrayName = protectionVirtualArray.getLabel();
}
String cgName = sourceCG.getLabel() + "-Target-" + vArrayName;
List<BlockConsistencyGroup> groups = CustomQueryUtility
.queryActiveResourcesByConstraint(_dbClient,
BlockConsistencyGroup.class, PrefixConstraint.Factory
.getFullMatchConstraint(
BlockConsistencyGroup.class, "label",
cgName));
BlockConsistencyGroup targetCG = null;
boolean createGroup = false;
if (groups.isEmpty()) {
// create target CG
targetCG = new BlockConsistencyGroup();
targetCG.setId(URIUtil.createId(BlockConsistencyGroup.class));
targetCG.setLabel(cgName);
// TODO verify project and tenant NamedURIs
targetCG.setProject(sourceCG.getProject());
targetCG.setTenant(sourceCG.getTenant());
targetCG.setAlternateLabel(sourceCG.getLabel());
targetCG.getRequestedTypes().add(Types.SRDF.name());
_dbClient.createObject(targetCG);
createGroup = true;
} else {
targetCG = groups.get(0);
}
// Get SRDF Target volume list for the source volume list
List<URI> addTargetVolumesList = getSRDFTargetVolumes(addVolumesList);
if (!addTargetVolumesList.isEmpty()) {
Volume targetVolume = _dbClient.queryObject(Volume.class, addTargetVolumesList.get(0));
StorageSystem targetSystem = _dbClient.queryObject(StorageSystem.class, targetVolume.getStorageController());
String groupName = ControllerUtils.generateReplicationGroupName(targetSystem, targetCG, null, _dbClient);
if (createGroup || !targetCG.created()) {
_log.info("Creating target Consistency group on Array.");
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Creating consistency group %s", targetCG.getId()),
waitFor, targetSystem.getId(), targetSystem.getSystemType(),
this.getClass(),
createConsistencyGroupMethod(targetSystem.getId(), targetCG.getId(), groupName),
rollbackMethodNullMethod(), null);
}
_log.info("Adding target volumes to target Consistency group.");
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Updating consistency group %s", targetCG.getId()),
waitFor, targetSystem.getId(), targetSystem.getSystemType(),
this.getClass(),
addToConsistencyGroupMethod(targetSystem.getId(), targetCG.getId(), null, addTargetVolumesList),
rollbackMethodNullMethod(), null);
// call ReplicaDeviceController
waitFor = _replicaDeviceController.addStepsForAddingSessionsToCG(workflow, waitFor, targetCG.getId(), addTargetVolumesList,
groupName, task);
}
}
/**
*
* @param sourceVolumeList
* @return URI list of target volumes for the given source volumes
*/
private List<URI> getSRDFTargetVolumes(List<URI> sourceVolumeList) {
List<URI> addTargetVolumesList = new ArrayList<URI>();
Iterator<URI> it = sourceVolumeList.iterator();
while (it.hasNext()) {
URI sourceVolumeURI = it.next();
Volume sourceVolume = _dbClient.queryObject(Volume.class, sourceVolumeURI);
if (sourceVolume.isSRDFSource()) {
StringSet targets = sourceVolume.getSrdfTargets();
for (String target : targets) {
addTargetVolumesList.add(URI.create(target));
}
}
}
return addTargetVolumesList;
}
@Override
public boolean validateStorageProviderConnection(String ipAddress,
Integer portNumber, String interfaceType) {
List<String> systemType = InterfaceType
.getSystemTypesForInterfaceType(InterfaceType
.valueOf(interfaceType));
return getDevice(systemType.get(0)).validateStorageProviderConnection(
ipAddress, portNumber);
}
@Override
public String addStepsForPostDeleteVolumes(Workflow workflow, String waitFor,
List<VolumeDescriptor> volumes, String taskId,
VolumeWorkflowCompleter completer) {
// delete replication group if it becomes empty
// 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 },
null);
// If no source volumes, just return
if (volumeDescriptors.isEmpty()) {
_log.info("No post deletion step required");
return waitFor;
}
// Sort the volumes by its system, and replicationGroup
Map<String, Set<URI>> rgVolsMap = new HashMap<String, Set<URI>>();
for (VolumeDescriptor volumeDescriptor : volumeDescriptors) {
URI volumeURI = volumeDescriptor.getVolumeURI();
Volume volume = _dbClient.queryObject(Volume.class, volumeURI);
if (volume != null) {
/*
* No need to remove replication group as SRDF volume's
* rep group will be removed as part of srdf volume delete steps.
*/
if (!Volume.isSRDFProtectedVolume(volume)) {
String replicationGroup = volume.getReplicationGroupInstance();
if (NullColumnValueGetter.isNotNullValue(replicationGroup)) {
URI storage = volume.getStorageController();
String key = storage.toString() + replicationGroup;
Set<URI> rgVolumeList = rgVolsMap.get(key);
if (rgVolumeList == null) {
rgVolumeList = new HashSet<URI>();
rgVolsMap.put(key, rgVolumeList);
}
rgVolumeList.add(volumeURI);
}
} else {
_log.info("post delete not required for SRDF Volume :{}", volume.getId());
}
}
}
if (rgVolsMap.isEmpty()) {
return waitFor;
}
for (Set<URI> volumeURIs : rgVolsMap.values()) {
// find member volumes in the group
List<Volume> volumeList = new ArrayList<Volume>();
Iterator<Volume> volumeIterator = _dbClient.queryIterativeObjects(Volume.class, volumeURIs);
while (volumeIterator.hasNext()) {
Volume volume = volumeIterator.next();
if (volume != null && !volume.getInactive()) {
volumeList.add(volume);
}
}
Volume firstVol = volumeList.get(0);
String rgName = firstVol.getReplicationGroupInstance();
URI storage = firstVol.getStorageController();
URI cgURI = firstVol.getConsistencyGroup();
// delete replication group from array
if (ControllerUtils.replicationGroupHasNoOtherVolume(_dbClient, rgName, volumeURIs, storage)) {
_log.info(String.format("Adding step to delete the replication group %s", rgName));
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Deleting replication group %s", rgName),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
deleteConsistencyGroupMethod(storage, cgURI, rgName, false, false, false),
rollbackMethodNullMethod(), null);
}
}
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;
}
private static final String RESTORE_FROM_CLONE_WF_NAME = "RESTORE_FROM_CLONE_WORKFLOW";
private static final String RESTORE_FROM_CLONE_GROUP = "BlockDeviceRestoreFromClone";
private static final String FRACTURE_CLONE_GROUP = "PostBlockDeviceFractureClone";
@Override
public void restoreFromFullCopy(URI storage, List<URI> clones,
Boolean updateOpStatus, String opId) throws InternalException {
TaskCompleter completer = new CloneWorkflowCompleter(clones, opId);
try {
Workflow workflow = _workflowService.getNewWorkflow(this, RESTORE_FROM_CLONE_WF_NAME, false, opId);
_log.info("Created new restore workflow with operation id {}", opId);
String waitFor = null;
Volume clone = _dbClient.queryObject(Volume.class, clones.get(0));
URI source = clone.getAssociatedSourceVolume();
BlockObject sourceObj = BlockObject.fetch(_dbClient, source);
/**
* We need to detach SRDF link before performing clone restore to SRDF R2 volume.
* OPT#477320
*/
if (sourceObj instanceof Volume && isNonSplitSRDFTargetVolume((Volume) sourceObj)) {
// PRIOR to Restoring R2 Device from its Full copy Clone, we need to
// a) SUSPEND the R1-R2 pair if the Copy Mode is ACTIVE Or
// b) SPLIT the R1-R2 pair if the Copy Mode is SYNC/ ASYNC
Volume sourceVolume = (Volume) sourceObj;
URI srdfSourceVolumeURI = sourceVolume.getSrdfParent().getURI();
Volume srdfSourceVolume = _dbClient.queryObject(Volume.class, srdfSourceVolumeURI);
URI srdfSourceStorageSystemURI = srdfSourceVolume.getStorageController();
if (Mode.ACTIVE.equals(Mode.valueOf(sourceVolume.getSrdfCopyMode()))) {
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
srdfSourceVolumeURI, source, workflow);
} else {
// split all members the group
Workflow.Method splitMethod = srdfDeviceController.splitSRDFLinkMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, source, false);
Workflow.Method splitRollbackMethod = srdfDeviceController.resumeSyncPairMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, source);
waitFor = workflow.createStep(SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_DESC, waitFor, srdfSourceStorageSystemURI,
getDeviceType(srdfSourceStorageSystemURI), SRDFDeviceController.class, splitMethod,
splitRollbackMethod, null);
}
} else if (sourceObj instanceof Volume && isNonSplitSRDFSourceVolume((Volume) sourceObj)) {
// PRIOR to Restoring R1 Device from its Full copy Clone, we need to SUSPEND the R1-R2 pair if the Copy
// Mode is ACTIVE
Volume srdfSourceVolume = (Volume) sourceObj;
URI srdfSourceStorageSystemURI = srdfSourceVolume.getStorageController();
StringSet targets = srdfSourceVolume.getSrdfTargets();
if (null != targets) {
for (String target : targets) {
if (NullColumnValueGetter.isNotNullValue(target)) {
Volume srdfTargetVolume = _dbClient.queryObject(Volume.class, URI.create(target));
if (null != srdfTargetVolume && Mode.ACTIVE.equals(Mode.valueOf(srdfTargetVolume.getSrdfCopyMode()))) {
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
srdfSourceVolume.getId(), srdfTargetVolume.getId(), workflow);
}
break;
}
}
}
}
StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage);
// add CG to taskCompleter
boolean isCG = checkCloneConsistencyGroup(clones.get(0), _dbClient, completer);
String description = String.format("Restore volume from %s", clones.get(0));
String previousStep = workflow.createStep(RESTORE_FROM_CLONE_GROUP, description, waitFor,
storage, getDeviceType(storage), BlockDeviceController.class,
restoreFromCloneMethod(storage, clones, updateOpStatus, isCG),
rollbackMethodNullMethod(), null);
if (isCG && system.deviceIsType(Type.vnxblock)) {
for (URI cloneUri : clones) {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, Arrays.asList(cloneUri), false);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
system.getSystemType(), getClass(), waitForSyncMethod, rollbackMethodNullMethod(), null);
previousStep = waitForSyncStep;
}
} else {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, clones, isCG);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
system.getSystemType(), getClass(), waitForSyncMethod, rollbackMethodNullMethod(), null);
previousStep = waitForSyncStep;
}
if (system.deviceIsType(Type.vmax) || system.deviceIsType(Type.vnxblock)) {
addFractureSteps(workflow, system, clones, previousStep, isCG);
}
_log.info("Executing workflow {}", RESTORE_FROM_CLONE_GROUP);
String msg = String.format("Restore from %s completed successfully", clones.get(0));
workflow.executePlan(completer, msg);
} catch (Exception e) {
String msg = String.format("Could not restore from the clone %s", clones.get(0));
_log.error(msg, e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
}
/**
* Return a Workflow.Method for restoreVolume
*
* @param storage
* storage system
* @param pool
* storage pool
* @param volume
* target of restore operation
* @param snapshot
* snapshot to restore from
* @param updateOpStatus
* update operation status flag
* @return Workflow.Method
*/
public static Workflow.Method restoreFromCloneMethod(URI storage, List<URI> clone, Boolean updateOpStatus, boolean isCG) {
return new Workflow.Method("restoreFromCloneStep", storage, clone, updateOpStatus, isCG);
}
public boolean restoreFromCloneStep(URI storage, List<URI> clones, Boolean updateOpStatus, boolean isCG, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
StorageSystem storageDevice = _dbClient.queryObject(StorageSystem.class, storage);
if (!isCG) {
completer = new CloneRestoreCompleter(clones.get(0), opId);
getDevice(storageDevice.getSystemType()).doRestoreFromClone(storageDevice, clones.get(0), completer);
} else {
CloneRestoreCompleter taskCompleter = new CloneRestoreCompleter(clones, opId);
getDevice(storageDevice.getSystemType()).doRestoreFromGroupClone(storageDevice, clones, taskCompleter);
}
} catch (Exception e) {
_log.error(String.format("restoreFromClone failed - storage: %s,clone: %s",
storage.toString(), clones.get(0).toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
doFailTask(Volume.class, clones, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
private void addFractureSteps(Workflow workflow, StorageSystem system, List<URI> clone, String previousStep, boolean isCG) {
_log.info("Adding fracture restore post-step for clone {}", clone.toString());
String description = String.format("Fracture clone %s", clone.toString());
workflow.createStep(FRACTURE_CLONE_GROUP, description, previousStep,
system.getId(), system.getSystemType(), BlockDeviceController.class,
fractureCloneMethod(system.getId(), clone, isCG),
rollbackMethodNullMethod(), null);
}
public static Workflow.Method fractureCloneMethod(URI storage, List<URI> clone, boolean isCG) {
return new Workflow.Method(FRACTURE_CLONE_METHOD, storage, clone, isCG);
}
public boolean fractureClone(URI storage, List<URI> clone, boolean isCG, String opId) {
_log.info("Fracture clone: {}", clone.get(0));
TaskCompleter completer = null;
try {
StorageSystem storageDevice = _dbClient.queryObject(StorageSystem.class, storage);
WorkflowStepCompleter.stepExecuting(opId);
if (!isCG) {
Volume cloneVol = _dbClient.queryObject(Volume.class, clone.get(0));
completer = new CloneFractureCompleter(clone.get(0), opId);
WorkflowStepCompleter.stepExecuting(opId);
// Synchronous operation
getDevice(storageDevice.getSystemType()).doFractureClone(storageDevice, cloneVol.getAssociatedSourceVolume(), clone.get(0),
completer);
} else {
_log.info("Fracture group clone.");
completer = new CloneFractureCompleter(clone, opId);
WorkflowStepCompleter.stepExecuting(opId);
// Synchronous operation
getDevice(storageDevice.getSystemType()).doFractureGroupClone(storageDevice, clone, completer);
}
} catch (Exception e) {
_log.error(
String.format("Fracture restore sessions step failed - storage: %s, clone: %s",
storage.toString(), clone.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
doFailTask(Volume.class, clone, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
return false;
}
return true;
}
private static final String RESYNC_CLONE_WF_NAME = "RESYNC_CLONE_WORKFLOW";
private static final String RESYNC_CLONE_GROUP = "BlockDeviceResyncClone";
@Override
public void resyncFullCopy(URI storage, List<URI> clones,
Boolean updateOpStatus, String opId) throws InternalException {
TaskCompleter completer = new CloneWorkflowCompleter(clones, opId);
try {
Workflow workflow = _workflowService.getNewWorkflow(this, RESYNC_CLONE_WF_NAME, false, opId);
_log.info("Created new resync workflow with operation id {}", opId);
StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage);
// add CG to taskCompleter
boolean isCG = checkCloneConsistencyGroup(clones.get(0), _dbClient, completer);
String description = String.format("Resync clone %s", clones.get(0));
String previousStep = workflow.createStep(RESYNC_CLONE_GROUP, description, null,
storage, getDeviceType(storage), BlockDeviceController.class,
resyncCloneMethod(storage, clones, updateOpStatus, isCG),
rollbackMethodNullMethod(), null);
if (isCG && system.deviceIsType(Type.vnxblock)) {
for (URI cloneUri : clones) {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, Arrays.asList(cloneUri), false);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
system.getSystemType(), getClass(), waitForSyncMethod, null, null);
previousStep = waitForSyncStep;
}
} else {
Workflow.Method waitForSyncMethod = waitForSynchronizedMethod(Volume.class, storage, clones, isCG);
String waitForSyncStep = workflow.createStep(FULL_COPY_WFS_STEP_GROUP,
"Waiting for synchronization", previousStep, storage,
system.getSystemType(), getClass(), waitForSyncMethod, null, null);
previousStep = waitForSyncStep;
}
if (system.deviceIsType(Type.vnxblock)) {
addFractureSteps(workflow, system, clones, previousStep, isCG);
} else {
setCloneReplicaStateStep(workflow, system, clones, previousStep, ReplicationState.SYNCHRONIZED);
}
_log.info("Executing workflow {}", RESYNC_CLONE_GROUP);
String msg = String.format("Resync %s completed successfully", clones.get(0));
workflow.executePlan(completer, msg);
} catch (Exception e) {
String msg = String.format("Could not resync the clone %s", clones.get(0));
_log.error(msg, e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
}
/**
* Return a Workflow.Method for resync
*
* @param storage
* storage system
* @param clone
* list of clones
* @param updateOpStatus
* update operation status flag
* @return Workflow.Method
*/
public static Workflow.Method resyncCloneMethod(URI storage, List<URI> clone, Boolean updateOpStatus, boolean isCG) {
return new Workflow.Method("resyncFullCopyStep", storage, clone, updateOpStatus, isCG);
}
public boolean resyncFullCopyStep(URI storage, List<URI> clone, Boolean updateOpStatus, boolean isCG, String opId)
throws ControllerException {
_log.info("Start resync full copy");
CloneResyncCompleter taskCompleter = new CloneResyncCompleter(clone, opId);
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
if (checkCloneConsistencyGroup(clone.get(0), _dbClient, taskCompleter)) {
_log.info("resync group full copy");
getDevice(storageSystem.getSystemType()).doResyncGroupClone(storageSystem, clone, taskCompleter);
} else {
getDevice(storageSystem.getSystemType()).doResyncClone(storageSystem, clone.get(0), taskCompleter);
}
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
taskCompleter.error(_dbClient, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
doFailTask(Volume.class, clone, opId, serviceError);
return false;
}
return true;
}
private void setCloneReplicaStateStep(Workflow workflow, StorageSystem system, List<URI> clones, String previousStep,
ReplicationState state) {
_log.info("Setting the clone replica state.");
workflow.createStep("SET_FINAL_REPLICA_STATE", "Set the clones replica state", previousStep,
system.getId(), system.getSystemType(), BlockDeviceController.class,
setCloneStateMethod(clones, state), rollbackMethodNullMethod(), null);
}
private final static String SET_CLONE_STATE_METHOD = "setCloneState";
public static Workflow.Method setCloneStateMethod(List<URI> clone, ReplicationState state) {
return new Workflow.Method(SET_CLONE_STATE_METHOD, clone, state);
}
public void setCloneState(List<URI> clones, ReplicationState state, String opId) {
_log.info("Set clones state");
List<Volume> cloneVols = _dbClient.queryObject(Volume.class, clones);
for (Volume cloneVol : cloneVols) {
cloneVol.setReplicaState(state.name());
}
_dbClient.updateObject(cloneVols);
CloneCreateWorkflowCompleter completer = new CloneCreateWorkflowCompleter(clones, opId);
completer.ready(_dbClient);
}
public void setSrdfDeviceController(SRDFDeviceController srdfDeviceController) {
this.srdfDeviceController = srdfDeviceController;
}
@Override
public void resyncSnapshot(URI storage, URI volume, URI snapshot, Boolean updateOpStatus, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
StorageSystem storageDevice = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);
completer = new BlockSnapshotResyncCompleter(snapObj, opId, updateOpStatus);
getDevice(storageDevice.getSystemType()).doResyncSnapshot(storageDevice, volume, snapshot, completer);
} catch (Exception e) {
_log.error(String.format("resync snapshot failed - storage: %s, volume: %s, snapshot: %s",
storage.toString(), volume.toString(), snapshot.toString()));
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
doFailTask(BlockSnapshot.class, snapshot, opId, serviceError);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public String detachCloneStep(Workflow workflow, String waitFor, StorageSystem storageSystem, List<URI> cloneList,
boolean isRemoveAll) {
URI storage = storageSystem.getId();
if (isRemoveAll) {
Workflow.Method detachMethod = detachFullCopyMethod(storage, cloneList);
waitFor = workflow.createStep(FULL_COPY_DETACH_STEP_GROUP, "Detaching group clone", waitFor,
storage, storageSystem.getSystemType(), getClass(), detachMethod, null, null);
} else {
Workflow.Method detachMethod = detachListCloneMethod(storage, cloneList);
waitFor = workflow.createStep(FULL_COPY_DETACH_STEP_GROUP, "Detaching list clone", waitFor,
storage, storageSystem.getSystemType(), getClass(), detachMethod, null, null);
}
return waitFor;
}
public Workflow.Method detachListCloneMethod(URI storage, List<URI> cloneList) {
return new Workflow.Method("detachListClone", storage, cloneList);
}
public void detachListClone(URI storage, List<URI> cloneList, String taskId)
throws ControllerException {
_log.info("START detachListClone: {}", Joiner.on("\t").join(cloneList));
try {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter taskCompleter = new VolumeDetachCloneCompleter(cloneList, taskId);
getDevice(storageSystem.getSystemType()).doDetachListReplica(storageSystem, cloneList, taskCompleter);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, cloneList, taskId, serviceError);
}
}
public String deleteListMirrorStep(Workflow workflow, String waitFor, URI storage, StorageSystem storageSystem, List<URI> mirrorList,
boolean isRemoveAll) {
String mirrorStr = Joiner.on("\t").join(mirrorList);
_log.info("Start deleteMirror Step for mirror:{}", mirrorStr);
List<BlockMirror> mirrors = _dbClient.queryObject(BlockMirror.class, mirrorList);
if (isRemoveAll) {
// Optionally create a step to pause (fracture) the mirror
if (mirrorIsPausable(mirrors)) {
_log.info("Adding group fracture mirror step");
waitFor = workflow.createStep("deactivate",
String.format("fracture group mirror: %s", mirrorStr),
waitFor, storage, getDeviceType(storage),
this.getClass(),
fractureMirrorMethod(storage, mirrorList, isRemoveAll, false),
null, null);
}
_log.info("Adding group detach mirror step");
Workflow.Method detach = detachMirrorMethod(storage, mirrorList, isRemoveAll);
waitFor = workflow.createStep("deactivate", "detaching grop mirror: " + mirrorStr, waitFor, storage,
storageSystem.getSystemType(), getClass(), detach, null, null);
_log.info("Adding group delete mirror step");
Workflow.Method delete = deleteMirrorMethod(storage, mirrorList, isRemoveAll);
waitFor = workflow.createStep("deactivate", "deleting group mirror: " + mirrorStr, waitFor, storage,
storageSystem.getSystemType(), getClass(), delete, null, null);
} else {
// Optionally create a step to pause (fracture) the mirrors
if (mirrorIsPausable(mirrors)) {
_log.info("Adding fracture mirror step");
waitFor = workflow.createStep("deactivate",
String.format("fracture list mirror: %s", mirrorStr),
waitFor, storage, getDeviceType(storage),
this.getClass(),
fractureListMirrorMethod(storage, mirrorList, false), null, null);
}
_log.info("Adding detach mirror step");
Workflow.Method detach = detachListMirrorMethod(storage, mirrorList);
waitFor = workflow.createStep("deactivate", "detaching list mirror: " + mirrorStr, waitFor, storage,
storageSystem.getSystemType(), getClass(), detach, null, null);
_log.info("Adding delete mirror step");
Workflow.Method delete = deleteListMirrorMethod(storage, mirrorList);
waitFor = workflow.createStep("deactivate", "deleting list mirror: " + mirrorStr, waitFor, storage,
storageSystem.getSystemType(), getClass(), delete, null, null);
}
return waitFor;
}
public Workflow.Method fractureListMirrorMethod(URI storage, List<URI> mirrorList, Boolean sync) {
return new Workflow.Method("fractureListMirror", storage, mirrorList, sync);
}
public void fractureListMirror(URI storage, List<URI> mirrorList, Boolean sync, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorFractureCompleter(mirrorList, opId);
getDevice(storageObj.getSystemType()).doFractureListReplica(storageObj, mirrorList, sync, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method detachListMirrorMethod(URI storage, List<URI> mirrorList) {
return new Workflow.Method("detachListMirror", storage, mirrorList);
}
public void detachListMirror(URI storage, List<URI> mirrorList, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorDetachCompleter(mirrorList, opId);
getDevice(storageObj.getSystemType()).doDetachListReplica(storageObj, mirrorList, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method deleteListMirrorMethod(URI storage, List<URI> mirrorList) {
return new Workflow.Method("deleteListMirror", storage, mirrorList);
}
public void deleteListMirror(URI storage, List<URI> mirrorList, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorDeleteCompleter(mirrorList, opId);
getDevice(storageObj.getSystemType()).doDeleteListReplica(storageObj, mirrorList, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public String deleteSnapshotStep(Workflow workflow, String waitFor, URI storage, StorageSystem storageSystem,
List<URI> snapshotList, boolean isRemoveAll) {
if (isRemoveAll) {
_log.info("Adding group remove snapshot step");
Workflow.Method deleteMethod = deleteSnapshotMethod(storage, snapshotList.get(0));
waitFor = workflow.createStep(SNAPSHOT_DELETE_STEP_GROUP, "Deleting snapshot", waitFor,
storage, storageSystem.getSystemType(), getClass(), deleteMethod, null, null);
} else {
for (URI uri : snapshotList) {
_log.info("Adding single remove snapshot step: {}", uri);
Workflow.Method deleteMethod = deleteSelectedSnapshotMethod(storage, uri);
waitFor = workflow.createStep(SNAPSHOT_DELETE_STEP_GROUP, "Deleting snapshot", waitFor,
storage, storageSystem.getSystemType(), getClass(), deleteMethod, null, null);
}
}
return waitFor;
}
public Workflow.Method deleteSnapshotMethod(URI storage, URI snapshot) {
return new Workflow.Method("deleteSnapshot", storage, snapshot);
}
public Workflow.Method deleteSelectedSnapshotMethod(URI storage, URI snapshot) {
return new Workflow.Method("deleteSelectedSnapshot", storage, snapshot);
}
public void deleteSelectedSnapshot(URI storage, URI snapshot, String opId) throws ControllerException {
_log.info("START deleteSelectedSnapshot");
TaskCompleter completer = null;
WorkflowStepCompleter.stepExecuting(opId);
try {
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
BlockSnapshot snapObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);
completer = BlockSnapshotDeleteCompleter.createCompleter(_dbClient, snapObj, opId);
getDevice(storageObj.getSystemType()).doDeleteSelectedSnapshot(storageObj, snapshot, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.deleteVolumeSnapshotFailed(e);
}
}
}
/**
* Add step to create list clone.
*
* @param workflow
* The Workflow being built
* @param storageSystem
* Storage system
* @param waitFor
* Previous step to waitFor
* @param cloneList
* List of URIs for clones to be created
* @return last step added to waitFor
*/
public String createListCloneStep(Workflow workflow, StorageSystem storageSystem, List<URI> cloneList, String waitFor) {
URI storage = storageSystem.getId();
Workflow.Method createMethod = createListCloneMethod(storage, cloneList, false);
Workflow.Method rollbackMethod = rollbackListCloneMethod(storage, cloneList);
waitFor = workflow.createStep(BlockDeviceController.FULL_COPY_CREATE_STEP_GROUP, "Creating list clone", waitFor, storage,
storageSystem.getSystemType(), getClass(), createMethod, rollbackMethod, null);
if (storageSystem.deviceIsType(Type.vnxblock)) {
waitFor = workflow.createStep(BlockDeviceController.FULL_COPY_CREATE_STEP_GROUP, "fracture list clone", waitFor,
storage, storageSystem.getSystemType(), BlockDeviceController.class,
fractureListCloneMethod(storage, cloneList, false), null, null);
}
return waitFor;
}
public Workflow.Method createListCloneMethod(URI storage, List<URI> cloneList, Boolean createInactive) {
return new Workflow.Method("createListClone", storage, cloneList, createInactive);
}
public void createListClone(URI storage, List<URI> cloneList, Boolean createInactive, String taskId) {
try {
WorkflowStepCompleter.stepExecuting(taskId);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
TaskCompleter taskCompleter = new CloneCreateCompleter(cloneList, taskId);
getDevice(storageSystem.getSystemType()).doCreateListReplica(storageSystem, cloneList, createInactive, taskCompleter);
} catch (Exception e) {
_log.error(e.getMessage(), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, cloneList, taskId, serviceError);
}
}
public Workflow.Method rollbackListCloneMethod(URI storage, List<URI> cloneList) {
return new Workflow.Method("rollbackListClone", storage, cloneList);
}
public void rollbackListClone(URI storage, List<URI> cloneList, String taskId) {
WorkflowStepCompleter.stepExecuting(taskId);
_log.info("Rollback list clone");
List<Volume> clones = _dbClient.queryObject(Volume.class, cloneList);
List<Volume> clonesNoRollback = new ArrayList<Volume>();
List<URI> clonesToRollback = new ArrayList<URI>();
try {
for (Volume clone : clones) {
if (isNullOrEmpty(clone.getNativeId())) {
clone.setInactive(true);
clonesNoRollback.add(clone);
} else {
clonesToRollback.add(clone.getId());
}
}
if (!clonesNoRollback.isEmpty()) {
_dbClient.updateObject(clonesNoRollback);
}
if (!clonesToRollback.isEmpty()) {
_log.info("Detach list clone for rollback");
detachListClone(storage, clonesToRollback, generateStepIdForDependentCallDuringRollback());
_log.info("Delete clones for rollback");
deleteVolumes(storage, clonesToRollback, generateStepIdForDependentCallDuringRollback());
}
WorkflowStepCompleter.stepSucceded(taskId);
} catch (InternalException ie) {
_log.error(String.format("rollbackListClone failed - Array: %s, clones: %s", storage, Joiner.on("\t").join(cloneList)));
_log.error(ie.getMessage(), ie);
doFailTask(Volume.class, cloneList, taskId, ie);
WorkflowStepCompleter.stepFailed(taskId, ie);
} catch (Exception e) {
_log.error(e.getMessage(), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(Volume.class, cloneList, taskId, serviceError);
}
}
public Workflow.Method fractureListCloneMethod(URI storage, List<URI> cloneList, Boolean sync) {
return new Workflow.Method("fractureListClone", storage, cloneList, sync);
}
public void fractureListClone(URI storage, List<URI> cloneList, Boolean sync, String taskId) {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(taskId);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
completer = new CloneFractureCompleter(cloneList, taskId);
getDevice(storageSystem.getSystemType()).doFractureListReplica(storageSystem, cloneList, sync, completer);
} catch (Exception e) {
_log.error(e.getMessage(), e);
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
doFailTask(Volume.class, cloneList, taskId, serviceError);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(taskId, serviceError);
}
}
/**
* Add Steps to create list mirror.
*
* @param workflow
* The Workflow being built
* @param storageSystem
* Storage system
* @param waitFor
* Previous step to waitFor
* @param mirrorList
* List of URIs for mirrors to be created
* @return last step added to waitFor
*/
public String createListMirrorStep(Workflow workflow, String waitFor, StorageSystem storageSystem, List<URI> mirrorList)
throws ControllerException {
URI storage = storageSystem.getId();
waitFor = workflow.createStep(CREATE_MIRRORS_STEP_GROUP, "Creating list mirror", waitFor,
storage, storageSystem.getSystemType(),
this.getClass(),
createListMirrorMethod(storage, mirrorList, false),
rollbackListMirrorMethod(storage, mirrorList), null);
return waitFor;
}
public Workflow.Method createListMirrorMethod(URI storage, List<URI> mirrorList, Boolean createInactive) {
return new Workflow.Method("createListMirror", storage, mirrorList, createInactive);
}
public void createListMirror(URI storage, List<URI> mirrorList, Boolean createInactive, String opId)
throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockMirrorCreateCompleter(mirrorList, opId);
getDevice(storageObj.getSystemType()).doCreateListReplica(storageObj, mirrorList, createInactive, completer);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
if (completer != null) {
completer.error(_dbClient, serviceError);
}
WorkflowStepCompleter.stepFailed(opId, serviceError);
}
}
public Workflow.Method rollbackListMirrorMethod(URI storage, List<URI> mirrorList) {
return new Workflow.Method("rollbackListMirror", storage, mirrorList);
}
public void rollbackListMirror(URI storage, List<URI> mirrorList, String taskId) {
WorkflowStepCompleter.stepExecuting(taskId);
try {
List<BlockMirror> mirrorsNoRollback = new ArrayList<BlockMirror>();
List<BlockMirror> mirrorsToRollback = new ArrayList<BlockMirror>();
Iterator<BlockMirror> mirrorIterator = _dbClient.queryIterativeObjects(BlockMirror.class, mirrorList);
while (mirrorIterator.hasNext()) {
BlockMirror mirror = mirrorIterator.next();
if (mirror != null && !mirror.getInactive()) {
if (isNullOrEmpty(mirror.getNativeId())) {
mirror.setInactive(true);
mirrorsNoRollback.add(mirror);
} else {
mirrorsToRollback.add(mirror);
}
}
}
if (!mirrorsNoRollback.isEmpty()) {
_dbClient.updateObject(mirrorsNoRollback);
}
if (!mirrorsToRollback.isEmpty()) {
List<URI> mirrorURIsToRollback = new ArrayList<URI>(transform(mirrorsToRollback, FCTN_MIRROR_TO_URI));
String mirrorNativeIds = Joiner.on(", ").join(transform(mirrorsToRollback, fctnBlockObjectToNativeID()));
if (mirrorIsPausable(mirrorsToRollback)) {
_log.info("Attempting to fracture {} for rollback", mirrorNativeIds);
fractureMirror(storage, mirrorURIsToRollback, false, false, generateStepIdForDependentCallDuringRollback());
}
_log.info("Attempting to detach {} for rollback", mirrorNativeIds);
detachMirror(storage, mirrorURIsToRollback, false, false, generateStepIdForDependentCallDuringRollback());
_log.info("Attempting to delete {} for rollback", mirrorNativeIds);
deleteMirror(storage, mirrorURIsToRollback, false, generateStepIdForDependentCallDuringRollback());
}
WorkflowStepCompleter.stepSucceded(taskId);
} catch (InternalException ie) {
_log.error(String.format("rollbackListMirror failed - Array:%s, Mirror:%s", storage, Joiner.on("\t").join(mirrorList)));
doFailTask(BlockMirror.class, mirrorList, taskId, ie);
WorkflowStepCompleter.stepFailed(taskId, ie);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(BlockMirror.class, mirrorList, taskId, serviceError);
}
}
/**
* Add Steps to create list snapshot.
*
* @param workflow
* The Workflow being built
* @param storageSystem
* Storage system
* @param waitFor
* Previous step to waitFor
* @param snapshotList
* List of URIs for snapshots to be created
* @return last step added to waitFor
*/
public String createListSnapshotStep(Workflow workflow, String waitFor, StorageSystem storageSystem, List<URI> snapshotList)
throws ControllerException {
URI storage = storageSystem.getId();
waitFor = workflow.createStep(CREATE_SNAPSHOTS_STEP_GROUP,
"Create list snapshot", waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
createListSnapshotMethod(storage, snapshotList, false, false),
rollbackListSnapshotMethod(storage, snapshotList), null);
return waitFor;
}
public Workflow.Method createListSnapshotMethod(URI storage, List<URI> snapshotList, Boolean createInactive, Boolean readOnly) {
return new Workflow.Method("createListSnapshot", storage, snapshotList, createInactive, readOnly);
}
public void createListSnapshot(URI storage, List<URI> snapshotList, Boolean createInactive, Boolean readOnly, String opId)
throws ControllerException {
WorkflowStepCompleter.stepExecuting(opId);
TaskCompleter completer = null;
try {
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockSnapshotCreateCompleter(snapshotList, opId);
getDevice(storageObj.getSystemType()).doCreateListReplica(storageObj, snapshotList, createInactive, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(opId, serviceError);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.createVolumeSnapshotFailed(e);
}
}
}
public Workflow.Method rollbackListSnapshotMethod(URI storage, List<URI> snapshotList) {
return new Workflow.Method("rollbackListSnapshot", storage, snapshotList);
}
public void rollbackListSnapshot(URI storage, List<URI> snapshotList, String taskId) {
WorkflowStepCompleter.stepExecuting(taskId);
try {
List<BlockSnapshot> snapshotsNoRollback = new ArrayList<BlockSnapshot>();
List<BlockSnapshot> snapshotsToRollback = new ArrayList<BlockSnapshot>();
Iterator<BlockSnapshot> itr = _dbClient.queryIterativeObjects(BlockSnapshot.class, snapshotList);
while (itr.hasNext()) {
BlockSnapshot snapshot = itr.next();
if (snapshot != null && !snapshot.getInactive()) {
if (isNullOrEmpty(snapshot.getNativeId())) {
snapshot.setInactive(true);
snapshotsNoRollback.add(snapshot);
} else {
snapshotsToRollback.add(snapshot);
}
}
}
if (!snapshotsNoRollback.isEmpty()) {
_dbClient.updateObject(snapshotsNoRollback);
}
if (!snapshotsToRollback.isEmpty()) {
List<URI> snapshotURIsToRollback = new ArrayList<URI>(transform(snapshotsToRollback, fctnDataObjectToID()));
String snapshotNativeIds = Joiner.on(", ").join(transform(snapshotsToRollback, fctnBlockObjectToNativeID()));
_log.info("Attempting to delete {} for rollback", snapshotNativeIds);
for (URI snapshotURI : snapshotURIsToRollback) {
deleteSelectedSnapshot(storage, snapshotURI, generateStepIdForDependentCallDuringRollback());
}
}
WorkflowStepCompleter.stepSucceded(taskId);
} catch (InternalException ie) {
_log.error(String.format("rollbackListSnapshot failed - Array:%s, Snapshots:%s", storage, Joiner.on("\t").join(snapshotList)));
doFailTask(BlockSnapshot.class, snapshotList, taskId, ie);
WorkflowStepCompleter.stepFailed(taskId, ie);
} catch (Exception e) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
WorkflowStepCompleter.stepFailed(taskId, serviceError);
doFailTask(BlockSnapshot.class, snapshotList, taskId, serviceError);
}
}
/**
* Generates step id for dependent calls during rollback step execution.
* As we cannot add new step for dependent calls to ROLLBACK steps group during workflow execution,
* generate a new operation id for the dependent call. This is to avoid dependent call marking the rollback step
* completed.
* TODO find a solution
*
* @return the string
*/
private String generateStepIdForDependentCallDuringRollback() {
return UUID.randomUUID().toString();
}
/**
* Adding/removing volumes to/from an application is ViPR DB only operation (no controller side involved),
* except for adding VNX volumes to an application, if the VNX volumes are in a real replication group.
*
* 1. remove volumes from replication group, keep volume's replicationGroupInstance unchanged
* 2. delete the replication group from array, keep CG's systemConsistencyGroup unchanged
* 3. change CG's arrayConsistency to false, update volume's volumeGroupIds, update clone's fullCopySetName
* (performed in the completer class)
*/
@Override
public void updateApplication(URI storage, ApplicationAddVolumeList addVolList, URI application,
String opId) throws ControllerException {
TaskCompleter completer = null;
String waitFor = null;
try {
// Generate the Workflow.
Workflow workflow = _workflowService.getNewWorkflow(this,
UPDATE_VOLUMES_FOR_APPLICATION_WS_NAME, false, opId);
List<URI> volumesToAdd = null;
if (addVolList != null) {
volumesToAdd = addVolList.getVolumes();
}
if (volumesToAdd != null && !volumesToAdd.isEmpty()) {
Map<URI, List<URI>> addVolsMap = new HashMap<URI, List<URI>>();
for (URI voluri : volumesToAdd) {
Volume vol = _dbClient.queryObject(Volume.class, voluri);
if (vol != null && !vol.getInactive()) {
if (ControllerUtils.isVnxVolume(vol, _dbClient) && vol.isInCG()
&& !ControllerUtils.isNotInRealVNXRG(vol, _dbClient)) {
URI cguri = vol.getConsistencyGroup();
List<URI> vols = addVolsMap.get(cguri);
if (vols == null) {
vols = new ArrayList<URI>();
}
vols.add(voluri);
addVolsMap.put(cguri, vols);
}
}
}
List<URI> cgs = new ArrayList<URI>(addVolsMap.keySet());
completer = new ApplicationTaskCompleter(application, volumesToAdd, null, cgs, opId);
for (Map.Entry<URI, List<URI>> entry : addVolsMap.entrySet()) {
_log.info("Creating workflows for adding CG volumes to application");
URI cguri = entry.getKey();
List<URI> cgVolsToAdd = entry.getValue();
URI voluri = cgVolsToAdd.get(0);
Volume vol = _dbClient.queryObject(Volume.class, voluri);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, vol.getStorageController());
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cguri);
String groupName = ControllerUtils.generateReplicationGroupName(storageSystem, cguri, vol.getReplicationGroupInstance(),
_dbClient);
// remove volumes from array replication group, and delete the group, but keep volumes reference
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Removing volumes from consistency group %s", cg.getLabel()),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
removeFromConsistencyGroupMethod(storage, cguri, cgVolsToAdd, true),
rollbackMethodNullMethod(), null);
// remove replication group
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Deleting replication group for consistency group %s", cg.getLabel()),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
deleteConsistencyGroupMethod(storage, cguri, groupName, true, false, false),
rollbackMethodNullMethod(), null);
}
}
// Finish up and execute the plan.
_log.info("Executing workflow plan {}", UPDATE_VOLUMES_FOR_APPLICATION_WS_NAME);
String successMessage = String.format(
"Update application successful for %s", application.toString());
workflow.executePlan(completer, successMessage);
} catch (Exception e) {
_log.error("Exception while updating the application", e);
if (completer != null) {
completer.error(_dbClient,
DeviceControllerException.exceptions.failedToUpdateVolumesFromAppication(application.toString(), e.getMessage()));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void createSnapshotSession(URI systemURI, URI snapSessionURI,
List<List<URI>> sessionSnapshotURIs, String copyMode, String opId)
throws InternalException {
TaskCompleter completer = new BlockSnapshotSessionCreateWorkflowCompleter(snapSessionURI, sessionSnapshotURIs, opId);
try {
// Get a new workflow to execute creation of the snapshot session and if
// necessary creation and linking of target volumes to the new session.
Workflow workflow = _workflowService.getNewWorkflow(this, CREATE_SAPSHOT_SESSION_WF_NAME, false, opId);
_log.info("Created new workflow to create a new snapshot session for source with operation id {}", opId);
// When creating a group snapshot we need the name of the group.
String groupName = null;
boolean isCG = checkSnapshotSessionConsistencyGroup(snapSessionURI, _dbClient, completer);
if (isCG) {
BlockSnapshotSession snapSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI);
groupName = snapSession.getReplicationGroupInstance();
}
// Create a step to create the session.
String waitFor = workflow.createStep(CREATE_SNAPSHOT_SESSION_STEP_GROUP, String.format("Creating block snapshot session"),
null, systemURI, getDeviceType(systemURI), getClass(),
createBlockSnapshotSessionMethod(systemURI, snapSessionURI, groupName),
rollbackMethodNullMethod(), null);
// Add steps to create any new targets and link them to the session, if necessary
if ((sessionSnapshotURIs != null) && (!sessionSnapshotURIs.isEmpty())) {
if (isCG) {
for (List<URI> snapshotURIs : sessionSnapshotURIs) {
workflow.createStep(
LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Linking group targets snapshot sessions %s", snapSessionURI),
waitFor,
systemURI,
getDeviceType(systemURI),
getClass(),
linkBlockSnapshotSessionTargetGroupMethod(systemURI, snapSessionURI, snapshotURIs, copyMode,
Boolean.FALSE),
rollbackLinkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURIs.get(0)), null);
}
} else {
for (List<URI> snapshotURIs : sessionSnapshotURIs) {
if ((snapshotURIs != null) && (!snapshotURIs.isEmpty())) {
for (URI snapshotURI : snapshotURIs) {
workflow.createStep(
LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Linking targets for snapshot session %s", snapSessionURI),
waitFor,
systemURI,
getDeviceType(systemURI),
getClass(),
linkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURI, copyMode,
Boolean.FALSE),
rollbackLinkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURI), null);
}
}
}
}
}
workflow.executePlan(completer, "Create block snapshot session successful");
} catch (Exception e) {
_log.error("Create block snapshot session failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.createBlockSnapshotSessionFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* Adds the step to create block snapshot session.
*
* @param workflow
* the workflow
* @param systemURI
* the system uri
* @param session
* the snapshot session
* @param repGroupName
* the replication group name
* @param waitFor
* the wait for
* @return the stepId
*/
public String addStepToCreateSnapshotSession(Workflow workflow, URI systemURI, URI session,
String repGroupName, String waitFor) {
String stepId = workflow.createStep(BlockDeviceController.CREATE_SNAPSHOT_SESSION_STEP_GROUP,
String.format("Creating block snapshot session"),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
createBlockSnapshotSessionMethod(systemURI, session, repGroupName),
rollbackMethodNullMethod(), null);
return stepId;
}
/**
* Create the workflow method that is invoked by the workflow service
* to create block snapshot sessions.
*
* @param systemURI
* The URI of the storage system on which the snapshot sessions are created.
* @param snapSessionURI
* The URIs of the sessions in ViPR.
* @param groupName
* The group name when creating a group session.
*
* @return A reference to a Workflow.Method for creating an array snapshot.
*/
public static Workflow.Method createBlockSnapshotSessionMethod(URI systemURI, URI snapSessionURI, String groupName) {
return new Workflow.Method(CREATE_SNAPSHOT_SESSION_METHOD, systemURI, snapSessionURI, groupName);
}
/**
* Creates array snapshots on the array with the passed URI and associates these
* with the BlockSnapshotSession instances with the passed URIs.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URIs of the BlockSnapshotSessioninstances representing the array snapshots.
* @param groupName
* The group name when creating a group session.
* @param stepId
* The unique id of the workflow step in which the snapshots are be created.
*/
public void createBlockSnapshotSession(URI systemURI, URI snapSessionURI, String groupName, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionCreateCompleter(snapSessionURI, stepId);
WorkflowStepCompleter.stepExecuting(stepId);
getDevice(system.getSystemType()).doCreateSnapshotSession(system, snapSessionURI, groupName, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.createBlockSnapshotSessionFailed(e);
}
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to link a target volume to the array snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionSnapshotMap
* Map of BlockSnapshotSession URI's to their BlockSnapshot instance URI,
* representing the linked target.
* @param copyMode
* The manner in which the target is linked to the array snapshot.
* @param targetsExist
* true if the target exists, false if a new one needs to be created.
*
* @return A reference to a Workflow.Method for linking a target volume to an array snapshot.
*/
public static Workflow.Method linkBlockSnapshotSessionTargetGroupMethod(URI systemURI,
URI snapshotSessionURI,
List<URI> snapshotURIs,
String copyMode, Boolean targetsExist) {
return new Workflow.Method(LINK_SNAPSHOT_SESSION_TARGET_GROUP_METHOD, systemURI,
snapshotSessionURI,
snapshotURIs, copyMode,
targetsExist);
}
/**
* Create and link a target volume group to an array snapshot on the storage system
* with the passed URI. The new target group is linked to the array snapshot
* based on the passed copy mode and is associated with the BlockSnapshot
* instances with the passed URI.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionSnapshotMap
* Map of BlockSnapshotSession URI's to their BlockSnapshot instance URI,
* representing the linked target.
* @param copyMode
* The manner in which the target is linked to the array snapshot.
* @param targetsExist
* true if the target exists, false if a new one needs to be created.
* @param stepId
* The unique id of the workflow step in which the target is linked.
*/
public boolean linkBlockSnapshotSessionTargetGroup(URI systemURI, URI snapshotSessionURI, List<URI> snapshotURIs,
String copyMode, Boolean targetsExist, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionLinkTargetCompleter(snapshotSessionURI, snapshotURIs, stepId);
getDevice(system.getSystemType()).doLinkBlockSnapshotSessionTargetGroup(system, snapshotSessionURI,
snapshotURIs, copyMode, targetsExist, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.linkBlockSnapshotSessionTargetsFailed(e);
}
}
return true;
}
public String addStepToLinkBlockSnapshotSessionTarget(Workflow workflow, URI systemURI,
BlockSnapshotSession session, URI snapshot, String copyMode, String waitFor) {
String stepId = workflow.createStep(
BlockDeviceController.LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Linking targets for snapshot session %s", session.getId()),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
linkBlockSnapshotSessionTargetMethod(systemURI, session.getId(), snapshot, copyMode, Boolean.FALSE),
rollbackLinkBlockSnapshotSessionTargetMethod(systemURI, session.getId(), snapshot), null);
return stepId;
}
/**
* Create the workflow method that is invoked by the workflow service
* to link a target volume to the array snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
* @param copyMode
* The manner in which the target is linked to the array snapshot.
* @param targetExists
* true if the target exists, false if a new one needs to be created.
*
* @return A reference to a Workflow.Method for linking a target volume to an array snapshot.
*/
public static Workflow.Method linkBlockSnapshotSessionTargetMethod(URI systemURI,
URI snapSessionURI, URI snapshotURI, String copyMode, Boolean targetExists) {
return new Workflow.Method(LINK_SNAPSHOT_SESSION_TARGET_METHOD, systemURI, snapSessionURI, snapshotURI, copyMode, targetExists);
}
/**
* Creates and link a target volume to an array snapshot on the storage system
* with the passed URI. The new target volume is linked to the array snapshot
* based on the passed copy mode and is associated with the BlockSnapshot
* instance with the passed URI.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
* @param copyMode
* The manner in which the target is linked to the array snapshot.
* @param targetExists
* true if the target exists, false if a new one needs to be created.
* @param stepId
* The unique id of the workflow step in which the target is linked.
*/
public void linkBlockSnapshotSessionTarget(URI systemURI, URI snapSessionURI,
URI snapshotURI, String copyMode, Boolean targetExists, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionLinkTargetCompleter(snapSessionURI, Lists.newArrayList(snapshotURI), stepId);
getDevice(system.getSystemType()).doLinkBlockSnapshotSessionTarget(system, snapSessionURI,
snapshotURI, copyMode, targetExists, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.linkBlockSnapshotSessionTargetsFailed(e);
}
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to rollback a failed attempt to link a target volume to the array
* snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance.
*
* @return A reference to a Workflow.Method for rolling back a failed attempt to link
* a target volume to an array snapshot.
*/
public static Workflow.Method rollbackLinkBlockSnapshotSessionTargetMethod(URI systemURI, URI snapSessionURI, URI snapshotURI) {
return new Workflow.Method(RB_LINK_SNAPSHOT_SESSION_TARGET_METHOD, systemURI, snapSessionURI, snapshotURI);
}
/**
* Rollback a failed attempt to link a target volume to the array snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance.
* @param stepId
* The unique id of the workflow step in which the rollback is executed.
*/
public void rollbackLinkBlockSnapshotSessionTarget(URI systemURI, URI snapSessionURI, URI snapshotURI, String stepId) {
// We do not rollback successfully linked targets. If the target
// was not successfully created and linked, it could in one of two
// states. Either the target is not provisioned, or the target is
// provisioned but not linked to the array snapshot. We call the
// method to unlink the target and make sure the unlink target
// algorithm accounts for these possibilities. Successfully linked
// targets will be in the list of linked targets for the session.
BlockSnapshotSession snapSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI);
StringSet linkedTargets = snapSession.getLinkedTargets();
if ((linkedTargets == null) || (!linkedTargets.contains(snapshotURI.toString()))) {
unlinkBlockSnapshotSessionTarget(systemURI, snapSessionURI, snapshotURI, Boolean.TRUE, stepId);
} else {
WorkflowStepCompleter.stepSucceded(stepId);
}
}
/**
* {@inheritDoc}
*/
@Override
public void linkNewTargetVolumesToSnapshotSession(URI systemURI, URI snapSessionURI, List<List<URI>> snapshotURIs,
String copyMode, String opId) throws InternalException {
TaskCompleter completer = new BlockSnapshotSessionLinkTargetsWorkflowCompleter(snapSessionURI, snapshotURIs, opId);
try {
// Get a new workflow to execute the linking of the target volumes
// to the new session.
Workflow workflow = _workflowService.getNewWorkflow(this, LINK_SNAPSHOT_SESSION_TARGETS_WF_NAME, false, opId);
_log.info("Created new workflow to create and link new targets for snapshot session {} with operation id {}",
snapSessionURI, opId);
if (checkSnapshotSessionConsistencyGroup(snapSessionURI, _dbClient, completer)) {
String waitFor = null;
for (List<URI> snapshotURI : snapshotURIs) {
waitFor = workflow.createStep(
LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Linking target for snapshot session %s", snapSessionURI),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
linkBlockSnapshotSessionTargetGroupMethod(systemURI, snapSessionURI, snapshotURI, copyMode, Boolean.FALSE),
null, null);
}
} else {
for (List<URI> snapshotURI : snapshotURIs) {
workflow.createStep(
LINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Linking target for snapshot session %s", snapSessionURI),
null, systemURI, getDeviceType(systemURI), getClass(),
linkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURI.get(0), copyMode, Boolean.FALSE),
rollbackLinkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURI.get(0)), null);
}
}
workflow.executePlan(completer, "Create and link new target volumes for block snapshot session successful");
} catch (Exception e) {
_log.error("Create and link new target volumes for block snapshot session failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.linkBlockSnapshotSessionTargetsFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* {@inheritDoc}
*/
@Override
public void relinkTargetsToSnapshotSession(URI systemURI, URI tgtSnapSessionURI, List<URI> snapshotURIs,
Boolean updateStatus, String opId) throws InternalException {
TaskCompleter completer = new BlockSnapshotSessionRelinkTargetsWorkflowCompleter(tgtSnapSessionURI, updateStatus, opId);
try {
// Get a new workflow to execute the linking of the target volumes
// to the new session.
Workflow workflow = _workflowService.getNewWorkflow(this, RELINK_SNAPSHOT_SESSION_TARGETS_WF_NAME, false, opId);
_log.info("Created new workflow to re-link targets to snapshot session {} with operation id {}",
tgtSnapSessionURI, opId);
Iterable<URI> snapshotsIterable = snapshotURIs;
BlockSnapshotSession tgtSnapSession = _dbClient.queryObject(BlockSnapshotSession.class, tgtSnapSessionURI);
// For CG's, ensure 1 target per ReplicationGroup
if (tgtSnapSession.hasConsistencyGroup()
&& NullColumnValueGetter.isNotNullValue(tgtSnapSession.getReplicationGroupInstance())) {
snapshotsIterable = ControllerUtils.ensureOneSnapshotPerReplicationGroup(snapshotURIs, _dbClient);
}
String waitFor = null;
for (URI snapshotURI : snapshotsIterable) {
waitFor = workflow.createStep(
RELINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Re-linking target to snapshot session %s", tgtSnapSessionURI),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
relinkBlockSnapshotSessionTargetMethod(systemURI, tgtSnapSessionURI, snapshotURI),
null, null);
}
workflow.executePlan(completer, "Re-link target volumes to block snapshot session successful");
} catch (Exception e) {
_log.error("Re-link target volumes to block snapshot session failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.relinkBlockSnapshotSessionTargetsFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to re-link a target volume to the target array snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param tgtSnapSessionURI
* The URI of the target BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
*
* @return A reference to a Workflow.Method for re-linking a target volume to an array snapshot.
*/
public static Workflow.Method relinkBlockSnapshotSessionTargetMethod(URI systemURI,
URI tgtSnapSessionURI, URI snapshotURI) {
return new Workflow.Method(RELINK_SNAPSHOT_SESSION_TARGET_METHOD, systemURI, tgtSnapSessionURI, snapshotURI);
}
/**
* Re-link a linked target volume to the target array snapshot on the storage
* system with the passed URI.
*
* @param systemURI
* The URI of the storage system.
* @param tgtSnapSessionURI
* The URI of the target BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
* @param stepId
* The unique id of the workflow step in which the target is re-linked.
*/
public void relinkBlockSnapshotSessionTarget(URI systemURI, URI tgtSnapSessionURI,
URI snapshotURI, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionRelinkTargetCompleter(tgtSnapSessionURI, snapshotURI, stepId);
getDevice(system.getSystemType()).doRelinkBlockSnapshotSessionTarget(system, tgtSnapSessionURI,
snapshotURI, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.relinkBlockSnapshotSessionTargetsFailed(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void unlinkTargetsFromSnapshotSession(URI systemURI, URI snapSessionURI,
Map<URI, Boolean> snapshotDeletionMap, OperationTypeEnum opType, String opId) {
TaskCompleter completer = new BlockSnapshotSessionUnlinkTargetsWorkflowCompleter(snapSessionURI, opType, opId);
try {
// Get a new workflow to unlinking of the targets from session.
Workflow workflow = _workflowService.getNewWorkflow(this, UNLINK_SNAPSHOT_SESSION_TARGETS_WF_NAME, false, opId);
_log.info("Created new workflow to unlink targets for snapshot session {} with operation id {}",
snapSessionURI, opId);
Set<URI> targetKeys = snapshotDeletionMap.keySet();
BlockSnapshotSession snapSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI);
// For CG's, ensure 1 target per ReplicationGroup
if (snapSession.hasConsistencyGroup() && NullColumnValueGetter.isNotNullValue(snapSession.getReplicationGroupInstance())) {
Iterator<BlockSnapshot> snapshots = _dbClient.queryIterativeObjects(BlockSnapshot.class, snapshotDeletionMap.keySet());
final Set<String> replicationGroups = new HashSet<>();
final Map<URI, BlockSnapshot> uriToSnapshotCache = new HashMap<>();
while (snapshots.hasNext()) {
BlockSnapshot snapshot = snapshots.next();
uriToSnapshotCache.put(snapshot.getId(), snapshot);
}
Map<URI, Boolean> filtered = Maps.filterEntries(snapshotDeletionMap, new Predicate<Map.Entry<URI, Boolean>>() {
@Override
public boolean apply(Map.Entry<URI, Boolean> input) {
BlockSnapshot blockSnapshot = uriToSnapshotCache.get(input.getKey());
String repGrpInstance = blockSnapshot.getReplicationGroupInstance();
if (replicationGroups.contains(repGrpInstance)) {
return false;
}
replicationGroups.add(repGrpInstance);
return true;
}
});
// assign to targetKeys filtered keySet view of snapshotDeletionMap.
targetKeys = filtered.keySet();
}
// TODO Use ModifyListSettingsDefineState here and remove the for-loop.
String waitFor = null;
// Create a workflow step to unlink each target specified in targetKeys
for (URI snapshotURI : targetKeys) {
waitFor = workflow.createStep(UNLINK_SNAPSHOT_SESSION_TARGET_STEP_GROUP,
String.format("Unlinking target for snapshot session %s", snapSessionURI),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
unlinkBlockSnapshotSessionTargetMethod(systemURI, snapSessionURI, snapshotURI,
snapshotDeletionMap.get(snapshotURI)),
null, null);
}
// Execute the workflow.
workflow.executePlan(completer, "Unlink block snapshot session targets successful");
} catch (Exception e) {
_log.error("Unlink block snapshot session targets failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.unlinkBlockSnapshotSessionTargetsFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to unlink a target from an array snapshot.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
* @param deleteTarget
* True if the target volume should be deleted.
*
* @return A reference to a Workflow.Method for linking a target volume to an array snapshot.
*/
public static Workflow.Method unlinkBlockSnapshotSessionTargetMethod(URI systemURI,
URI snapSessionURI, URI snapshotURI, Boolean deleteTarget) {
return new Workflow.Method(UNLINK_SNAPSHOT_SESSION_TARGET_METHOD, systemURI, snapSessionURI, snapshotURI, deleteTarget);
}
/**
* Unlinks the target from the array snapshot on the storage system
* with the passed URI. Additionally, the target device will be deleted
* if so requested.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param snapshotURI
* The URI of the BlockSnapshot instance representing the linked target volume.
* @param deleteTarget
* True if the target volume should be deleted.
* @param stepId
* The unique id of the workflow step in which the target is unlinked.
*/
public void unlinkBlockSnapshotSessionTarget(URI systemURI, URI snapSessionURI,
URI snapshotURI, Boolean deleteTarget, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionUnlinkTargetCompleter(snapSessionURI, snapshotURI, deleteTarget, stepId);
getDevice(system.getSystemType()).doUnlinkBlockSnapshotSessionTarget(system, snapSessionURI,
snapshotURI, deleteTarget, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.unlinkBlockSnapshotSessionTargetsFailed(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void restoreSnapshotSession(URI systemURI, URI snapSessionURI, Boolean updateStatus, String opId) {
BlockSnapshotSession snapshotSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI);
TaskCompleter completer = new BlockSnapshotSessionRestoreWorkflowCompleter(snapshotSession.getId(), updateStatus, opId);
try {
// Get a new workflow to restore the snapshot session.
Workflow workflow = _workflowService.getNewWorkflow(this, RESTORE_SNAPSHOT_SESSION_WF_NAME, false, opId);
_log.info("Created new workflow to restore snapshot session {} with operation id {}",
snapSessionURI, opId);
String waitFor = null;
// Check if we are dealing with a single volume or a group...
BlockObject sourceObj = null;
if (snapshotSession.hasConsistencyGroup()
&& NullColumnValueGetter.isNotNullValue(snapshotSession.getReplicationGroupInstance())) {
// We need a single source volume for the session.
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, snapshotSession.getConsistencyGroup());
List<Volume> nativeVolumes = BlockConsistencyGroupUtils.getActiveNativeVolumesInCG(cg, _dbClient);
// get source group name from the session.
String sourceGroupName = snapshotSession.getReplicationGroupInstance();
for (Volume volume : nativeVolumes) {
if (sourceGroupName.equals(volume.getReplicationGroupInstance())) {
sourceObj = volume;
break; // get source volume which matches session's RG name
}
}
} else {
sourceObj = BlockObject.fetch(_dbClient, snapshotSession.getParent().getURI());
}
if (sourceObj instanceof Volume && isNonSplitSRDFTargetVolume((Volume) sourceObj)) {
// PRIOR to Restoring R2 Device from its session, we need to
// a) SUSPEND the R1-R2 pair if the Copy Mode is ACTIVE Or
// b) SPLIT the R1-R2 pair if the Copy Mode is SYNC/ ASYNC
Volume sourceVolume = (Volume) sourceObj;
URI srdfSourceVolumeURI = sourceVolume.getSrdfParent().getURI();
Volume srdfSourceVolume = _dbClient.queryObject(Volume.class, srdfSourceVolumeURI);
URI srdfSourceStorageSystemURI = srdfSourceVolume.getStorageController();
if (Mode.ACTIVE.equals(Mode.valueOf(sourceVolume.getSrdfCopyMode()))) {
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
srdfSourceVolumeURI, sourceObj.getId(), workflow);
} else {
// split all members the group
Workflow.Method splitMethod = srdfDeviceController.splitSRDFLinkMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, sourceObj.getId(), false);
Workflow.Method splitRollbackMethod = srdfDeviceController.resumeSyncPairMethod(srdfSourceStorageSystemURI,
srdfSourceVolumeURI, sourceObj.getId());
waitFor = workflow.createStep(SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_GROUP,
SRDFDeviceController.SPLIT_SRDF_MIRRORS_STEP_DESC, waitFor, srdfSourceStorageSystemURI,
getDeviceType(srdfSourceStorageSystemURI), SRDFDeviceController.class, splitMethod,
splitRollbackMethod, null);
}
} else if (sourceObj instanceof Volume && isNonSplitSRDFSourceVolume((Volume) sourceObj)) {
// PRIOR to Restoring R1 Device from its session, we need to SUSPEND the R1-R2 pair if the Copy Mode is
// ACTIVE
Volume srdfSourceVolume = (Volume) sourceObj;
URI srdfSourceStorageSystemURI = srdfSourceVolume.getStorageController();
StringSet targets = srdfSourceVolume.getSrdfTargets();
if (null != targets) {
for (String target : targets) {
if (NullColumnValueGetter.isNotNullValue(target)) {
Volume srdfTargetVolume = _dbClient.queryObject(Volume.class, URI.create(target));
if (null != srdfTargetVolume && Mode.ACTIVE.equals(Mode.valueOf(srdfTargetVolume.getSrdfCopyMode()))) {
waitFor = suspendSRDFLinkWorkflowStep(waitFor, srdfSourceStorageSystemURI,
srdfSourceVolume.getId(), srdfTargetVolume.getId(), workflow);
}
break;
}
}
}
}
// Create the workflow step to restore the snapshot session.
waitFor = workflow.createStep(RESTORE_SNAPSHOT_SESSION_STEP_GROUP,
String.format("Restore snapshot session %s", snapSessionURI),
waitFor, systemURI, getDeviceType(systemURI), getClass(),
restoreBlockSnapshotSessionMethod(systemURI, snapSessionURI),
rollbackMethodNullMethod(), null);
// Execute the workflow.
workflow.executePlan(completer, "Restore block snapshot session successful");
} catch (Exception e) {
_log.error("Restore block snapshot session failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.restoreBlockSnapshotSessionFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to restore the data on the array snapshot represented by the
* BlockSnapshotSession instance with the passed URI to its source.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
*
* @return A reference to a Workflow.Method for restoring the array snapshot to its source.
*/
public static Workflow.Method restoreBlockSnapshotSessionMethod(URI systemURI, URI snapSessionURI) {
return new Workflow.Method(RESTORE_SNAPSHOT_SESSION_METHOD, systemURI, snapSessionURI);
}
/**
* Restore the data on the array snapshot represented by the
* BlockSnapshotSession instance with the passed URI to its source.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param stepId
* The unique id of the workflow step in which the session is restored.
*/
public void restoreBlockSnapshotSession(URI systemURI, URI snapSessionURI, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionRestoreCompleter(snapSessionURI, stepId);
getDevice(system.getSystemType()).doRestoreBlockSnapshotSession(system, snapSessionURI, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.restoreBlockSnapshotSessionFailed(e);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void deleteSnapshotSession(URI systemURI, URI snapSessionURI, String opId) {
TaskCompleter completer = new BlockSnapshotSessionDeleteWorkflowCompleter(snapSessionURI, opId);
try {
// Get a new workflow delete the snapshot session.
Workflow workflow = _workflowService.getNewWorkflow(this, DELETE_SNAPSHOT_SESSION_WF_NAME, false, opId);
_log.info("Created new workflow to delete snapshot session {} with operation id {}",
snapSessionURI, opId);
// When deleting a group snapshot we need the name of the group.
String groupName = null;
boolean isCG = checkSnapshotSessionConsistencyGroup(snapSessionURI, _dbClient, completer);
if (isCG) {
BlockSnapshotSession snapSession = _dbClient.queryObject(BlockSnapshotSession.class, snapSessionURI);
groupName = snapSession.getReplicationGroupInstance();
}
// Create the workflow step to delete the snapshot session.
workflow.createStep(DELETE_SNAPSHOT_SESSION_STEP_GROUP,
String.format("Delete snapshot session %s", snapSessionURI),
null, systemURI, getDeviceType(systemURI), getClass(),
deleteBlockSnapshotSessionMethod(systemURI, snapSessionURI, groupName, Boolean.FALSE),
null, null);
// Execute the workflow.
workflow.executePlan(completer, "Delete block snapshot session successful");
} catch (Exception e) {
_log.error("Delete block snapshot session failed", e);
ServiceCoded serviceException = DeviceControllerException.exceptions.deleteBlockSnapshotSessionFailed(e);
completer.error(_dbClient, serviceException);
}
}
/**
* Create the workflow method that is invoked by the workflow service
* to delete the array snapshot represented by the BlockSnapshotSession
* instance with the passed URI to its source.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param markInactive
* true if the step should mark the session inactive, false otherwise.
* @param groupName
* The group name when deleting a group snapshot session.
*
* @return A reference to a Workflow.Method for deleting the array snapshot.
*/
public static Workflow.Method deleteBlockSnapshotSessionMethod(URI systemURI, URI snapSessionURI, String groupName,
Boolean markInactive) {
return new Workflow.Method(DELETE_SNAPSHOT_SESSION_METHOD, systemURI, snapSessionURI, groupName, markInactive);
}
/**
* Delete the array snapshot represented by the BlockSnapshotSession instance
* with the passed URI to its source.
*
* @param systemURI
* The URI of the storage system.
* @param snapSessionURI
* The URI of the BlockSnapshotSession instance.
* @param stepId
* The unique id of the workflow step in which the session is deleted.
* @param groupName
* The group name when deleting a group snapshot session.
* @param markInactive
* true if the step should mark the session inactive, false otherwise.
*/
public void deleteBlockSnapshotSession(URI systemURI, URI snapSessionURI, String groupName, Boolean markInactive, String stepId) {
TaskCompleter completer = null;
try {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
completer = new BlockSnapshotSessionDeleteCompleter(snapSessionURI, markInactive, stepId);
WorkflowStepCompleter.stepExecuting(stepId);
getDevice(system.getSystemType()).doDeleteBlockSnapshotSession(system, snapSessionURI, groupName, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
} else {
throw DeviceControllerException.exceptions.restoreBlockSnapshotSessionFailed(e);
}
}
}
/**
* Gets the full copies for the given volumes.
*
* @param allFullCopies
* the requested full copies to be created
* @param volumes
* the volumes
* @return the full copies
*/
private List<URI> getFullCopiesForVolumes(List<URI> allFullCopies, List<Volume> volumes) {
List<URI> fullCopyURIs = new ArrayList<URI>();
for (Volume vol : volumes) {
if (vol.getFullCopies() != null) {
for (String fullCopy : vol.getFullCopies()) {
URI fullCopyURI = URI.create(fullCopy);
if (allFullCopies.contains(fullCopyURI)) {
fullCopyURIs.add(fullCopyURI);
}
}
}
}
return fullCopyURIs;
}
/**
* Given a list of volumes, returns a table of storage id, replication group name, and volume list
*
* If given newRGName is same as the volume's RG name, the volume will be skipped, if it is non VNX volume.
* For VNX volume, it need to be removed from real replication group, so it cannot be skipped.
*
* @param volumeIds
* @param newRGName
* (for adding volumes to application only, null otherwise)
* @return table with storage URI, replication group name, and volume URIs
*/
private Table<URI, String, List<URI>> getStorageSystemRGVolumes(Collection<URI> volumeIds, String newRGName) {
Table<URI, String, List<URI>> storgeRGToVolumes = HashBasedTable.create();
if (volumeIds != null && !volumeIds.isEmpty()) {
Iterator<Volume> volumes = _dbClient.queryIterativeObjects(Volume.class, volumeIds);
while (volumes.hasNext()) {
Volume volume = volumes.next();
if (volume != null && !volume.getInactive()) {
URI storage = volume.getStorageController();
String rgName = volume.getReplicationGroupInstance();
if (rgName == null) {
rgName = NullColumnValueGetter.getNullStr();
} else if (newRGName != null && rgName.equals(newRGName) && !ControllerUtils.isVnxVolume(volume, _dbClient)) {
// volume is already in the designated group
_log.info("Volume {} is already in the desired group {}", volume.getLabel(), newRGName);
continue;
}
List<URI> volumeUris = storgeRGToVolumes.get(storage, rgName);
if (volumeUris == null) {
volumeUris = new ArrayList<URI>();
storgeRGToVolumes.put(storage, rgName, volumeUris);
}
volumeUris.add(volume.getId());
}
}
}
return storgeRGToVolumes;
}
/**
* add step for update application, using by VPLEX and RP to
* add/remove block volumes to/from replication groups on multiple storage systems
*
* @param workflow
* @param addVolList
* @param removeVolumeURIs
* @param waitForStep
* @param taskId
* @return
*/
public String addStepsForUpdateApplication(Workflow workflow, ApplicationAddVolumeList addVolList, List<URI> removeVolumeURIs,
String waitForStep, String taskId) {
String waitFor = waitForStep;
// split up volumes by storage system, replication group, and add steps for each storage system and RG
Table<URI, String, List<URI>> storageRGToRemoveVolumes = getStorageSystemRGVolumes(removeVolumeURIs, null);
// map volumes to add by storage system, replication group
if (addVolList != null) {
// add source and target volumes from array replication groups
Table<URI, String, List<URI>> storageRGToAddVolumes = getStorageSystemRGVolumes(addVolList.getVolumes(),
addVolList.getReplicationGroupName());
for (Cell<URI, String, List<URI>> cell : storageRGToAddVolumes.cellSet()) {
URI storage = cell.getRowKey();
String rgName = cell.getColumnKey();
List<URI> addVolumes = cell.getValue();
List<URI> removeVolumes = new ArrayList<URI>();
if (NullColumnValueGetter.isNotNullValue(rgName)) {
// volumes have already been in a RG, need remove them from original RG
removeVolumes.addAll(addVolumes);
}
if (storageRGToRemoveVolumes.contains(storage, rgName)) {
removeVolumes.addAll(storageRGToRemoveVolumes.remove(storage, rgName));
}
waitFor = addStepsForUpdateApplicationSingleStorage(workflow, storage, addVolList.getReplicationGroupName(),
addVolumes, removeVolumes, waitFor, taskId);
}
}
// process remaining storage system and RG that has volumes to remove from application
for (Cell<URI, String, List<URI>> cell : storageRGToRemoveVolumes.cellSet()) {
waitFor = addStepsForUpdateApplicationSingleStorage(workflow, cell.getRowKey(), null, null, cell.getValue(), waitFor, taskId);
}
return waitFor;
}
/**
* add step for update application
* adds volumes to replication groups on a single storage systems
*
* @param workflow
* @param storage
* @param rgName
* @param addVolumeList
* @param removeVolumeList
* @param waitForStep
* @param opId
* @return
* @throws ControllerException
*/
private String addStepsForUpdateApplicationSingleStorage(Workflow workflow, URI storage, String rgName, List<URI> addVolumeList,
List<URI> removeVolumeList,
String waitForStep, String opId) throws ControllerException {
String waitFor = waitForStep;
// Note volumes could be in both addVolumeList and removeVolumeList, e.g., remove from original RG, and add to a
// new RG
// Need to process remove list first
if (removeVolumeList != null && !removeVolumeList.isEmpty()) {
Volume vol = _dbClient.queryObject(Volume.class, removeVolumeList.get(0));
URI cgUri = vol.getConsistencyGroup();
String groupName = vol.getReplicationGroupInstance();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
// call ReplicaDeviceController
waitFor = _replicaDeviceController.addStepsForRemovingVolumesFromCG(workflow, waitFor, cgUri, removeVolumeList, opId);
// Remove the volumes from the consistency group
waitFor = workflow.createStep(REMOVE_VOLUMES_FROM_CG_STEP_GROUP,
String.format("Remove volumes from consistency group %s", cgUri.toString()),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
removeFromConsistencyGroupMethod(storage, cgUri, removeVolumeList, false),
addToConsistencyGroupMethod(storage, cgUri, groupName, removeVolumeList), null);
// remove replication group if the CG will become empty
if (ControllerUtils.replicationGroupHasNoOtherVolume(_dbClient, groupName, removeVolumeList, storage)) {
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Deleting replication group for consistency group %s", cgUri.toString()),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
deleteConsistencyGroupMethod(storage, cgUri, groupName, false, false, false),
createConsistencyGroupMethod(storage, cgUri, groupName), null);
}
}
if (addVolumeList != null && !addVolumeList.isEmpty()) {
_log.info("Creating workflows for adding volumes to CG and application");
Volume vol = _dbClient.queryObject(Volume.class, addVolumeList.get(0));
URI cgUri = vol.getConsistencyGroup();
BlockConsistencyGroup cg = _dbClient.queryObject(BlockConsistencyGroup.class, cgUri);
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storage);
// check if cg is created, if not create it
boolean isNewRG = false;
if (!cg.created(rgName, storage)) {
_log.info("Consistency group not created. Creating it");
isNewRG = true;
if (storageSystem.deviceIsType(Type.vnxblock)) {
// set arrayConsistency to false, so that no replication group will be created on array
cg.setArrayConsistency(false);
_dbClient.updateObject(cg);
}
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Creating consistency group %s", rgName),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
createConsistencyGroupMethod(storage, cgUri, rgName),
deleteConsistencyGroupMethod(storage, cgUri, rgName, false, false, false), null);
}
waitFor = workflow.createStep(UPDATE_CONSISTENCY_GROUP_STEP_GROUP,
String.format("Adding volumes to consistency group %s", cgUri.toString()),
waitFor, storage, storageSystem.getSystemType(),
this.getClass(),
addToConsistencyGroupMethod(storage, cgUri, rgName, addVolumeList),
removeFromConsistencyGroupMethod(storage, cgUri, addVolumeList, false), null);
if (!isNewRG) {
// call ReplicaDeviceController
waitFor = _replicaDeviceController.addStepsForAddingVolumesToRG(workflow, waitFor, cgUri, addVolumeList, rgName, opId);
}
}
return waitFor;
}
/**
* Create a method for workflow to delete array clone replication group
*
* @param storage
* storage system
* @param consistencyGroup
* consistency group URI
* @param groupName
* clone group name
* @param keepRGName
* @param markInactive
* @param sourceGroupName
* source group name
* @return the created workflow Method
*/
public Workflow.Method deleteReplicationGroupMethod(URI storage, URI consistencyGroup, String groupName, Boolean keepRGName,
Boolean markInactive, String sourceGroupName) {
return new Workflow.Method("deleteReplicationGroup", storage, consistencyGroup, groupName, keepRGName, markInactive,
sourceGroupName);
}
/**
* Delete array clone replication group
*
* @param storage
* storage system
* @param consistencyGroup
* consistency group URI
* @param groupName
* clone group name
* @param keepRGName
* @param markInactive
* @param sourceGroupName
* source group name
* @return the created workflow Method
*/
public void deleteReplicationGroup(URI storage, URI consistencyGroup, String groupName, Boolean keepRGName, Boolean markInactive,
String sourceGroupName, String opId) throws ControllerException {
TaskCompleter completer = null;
try {
WorkflowStepCompleter.stepExecuting(opId);
StorageSystem storageObj = _dbClient.queryObject(StorageSystem.class, storage);
completer = new BlockConsistencyGroupDeleteCompleter(consistencyGroup, storage, groupName, keepRGName, markInactive, opId);
getDevice(storageObj.getSystemType()).doDeleteConsistencyGroup(storageObj, consistencyGroup, groupName, keepRGName,
markInactive,
sourceGroupName, completer);
} catch (Exception e) {
if (completer != null) {
ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
completer.error(_dbClient, serviceError);
}
throw DeviceControllerException.exceptions.deleteConsistencyGroupFailed(e);
}
}
/**
* Add steps to restore full copy
*
* @param workflow
* - the workflow the steps would be added to
* @param waitFor
* - the step would be waited before the added steps would be executed
* @param storage
* - the storage controller URI
* @param fullcopies
* - the full copies to restore
* @param opId
* @param completer
* - the CloneRestoreCompleter
* @return the step id for the added step
* @throws InternalException
*/
public String addStepsForRestoreFromFullcopy(Workflow workflow,
String waitFor, URI storage, List<URI> fullcopies, String opId,
TaskCompleter completer) throws InternalException {
Volume firstFullCopy = _dbClient.queryObject(Volume.class, fullcopies.get(0));
// Don't do anything if this is VPLEX full copy
if (firstFullCopy.isVPlexVolume(_dbClient)) {
return waitFor;
}
BlockObject firstSource = BlockObject.fetch(_dbClient, firstFullCopy.getAssociatedSourceVolume());
if (!NullColumnValueGetter.isNullURI(firstSource.getConsistencyGroup())) {
completer.addConsistencyGroupId(firstSource.getConsistencyGroup());
}
StorageSystem system = _dbClient.queryObject(StorageSystem.class, storage);
Workflow.Method restoreFromFullcopyMethod = new Workflow.Method(
RESTORE_FROM_FULLCOPY_METHOD_NAME, storage, fullcopies, Boolean.TRUE);
waitFor = workflow.createStep(RESTORE_FROM_FULLCOPY_STEP,
"Restore volumes from full copies", waitFor,
storage, system.getSystemType(),
this.getClass(), restoreFromFullcopyMethod, null, null);
_log.info("Created workflow step to restore volume from full copies");
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 {
List<VolumeDescriptor> blockVolmeDescriptors = VolumeDescriptor.filterByType(volumeDescriptors,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_DATA, VolumeDescriptor.Type.VPLEX_IMPORT_VOLUME },
new VolumeDescriptor.Type[] {});
// If no volumes to create, just return
if (blockVolmeDescriptors.isEmpty()) {
return waitFor;
}
URI storageURI = null;
boolean createInactive = false;
List<URI> fullCopyList = new ArrayList<URI>();
for (VolumeDescriptor descriptor : blockVolmeDescriptors) {
Volume volume = _dbClient.queryObject(Volume.class, descriptor.getVolumeURI());
if (volume != null && !volume.getInactive()) {
URI parentId = volume.getAssociatedSourceVolume();
if (!NullColumnValueGetter.isNullURI(parentId)) {
fullCopyList.add(volume.getId());
storageURI = volume.getStorageController();
createInactive = Boolean.getBoolean(descriptor.getCapabilitiesValues().getReplicaCreateInactive());
}
}
}
if (!fullCopyList.isEmpty()) {
String stepId = workflow.createStepId();
// Now add the steps to create the block full copy on the storage system
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storageURI);
Workflow.Method createFullCopyMethod = new Workflow.Method(METHOD_CREATE_FULL_COPY_STEP, storageURI, fullCopyList,
createInactive);
Workflow.Method createFullCopyOrchestrationExecutionRollbackMethod = new Workflow.Method(METHOD_CREATE_FULLCOPY_ORCHESTRATE_ROLLBACK_STEP,
workflow.getWorkflowURI(), stepId);
waitFor = workflow.createStep(FULL_COPY_CREATE_ORCHESTRATION_STEP, "Create Block Full Copy", waitFor, storageSystem.getId(),
storageSystem.getSystemType(), this.getClass(), createFullCopyMethod, createFullCopyOrchestrationExecutionRollbackMethod, stepId);
_log.info(String.format("Added %s step [%s] in workflow", FULL_COPY_CREATE_STEP_GROUP, stepId));
}
return waitFor;
}
/**
* calls the child workflow step rollback methods
*
* @param parentWorkflow
* @param orchestrationStepId
* @param token
* @return
* @throws WorkflowException
*/
public boolean createFullCopyOrchestrationRollbackSteps(URI parentWorkflow, String orchestrationStepId, String token)
throws WorkflowException {
// The workflow service now provides a rollback facility for a child workflow. It rolls back every step in an already
// (successfully) completed child workflow. The child workflow is located by the parentWorkflow URI and
// exportOrchestrationStepId.
_workflowService.rollbackChildWorkflow(parentWorkflow, orchestrationStepId, token);
return true;
}
/* (non-Javadoc)
* @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface#addStepsForPostCreateReplica(com.emc.storageos.workflow.Workflow, java.lang.String, java.util.List, java.lang.StringBuffer, java.lang.String)
*/
@Override
public String addStepsForPostCreateReplica(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors,
String taskId) throws InternalException {
// nothing to do post create replica
return waitFor;
}
/**
* @param workflow
* @param waitFor
* @param volumeDescriptors
* @param taskId
* @return
*/
public String addStepsForCreateSnapshotSession(Workflow workflow, String waitFor, List<VolumeDescriptor> volumeDescriptors,
String taskId) {
List<VolumeDescriptor> blockVolmeDescriptors = VolumeDescriptor.filterByType(volumeDescriptors,
new VolumeDescriptor.Type[] { VolumeDescriptor.Type.BLOCK_SNAPSHOT_SESSION },
new VolumeDescriptor.Type[] {});
// If no volumes to create, just return
if (blockVolmeDescriptors.isEmpty()) {
return waitFor;
}
// we expect just one snapshot session volume descriptor
VolumeDescriptor descriptor = blockVolmeDescriptors.get(0);
BlockSnapshotSession session = _dbClient.queryObject(BlockSnapshotSession.class, descriptor.getVolumeURI());
if (session != null && !session.getInactive()) {
String stepId = workflow.createStepId();
// Now add the steps to create the snapshot session on the storage system
URI storageURI = session.getStorageController();
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storageURI);
Workflow.Method createSnapshotSessionMethod = new Workflow.Method(METHOD_CREATE_SNAPSHOT_SESSION_STEP, storageURI,
descriptor.getVolumeURI(),
descriptor.getSnapSessionSnapshotURIs(),
descriptor.getCapabilitiesValues().getSnapshotSessionCopyMode());
Workflow.Method nullRollbackMethod = new Workflow.Method(ROLLBACK_METHOD_NULL);
waitFor = workflow.createStep(SNAPSHOT_SESSION_CREATE_ORCHESTRATION_STEP, "Create Block Snapshot Session", waitFor, storageSystem.getId(),
storageSystem.getSystemType(), this.getClass(), createSnapshotSessionMethod, nullRollbackMethod, stepId);
_log.info(String.format("Added %s step [%s] in workflow", SNAPSHOT_SESSION_CREATE_STEP_GROUP, stepId));
}
return waitFor;
}
/* (non-Javadoc)
* @see com.emc.storageos.blockorchestrationcontroller.BlockOrchestrationInterface#addStepsForPreCreateReplica(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;
}
@Override
public void setInitiatorAlias(URI systemURI, URI initiatorURI, String initiatorAlias) throws Exception {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI);
getDevice(system.getSystemType()).doInitiatorAliasSet(
system, initiator, initiatorAlias);
}
@Override
public String getInitiatorAlias(URI systemURI, URI initiatorURI) throws Exception {
StorageSystem system = _dbClient.queryObject(StorageSystem.class, systemURI);
Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI);
return getDevice(system.getSystemType()).doInitiatorAliasGet(
system, initiator);
}
}