/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.vplexcontroller;
import static com.emc.storageos.vplexcontroller.VPlexControllerUtils.getDataObject;
import static com.emc.storageos.vplexcontroller.VPlexControllerUtils.getVPlexAPIClient;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.Controller;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.BlockConsistencyGroup;
import com.emc.storageos.db.client.model.BlockConsistencyGroup.Types;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.model.StringSet;
import com.emc.storageos.db.client.model.StringSetMap;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.model.ResourceOperationTypeEnum;
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.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.BlockDeviceController;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.BlockConsistencyGroupUpdateCompleter;
import com.emc.storageos.volumecontroller.impl.utils.ClusterConsistencyGroupWrapper;
import com.emc.storageos.vplex.api.VPlexApiClient;
import com.emc.storageos.vplex.api.VPlexApiException;
import com.emc.storageos.vplex.api.VPlexApiFactory;
import com.emc.storageos.vplex.api.VPlexConsistencyGroupInfo;
import com.emc.storageos.vplexcontroller.VPlexDeviceController.VPlexTaskCompleter;
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;
public abstract class AbstractConsistencyGroupManager implements ConsistencyGroupManager, Controller {
protected static final String CREATE_CG_STEP = "createCG";
protected static final String DELETE_CG_STEP = "deleteCG";
protected static final String ADD_VOLUMES_TO_CG_STEP = "addVolumesToCG";
protected static final String REMOVE_VOLUMES_FROM_CG_STEP = "removeVolumesFromCG";
protected static final String DELETE_LOCAL_CG_STEP = "deleteLocalCG";
protected static final String CREATE_CG_METHOD_NAME = "createCG";
protected static final String DELETE_CG_METHOD_NAME = "deleteCG";
protected static final String ROLLBACK_METHOD_NULL = "rollbackMethodNull";
protected static final String UPDATE_CONSISTENCY_GROUP_METHOD_NAME = "updateConsistencyGroup";
protected static final String DELETE_CONSISTENCY_GROUP_METHOD_NAME = "deleteReplicationGroupInConsistencyGroup";
protected static final String CREATE_CONSISTENCY_GROUP_METHOD_NAME = "createConsistencyGroup";
protected static final String RB_DELETE_CG_METHOD_NAME = "rollbackDeleteCG";
protected static final String RB_CREATE_CG_METHOD_NAME = "rollbackCreateCG";
// logger reference.
private static final Logger log = LoggerFactory
.getLogger(AbstractConsistencyGroupManager.class);
protected DbClient dbClient;
protected VPlexApiFactory vplexApiFactory;
protected WorkflowService workflowService;
public void setDbClient(DbClient dbClient) {
this.dbClient = dbClient;
}
public DbClient getDbClient() {
return dbClient;
}
public VPlexApiFactory getVplexApiFactory() {
return vplexApiFactory;
}
public void setVplexApiFactory(VPlexApiFactory vplexApiFactory) {
this.vplexApiFactory = vplexApiFactory;
}
public void setWorkflowService(WorkflowService workflowService) {
this.workflowService = workflowService;
}
public WorkflowService getWorkflowService() {
return workflowService;
}
/**
* Based on a list of VPlex volumes and a ViPR consistency group, this method determines
* the corresponding VPlex cluster names and VPlex consistency group names.
*
* @param vplexVolumeURIs The VPlex virtual volumes to analyze
* @param cgName The ViPR BlockConsistencyGroup name
* @return A mapping of VPlex cluster/consistency groups to their associated volumes
* @throws Exception
*/
protected Map<ClusterConsistencyGroupWrapper, List<URI>> getClusterConsistencyGroupVolumes(List<URI> vplexVolumeURIs,
BlockConsistencyGroup cg)
throws Exception {
Map<ClusterConsistencyGroupWrapper, List<URI>> clusterConsistencyGroups =
new HashMap<ClusterConsistencyGroupWrapper, List<URI>>();
for (URI vplexVolumeURI : vplexVolumeURIs) {
Volume vplexVolume = getDataObject(Volume.class, vplexVolumeURI, dbClient);
addVolumeToClusterConsistencyGroup(vplexVolume, clusterConsistencyGroups, cg);
}
return clusterConsistencyGroups;
}
/**
* Builds and adds a VPlex cluster/consistency group mapping for a given VPlex volume and ViPR
* BlockConsistencyGroup name.
*
* @param vplexVolume The VPlex virtual volume for which we want to find a VPlex cluster/
* consistency group mapping.
* @param clusterConsistencyGroupVolumes The Map to which we want to add the VPlex cluster/
* consistency group volume mapping.
* @param cgName The ViPR BlockConsistencyGroup name.
* @throws Exception
*/
protected void addVolumeToClusterConsistencyGroup(Volume vplexVolume, Map<ClusterConsistencyGroupWrapper,
List<URI>> clusterConsistencyGroupVolumes, BlockConsistencyGroup cg) throws Exception {
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(vplexVolume, cg);
if (!clusterConsistencyGroupVolumes.containsKey(clusterConsistencyGroup)) {
clusterConsistencyGroupVolumes.put(clusterConsistencyGroup, new ArrayList<URI>());
}
clusterConsistencyGroupVolumes.get(clusterConsistencyGroup).add(vplexVolume.getId());
}
protected Workflow.Method rollbackMethodNullMethod() {
return new Workflow.Method(ROLLBACK_METHOD_NULL);
}
public void rollbackMethodNull(String stepId) throws WorkflowException {
WorkflowStepCompleter.stepSucceded(stepId);
}
@Override
public String addStepsForDeleteConsistencyGroup(Workflow workflow, String waitFor, URI vplexSystemURI, URI cgURI, boolean markInactive)
throws ControllerException {
// Get the CG.
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Add step to delete local. There should only be a local for
// VPLEX CGs and there should be either one or two depending upon
// if the volumes in the CG are local or distributed.
String returnWaitFor = waitFor;
if (cg.checkForType(Types.LOCAL)) {
List<URI> localSystemUris = BlockConsistencyGroupUtils
.getLocalSystems(cg, dbClient);
for (URI localSystemUri : localSystemUris) {
StorageSystem localSystem = getDataObject(StorageSystem.class,
localSystemUri, dbClient);
String localCgName = cg.getCgNameOnStorageSystem(localSystemUri);
Workflow.Method deleteCGMethod = new Workflow.Method(
DELETE_CONSISTENCY_GROUP_METHOD_NAME, localSystemUri, cgURI, null, false, Boolean.FALSE, true);
Workflow.Method rollbackDeleteCGMethod = new Workflow.Method(
CREATE_CONSISTENCY_GROUP_METHOD_NAME, localSystemUri, cgURI);
returnWaitFor = workflow.createStep(DELETE_LOCAL_CG_STEP, String.format(
"Deleting Consistency Group %s on local system %s", localCgName,
localSystemUri.toString()), returnWaitFor, localSystemUri, localSystem.getSystemType(),
BlockDeviceController.class, deleteCGMethod, rollbackDeleteCGMethod, null);
}
}
// We need to examine the association of VPlex systems to VPlex CGs that
// have been created. We can't depend on the Volume's in the CG to determine
// the VPlex systems and CG names because there may not be any volumes in the CG
// at this point.
if (BlockConsistencyGroupUtils.referencesVPlexCGs(cg, dbClient)) {
for (StorageSystem storageSystem : BlockConsistencyGroupUtils.getVPlexStorageSystems(cg, dbClient)) {
URI vplexSystemUri = storageSystem.getId();
// Iterate over the VPlex consistency groups that need to be
// deleted.
Map<String, String> vplexCgsToDelete = new HashMap<String, String>();
for (String clusterCgName : cg.getSystemConsistencyGroups().get(vplexSystemUri.toString())) {
String cgName = BlockConsistencyGroupUtils.fetchCgName(clusterCgName);
String clusterName = BlockConsistencyGroupUtils.fetchClusterName(clusterCgName);
if (!vplexCgsToDelete.containsKey(cgName)) {
vplexCgsToDelete.put(cgName, clusterName);
}
}
for (Map.Entry<String, String> vplexCg : vplexCgsToDelete.entrySet()) {
String stepId = workflow.createStepId();
Workflow.Method deletCGRollbackMethod = rollbackDeleteCGMethod(cgURI, stepId);
// Create the steps in the workflow to delete the consistency
// group. Note that we assume the consistency group does not
// contain any volumes. Currently, the API service does not allow
// this, and so this should never be called otherwise.
returnWaitFor = addStepForRemoveVPlexCG(workflow, stepId, returnWaitFor, storageSystem,
cgURI, vplexCg.getKey(), vplexCg.getValue(), markInactive, deletCGRollbackMethod);
}
}
}
return returnWaitFor;
}
@Override
public void deleteConsistencyGroup(Workflow workflow, URI vplexURI, URI cgURI, String opId)
throws ControllerException {
try {
// Get the CG.
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Add step to delete local. There should only be a local for
// VPLEX CGs and there should be either one or two depending upon
// if the volumes in the CG are local or distributed.
addStepsForDeleteConsistencyGroup(workflow, null, vplexURI, cgURI, true);
TaskCompleter completer = new VPlexTaskCompleter(BlockConsistencyGroup.class,
Arrays.asList(cgURI), opId, null);
log.info("Executing workflow plan");
workflow.executePlan(completer, String.format(
"Deletion of consistency group %s completed successfully", cgURI));
log.info("Workflow plan executed");
} catch (Exception e) {
String failMsg = String.format("Deletion of consistency group %s failed",
cgURI);
log.error(failMsg, e);
TaskCompleter completer = new VPlexTaskCompleter(BlockConsistencyGroup.class,
Arrays.asList(cgURI), opId, null);
String opName = ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.deleteConsistencyGroupFailed(
cgURI.toString(), opName, e);
completer.error(dbClient, serviceError);
}
}
/**
* Deletes the consistency group with the passed URI on the VPLEX storage
* system with the passed URU.
*
* @param vplexSystemURI The URI of the VPlex system.
* @param cgUri The URI of the ViPR consistency group.
* @param cgName The name of the VPlex consistency group to delete.
* @param clusterName The name of the VPlex cluster.
* @param setInactive true to mark the CG for deletion.
* @param stepId The workflow step identifier.
*
* @throws WorkflowException When an error occurs updating the work step
* state.
*/
public void deleteCG(URI vplexSystemURI, URI cgUri, String cgName,
String clusterName, Boolean setInactive, String stepId) throws WorkflowException {
try {
// Update step state to executing.
WorkflowStepCompleter.stepExecuting(stepId);
log.info(String.format("Executing workflow step deleteCG. Storage System: %s, CG Name: %s, Cluster Name: %s",
vplexSystemURI, cgName, clusterName));
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexSystemURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPlex API client for VPlex system {}", vplexSystemURI);
// Make a call to the VPlex API client to delete the consistency group.
client.deleteConsistencyGroup(cgName);
log.info(String.format("Deleted consistency group %s", cgName));
// Create the rollback data in case this needs to be recreated.
VPlexCGRollbackData rbData = new VPlexCGRollbackData();
rbData.setVplexSystemURI(vplexSystemURI);
rbData.setCgName(cgName);
rbData.setClusterName(clusterName);
rbData.setIsDistributed(new Boolean(getIsCGDistributed(client, cgName, clusterName)));
workflowService.storeStepData(stepId, rbData);
cleanUpVplexCG(vplexSystemURI, cgUri, cgName, setInactive);
// Update step status to success.
WorkflowStepCompleter.stepSucceded(stepId);
} catch (VPlexApiException vae) {
log.error("Exception deleting consistency group: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
log.error("Exception deleting consistency group: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.deleteCGFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* Method to clean up a VPLEX consistency group.
*
* @param vplexSystemURI The URI of the VPlex system.
* @param cgUri The URI of the ViPR consistency group.
* @param cgName The name of the VPlex consistency group to cleanup.
* @param setInactive true to mark the CG for deletion.
*/
protected void cleanUpVplexCG(URI vplexSystemURI, URI cgUri, String cgName, boolean setInactive) {
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgUri, dbClient);
// Remove all storage system CG entries stored on the BlockConsistencyGroup that match
// the give CG name. For RecoverPoint, there will be an entry for the distributed CG
// on each cluster so this takes care of removing each of those.
List<String> cgRefsToDelete = new ArrayList<String>();
StringSetMap sysCgs = cg.getSystemConsistencyGroups();
if (sysCgs != null && !sysCgs.isEmpty()) {
StringSet cgsForVplex = sysCgs.get(vplexSystemURI.toString());
if (cgsForVplex != null && !cgsForVplex.isEmpty()) {
Iterator<String> itr = cgsForVplex.iterator();
while (itr.hasNext()) {
String clusterCgName = itr.next();
if (BlockConsistencyGroupUtils.fetchCgName(clusterCgName).equals(cgName)) {
cgRefsToDelete.add(clusterCgName);
}
}
}
}
// Remove the CG/Cluster references from the BlockConsistencyGroup.
if (!cgRefsToDelete.isEmpty()) {
for (String cgRef : cgRefsToDelete) {
log.info(String.format("Removing system consistency group %s from storage system %s",
cgRef, vplexSystemURI.toString()));
cg.removeSystemConsistencyGroup(vplexSystemURI.toString(), cgRef);
}
}
// Only mark the ViPR CG for deletion when all associated VPlex CGs
// have been deleted.
if (!BlockConsistencyGroupUtils.referencesVPlexCGs(cg, dbClient)) {
// Remove the VPLEX type
StringSet cgTypes = cg.getTypes();
cgTypes.remove(BlockConsistencyGroup.Types.VPLEX.name());
cg.setTypes(cgTypes);
StringSet requestedTypes = cg.getRequestedTypes();
requestedTypes.remove(BlockConsistencyGroup.Types.VPLEX.name());
cg.setRequestedTypes(requestedTypes);
cg.setStorageController(NullColumnValueGetter.getNullURI());
cg.setVirtualArray(NullColumnValueGetter.getNullURI());
if (setInactive) {
dbClient.markForDeletion(cg);
log.info(String.format("Marking consistency group %s for deletion", cg.getId()));
}
}
dbClient.updateObject(cg);
}
@Override
public void deleteConsistencyGroupVolume(URI vplexURI, Volume volume, BlockConsistencyGroup cg) throws Exception {
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexURI, dbClient);
// Determine the VPlex CG corresponding to the this volume
ClusterConsistencyGroupWrapper clusterCg =
getClusterConsistencyGroup(volume, cg);
if (clusterCg != null) {
log.info(String.format("Removing VPlex virtual volume [%s - %s] from VPLEX CG [%s]",
volume.getLabel(), volume.getDeviceLabel(), clusterCg.getCgName()));
// Remove the volume from the CG. Delete the CG if it's empty
// and the deleteCGWhenEmpty flag is set.
client.removeVolumesFromConsistencyGroup(
Arrays.asList(volume.getDeviceLabel()), clusterCg.getCgName(),
false);
// De-reference the CG
volume.setConsistencyGroup(NullColumnValueGetter.getNullURI());
dbClient.updateObject(volume);
}
}
/**
* Gets the VPlex consistency group name that corresponds to the volume
* and BlockConsistencyGroup.
*
* @param volume The virtual volume used to determine cluster configuration.
* @param cgURI The BlockConsistencyGroup id.
* @return The VPlex consistency group name
* @throws Exception
*/
protected String getVplexCgName(Volume volume, URI cgURI) throws Exception {
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(volume, cg);
return clusterConsistencyGroup.getCgName();
}
@Override
public void addVolumeToCg(URI cgURI, Volume vplexVolume, VPlexApiClient client, boolean addToViPRCg) throws Exception {
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class,
cgURI, dbClient);
ClusterConsistencyGroupWrapper clusterCgWrapper =
this.getClusterConsistencyGroup(vplexVolume, cg);
log.info("Adding volumes to consistency group: " + clusterCgWrapper.getCgName());
// Add the volume from the CG.
client.addVolumesToConsistencyGroup(clusterCgWrapper.getCgName(), Arrays.asList(vplexVolume.getDeviceLabel()));
if (addToViPRCg) {
vplexVolume.setConsistencyGroup(cgURI);
dbClient.updateAndReindexObject(vplexVolume);
}
}
@Override
public void removeVolumeFromCg(URI cgURI, Volume vplexVolume, VPlexApiClient client, boolean removeFromViPRCg) throws Exception {
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class,
cgURI, dbClient);
ClusterConsistencyGroupWrapper clusterCgWrapper =
this.getClusterConsistencyGroup(vplexVolume, cg);
log.info("Removing volumes from consistency group: " + clusterCgWrapper.getCgName());
// Remove the volumes from the CG.
client.removeVolumesFromConsistencyGroup(
Arrays.asList(vplexVolume.getDeviceLabel()), clusterCgWrapper.getCgName(), false);
if (removeFromViPRCg) {
vplexVolume.setConsistencyGroup(NullColumnValueGetter.getNullURI());
dbClient.updateAndReindexObject(vplexVolume);
}
}
/**
* {@inheritDoc}
*/
@Override
public void updateConsistencyGroup(Workflow workflow, URI vplexURI, URI cgURI,
List<URI> addVolumesList, List<URI> removeVolumesList, String opId)
throws InternalException {
BlockConsistencyGroupUpdateCompleter completer = new BlockConsistencyGroupUpdateCompleter(cgURI, opId);
ServiceError error = VPlexApiException.errors.unsupportedConsistencyGroupOpError(
UPDATE_CONSISTENCY_GROUP_METHOD_NAME, cgURI.toString());
completer.error(dbClient, error);
}
/**
* Determine if the consistency group with the passed name on the passed cluster
* is distributed.
*
* @param client A reference to a VPLEX client
* @param cgName The consistency group name
* @param cgCluster The consistency group cluster
*
* @return true if the consistency group is distributed, false otherwise.
*/
protected boolean getIsCGDistributed(VPlexApiClient client, String cgName, String cgCluster) {
log.info("Determine if CG {} on cluster {} is distributed", cgName, cgCluster);
boolean isDistributed = false;
List<VPlexConsistencyGroupInfo> cgInfos = client.getConsistencyGroups();
for (VPlexConsistencyGroupInfo cgInfo : cgInfos) {
if ((cgInfo.getClusterName().equals(cgCluster)) && (cgInfo.getName().equals(cgName))) {
log.info("CG is distributed");
isDistributed = cgInfo.isDistributed();
}
}
return isDistributed;
}
/**
* Create the workflow method to rollback a CG deletion on a VPLEX system.
*
* @param cgURI The consistency group URI
* @param deleteStepId The step that deleted the CG.
*
* @return A reference to the workflow method
*/
private Workflow.Method rollbackDeleteCGMethod(URI cgURI, String deleteStepId) {
return new Workflow.Method(RB_DELETE_CG_METHOD_NAME, cgURI, deleteStepId);
}
/**
* Method call when we need to rollback the deletion of a consistency group.
*
* @param cgURI The consistency group URI
* @param deleteStepId The step that deleted the CG.
* @param stepId The step id.
*/
public void rollbackDeleteCG(URI cgURI, String deleteStepId, String stepId) {
try {
// Update step state to executing.
WorkflowStepCompleter.stepExecuting(stepId);
log.info("Updated workflow step to executing");
// Get the rollback data.
Object rbDataObj = workflowService.loadStepData(deleteStepId);
if (rbDataObj == null) {
// Update step state to done.
log.info("CG was not deleted, nothing to do.");
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
VPlexCGRollbackData rbData = (VPlexCGRollbackData) rbDataObj;
// Get the VPlex API client.
URI vplexSystemURI = rbData.getVplexSystemURI();
StorageSystem vplexSystem = getDataObject(StorageSystem.class,
vplexSystemURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem,
dbClient);
log.info("Got VPlex API client for VPlex system {}", vplexSystemURI);
// Recreate the consistency group on the VPLEX.
String cgName = rbData.getCgName();
String clusterName = rbData.getClusterName();
client.createConsistencyGroup(cgName, clusterName, rbData.getIsDistributed()
.booleanValue());
log.info("Recreated CG {} on system {}", cgName, vplexSystemURI);
// Update the consistency group in the database.
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI,
dbClient);
cg.addSystemConsistencyGroup(vplexSystemURI.toString(),
BlockConsistencyGroupUtils.buildClusterCgName(clusterName, cgName));
dbClient.persistObject(cg);
log.info("Updated consistency group in database");
// Update step state to done.
WorkflowStepCompleter.stepSucceded(stepId);
} catch (VPlexApiException vae) {
log.error("Exception rolling back VPLEX consistency group deletion: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
log.error("Exception rolling back VPLEX consistency group deletion: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.rollbackDeleteCGFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* Add step method for removing a VPlex consistency group.
*
* @param workflow the workflow.
* @param stepId the step ID.
* @param waitFor the wait for.
* @param vplexSystem the VPlex storage system.
* @param cgURI the consistency group URI.
* @param cgName the consistency group name.
* @param clusterName the cluster name.
* @param setInactive whether or not to set the BlockConsistencyGroup to inactive.
* @param rollbackMethod the rollback method
*/
public String addStepForRemoveVPlexCG(Workflow workflow, String stepId,
String waitFor, StorageSystem vplexSystem, URI cgURI, String cgName,
String clusterName, Boolean setInactive, Workflow.Method rollbackMethod) {
URI vplexSystemURI = vplexSystem.getId();
Workflow.Method vplexExecuteMethod = new Workflow.Method(
DELETE_CG_METHOD_NAME, vplexSystemURI, cgURI, cgName, clusterName, setInactive);
String step = workflow.createStep(DELETE_CG_STEP, String.format(
"Deleting Consistency Group %s on VPLEX system %s", cgName, vplexSystemURI.toString()),
waitFor, vplexSystemURI, vplexSystem.getSystemType(), getClass(),
vplexExecuteMethod, rollbackMethod, stepId);
log.info("Created step for delete CG {} on VPLEX {}", clusterName + ":" + cgName, vplexSystemURI);
return step;
}
/**
* The method called by the workflow to remove VPLEX volumes from a VPLEX
* consistency group.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the consistency group.
* @param vplexVolumeURIs The URIs of the volumes to be removed from the
* consistency group.
* @param stepId The workflow step id.
*
* @throws WorkflowException When an error occurs updating the workflow step
* state.
*/
public void removeVolumesFromCG(URI vplexURI, URI cgURI, List<URI> vplexVolumeURIs,
String stepId) throws WorkflowException {
try {
// Update workflow step.
WorkflowStepCompleter.stepExecuting(stepId);
log.info("Updated workflow step state to execute for remove volumes from consistency group.");
ServiceCoded codedError = removeVolumesFromCGInternal(vplexURI, cgURI, vplexVolumeURIs);
if (codedError != null) {
WorkflowStepCompleter.stepFailed(stepId, codedError);
} else {
WorkflowStepCompleter.stepSucceded(stepId);
}
} catch (Exception ex) {
log.error("Exception removing volumes from consistency group: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.DELETE_CG_VOLUME.getName();
ServiceError serviceError = VPlexApiException.errors.removeVolumesFromCGFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* The method called by the workflow to remove VPLEX volumes from a VPLEX
* consistency group.
* @param vplexURI -- URI of VPlex device
* @param cgURI -- URI of Consistency Group
* @param vplexVolumeURIs -- URI list of volumes that will be removed
* @return ServiceCoded exception if an error occurred, otherwise null if no error occurred.
*/
protected ServiceCoded removeVolumesFromCGInternal(URI vplexURI, URI cgURI, List<URI> vplexVolumeURIs) {
try {
if (vplexVolumeURIs.isEmpty()) {
log.info("Empty volume list; no volumes to remove from CG %s", cgURI.toString());
return null;
}
// Get the API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPLEX API client.");
Map<String, List<String>> cgToVolumesMap = new HashMap<String, List<String>>();
// Get the names of the volumes to be removed.
List<Volume> vplexVolumes = new ArrayList<Volume>();
for (URI vplexVolumeURI : vplexVolumeURIs) {
Volume vplexVolume = getDataObject(Volume.class, vplexVolumeURI, dbClient);
if (vplexVolume == null || vplexVolume.getInactive()) {
log.error(String.format("Skipping null or inactive vplex volume %s", vplexVolumeURI.toString()));
continue;
}
// Remove CG reference from volume, it won't be persisted until after the operation completes.
vplexVolume.setConsistencyGroup(NullColumnValueGetter.getNullURI());
vplexVolumes.add(vplexVolume);
// Get the CG name for this VPLEX volume, it could be a CG
// that is distributed, a CG on cluster-1, or a CG on cluster-2.
String cgName = getVplexCgName(vplexVolume, cgURI);
// Keep a map of CG name grouped by all VPLEX volumes in that same CG.
List<String> vplexVolumeNames = cgToVolumesMap.get(cgName);
if (vplexVolumeNames == null) {
vplexVolumeNames = new ArrayList<String>();
cgToVolumesMap.put(cgName, vplexVolumeNames);
}
vplexVolumeNames.add(vplexVolume.getDeviceLabel());
log.info(String.format("Adding VPLEX volume [%s](%s) with device label [%s] to be removed from VPLEX CG [%s]",
vplexVolume.getLabel(), vplexVolume.getId(), vplexVolume.getDeviceLabel(), cgName));
}
for (Map.Entry<String, List<String>> entry : cgToVolumesMap.entrySet()) {
String cgName = entry.getKey();
List<String> vplexVolumeNames = entry.getValue();
log.info(String.format("Removing the following VPLEX volumes from VPLEX CG [%s]: %s",
cgName, Joiner.on(", ").join(vplexVolumeNames)));
// Remove the volumes from the CG.
client.removeVolumesFromConsistencyGroup(vplexVolumeNames, cgName, false);
}
log.info("Removed volumes from consistency group.");
dbClient.updateObject(vplexVolumes);
return null;
} catch (VPlexApiException vae) {
log.error("Exception removing volumes from consistency group: " + vae.getMessage(), vae);
return vae;
} catch (Exception ex) {
log.error(
"Exception removing volumes from consistency group: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.DELETE_CG_VOLUME.getName();
ServiceError serviceError = VPlexApiException.errors.removeVolumesFromCGFailed(opName, ex);
return serviceError;
}
}
/**
* A method that creates the workflow method for removing VPLEX volumes from a
* consistency group.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the consistency group.
* @param vplexVolumeURIs The URIs of the volumes to be removed from the
* consistency group.
*
* @return A reference to the workflow method to remove VPLEX volumes from a
* consistency group.
*/
protected Workflow.Method createRemoveVolumesFromCGMethod(URI vplexURI, URI cgURI,
List<URI> vplexVolumeURIs) {
return new Workflow.Method(REMOVE_VOLUMES_FROM_CG_STEP, vplexURI, cgURI,
vplexVolumeURIs);
}
@Override
public String addStepsForAddingVolumesToSRDFTargetCG(Workflow workflow, StorageSystem vplexSystem, List<URI> vplexVolumeURIs,
String waitFor) throws Exception {
// Default is this is not supported and adds no steps.
return waitFor;
}
@Override
public String addStepsForRemovingVolumesFromSRDFTargetCG(Workflow workflow, StorageSystem vplexSystem, List<URI> vplexVolumeURIs,
String waitFor) throws Exception {
// Default is this is not supported and adds no steps.
return waitFor;
}
}