/*
* 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.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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.db.client.constraint.ContainmentConstraint;
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.Volume;
import com.emc.storageos.db.client.model.util.BlockConsistencyGroupUtils;
import com.emc.storageos.db.client.util.CustomQueryUtility;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.locking.LockTimeoutValue;
import com.emc.storageos.locking.LockType;
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.svcs.errorhandling.resources.InternalServerErrorException;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.emc.storageos.util.VPlexSrdfUtil;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.TaskCompleter;
import com.emc.storageos.volumecontroller.impl.ControllerLockingUtil;
import com.emc.storageos.volumecontroller.impl.ControllerUtils;
import com.emc.storageos.volumecontroller.impl.block.BlockDeviceController;
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.vplexcontroller.VPlexDeviceController.VPlexTaskCompleter;
import com.emc.storageos.workflow.Workflow;
import com.emc.storageos.workflow.WorkflowException;
import com.emc.storageos.workflow.WorkflowStepCompleter;
public class VPlexConsistencyGroupManager extends AbstractConsistencyGroupManager {
private static final String ADD_VOLUMES_TO_CG_METHOD_NAME = "addVolumesToCG";
private static final String REMOVE_VOLUMES_FROM_CG_METHOD_NAME = "removeVolumesFromCG";
private static final String SET_CG_PROPERTIES_METHOD_NAME = "setCGProperties";
private static final String SET_CG_PROPERTIES_STEP = "setCGProperties";
private static final String UPDATE_LOCAL_CG_STEP = "updateLocalCG";
// logger reference.
private static final Logger log = LoggerFactory
.getLogger(VPlexConsistencyGroupManager.class);
@Override
public String addStepsForCreateConsistencyGroup(Workflow workflow, String waitFor,
StorageSystem vplexSystem, List<URI> vplexVolumeURIs,
boolean willBeRemovedByEarlierStep) throws ControllerException {
// No volumes, all done.
if (vplexVolumeURIs.isEmpty()) {
log.info("No volumes specified consistency group.");
return waitFor;
}
// Grab the first volume
Volume firstVolume = getDataObject(Volume.class, vplexVolumeURIs.get(0), dbClient);
URI cgURI = firstVolume.getConsistencyGroup();
if (cgURI == null) {
log.info("No consistency group for volume creation.");
return waitFor;
}
return addStepsForCreateConsistencyGroup(workflow, waitFor, vplexSystem, vplexVolumeURIs,
willBeRemovedByEarlierStep, cgURI);
}
/**
* Create consistency group and add volumes to it
*
* @param workflow The workflow
* @param waitFor The previous step that it needs to wait for
* @param vplexSystem The vplex system
* @param vplexVolumeURIs The vplex volumes to be added to the consistency group
* @param willBeRemovedByEarlierStep if the consistency group could be removed by previous step
* @param cgURI The consistency group URI
* @return
* @throws ControllerException
*/
private String addStepsForCreateConsistencyGroup(Workflow workflow, String waitFor,
StorageSystem vplexSystem, List<URI> vplexVolumeURIs,
boolean willBeRemovedByEarlierStep, URI cgURI) throws ControllerException {
// No volumes, all done.
if (vplexVolumeURIs.isEmpty()) {
log.info(String.format("No volumes specified to add to the consistency group %s", cgURI.toString()));
return waitFor;
}
URI vplexURI = vplexSystem.getId();
String nextStep = waitFor;
BlockConsistencyGroup cg = null;
// Load the CG.
cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Get a list of the active VPLEX volumes associated to this CG.
List<Volume> cgVPLEXVolumes = getActiveVPLEXVolumesForCG(cgURI);
// Determine the list of volumes to be added to the CG. For straight VPLEX,
// this is just the passed VPLEX volumes, for RP+VPLEX this will just be the
// one volume that is currently associated to the CG.
List<URI> volumeList = new ArrayList<URI>();
volumeList.addAll(vplexVolumeURIs);
// Check to see if the CG has been created on the VPlex already
// or if the CG will be removed by an earlier step such that
// when the workflow executes, the CG will no longer be on the
// array.
if ((!cg.created(vplexURI)) || (willBeRemovedByEarlierStep)) {
// If the CG doesn't exist at all.
log.info("Consistency group not created.");
String stepId = workflow.createStepId();
// Create a step to create the CG.
nextStep = workflow.createStep(CREATE_CG_STEP,
String.format("VPLEX %s creating consistency group %s", vplexURI, cgURI),
nextStep, vplexURI, vplexSystem.getSystemType(), this.getClass(),
createCGMethod(vplexURI, cgURI, volumeList), rollbackCreateCGMethod(vplexURI, cgURI, stepId), stepId);
log.info("Created step for consistency group creation.");
} else {
// See if the CG is created but contains no volumes.
// That is there should be no volumes other than these
// volumes we are trying to create and add to the CG.
// If so, we need to make sure the visibility and storage
// cluster info for the VPLEX CG is correct for these
// volumes we are adding. It is the case this CG existed
// previously for other volumes, but the volumes were
// deleted and removed from the CG. The visibility and
// cluster info would have been set for those volumes
// and may not be appropriate for these volumes.
if (cgVPLEXVolumes.size() == vplexVolumeURIs.size()) {
// There are no volumes for the CG, other than these
// we are adding, so we need to add a step to ensure
// the visibility and cluster info for the CG is
// correct.
nextStep = workflow.createStep(SET_CG_PROPERTIES_STEP, String.format(
"Setting consistency group %s properties", cgURI), nextStep,
vplexURI, vplexSystem.getSystemType(), this.getClass(),
createSetCGPropertiesMethod(vplexURI, cgURI, volumeList),
rollbackMethodNullMethod(), null);
log.info("Created step for setting consistency group properties.");
}
}
// Create a step to add the volumes to the CG.
nextStep = workflow.createStep(ADD_VOLUMES_TO_CG_STEP, String.format(
"VPLEX %s adding volumes to consistency group %s", vplexURI, cgURI),
nextStep, vplexURI, vplexSystem.getSystemType(), this.getClass(),
createAddVolumesToCGMethod(vplexURI, cgURI, volumeList),
createRemoveVolumesFromCGMethod(vplexURI, cgURI, volumeList), null);
log.info(String.format("Created step for adding volumes to the consistency group %s", cgURI.toString()));
return nextStep;
}
/**
* Create the workflow method to rollback a CG creation on a VPLEX system.
*
* @param cgURI The consistency group URI
* @param createStepId The step that created the CG.
*
* @return A reference to the workflow method
*/
private Workflow.Method rollbackCreateCGMethod(URI vplexURI, URI cgURI, String createStepId) {
return new Workflow.Method(RB_CREATE_CG_METHOD_NAME, vplexURI, cgURI, createStepId);
}
/**
* Method call when we need to rollback the deletion of a consistency group.
*
* @param vplexSystemURI The URI of the VPlex system.
* @param cgURI The consistency group URI
* @param deleteStepId The step that deleted the CG.
* @param stepId The step id.
*/
public void rollbackCreateCG(URI vplexSystemURI, URI cgURI, String createStepId, 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(createStepId);
if (rbDataObj == null) {
// Update step state to done.
log.info("CG was not created, nothing to do.");
cleanUpVplexCG(vplexSystemURI, cgURI, null, false);
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
StorageSystem vplexSystem = getDataObject(StorageSystem.class,
vplexSystemURI, dbClient);
// Get the CG.
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Get the VPlex API client.
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem,
dbClient);
log.info("Got VPlex API client for VPlex system {}", vplexSystemURI);
// 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 cgName = vplexCg.getKey();
// 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));
cleanUpVplexCG(vplexSystemURI, cgURI, cgName, false);
}
}
}
// Update step state to done.
WorkflowStepCompleter.stepSucceded(stepId);
} catch (VPlexApiException vae) {
log.error("Exception rolling back VPLEX consistency group creation: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
log.error("Exception rolling back VPLEX consistency group creation: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.DELETE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.deleteCGFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* A method the creates the method to create a new VPLEX consistency group.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the consistency group to be created.
* @param vplexVolumeURIs The URIs of the VPLEX volumes that will be used
* to create a VPlex consistency group.
*
* @return A reference to the consistency group creation workflow method.
*/
protected Workflow.Method createCGMethod(URI vplexURI, URI cgURI, List<URI> vplexVolumeURIs) {
return new Workflow.Method(CREATE_CG_METHOD_NAME, vplexURI, cgURI, vplexVolumeURIs);
}
/**
* Called by the workflow to create a new VPLEX consistency group.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the Bourne consistency group
* @param vplexVolumeURIs The URI of the VPLEX used to determine the VPlex
* cluster/distributed information.
* @param stepId The workflow step id.
*
* @throws WorkflowException When an error occurs updating the workflow step
* state.
*/
public void createCG(URI vplexURI, URI cgURI, List<URI> vplexVolumeURIs, String stepId)
throws WorkflowException {
try {
// Update workflow step.
WorkflowStepCompleter.stepExecuting(stepId);
log.info("Updated step state for consistency group creation to execute.");
// Lock the CG for the step duration.
List<String> lockKeys = new ArrayList<String>();
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(dbClient, cgURI, vplexURI));
workflowService.acquireWorkflowStepLocks(stepId, lockKeys, LockTimeoutValue.get(LockType.RP_VPLEX_CG));
// Get the API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPLEX API client.");
// Get the consistency group
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Check to see if it was created since we defined the workflow.
if (cg.created(vplexURI)) {
StringSet cgNames = cg.getSystemConsistencyGroups().get(vplexURI.toString());
log.info("Consistency group(s) already created: " + cgNames.toString());
if (!cg.getTypes().contains(Types.VPLEX.name())) {
// SRDF will reset the CG types. If the CG was existing on VPLEX need to make sure it is in types.
cg.addConsistencyGroupTypes(Types.VPLEX.name());
dbClient.updateObject(cg);
}
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
// We need to know on what cluster to create the consistency group.
// The cluster would be determined by the virtual array specified in
// a volume creation request, which is the virtual array of the
// passed virtual volumes. Get the virtual array for one of the
// vplex volumes.
Volume firstVPlexVolume = getDataObject(Volume.class, vplexVolumeURIs.get(0), dbClient);
// Lets determine the VPlex consistency group that need to be created for this volume.
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(firstVPlexVolume, cg);
String cgName = clusterConsistencyGroup.getCgName();
String clusterName = clusterConsistencyGroup.getClusterName();
boolean isDistributed = clusterConsistencyGroup.isDistributed();
URI vaURI = firstVPlexVolume.getVirtualArray();
log.info("Got virtual array for VPLEX volume.");
// Now we can create the consistency group.
client.createConsistencyGroup(cgName, clusterName, isDistributed);
log.info("Created VPLEX consistency group.");
// Create the rollback data in case this needs to be deleted.
VPlexCGRollbackData rbData = new VPlexCGRollbackData();
rbData.setVplexSystemURI(vplexURI);
rbData.setCgName(cgName);
rbData.setClusterName(clusterName);
rbData.setIsDistributed(new Boolean(getIsCGDistributed(client, cgName, clusterName)));
workflowService.storeStepData(stepId, rbData);
// Now update the CG in the DB.
cg.setVirtualArray(vaURI);
cg.setStorageController(vplexURI);
cg.addSystemConsistencyGroup(vplexSystem.getId().toString(),
BlockConsistencyGroupUtils.buildClusterCgName(clusterName, cgName));
cg.addConsistencyGroupTypes(Types.VPLEX.name());
dbClient.persistObject(cg);
log.info("Updated consistency group in DB.");
// Update workflow step state to success.
WorkflowStepCompleter.stepSucceded(stepId);
log.info("Updated workflow step for consistency group creation to success.");
} catch (VPlexApiException vex) {
log.error("Exception creating consistency group: " + vex.getMessage(), vex);
WorkflowStepCompleter.stepFailed(stepId, vex);
} catch (Exception ex) {
log.error("Exception creating consistency group: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.CREATE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.createConsistencyGroupFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* A method that creates the workflow method for adding VPLEX volumes to 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 added to the
* consistency group.
*
* @return A reference to the workflow method to add VPLEX volumes to a
* consistency group.
*/
protected Workflow.Method createAddVolumesToCGMethod(URI vplexURI, URI cgURI,
List<URI> vplexVolumeURIs) {
return new Workflow.Method(ADD_VOLUMES_TO_CG_METHOD_NAME, vplexURI, cgURI,
vplexVolumeURIs);
}
/**
* The method called by the workflow to add VPLEX volumes to 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 added to the
* consistency group.
* @param stepId The workflow step id.
*
* @throws WorkflowException When an error occurs updating the workflow step
* state.
*/
public void addVolumesToCG(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 add volumes to consistency group.");
// Get the API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPLEX API client.");
Volume firstVPlexVolume = getDataObject(Volume.class, vplexVolumeURIs.get(0), dbClient);
String cgName = getVplexCgName(firstVPlexVolume, cgURI);
// Get the names of the volumes to be added.
List<Volume> vplexVolumes = new ArrayList<Volume>();
List<String> vplexVolumeNames = new ArrayList<String>();
for (URI vplexVolumeURI : vplexVolumeURIs) {
Volume vplexVolume = getDataObject(Volume.class, vplexVolumeURI, dbClient);
vplexVolumes.add(vplexVolume);
vplexVolumeNames.add(vplexVolume.getDeviceLabel());
log.info("VPLEX volume:" + vplexVolume.getDeviceLabel());
}
log.info("Got VPLEX volume names.");
long startTime = System.currentTimeMillis();
// Add the volumes to the CG.
client.addVolumesToConsistencyGroup(cgName, vplexVolumeNames);
long elapsed = System.currentTimeMillis() - startTime;
log.info(String.format("TIMER: Adding %s virtual volume(s) %s to the consistency group %s took %f seconds",
vplexVolumeNames.size(), vplexVolumeNames, cgName, (double) elapsed / (double) 1000));
// Make sure the volumes are updated. Necessary when
// adding volumes to a CG after volume creation.
for (Volume vplexVolume : vplexVolumes) {
vplexVolume.setConsistencyGroup(cgURI);
dbClient.updateObject(vplexVolume);
}
// Update workflow step state to success.
WorkflowStepCompleter.stepSucceded(stepId);
log.info("Updated workflow step state to success for add volumes to consistency group.");
} catch (VPlexApiException vae) {
log.error("Exception adding volumes to consistency group: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
log.error(
"Exception adding volumes to consistency group: " + ex.getMessage(), ex);
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
}
}
/**
* A method the creates the method to set the properties for an existing
* VPLEX consistency group with no volumes.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the consistency group to be created.
* @param vplexVolumeURIs The URIs of the VPLEX volumes that will be added
* to the consistency group.
*
* @return A reference to the consistency group creation workflow method.
*/
private Workflow.Method createSetCGPropertiesMethod(URI vplexURI, URI cgURI,
List<URI> vplexVolumeURIs) {
return new Workflow.Method(SET_CG_PROPERTIES_METHOD_NAME, vplexURI, cgURI,
vplexVolumeURIs);
}
/**
* Called by the workflow to set the properties for an existing VPLEX
* consistency group with no volumes.
*
* @param vplexURI The URI of the VPLEX storage system.
* @param cgURI The URI of the Bourne consistency group.
* @param vplexVolumeURIs The URIs of the VPLEX volumes to be added to the
* consistency group.
* @param stepId The workflow step id.
*
* @throws WorkflowException When an error occurs updating the workflow step
* state.
*/
public void setCGProperties(URI vplexURI, URI cgURI, List<URI> vplexVolumeURIs,
String stepId) throws WorkflowException {
try {
// Update workflow step.
WorkflowStepCompleter.stepExecuting(stepId);
log.info("Updated step state for consistency group properties to execute.");
// Lock the CG for the step duration.
List<String> lockKeys = new ArrayList<String>();
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(dbClient, cgURI, vplexURI));
workflowService.acquireWorkflowStepLocks(stepId, lockKeys, LockTimeoutValue.get(LockType.RP_VPLEX_CG));
// Get the API client.
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, dbClient);
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPLEX API client.");
// Get the consistency group
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// We need to know on what cluster to find the consistency group.
// The cluster would be determined by the virtual array specified in
// a volume creation request, which is the virtual array of the
// passed virtual volumes. Get the virtual array for one of the
// vplex volumes.
Volume firstVPlexVolume = getDataObject(Volume.class, vplexVolumeURIs.get(0), dbClient);
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(firstVPlexVolume, cg);
String cgName = clusterConsistencyGroup.getCgName();
String clusterName = clusterConsistencyGroup.getClusterName();
boolean isDistributed = clusterConsistencyGroup.isDistributed();
// Now we can update the consistency group properties.
client.updateConsistencyGroupProperties(cgName, clusterName, isDistributed);
log.info("Updated VPLEX consistency group properties.");
// Update workflow step state to success.
WorkflowStepCompleter.stepSucceded(stepId);
log.info("Updated workflow step for consistency group properties to success.");
} catch (VPlexApiException vae) {
log.error("Exception updating consistency group properties: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
} catch (Exception ex) {
log.error("Exception updating consistency group properties: " + ex.getMessage(), ex);
ServiceError serviceError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
}
}
/**
* {@inheritDoc}
*/
@Override
public void updateConsistencyGroup(Workflow workflow, URI vplexURI, URI cgURI,
List<URI> addVolumesList, List<URI> removeVolumesList, String opId)
throws InternalException {
try {
String waitFor = null;
StorageSystem vplexSystem = getDataObject(StorageSystem.class, vplexURI, dbClient);
BlockConsistencyGroup cg = getDataObject(BlockConsistencyGroup.class, cgURI, dbClient);
// Lock the CG for the duration of update CG workflow.
List<String> lockKeys = new ArrayList<String>();
lockKeys.add(ControllerLockingUtil.getConsistencyGroupStorageKey(dbClient, cgURI, vplexURI));
boolean acquiredLocks = workflowService.acquireWorkflowLocks(workflow, lockKeys, LockTimeoutValue.get(LockType.RP_VPLEX_CG));
if (!acquiredLocks) {
throw DeviceControllerException.exceptions.failedToAcquireLock(lockKeys.toString(),
"UpdateConsistencyGroup: " + cg.getLabel());
}
// The addVolumesList could be full copies or volumes.
boolean isFullCopy = false;
if (addVolumesList != null && !addVolumesList.isEmpty()) {
URI volURI = addVolumesList.get(0);
Volume vol = getDataObject(Volume.class, volURI, dbClient);
isFullCopy = ControllerUtils.isVolumeFullCopy(vol, dbClient);
}
// Users could use updateConsistencyGroup operation to add backend CGs for ingested CGs.
// if that's the case, we will only add the backend CGs, but not add those virtual volumes to
// the VPlex CG.
boolean isIngestedCG = isAddingBackendCGForIngestedCG(cg, addVolumesList);
// Check if the CG has been created in VPlex yet
boolean isNewCg = !cg.created();
// If necessary, create a step to update the local CGs.
if (cg.getTypes().contains(Types.LOCAL.toString()) || isIngestedCG || isNewCg) {
// We need to determine the backend systems that own the local CGs and the
// volumes to be added/removed from each. There should really only be either
// one of two backend systems depending upon whether or not the volumes are
// local or distributed. In addition, when volumes are being both added and
// removed, the maps should contains the same key set so it doesn't matter
// which is used.
Map<URI, List<URI>> localAddVolumesMap = getLocalVolumesForUpdate(addVolumesList);
Map<URI, List<URI>> localRemoveVolumesMap = getLocalVolumesForRemove(removeVolumesList);
Set<URI> localSystems = localAddVolumesMap.keySet();
if (localSystems.isEmpty()) {
localSystems = localRemoveVolumesMap.keySet();
}
// Now we need to iterate over the backend systems and create a step to
// update the corresponding consistency groups on the backend arrays.
Iterator<URI> localSystemIter = localSystems.iterator();
while (localSystemIter.hasNext()) {
URI localSystemURI = localSystemIter.next();
StorageSystem localSystem = getDataObject(StorageSystem.class, localSystemURI, dbClient);
List<URI> localAddVolumesList = localAddVolumesMap.get(localSystemURI);
List<URI> localRemoveVolumesList = localRemoveVolumesMap.get(localSystemURI);
Workflow.Method updateLocalMethod = new Workflow.Method(
UPDATE_CONSISTENCY_GROUP_METHOD_NAME, localSystemURI, cgURI,
localAddVolumesList, localRemoveVolumesList);
Workflow.Method rollbackLocalMethod = new Workflow.Method(
UPDATE_CONSISTENCY_GROUP_METHOD_NAME, localSystemURI, cgURI,
localRemoveVolumesList, localAddVolumesList);
workflow.createStep(UPDATE_LOCAL_CG_STEP, String.format(
"Updating consistency group %s on system %s",
cgURI, localSystemURI), null,
localSystemURI, localSystem.getSystemType(),
BlockDeviceController.class, updateLocalMethod,
rollbackLocalMethod, null);
}
if (!localSystems.isEmpty()) {
waitFor = UPDATE_LOCAL_CG_STEP;
log.info("Created steps to remove volumes from native consistency groups.");
}
}
// First remove any volumes to be removed.
int removeVolumeCount = 0;
if ((removeVolumesList != null) && !removeVolumesList.isEmpty()) {
removeVolumeCount = removeVolumesList.size();
addStepForRemoveVolumesFromCG(workflow, waitFor, vplexSystem,
removeVolumesList, cgURI);
}
// Now create a step to add volumes to the CG.
if ((addVolumesList != null) && !addVolumesList.isEmpty() && !isIngestedCG && !isNewCg && !isFullCopy) {
// See if the CG contains no volumes. If so, we need to
// make sure the visibility and storage cluster info for
// the VPLEX CG is correct for these volumes we are adding.
// It is the case this CG existed previously for other
// volumes, but the volumes were deleted and removed from
// the CG. The visibility and cluster info would have been
// set for those volumes and may not be appropriate for these
// volumes. It could also be that this request removes all
// the existing volumes and the volumes being added have
// different property requirements.
List<Volume> cgVPLEXVolumes = getActiveVPLEXVolumesForCG(cgURI);
if ((cgVPLEXVolumes.isEmpty()) || (cgVPLEXVolumes.size() == removeVolumeCount)) {
Workflow.Method setPropsMethod = createSetCGPropertiesMethod(vplexURI,
cgURI, addVolumesList);
// We only need to reset the properties if it's empty because
// we just removed all the volumes. The properties are reset
// back to those appropriate for the removed volumes before
// they get added back in.
Workflow.Method rollbackSetPropsMethod =
(removeVolumesList != null && !removeVolumesList.isEmpty()) ?
createSetCGPropertiesMethod(vplexURI, cgURI, removeVolumesList) :
rollbackMethodNullMethod();
waitFor = workflow.createStep(SET_CG_PROPERTIES_STEP, String.format(
"Setting consistency group %s properties", cgURI), waitFor,
vplexURI, vplexSystem.getSystemType(), this.getClass(),
setPropsMethod, rollbackSetPropsMethod, null);
log.info("Created step for setting consistency group properties.");
}
// Now create a step to add the volumes.
Workflow.Method addMethod = createAddVolumesToCGMethod(vplexURI, cgURI,
addVolumesList);
workflow.createStep(ADD_VOLUMES_TO_CG_STEP, String.format(
"VPLEX %s adding volumes to consistency group %s", vplexURI, cgURI),
waitFor, vplexURI, vplexSystem.getSystemType(), this.getClass(),
addMethod, rollbackMethodNullMethod(), null);
log.info("Created step for add volumes to consistency group.");
} else if (isNewCg && addVolumesList != null && !addVolumesList.isEmpty() && !isFullCopy) {
addStepsForCreateConsistencyGroup(workflow, waitFor, vplexSystem, addVolumesList, false, cgURI);
}
TaskCompleter completer = new VPlexTaskCompleter(BlockConsistencyGroup.class,
Arrays.asList(cgURI), opId, null);
log.info("Executing workflow plan");
workflow.executePlan(completer, String.format(
"Update of consistency group %s completed successfully", cgURI));
log.info("Workflow plan executed");
} catch (Exception e) {
String failMsg = String.format("Update of consistency group %s failed",
cgURI);
log.error(failMsg, e);
// Release the locks
workflowService.releaseAllWorkflowLocks(workflow);
TaskCompleter completer = new VPlexTaskCompleter(BlockConsistencyGroup.class,
Arrays.asList(cgURI), opId, null);
String opName = ResourceOperationTypeEnum.UPDATE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.updateConsistencyGroupFailed(
cgURI.toString(), opName, e);
completer.error(dbClient, serviceError);
}
}
/**
* Maps a VPlex cluster/consistency group to its volumes.
*
* @param vplexVolume The virtual volume from which to obtain the VPlex cluster.
* @param clusterConsistencyGroupVolumes The map to store cluster/cg/volume relationships.
* @param cgName The VPlex consistency group name.
* @throws Exception
*/
@Override
public ClusterConsistencyGroupWrapper getClusterConsistencyGroup(Volume vplexVolume, BlockConsistencyGroup cg) throws Exception {
ClusterConsistencyGroupWrapper clusterConsistencyGroup = new ClusterConsistencyGroupWrapper();
// If there are no associated volumes, we cannot determine the cluster name and if the
// volume is distributed or not. This is typical in VPlex ingested volume cases. So for
// these cases, we just set the cgName value only.
if (vplexVolume.getAssociatedVolumes() != null && !vplexVolume.getAssociatedVolumes().isEmpty()) {
String clusterName = VPlexControllerUtils.getVPlexClusterName(dbClient, vplexVolume);
StringSet assocVolumes = vplexVolume.getAssociatedVolumes();
boolean distributed = false;
if (assocVolumes.size() > 1) {
distributed = true;
}
clusterConsistencyGroup.setClusterName(clusterName);
clusterConsistencyGroup.setDistributed(distributed);
}
clusterConsistencyGroup.setCgName(cg.getLabel());
return clusterConsistencyGroup;
}
/**
* Gets the active VPLEX volumes in the CG.
*
* @param cgURI The consistency group URI
*
* @return A list of the active VPLEX volumes in the CG.
*/
private List<Volume> getActiveVPLEXVolumesForCG(URI cgURI) {
List<Volume> cgVPLEXVolumes = new ArrayList<Volume>();
List<Volume> cgVolumes = CustomQueryUtility.queryActiveResourcesByConstraint(
dbClient, Volume.class, ContainmentConstraint.Factory.
getVolumesByConsistencyGroup(cgURI));
for (Volume cgVolume : cgVolumes) {
if (!Volume.checkForVplexBackEndVolume(dbClient, cgVolume)) {
cgVPLEXVolumes.add(cgVolume);
}
}
return cgVPLEXVolumes;
}
/**
* Create a map of the backend volumes for the passed VPLEX volumes key'd by
* the backend systems. Called during a consistency group update so that
* the corresponding backend consistency groups can be updated.
*
* @param vplexVolumes A list of VPLEX volumes.
*
* @return A map of the backend volumes for the passed VPLEX volumes key'd
* by the backend systems.
*/
private Map<URI, List<URI>> getLocalVolumesForUpdate(List<URI> vplexVolumes) {
Map<URI, List<URI>> localVolumesMap = new HashMap<URI, List<URI>>();
if ((vplexVolumes != null) && (!vplexVolumes.isEmpty())) {
for (URI vplexVolumeURI : vplexVolumes) {
Volume vplexVolume = getDataObject(Volume.class, vplexVolumeURI, dbClient);
StringSet associatedVolumes = vplexVolume.getAssociatedVolumes();
if (null == associatedVolumes || associatedVolumes.isEmpty()) {
log.warn("VPLEX volume {} has no backend volumes.", vplexVolume.forDisplay());
} else {
for (String assocVolumeId : associatedVolumes) {
URI assocVolumeURI = URI.create(assocVolumeId);
Volume assocVolume = getDataObject(Volume.class, assocVolumeURI, dbClient);
URI assocSystemURI = assocVolume.getStorageController();
if (!localVolumesMap.containsKey(assocSystemURI)) {
List<URI> systemVolumes = new ArrayList<URI>();
localVolumesMap.put(assocSystemURI, systemVolumes);
}
localVolumesMap.get(assocSystemURI).add(assocVolumeURI);
}
}
}
}
return localVolumesMap;
}
/**
* Adds a step to the passed workflow to remove the passed volumes from the
* consistency group with the passed URI.
*
* @param workflow The workflow to which the step is added
* @param waitFor The step for which this step should wait.
* @param vplexSystem The VPLEX system
* @param volumes The volumes to be removed
* @param cgURI The URI of the consistency group
*
* @return The step id of the added step.
*/
public String addStepForRemoveVolumesFromCG(Workflow workflow, String waitFor,
StorageSystem vplexSystem, List<URI> volumes, URI cgURI) {
URI vplexURI = vplexSystem.getId();
Workflow.Method removeMethod = createRemoveVolumesFromCGMethod(vplexURI, cgURI, volumes);
Workflow.Method removeRollbackMethod = createAddVolumesToCGMethod(vplexURI, cgURI, volumes);
waitFor = workflow.createStep(REMOVE_VOLUMES_FROM_CG_STEP, String.format(
"Removing volumes %s from consistency group %s on VPLEX %s", volumes, cgURI,
vplexURI), waitFor, vplexURI, vplexSystem.getSystemType(), this.getClass(),
removeMethod, removeRollbackMethod, null);
log.info("Created step for remove volumes from consistency group.");
return waitFor;
}
/**
* Check if update consistency group operation is for adding back end consistency groups for ingested CG.
*
* @param cg
* @param addVolumesList
* @return true or false
*/
private boolean isAddingBackendCGForIngestedCG(BlockConsistencyGroup cg, List<URI> addVolumesList) {
boolean result = false;
if (cg.getTypes().contains(Types.LOCAL.toString())) {
// Not ingested CG
return result;
}
List<Volume> cgVolumes = BlockConsistencyGroupUtils.getActiveVplexVolumesInCG(cg, dbClient, null);
Set<String> cgVolumeURIs = new HashSet<String>();
for (Volume cgVolume : cgVolumes) {
cgVolumeURIs.add(cgVolume.getId().toString());
}
if (!addVolumesList.isEmpty() && cgVolumeURIs.contains(addVolumesList.get(0).toString())) {
result = true;
}
return result;
}
/**
* Create a map of the backend volumes that need to remove from backend CG.
* Called during a consistency group update so that the corresponding backend
* consistency groups can be updated.
*
* @param vplexVolumes A list of VPLEX volumes.
*
* @return A map of the backend volumes for the passed VPLEX volumes key'd
* by the backend systems.
*/
private Map<URI, List<URI>> getLocalVolumesForRemove(List<URI> vplexVolumes) {
Map<URI, List<URI>> localVolumesMap = new HashMap<URI, List<URI>>();
if ((vplexVolumes != null) && (!vplexVolumes.isEmpty())) {
for (URI vplexVolumeURI : vplexVolumes) {
Volume vplexVolume = getDataObject(Volume.class, vplexVolumeURI, dbClient);
StringSet associatedVolumes = vplexVolume.getAssociatedVolumes();
if (null == associatedVolumes || associatedVolumes.isEmpty()) {
log.warn("VPLEX volume {} has no backend volumes.", vplexVolume.forDisplay());
} else {
for (String assocVolumeId : associatedVolumes) {
URI assocVolumeURI = URI.create(assocVolumeId);
Volume assocVolume = getDataObject(Volume.class, assocVolumeURI, dbClient);
if (NullColumnValueGetter.isNotNullValue(assocVolume.getReplicationGroupInstance())) {
// The backend volume is in a backend CG
URI assocSystemURI = assocVolume.getStorageController();
if (!localVolumesMap.containsKey(assocSystemURI)) {
List<URI> systemVolumes = new ArrayList<URI>();
localVolumesMap.put(assocSystemURI, systemVolumes);
}
localVolumesMap.get(assocSystemURI).add(assocVolumeURI);
}
}
}
}
}
return localVolumesMap;
}
@Override
public String addStepsForAddingVolumesToSRDFTargetCG(Workflow workflow, StorageSystem vplexSystem,
List<URI> vplexVolumeURIs, String argWaitFor) {
String waitFor = argWaitFor; // to fix Sonar
StringBuilder volumeList = new StringBuilder();
for (URI vplexVolumeURI : vplexVolumeURIs) {
Volume volume = dbClient.queryObject(Volume.class, vplexVolumeURI);
if (volumeList.length() != 0) {
volumeList.append(", ");
}
volumeList.append(volume.getLabel());
}
Workflow.Method executeMethod = addVplexVolumesToSRDFTagetCGMethod(vplexSystem.getId(), vplexVolumeURIs);
Workflow.Method rollbackMethod = removeVplexVolumesFromSRDFTargetCGMethod(vplexSystem.getId(), vplexVolumeURIs);
waitFor = workflow.createStep(null,
"Add VplexVolumes to Target CG: " + volumeList.toString(), waitFor, vplexSystem.getId(),
vplexSystem.getSystemType(), this.getClass(), executeMethod, rollbackMethod, null);
// Add a step to set the read-only flag to true (on rollback, revert to false)
waitFor = addStepForUpdateConsistencyGroupReadOnlyState(workflow, vplexVolumeURIs, true,
"Set Target CG read-only flag: " + volumeList.toString(), waitFor);
return waitFor;
}
public Workflow.Method addVplexVolumesToSRDFTagetCGMethod(URI vplexURI, List<URI> vplexVolumeURIs) {
return new Workflow.Method("addVplexVolumesToSRDFTargetCG", vplexURI, vplexVolumeURIs);
}
/**
* This workflow step will add vplex volumes fronting an SRDF target volume to the SRDF target consistency group.
* Note that the SRDF target consistency group may not be created until the SRDF Pairing operation has completed.
* At this time the underlying SRDF Targets will be associated with the correct CG. For this reason, we don't know
* what consistency group to associate the Vplex volumes with until the SRDF pairing operation has completed, and
* the workflow is executing.
*
* So here we find the appropriate SRDF target volume(s), determine the CG, and then set up code to
* create the Vplex CG and add the volume to that.
*
* @param vplexURI -- URI of VPlex system
* @param vplexVolumeURIs -- List of VPLEX Volume URIs fronting SRDF Target volumes
* @param stepId -- The step ID being processed
* @throws WorkflowException
*/
public void addVplexVolumesToSRDFTargetCG(URI vplexURI, List<URI> vplexVolumeURIs, String stepId)
throws WorkflowException {
try {
// Make a map of the VPlex volume to corresponding SRDF volume
Map<Volume, Volume> vplexToSrdfVolumeMap = VPlexSrdfUtil.makeVplexToSrdfVolumeMap(dbClient, vplexVolumeURIs);
// Make sure that the SRDF volumes have a consistency group and it is the same.
URI cgURI = null;
for (Volume srdfVolume : vplexToSrdfVolumeMap.values()) {
if (srdfVolume.getConsistencyGroup() != null) {
if (cgURI == null) {
cgURI = srdfVolume.getConsistencyGroup();
} else {
if (srdfVolume.getConsistencyGroup() != cgURI) {
log.info("Multiple CGs discovered: " + cgURI.toString() + " " + srdfVolume.getConsistencyGroup().toString());
}
}
}
}
// If there is no consistency group, That is not an error.
if (cgURI == null) {
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
// Get the consistency group, and make sure it has requested type Vplex. Change the VPlex volumes to point to the CG.
BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class, cgURI);
if (!consistencyGroup.getRequestedTypes().contains(Types.VPLEX)) {
consistencyGroup.addRequestedTypes(Arrays.asList(Types.VPLEX.name()));
dbClient.updateObject(consistencyGroup);
}
Volume protoVolume = null;
for (Volume vplexVolume : vplexToSrdfVolumeMap.keySet()) {
protoVolume = vplexVolume;
break;
}
StorageSystem vplexSystem = dbClient.queryObject(StorageSystem.class, protoVolume.getStorageController());
// Create the consistency group if it does not already exist. If there is an error, the step is completed
// and we return.
if (createVplexCG(vplexSystem, consistencyGroup, protoVolume, stepId) == false) {
return;
}
// Add the Vplex volumes to the CG, and fire off the Step completer.
addVolumesToCG(vplexSystem, consistencyGroup, vplexToSrdfVolumeMap.keySet(), stepId);
} catch (Exception ex) {
log.info("Exception adding Vplex volumes to SRDF Target CG: " + ex.getMessage(), ex);
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
}
}
/**
* Adds steps for remove volumes from the SRDF Target CG.
* @param workflow -- Workflow steps are to be added to
* @param vplexSystem -- VPlex system being provisioned
* @param vplexVolumeURIs -- List of volume URIs to be removed from CG
* @param waitFor -- Step or step group identifier of previous step(s) to be waited on
* @return waitFor -- step subsequent steps should wait on
*/
public String addStepsForRemovingVolumesFromSRDFTargetCG(Workflow workflow, StorageSystem vplexSystem,
List<URI> vplexVolumeURIs, String waitFor) {
StringBuilder volumeList = new StringBuilder();
if (vplexVolumeURIs.isEmpty()) {
return waitFor;
}
for (URI vplexVolumeURI : vplexVolumeURIs) {
Volume volume = dbClient.queryObject(Volume.class, vplexVolumeURI);
if (volumeList.length() != 0) {
volumeList.append(", ");
}
volumeList.append(volume.getLabel());
}
Workflow.Method executeMethod = removeVplexVolumesFromSRDFTargetCGMethod(vplexSystem.getId(), vplexVolumeURIs);
Workflow.Method rollbackMethod = removeVplexVolumesFromSRDFTargetCGMethod(vplexSystem.getId(), vplexVolumeURIs);
waitFor = workflow.createStep(null,
"Remove VplexVolumes from Target CG: " + volumeList.toString(), waitFor, vplexSystem.getId(),
vplexSystem.getSystemType(), this.getClass(), executeMethod, rollbackMethod, null);
return waitFor;
}
/**
* Returns a workflow method for removing Vplex Volumes form the SRDF Target CG.
* This is used for rolling back addVplexVolumesToSRDFTargetCG.
* @param vplexURI
* @param vplexVolumeURIs
* @return
*/
Workflow.Method removeVplexVolumesFromSRDFTargetCGMethod(URI vplexURI, List<URI> vplexVolumeURIs) {
return new Workflow.Method("removeVplexVolumesFromSRDFTargetCG", vplexURI, vplexVolumeURIs);
}
/**
* Removes volumes from SRDF Target.
* @param vplexURI
* @param vplexVolumeURIs
* @param stepId
* @throws WorkflowException
*/
public void removeVplexVolumesFromSRDFTargetCG(URI vplexURI, List<URI> vplexVolumeURIs, String stepId)
throws WorkflowException {
try {
WorkflowStepCompleter.stepExecuting(stepId);
Volume protoVolume = dbClient.queryObject(Volume.class, vplexVolumeURIs.get(0));
if (NullColumnValueGetter.isNullURI(protoVolume.getConsistencyGroup())) {
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
BlockConsistencyGroup consistencyGroup = dbClient.queryObject(BlockConsistencyGroup.class,
protoVolume.getConsistencyGroup());
if (consistencyGroup == null) {
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
// Remove the volumes from the ConsistencyGroup. Returns a codedError if failure.
ServiceCoded codedError = removeVolumesFromCGInternal(vplexURI, protoVolume.getConsistencyGroup(), vplexVolumeURIs);
if (codedError != null) {
WorkflowStepCompleter.stepFailed(stepId, codedError);
return;
}
// Determine if there are any remaining Vplex volumes in the consistency group.
List<Volume> vplexVolumesInCG =
BlockConsistencyGroupUtils.getActiveVplexVolumesInCG(consistencyGroup, dbClient, null);
if (vplexVolumesInCG.isEmpty()) {
ClusterConsistencyGroupWrapper clusterCGWrapper =
getClusterConsistencyGroup(protoVolume, consistencyGroup);
// No vplex volumes left, clean up the Vplex part of the consistency group.
// deleteCG will call the step completer.
deleteCG(vplexURI, consistencyGroup.getId(), clusterCGWrapper.getCgName(),
clusterCGWrapper.getClusterName(), false, stepId);
} else {
// Vplex volumes left... we're finished.
WorkflowStepCompleter.stepSucceded(stepId);
}
} catch (Exception ex) {
log.info("Exception removing Vplex volumes from SRDF Target CG: " + ex.getMessage(), ex);
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
}
}
/**
* Create a Vplex CG within a step. This routine does not complete the step unless there is an error (and it returns false).
* @param vplexSystem -- StorageSystem of the VPLEX
* @param cg -- BlockConsistencyGroup object
* @param protoVolume -- A prototypical Vplex volume
* @param stepId -- String stepId, completed only if error
* @return true if no error, false if error
*/
private boolean createVplexCG(StorageSystem vplexSystem, BlockConsistencyGroup cg, Volume protoVolume, String stepId) {
try {
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
log.info("Got VPLEX API client.");
// Check to see if it was created since we defined the workflow.
if (cg.created(vplexSystem.getId())) {
StringSet cgNames = cg.getSystemConsistencyGroups().get(vplexSystem.getId().toString());
log.info("Consistency group(s) already created: " + cgNames.toString());
return true;
}
// We need to know on what cluster to create the consistency group.
// The cluster would be determined by the virtual array specified in
// a volume creation request, which is the virtual array of the
// passed virtual volumes. Get the virtual array for one of the
// vplex volumes.
// Lets determine the VPlex consistency group that need to be created for this volume.
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(protoVolume, cg);
String cgName = clusterConsistencyGroup.getCgName();
String clusterName = clusterConsistencyGroup.getClusterName();
boolean isDistributed = clusterConsistencyGroup.isDistributed();
URI vaURI = protoVolume.getVirtualArray();
log.info("Got virtual array for VPLEX volume.");
// Now we can create the consistency group.
client.createConsistencyGroup(cgName, clusterName, isDistributed);
log.info("Created VPLEX consistency group.");
// Now update the CG in the DB.
cg.setVirtualArray(vaURI);
cg.addSystemConsistencyGroup(vplexSystem.getId().toString(),
BlockConsistencyGroupUtils.buildClusterCgName(clusterName, cgName));
cg.addConsistencyGroupTypes(Types.VPLEX.name());
dbClient.updateObject(cg);
log.info("Updated consistency group in DB.");
} catch (VPlexApiException vex) {
log.error("Exception creating consistency group: " + vex.getMessage(), vex);
WorkflowStepCompleter.stepFailed(stepId, vex);
return false;
} catch (Exception ex) {
log.error("Exception creating consistency group: " + ex.getMessage(), ex);
String opName = ResourceOperationTypeEnum.CREATE_CONSISTENCY_GROUP.getName();
ServiceError serviceError = VPlexApiException.errors.createConsistencyGroupFailed(opName, ex);
WorkflowStepCompleter.stepFailed(stepId, serviceError);
return false;
}
return true;
}
/**
* Adds volumes to a BlockConsistencyGroup.
* @param vplexSystem - StorageSystem representing the Vplex
* @param cg -- Block Consistency Group the volumes are to be added to
* @param vplexVolumes -- Collection of Vplex volumes to be added
* @param stepId -- String Stepid. WorkflowStepCompleter is called if successful or not.
* @return true if successful, false if not
*/
private boolean addVolumesToCG(StorageSystem vplexSystem, BlockConsistencyGroup cg,
Collection<Volume> vplexVolumes, String stepId) {
try {
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
// Get the names of the volumes to be added.
Volume protoVolume = null;
List<String> vplexVolumeNames = new ArrayList<String>();
for (Volume vplexVolume : vplexVolumes) {
if (protoVolume == null) {
protoVolume = vplexVolume;
}
vplexVolumeNames.add(vplexVolume.getDeviceLabel());
log.info("VPLEX volume:" + vplexVolume.getDeviceLabel());
}
log.info("Got VPLEX volume names.");
String cgName = getVplexCgName(protoVolume, cg.getId());
long startTime = System.currentTimeMillis();
// Add the volumes to the CG.
client.addVolumesToConsistencyGroup(cgName, vplexVolumeNames);
long elapsed = System.currentTimeMillis() - startTime;
log.info(String.format("TIMER: Adding %s virtual volume(s) %s to the consistency group %s took %f seconds",
vplexVolumeNames.size(), vplexVolumeNames, cgName, (double) elapsed / (double) 1000));
// Make sure the volumes are updated. Necessary when
// adding volumes to a CG after volume creation.
for (Volume vplexVolume : vplexVolumes) {
vplexVolume.setConsistencyGroup(cg.getId());
dbClient.updateObject(vplexVolume);
}
// Update workflow step state to success.
WorkflowStepCompleter.stepSucceded(stepId);
log.info("Updated workflow step state to success for add volumes to consistency group.");
return true;
} catch (VPlexApiException vae) {
log.error("Exception adding volumes to consistency group: " + vae.getMessage(), vae);
WorkflowStepCompleter.stepFailed(stepId, vae);
return false;
} catch (Exception ex) {
log.error(
"Exception adding volumes to consistency group: " + ex.getMessage(), ex);
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
return false;
}
}
/**
* Adds a step to the passed workflow to set the consistency group read-only flag to the indicated state.
* If the passed volumes have no consistency group, no step is generated.
* @param workflow -- Workflow we're configuring
* @param vplexVolumeURIs -- List of Vplex Volume URIs in the CG to be set
* @param setToReadOnly -- if true set to read only, if false, clear read-only (so is read-write)
* @param stepDescription -- Description of step, helpful if lists volumes
* @param waitFor -- previous step in the workflow to wait for
* @return waitFor -- step id of last step that was created
*/
public String addStepForUpdateConsistencyGroupReadOnlyState(Workflow workflow,
List<URI> vplexVolumeURIs, boolean setToReadOnly,
String stepDescription, String argWaitFor) {
String waitFor = argWaitFor; // to fix Sonar
if (vplexVolumeURIs.isEmpty()) {
return waitFor;
}
Volume vplexVolume = dbClient.queryObject(Volume.class, vplexVolumeURIs.get(0));
// Note that volumes being added to SRDF target CGwill not have consistencyGroup attribute set
// here because that CG will not have been created when the workflow is configured.
// The CG is used here only for logging purposes.
BlockConsistencyGroup cg = null;
if (!NullColumnValueGetter.isNullURI(vplexVolume.getConsistencyGroup())) {
cg = dbClient.queryObject(BlockConsistencyGroup.class, vplexVolume.getConsistencyGroup());
}
StorageSystem vplexSystem = dbClient.queryObject(StorageSystem.class, vplexVolume.getStorageController());
Workflow.Method readOnlyExecuteMethod = updateConsistencyGroupReadOnlyStateMethod(vplexVolumeURIs, setToReadOnly);
Workflow.Method readOnlyRollbackMethod = updateConsistencyGroupReadOnlyStateMethod(vplexVolumeURIs, !setToReadOnly);
waitFor = workflow.createStep(null,
String.format("CG: %s: %s", (cg != null ? cg.getLabel() : ""), stepDescription),
waitFor, vplexSystem.getId(), vplexSystem.getSystemType(), this.getClass(),
readOnlyExecuteMethod, readOnlyRollbackMethod, null);
return waitFor;
}
/**
* Method to update ConsistencyGroup read-only state, must match args of updateConsistencyGroupReadOnlyState
* (except stepId)
* @param vplexVolumeURIs -- list of Vplex volume URIs
* @param isReadOnly if true set to read-only, if false set to read-write
* @return Workflow.Method for updateConsistencyGroupReadOnlyState
*/
public Workflow.Method updateConsistencyGroupReadOnlyStateMethod(
List<URI> vplexVolumeURIs, Boolean isReadOnly) {
return new Workflow.Method("updateConsistencyGroupReadOnlyState", vplexVolumeURIs, isReadOnly);
}
/**
* Step to call the VPLEX to update the read-only flag on a consistency group.
* If the Vplex Api library detects the firmware does not properly handle the flag,
* a warning message is put in the SUCCESS status.
* @param vplexVolumeURIs -- List of at least some volumes in the consistency group.
* @param isReadOnly - if true marks read-only, false read-write
* @param stepId - Workflow step id
*/
public void updateConsistencyGroupReadOnlyState(List<URI> vplexVolumeURIs,
Boolean isReadOnly, String stepId) {
try {
WorkflowStepCompleter.stepExecuting(stepId);
// Get the first virtual volume, as all volumes should be in the same CG
Volume vplexVolume = dbClient.queryObject(Volume.class, vplexVolumeURIs.get(0));
// Get the storage system for the Vplex.
StorageSystem vplexSystem = dbClient.queryObject(StorageSystem.class, vplexVolume.getStorageController());
// And from that a handle to the VplexApiClient
VPlexApiClient client = getVPlexAPIClient(vplexApiFactory, vplexSystem, dbClient);
// From that get the Consistency Group, if there is not one, just return success
if (NullColumnValueGetter.isNullURI(vplexVolume.getConsistencyGroup())) {
log.info("Volume has no ConsistencyGroup: " + vplexVolume.getLabel());
WorkflowStepCompleter.stepSucceded(stepId);
return;
}
BlockConsistencyGroup cg = dbClient.queryObject(BlockConsistencyGroup.class,
vplexVolume.getConsistencyGroup());
// Get the consistency group parameters.
ClusterConsistencyGroupWrapper clusterConsistencyGroup =
getClusterConsistencyGroup(vplexVolume, cg);
String cgName = clusterConsistencyGroup.getCgName();
String clusterName = clusterConsistencyGroup.getClusterName();
boolean isDistributed = clusterConsistencyGroup.isDistributed();
// Make the call to update the consistency group read-only status
client.updateConsistencyGroupReadOnly(cgName, clusterName, isDistributed, isReadOnly);
// Complete the step
WorkflowStepCompleter.stepSucceded(stepId);
} catch (VPlexApiException ex) {
if (ServiceCode.VPLEX_API_FIRMWARE_UPDATE_NEEDED.equals(ex.getServiceCode())) {
// The firmware doesn't support read-only flag, inform the user, but do not fail.
WorkflowStepCompleter.stepSucceeded(stepId, ex.getLocalizedMessage());
} else {
log.info("Exception setting Consistency Group read-only state: " + ex.getMessage());
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
}
} catch (Exception ex) {
log.info("Exception setting Consistency Group read-only state: " + ex.getMessage());
ServiceError svcError = VPlexApiException.errors.jobFailed(ex);
WorkflowStepCompleter.stepFailed(stepId, svcError);
}
}
}