/*
* Copyright (c) 2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.block;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.computecontroller.impl.HostRescanDeviceController;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.BlockObject;
import com.emc.storageos.db.client.model.DataObject.Flag;
import com.emc.storageos.db.client.model.DiscoveredDataObject;
import com.emc.storageos.db.client.model.DiscoveredSystemObject;
import com.emc.storageos.db.client.model.ExportGroup;
import com.emc.storageos.db.client.model.ExportMask;
import com.emc.storageos.db.client.model.Host;
import com.emc.storageos.db.client.model.Initiator;
import com.emc.storageos.db.client.model.ProtectionSystem;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.client.util.NullColumnValueGetter;
import com.emc.storageos.db.client.util.StringSetUtil;
import com.emc.storageos.exceptions.DeviceControllerException;
import com.emc.storageos.locking.LockTimeoutValue;
import com.emc.storageos.locking.LockType;
import com.emc.storageos.networkcontroller.impl.NetworkDeviceController;
import com.emc.storageos.networkcontroller.impl.NetworkZoningParam;
import com.emc.storageos.protectioncontroller.ProtectionExportController;
import com.emc.storageos.protectioncontroller.impl.recoverpoint.RPDeviceExportController;
import com.emc.storageos.util.ExportUtils;
import com.emc.storageos.volumecontroller.ControllerException;
import com.emc.storageos.volumecontroller.impl.ControllerLockingUtil;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ExportTaskCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ZoningAddPathsCompleter;
import com.emc.storageos.volumecontroller.impl.block.taskcompleter.ZoningRemovePathsCompleter;
import com.emc.storageos.volumecontroller.impl.utils.ExportMaskUtils;
import com.emc.storageos.workflow.Workflow;
import com.emc.storageos.workflow.WorkflowException;
import com.emc.storageos.workflow.WorkflowRestartedException;
import com.emc.storageos.workflow.WorkflowService;
import com.google.common.base.Joiner;
public class ExportWorkflowUtils {
private static final Logger _log =
LoggerFactory.getLogger(ExportWorkflowUtils.class);
private DbClient _dbClient;
private ExportWorkflowEntryPoints _exportWfEntryPoints;
private WorkflowService _workflowSvc;
private NetworkDeviceController networkDeviceController;
private HostRescanDeviceController hostRescanDeviceController;
public void setDbClient(DbClient dbc) {
_dbClient = dbc;
}
public WorkflowService getWorkflowService() {
return _workflowSvc;
}
public void setWorkflowService(WorkflowService workflowService) {
_workflowSvc = workflowService;
}
public void setExportWorkflowEntryPoints(ExportWorkflowEntryPoints entryPoints) {
_exportWfEntryPoints = entryPoints;
}
public void setNetworkDeviceController(NetworkDeviceController networkDeviceController) {
this.networkDeviceController = networkDeviceController;
}
public HostRescanDeviceController getHostRescanDeviceController() {
return hostRescanDeviceController;
}
public void setHostRescanDeviceController(HostRescanDeviceController hostRescanDeviceController) {
this.hostRescanDeviceController = hostRescanDeviceController;
}
public String generateExportGroupCreateWorkflow(Workflow workflow, String wfGroupId,
String waitFor, URI storage,
URI export,
Map<URI, Integer> volumeMap,
List<URI> initiatorURIs)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
// Filter the addedInitiators for non VPLEX system by the Export Group varray.
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, export);
initiatorURIs = ExportUtils.filterNonVplexInitiatorsByExportGroupVarray(
exportGroup, initiatorURIs, storage, _dbClient);
Workflow.Method method =
ExportWorkflowEntryPoints.exportGroupCreateMethod(storage, export,
volumeMap, initiatorURIs);
Workflow.Method rollback =
ExportWorkflowEntryPoints.exportGroupDeleteMethod(storage, export);
return newWorkflowStep(workflow, wfGroupId,
String.format("Creating export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, rollback, waitFor, null);
}
/**
* Creates the workflow for one export mask (storage system) for an update export
* group call. It creates a single step in the main workflow that wraps a workflow
* with necessary steps to:
* <ol>
* <li>add block objects (volumes/snapshots)</li>
* <li>remove volumes</li>
* <li>add initiators</li>
* <li>remove initiators</li>
* </ol>
* The steps are created based on the diff between the current and the requested for
* the storage system export mask
*
* @param workflow the main workflow
* @param wfGroupId the workflow group Id, if any
* @param waitFor the id of a step on which this workflow has to wait, if any
* @param exportGroupUri the export group being updated
* @param exportMask the export mask for the storage system
* @param addedBlockObjects the map of block objects to be added
* @param removedBlockObjects the map of block objects to be removed
* @param addedInitiators the new list of initiators to be added
* @param removedInitiators the new list of initiators to be removed
* @param blockStorageControllerUri the block storage controller. This will always
* be used for adding/removing initiators as we
* do not want a protection controller doing this.
* @param workFlowList holds workflow and sub workflow instances to release all locks during failure
* @param storageUri the storage controller used to perform the export update.
* This can be either a block storage controller or protection
* controller.
* @return the id of the wrapper step that was added to main workflow
* @throws IOException
* @throws WorkflowException
* @throws WorkflowRestartedException
*/
public String generateExportGroupUpdateWorkflow(Workflow workflow, String wfGroupId,
String waitFor,
URI exportGroupUri,
ExportMask exportMask,
Map<URI, Integer> addedBlockObjects,
Map<URI, Integer> removedBlockObjects,
List<URI> addedInitiators, List<URI> removedInitiators, URI blockStorageControllerUri, List<Workflow> workflowList)
throws IOException, WorkflowException, WorkflowRestartedException {
// Filter the addedInitiators for non VPLEX system by the Export Group varray.
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, exportGroupUri);
addedInitiators = ExportUtils.filterNonVplexInitiatorsByExportGroupVarray(
exportGroup, addedInitiators, blockStorageControllerUri, _dbClient);
if (allCollectionsAreEmpty(addedBlockObjects, removedBlockObjects, addedInitiators, removedInitiators)) {
_log.info(String.format("There is no export updated required for %s", blockStorageControllerUri.toString()));
return null;
}
// We would rather the task be the child stepID of the parent workflow's stepID.
// This helps us to preserve parent/child relationships.
String exportGroupUpdateStepId = workflow.createStepId();
Workflow storageWorkflow = newWorkflow("storageSystemExportGroupUpdate", false, exportGroupUpdateStepId);
workflowList.add(storageWorkflow);
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, blockStorageControllerUri);
String stepId = null;
// We willLock the host/storage system duples necessary for the workflows.
// There are two possibilities about locking. Here we just generate the lockKeys.
List<URI> lockedInitiatorURIs = new ArrayList<URI>();
lockedInitiatorURIs.addAll(addedInitiators);
lockedInitiatorURIs.addAll(StringSetUtil.stringSetToUriList(exportGroup.getInitiators()));
List<String> lockKeys = ControllerLockingUtil
.getHostStorageLockKeys(_dbClient,
ExportGroup.ExportGroupType.valueOf(exportGroup.getType()),
lockedInitiatorURIs, blockStorageControllerUri);
// for initiators do add first and then remove to avoid having
// the export mask getting deleted when all existing initiators
// are getting replaced.
if (addedInitiators != null && !addedInitiators.isEmpty()) {
stepId = generateExportGroupAddInitiators(storageWorkflow, null,
stepId, exportGroupUri, blockStorageControllerUri, addedInitiators);
}
if (removedInitiators != null && !removedInitiators.isEmpty()) {
stepId = generateExportGroupRemoveInitiators(storageWorkflow, null,
stepId, exportGroupUri, blockStorageControllerUri, removedInitiators);
}
// for volumes we must do remove first in case the same lun id
// will be used by one of the added volumes. This may result in
// the export mask getting deleted and then created. If this
// ends being a problem, we would need to tackle this issue
if (removedBlockObjects != null && !removedBlockObjects.isEmpty()) {
Map<URI, Integer> objectsToRemove = new HashMap<URI, Integer>(removedBlockObjects);
ProtectionExportController protectionExportController = getProtectionExportController();
stepId = protectionExportController.addStepsForExportGroupRemoveVolumes(storageWorkflow, null, stepId, exportGroupUri,
objectsToRemove, blockStorageControllerUri);
if (!objectsToRemove.isEmpty()) {
// Unexport the remaining block objects.
_log.info(String.format("Generating exportGroupRemoveVolumes step for objects %s associated with storage system [%s]",
objectsToRemove, blockStorageControllerUri));
List<URI> objectsToRemoveList = new ArrayList<URI>(objectsToRemove.keySet());
stepId = generateExportGroupRemoveVolumes(storageWorkflow,
null, stepId, blockStorageControllerUri, exportGroupUri,
objectsToRemoveList);
}
}
if (addedBlockObjects != null && !addedBlockObjects.isEmpty()) {
Map<URI, Integer> objectsToAdd = new HashMap<URI, Integer>(addedBlockObjects);
ProtectionExportController protectionExportController = getProtectionExportController();
stepId = protectionExportController.addStepsForExportGroupAddVolumes(storageWorkflow, null, stepId, exportGroupUri,
objectsToAdd, blockStorageControllerUri);
if (!objectsToAdd.isEmpty()) {
// Export the remaining block objects.
_log.info(String.format("Generating exportGroupAddVolumes step for objects %s associated with storage system [%s]",
objectsToAdd.keySet(), blockStorageControllerUri));
stepId = generateExportGroupAddVolumes(storageWorkflow, null,
stepId, blockStorageControllerUri, exportGroupUri, objectsToAdd);
}
}
boolean addObject = (addedInitiators != null && !addedInitiators.isEmpty())
|| (addedBlockObjects != null && !addedBlockObjects.isEmpty());
if (exportMask == null && addObject) { // recreate export mask only for add initiator/volume
if (addedInitiators == null) {
addedInitiators = new ArrayList<URI>();
}
if (addedInitiators.isEmpty()) {
addedInitiators.addAll(getInitiators(exportGroup));
}
// Add block volumes already in the export group
if (exportGroup.getVolumes() != null) {
for (String key : exportGroup.getVolumes().keySet()) {
BlockObject bobject = BlockObject.fetch(_dbClient, URI.create(key));
if (bobject.getStorageController().equals(blockStorageControllerUri)) {
addedBlockObjects.put(URI.create(key), Integer.valueOf(exportGroup.getVolumes().get(key)));
}
}
}
// Acquire locks for the parent workflow.
boolean acquiredLocks = getWorkflowService().acquireWorkflowLocks(
workflow, lockKeys, LockTimeoutValue.get(LockType.EXPORT_GROUP_OPS));
if (!acquiredLocks) {
throw DeviceControllerException.exceptions.failedToAcquireLock(lockKeys.toString(),
"ExportMaskUpdate: " + exportGroup.getLabel());
}
Map<URI, Integer> objectsToAdd = new HashMap<URI, Integer>(addedBlockObjects);
ProtectionExportController protectionController = getProtectionExportController();
waitFor = protectionController.addStepsForExportGroupCreate(workflow, wfGroupId, waitFor, exportGroupUri, objectsToAdd,
blockStorageControllerUri,
addedInitiators);
if (!objectsToAdd.isEmpty()) {
// There are no export BlockObjects tied to the current storage system that have an associated protection
// system. We can just create a step to call the block controller directly for export group create.
_log.info(String.format(
"Generating exportGroupCreate steps for objects %s associated with storage system [%s]",
objectsToAdd, blockStorageControllerUri));
// Add the new block objects to the existing ones and send all down
waitFor = generateExportGroupCreateWorkflow(workflow, wfGroupId, waitFor,
blockStorageControllerUri, exportGroupUri, addedBlockObjects, addedInitiators);
}
return waitFor;
}
try {
// Acquire locks for the storageWorkflow which is started just below.
boolean acquiredLocks = getWorkflowService().acquireWorkflowLocks(
storageWorkflow, lockKeys, LockTimeoutValue.get(LockType.EXPORT_GROUP_OPS));
if (!acquiredLocks) {
throw DeviceControllerException.exceptions.failedToAcquireLock(lockKeys.toString(),
"ExportMaskUpdate: " + exportMask.getMaskName());
}
// There will not be a rollback step for the overall update instead
// the code allows the user to retry update as needed.
Workflow.Method method =
ExportWorkflowEntryPoints.exportGroupUpdateMethod(blockStorageControllerUri, exportGroupUri, storageWorkflow);
return newWorkflowStep(workflow, wfGroupId,
String.format("Updating export (%s) on storage array %s",
exportGroupUri, storageSystem.getNativeGuid()),
storageSystem, method, null, waitFor, exportGroupUpdateStepId);
} catch (Exception ex) {
getWorkflowService().releaseAllWorkflowLocks(storageWorkflow);
throw ex;
}
}
public String generateExportGroupDeleteWorkflow(Workflow workflow, String wfGroupId,
String waitFor, URI storage, URI export)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
Workflow.Method method =
ExportWorkflowEntryPoints.exportGroupDeleteMethod(storage, export);
return newWorkflowStep(workflow, wfGroupId,
String.format("Deleting export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, null, waitFor, null);
}
public String generateExportGroupAddVolumes(Workflow workflow, String wfGroupId,
String waitFor, URI storage,
URI export, Map<URI, Integer> volumeMap)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
Workflow.Method rollback = rollbackMethodNullMethod();
if (export != null) {
ExportGroup exportGroup = _dbClient.queryObject(ExportGroup.class, export);
// Only add the rollback method to remove volumes from export in cases where we are dealing
// with RP+VPlex. This allows the RP orchestration sub-workflow to rollback correctly.
if (DiscoveredDataObject.Type.vplex.name().equals(storageSystem.getSystemType())
&& exportGroup.checkInternalFlags(Flag.RECOVERPOINT)) {
List<URI> volumeList = new ArrayList<URI>();
volumeList.addAll(volumeMap.keySet());
rollback = ExportWorkflowEntryPoints.exportRemoveVolumesMethod(storage, export, volumeList);
}
}
Workflow.Method method = ExportWorkflowEntryPoints.exportAddVolumesMethod(storage, export, volumeMap);
return newWorkflowStep(workflow, wfGroupId,
String.format("Adding volumes to export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, rollback, waitFor, null);
}
public String generateExportGroupRemoveVolumes(Workflow workflow, String wfGroupId,
String waitFor, URI storage,
URI export, List<URI> volumes)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
Workflow.Method method =
ExportWorkflowEntryPoints.exportRemoveVolumesMethod(storage, export,
volumes);
return newWorkflowStep(workflow, wfGroupId,
String.format("Removing volumes from export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, null, waitFor, null);
}
public String generateExportGroupRemoveInitiators(Workflow workflow,
String wfGroupId, String waitFor,
URI export, URI storage,
List<URI> initiatorURIs)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
Workflow.Method method =
ExportWorkflowEntryPoints.exportRemoveInitiatorsMethod(storage, export,
initiatorURIs);
return newWorkflowStep(workflow, wfGroupId,
String.format("Removing initiators from export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, null, waitFor, null);
}
public String generateExportGroupAddInitiators(Workflow workflow, String wfGroupId,
String waitFor, URI export, URI storage,
List<URI> initiatorURIs)
throws WorkflowException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storage);
Workflow.Method method =
ExportWorkflowEntryPoints.exportAddInitiatorsMethod(storage, export,
initiatorURIs);
return newWorkflowStep(workflow, wfGroupId,
String.format("Adding initiators from export (%s) on storage array %s",
export, storageSystem.getNativeGuid()),
storageSystem, method, rollbackMethodNullMethod(), waitFor, null);
}
/**
* Generates a Workflow Step to change the path parameters of a Volume in a specific Export Group.
*
* @param workflow
* @param wfGroupId - Workflow group for the Step
* @param waitFor - Wait on this step/group to complete in the workflow before execution
* @param storageURI - Storage system containing the volume
* @param exportGroupURI - Export group that has exported the volume
* @param volumeURI - Volume we are changing the path parameters for
* @return stepId for the generated workflow Step
* @throws ControllerException
*/
public String generateExportChangePathParams(Workflow workflow, String wfGroupId, String waitFor,
URI storageURI, URI exportGroupURI, URI volumeURI)
throws ControllerException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storageURI);
BlockObject volume = BlockObject.fetch(_dbClient, volumeURI);
Workflow.Method method = ExportWorkflowEntryPoints.exportGroupChangePathParamsMethod(
storageURI, exportGroupURI, volumeURI);
return newWorkflowStep(workflow, wfGroupId,
String.format("Updated Export PathParams on storage array %s (%s, args) for volume %s (%s)",
storageSystem.getNativeGuid(), storageURI, volume.getLabel(), volumeURI),
storageSystem, method, null, waitFor, null);
}
/**
* Generate a Workflow Step to change the Auto-tiering policy for the volumes
* in a specific Export Mask.
*
* @param workflow the workflow
* @param wfGroupId - Workflow group for the Step
* @param waitFor - Wait on this step/group to complete in the workflow before execution
* @param storageURI the storage system uri
* @param exportMaskURI the export mask uri
* @param volumeURIs the volume uris
* @param exportGroupURI
* @param newVpoolURI the new vpool uri
* @return stepId for the generated workflow Step
* @throws ControllerException the controller exception
*/
public String generateExportChangePolicyAndLimits(Workflow workflow,
String wfGroupId, String waitFor, URI storageURI,
URI exportMaskURI, URI exportGroupURI, List<URI> volumeURIs,
URI newVpoolURI, URI oldVpoolURI) throws ControllerException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storageURI);
Workflow.Method method = ExportWorkflowEntryPoints.exportChangePolicyAndLimitsMethod(
storageURI, exportMaskURI, exportGroupURI, volumeURIs, newVpoolURI, false);
// same method is called as a roll back step by passing old vPool.
// boolean is passed to differentiate it.
Workflow.Method rollback = ExportWorkflowEntryPoints.exportChangePolicyAndLimitsMethod(
storageURI, exportMaskURI, exportGroupURI, volumeURIs, oldVpoolURI, true);
return newWorkflowStep(workflow, wfGroupId,
String.format("Updating Auto-tiering Policy on storage array %s (%s, args) for volumes %s",
storageSystem.getNativeGuid(), storageURI, Joiner.on("\t").join(volumeURIs)),
storageSystem, method, rollback, waitFor, null);
}
/**
* Generate a Workflow Step to change the Auto-tiering policy for the volumes
* in a specific Export Mask.
*
* @param workflow the workflow
* @param wfGroupId - Workflow group for the Step
* @param waitFor - Wait on this step/group to complete in the workflow before execution
* @param storageURI the storage system uri
* @param volumeURIs the volume uris
* @param newVpoolURI the new vpool uri
* @param oldVpoolURI the old vpool uri *
* @return stepId for the generated workflow Step
* @throws ControllerException the controller exception
*/
public String generateChangeAutoTieringPolicy(Workflow workflow,
String wfGroupId, String waitFor, URI storageURI,
List<URI> volumeURIs, URI newVpoolURI, URI oldVpoolURI) throws ControllerException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storageURI);
Workflow.Method method = ExportWorkflowEntryPoints.changeAutoTieringPolicyMethod(
storageURI, volumeURIs, newVpoolURI, false);
// same method is called as a roll back step by passing old vPool.
// boolean is passed to differentiate it.
Workflow.Method rollback = ExportWorkflowEntryPoints.changeAutoTieringPolicyMethod(
storageURI, volumeURIs, oldVpoolURI, true);
return newWorkflowStep(workflow, wfGroupId,
String.format("Updating Auto-tiering Policy on storage array %s (%s, args) for volumes %s",
storageSystem.getNativeGuid(), storageURI, Joiner.on("\t").join(volumeURIs)),
storageSystem, method, rollback, waitFor, null);
}
/**
* Wrapper for WorkflowService.getNewWorkflow
*
* @param name - Name of workflow
* @param rollback - Boolean indicating whether rollback should be invoked or not.
* @param opId - Operation id
* @return Workflow object
* @throws com.emc.storageos.workflow.WorkflowRestartedException
*/
public Workflow newWorkflow(String name, boolean rollback, String opId)
throws WorkflowRestartedException {
return _workflowSvc.getNewWorkflow(_exportWfEntryPoints, name, rollback, opId);
}
/**
* Wrapper for WorkflowService.createStep. The method expects that the method and
* rollback (if specified) are in the ExportWorkflowEntryPoints class.
*
* @param workflow - Workflow in which to add the step
* @param groupId - String pointing to the group id of the step
* @param description - String description of the step
* @param storageSystem - StorageSystem object to which operation applies
* @param method - Step method to be called
* @param rollback - Step rollback method to be call (if any)
* @param waitFor - String of groupId of step to wait for
*
* @return String the stepId generated for the step.
* @throws WorkflowException
*/
public String newWorkflowStep(Workflow workflow,
String groupId, String description,
DiscoveredSystemObject storageSystem,
Workflow.Method method, Workflow.Method rollback,
String waitFor)
throws WorkflowException {
if (groupId == null) {
groupId = method.getClass().getSimpleName();
}
return workflow.createStep(groupId, description,
waitFor, storageSystem.getId(),
storageSystem.getSystemType(), ExportWorkflowEntryPoints.class, method,
rollback, null);
}
/**
* Wrapper for WorkflowService.createStep. The method expects that the method and
* rollback (if specified) are in the ExportWorkflowEntryPoints class.
*
* @param workflow
* - Workflow in which to add the step
* @param groupId
* - String pointing to the group id of the step
* @param description
* - String description of the step
* @param storageSystem
* - StorageSystem object to which operation applies
* @param method
* - Step method to be called
* @param rollback
* - Step rollback method to be call (if any)
* @param waitFor
* - String of groupId of step to wait for
* @param stepId
* - step ID
* @return String the stepId generated for the step.
* @throws WorkflowException
*/
public String newWorkflowStep(Workflow workflow,
String groupId, String description,
DiscoveredSystemObject storageSystem,
Workflow.Method method, Workflow.Method rollback,
String waitFor, String stepId)
throws WorkflowException {
if (groupId == null) {
groupId = method.getClass().getSimpleName();
}
return workflow.createStep(groupId, description,
waitFor, storageSystem.getId(),
storageSystem.getSystemType(), ExportWorkflowEntryPoints.class, method,
rollback, stepId);
}
/**
* Wrapper for WorkflowService.createStep. The method expects that the method and
* rollback (if specified) are in the ExportWorkflowEntryPoints class.
*
* @param workflow - Workflow in which to add the step
* @param groupId - String pointing to the group id of the step
* @param description - String description of the step
* @param storageSystemURI - StorageSystem URI hod / rollback method
* @param methodClass -- the Class containing the method / rollback method
* @param method - Step method to be called
* @param rollback - Step rollback method to be call (if any)
* @param waitFor - String of groupId of step to wait for
* @param setSuspend - Sets the suspend flag on the step
* @param suspendMessage -- String message that will be displayed on suspend
*
* @return String the stepId generated for the step.
* @throws WorkflowException
*/
public String newWorkflowStep(Workflow workflow,
String groupId, String description,
URI storageSystemURI,
Class methodClass,
Workflow.Method method, Workflow.Method rollback,
String waitFor, boolean setSuspend, String suspendMessage)
throws WorkflowException {
if (groupId == null) {
groupId = method.getClass().getSimpleName();
}
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, storageSystemURI);
String stepId = workflow.createStep(groupId, description,
waitFor, storageSystem.getId(),
storageSystem.getSystemType(),
methodClass, method, rollback, setSuspend, null);
workflow.setSuspendedStepMessage(stepId, suspendMessage);
return stepId;
}
/**
* Returns a DiscoveredDataObject which can either point to a StorageSystem or
* ProtectionSystem object.
*
* @param dbClient - DbClient object
* @param storageURI - URI pointing to storage array object
* @return DiscoveredDataObject
*/
public static DiscoveredSystemObject getStorageSystem(DbClient dbClient,
URI storageURI) {
DiscoveredSystemObject ddo;
if (URIUtil.isType(storageURI, ProtectionSystem.class)) {
ddo = dbClient.queryObject(ProtectionSystem.class, storageURI);
} else {
ddo = dbClient.queryObject(StorageSystem.class, storageURI);
}
return ddo;
}
/**
* Returns a list of Initiator URIs in the ExportGroup
*
* @param exportGroup
* @return
*/
private List<URI> getInitiators(ExportGroup exportGroup) {
List<URI> initiatorURIs = new ArrayList<URI>();
if (exportGroup.getInitiators() != null) {
for (String initiator : exportGroup.getInitiators()) {
try {
URI initiatorURI = new URI(initiator);
initiatorURIs.add(initiatorURI);
} catch (URISyntaxException ex) {
_log.error("Bad URI syntax: " + initiator);
}
}
}
return initiatorURIs;
}
private boolean allCollectionsAreEmpty(Map<URI, Integer> addedBlockObjects, Map<URI, Integer> removedBlockObjects,
List<URI> addedInitiators, List<URI> removedInitiators) {
return (addedBlockObjects != null && addedBlockObjects.isEmpty() &&
removedBlockObjects != null && removedBlockObjects.isEmpty() &&
addedInitiators != null && addedInitiators.isEmpty() &&
removedInitiators != null && removedInitiators.isEmpty());
}
/**
* 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
*/
private Workflow.Method rollbackMethodNullMethod() {
return new Workflow.Method("rollbackMethodNull");
}
/**
* Generates a Workflow Step to add paths in a storage system for a specific Export Mask.
*
* @param workflow - Workflow in which to add the step
* @param wfGroupId - String pointing to the group id of the step
* @param waitFor - Wait on this step/group to complete in the workflow before execution
* @param storageURI - Storage system URI
* @param exportGroupURI - Export group URI
* @param varray - URI of virtual array
* @param exportMask -- The Export Mask
* @param addedPaths - Paths going to be added or retained
* @param removedPath - Paths going to be removed
* @return - Step id
* @throws ControllerException
*/
public String generateExportAddPathsWorkflow(Workflow workflow, String wfGroupId, String waitFor,
URI storageURI, URI exportGroupURI, URI varray, ExportMask exportMask, Map<URI, List<URI>> adjustedPaths,
Map<URI, List<URI>> removedPaths) throws ControllerException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storageURI);
Workflow.Method method = ExportWorkflowEntryPoints.exportAddPathsMethod(
storageURI, exportGroupURI, varray, exportMask.getId(), adjustedPaths, removedPaths);
String stepDescription = String.format("Export add paths mask %s hosts %s", exportMask.getMaskName(),
ExportMaskUtils.getHostNamesInMask(exportMask, _dbClient));
return newWorkflowStep(workflow, wfGroupId, stepDescription,
storageSystem, method, null, waitFor);
}
/**
* Generate workflow step for remove path masking in a storage system for a specific export mask.
*
* @param workflow
* @param wfGroupId
* @param waitFor
* @param storageURI
* @param exportGroupURI
* @param exportMask
* @param adjustedpaths
* @param removePaths
* @param isPending
* @param suspendMessage
* @return
* @throws ControllerException
*/
public String generateExportRemovePathsWorkflow(Workflow workflow, String wfGroupId, String waitFor,
URI storageURI, URI exportGroupURI, URI varray, ExportMask exportMask, Map<URI, List<URI>> adjustedPaths,
Map<URI, List<URI>> removePaths)
throws ControllerException {
DiscoveredSystemObject storageSystem = getStorageSystem(_dbClient, storageURI);
Workflow.Method method = ExportWorkflowEntryPoints.exportRemovePathsMethod(
storageURI, exportGroupURI, varray, exportMask.getId(), adjustedPaths, removePaths);
String stepDescription = String.format("Export remove paths mask %s hosts %s", exportMask.getMaskName(),
ExportMaskUtils.getHostNamesInMask(exportMask, _dbClient));
return newWorkflowStep(workflow, wfGroupId, stepDescription, storageSystem, method, null, waitFor);
}
/**
* Generate workflow step for add path zoning
*
* @param workflow -- workflow step to be added to
* @param wfGroupId -- worflow group id
* @param storageURI -- Storage System URI
* @param exportGroupURI -- Export Group URI
* @param adjustedPaths - The paths going to be added and/or retained
* @param waitFor -- wait for this previous step
* @return stepId that was generated
* @throws ControllerException
*/
public String generateZoningAddPathsWorkflow(Workflow workflow, String wfGroupId, URI systemURI, URI exportGroupURI,
Map<URI, Map<URI, List<URI>>> exportMaskNewPathsMap, Map<URI, List<URI>>newPaths, String waitFor) throws ControllerException {
String zoningStep = workflow.createStepId();
ExportTaskCompleter taskCompleter = new ZoningAddPathsCompleter(exportGroupURI, zoningStep, exportMaskNewPathsMap);
List<URI> maskURIs = new ArrayList<URI> (exportMaskNewPathsMap.keySet());
Workflow.Method zoningExecuteMethod = networkDeviceController.zoneExportAddPathsMethod(systemURI, exportGroupURI,
maskURIs, newPaths, taskCompleter);
zoningStep = workflow.createStep(
wfGroupId,
"Zoning add paths subtask: " + exportGroupURI,
waitFor, NullColumnValueGetter.getNullURI(),
"network-system", networkDeviceController.getClass(),
zoningExecuteMethod, null, zoningStep);
return zoningStep;
}
/**
* Generate workflow step for remove paths zoning
*
* @param workflow - workflow
* @param wfGroupId - workflow group id
* @param storageURI - system URI
* @param exportGroupURI - export group URI
* @param maskAjustedPathMap - adjusted paths per mask
* @param maskRemovePaths - remove paths per mask
* @param waitFor - wait for step
* @return - generated step id
* @throws ControllerException
*/
public String generateZoningRemovePathsWorkflow(Workflow workflow, String wfGroupId, URI storageURI, URI exportGroupURI,
Map<URI, Map<URI, List<URI>>> maskAdjustedPathMap, Map<URI, Map<URI, List<URI>>> maskRemovePaths, String waitFor)
throws ControllerException {
String zoningStep = workflow.createStepId();
ZoningRemovePathsCompleter taskCompleter = new ZoningRemovePathsCompleter(exportGroupURI, zoningStep, maskAdjustedPathMap);
List<NetworkZoningParam> zoningParams = NetworkZoningParam.
convertPathsToNetworkZoningParam(exportGroupURI, maskRemovePaths, _dbClient);
Workflow.Method zoningExecuteMethod = networkDeviceController
.zoneExportRemovePathsMethod(zoningParams, taskCompleter);
zoningStep = workflow.createStep(
wfGroupId,
"Zoning subtask for remvoe paths: " + exportGroupURI,
waitFor, NullColumnValueGetter.getNullURI(),
"network-system", networkDeviceController.getClass(),
zoningExecuteMethod, null, zoningStep);
return zoningStep;
}
/**
* Generate workflow steps for rescanning hosts after a change in paths.
* @param workflow -- Workflow being generated
* @param zoningMap -- zoning map with the cahnges (used for initiators)
* @param waitFor -- previous step id to work on
* @return -- Step group or previous step to wait for
*/
public String generateHostRescanWorkflowSteps(Workflow workflow, Map<URI, List<URI>> zoningMap, String waitFor) {
// Determine the set of hosts that neet to be rescanned.
Set<URI> hostURIs = new HashSet<URI>();
for (URI initiatorURI : zoningMap.keySet()) {
Initiator initiator = _dbClient.queryObject(Initiator.class, initiatorURI);
if (!(initiator == null || initiator.getInactive() || initiator.getHost() == null)) {
hostURIs.add(initiator.getHost());
}
}
// Loop through each Host. Generate a step to rescan the host if it is not type Other.
String stepGroup = "hostRescan" + (waitFor != null ? waitFor : "");
boolean queuedStep = false;
for (URI hostURI : hostURIs) {
Host host = _dbClient.queryObject(Host.class, hostURI);
if (host == null || host.getInactive()) {
_log.info(String.format("Host not found or inactive: %s", hostURI));
continue;
}
if (!host.getDiscoverable()) {
_log.info(String.format("Host %s is not discoverable, so cannot rescan", host.getHostName()));
continue;
}
Workflow.Method rescan = hostRescanDeviceController.rescanHostStorageMethod(hostURI);
Workflow.Method nullMethod = hostRescanDeviceController.nullWorkflowStepMethod();
workflow.createStep(stepGroup,
String.format("Rescan Host Storage: %s", host.getHostName()),
waitFor, NullColumnValueGetter.getNullURI(),
"host-rescan", hostRescanDeviceController.getClass(),
rescan, nullMethod, null);
queuedStep = true;
}
return (queuedStep ? stepGroup : waitFor);
}
/**
* Gets an instance of ProtectionExportController.
* <p>
* NOTE: This method currently only returns an instance of RPDeviceExportController. In the future, this will need to return other
* protection export controllers if support is added.
*
* @return the ProtectionExportController
*/
private ProtectionExportController getProtectionExportController() {
return new RPDeviceExportController(_dbClient, this);
}
}