/* * Copyright (c) 2008-2011 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller; import java.io.Serializable; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import com.google.common.base.Objects; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.model.BlockConsistencyGroup; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Operation.Status; import com.emc.storageos.db.client.model.Task; import com.emc.storageos.db.client.model.util.TaskUtils; import com.emc.storageos.db.client.util.NullColumnValueGetter; import com.emc.storageos.exceptions.DeviceControllerException; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.workflow.Workflow; import com.emc.storageos.workflow.WorkflowException; import com.emc.storageos.workflow.WorkflowService; import com.emc.storageos.workflow.WorkflowStepCompleter; /** * Initiator param for block export operations. */ @XmlRootElement public abstract class TaskCompleter implements Serializable { private static final Logger _logger = LoggerFactory.getLogger(TaskCompleter.class); private static final long serialVersionUID = -1520175533121538383L; @XmlElement private Class _clazz; @XmlElement protected String _opId; @XmlElement private final List<URI> _ids = new ArrayList<URI>(); @XmlElement private final Set<URI> _consistencyGroupIds = new HashSet<>(); @XmlElement private final Set<URI> _volumeGroupIds = new HashSet<>(); // Whether to notify workflow when task is complete @XmlTransient private boolean notifyWorkflow = true; @XmlTransient private boolean asynchronous; @XmlTransient private boolean completed; @XmlTransient private boolean rollingBack; /** * JAXB requirement */ public TaskCompleter() { } public TaskCompleter(Class clazz, URI id, String opId) { _clazz = clazz; _ids.add(id); _opId = opId; } public TaskCompleter(Class clazz, List<URI> ids, String opId) { _clazz = clazz; _ids.addAll(ids); _opId = opId; } public TaskCompleter(AsyncTask task) { _clazz = task._clazz; _ids.add(task._id); _opId = task._opId; } public boolean isAsynchronous() { return asynchronous; } public void setAsynchronous(boolean asynchronous) { this.asynchronous = asynchronous; } public boolean isCompleted() { return completed; } public void setCompleted(boolean completed) { this.completed = completed; } public boolean isRollingBack() { return rollingBack; } public void setRollingBack(boolean rollingBack) { this.rollingBack = rollingBack; } public Class getType() { return _clazz; } public List<URI> getIds() { return _ids; } public void addIds(Collection<URI> ids) { if (ids != null) { for (URI uri : ids) { if (!_ids.contains(uri)) { _ids.add(uri); } } } } public URI getId(int index) { if ((index >= 0) && (index < _ids.size())) { return _ids.get(index); } else { return null; } } public URI getId() { return getId(0); } public String getOpId() { return _opId; } public void setOpId(String taskId) { _opId = taskId; } public Set<URI> getConsistencyGroupIds() { return _consistencyGroupIds; } public boolean addConsistencyGroupId(URI consistencyGroupId) { if (consistencyGroupId != null) { return _consistencyGroupIds.add(consistencyGroupId); } return false; } public Set<URI> getVolumeGroupIds() { return _volumeGroupIds; } public boolean addVolumeGroupId(URI volumeGroupId) { if (volumeGroupId != null) { return _volumeGroupIds.add(volumeGroupId); } return false; } public boolean isNotifyWorkflow() { return notifyWorkflow; } public void setNotifyWorkflow(boolean notifyWorkflow) { this.notifyWorkflow = notifyWorkflow; } /** * Update the Operation status of the overall task to "ready" and the current workflow step to "success" (if any) * * @param dbClient * @throws DeviceControllerException * TODO */ public void ready(DbClient dbClient) throws DeviceControllerException { ready(dbClient, (ControllerLockingService) null); } public void ready(DbClient dbClient, ControllerLockingService locker) throws DeviceControllerException { try { if (locker == null) { complete(dbClient, Status.ready, (ServiceCoded) null); } else { complete(dbClient, locker, Status.ready, (ServiceCoded) null); } } finally { clearAllTasks(dbClient, Status.ready, (ServiceCoded) null); setCompleted(true); } } /** * Update the Operation status of the task to "error" and the current workflow step to "error" too (if any) * * @param dbClient Database client * @param serviceCoded Service code * @throws DeviceControllerException */ public void error(DbClient dbClient, ServiceCoded serviceCoded) throws DeviceControllerException { error(dbClient, (ControllerLockingService) null, serviceCoded); setCompleted(true); } public void error(DbClient dbClient, ControllerLockingService locker, ServiceCoded serviceCoded) throws DeviceControllerException { try { if (locker == null) { complete(dbClient, Status.error, serviceCoded != null ? serviceCoded : DeviceControllerException.errors.unforeseen()); } else { complete(dbClient, locker, Status.error, serviceCoded != null ? serviceCoded : DeviceControllerException.errors.unforeseen()); } } finally { clearAllTasks(dbClient, Status.error, serviceCoded); setCompleted(true); } } public void suspendedNoError(DbClient dbClient, ControllerLockingService locker) throws DeviceControllerException { try { complete(dbClient, locker, Status.suspended_no_error, (ServiceCoded) null); } finally { clearAllTasks(dbClient, Status.suspended_no_error, (ServiceCoded) null); } } public void suspendedError(DbClient dbClient, ControllerLockingService locker, ServiceCoded serviceCoded) throws DeviceControllerException { try { complete(dbClient, locker, Status.suspended_error, serviceCoded != null ? serviceCoded : DeviceControllerException.errors.unforeseen()); } finally { clearAllTasks(dbClient, Status.suspended_error, serviceCoded); } } public void statusReady(DbClient dbClient) throws DeviceControllerException { setStatus(dbClient, Status.ready, (ServiceCoded) null, (String) null); } public void statusReady(DbClient dbClient, String message) throws DeviceControllerException { setStatus(dbClient, Status.ready, null, message); } public void statusPending(DbClient dbClient, String message) throws DeviceControllerException { setStatus(dbClient, Status.pending, null, message); } public void statusError(DbClient dbClient, ServiceCoded serviceCoded) throws DeviceControllerException { setStatus(dbClient, Status.error, serviceCoded); } protected void setStatus(DbClient dbClient, Operation.Status status, ServiceCoded coded) throws DeviceControllerException { setStatus(dbClient, status, coded, null); } protected void setStatus(DbClient dbClient, Operation.Status status, ServiceCoded coded, String message) throws DeviceControllerException { setStatus(_clazz, _ids, dbClient, status, coded, message); } protected void setStatus(Class<? extends DataObject> clazz, List<URI> ids, DbClient dbClient, Operation.Status status, ServiceCoded coded, String message) throws DeviceControllerException { switch (status) { case error: for (URI id : ids) { dbClient.error(clazz, id, _opId, coded); } break; case ready: for (URI id : ids) { if (message == null) { dbClient.ready(clazz, id, _opId); } else { dbClient.ready(clazz, id, _opId, message); } } break; case suspended_no_error: for (URI id : ids) { if (message == null) dbClient.suspended_no_error(clazz, id, _opId); else dbClient.suspended_no_error(clazz, id, _opId, message); } break; case suspended_error: for (URI id : ids) { dbClient.suspended_error(clazz, id, _opId, coded); } break; default: if (message != null) { for (URI id : ids) { dbClient.pending(clazz, id, _opId, message); } } } } /** * This method will be called upon the job execution finished * * @param dbClient * @param status * @param coded * @throws DeviceControllerException */ protected abstract void complete(DbClient dbClient, Operation.Status status, ServiceCoded coded) throws DeviceControllerException; /** * This method will be called upon job execution finish with a locking controller. * It is not expected that non-locking controllers will call this version, however we need a base * method so we don't need to ship around TaskLockingCompleters all over the code. * * @param dbClient * @param locker * @param status * @param coded * @throws DeviceControllerException */ protected void complete(DbClient dbClient, ControllerLockingService locker, Operation.Status status, ServiceCoded coded) throws DeviceControllerException { complete(dbClient, status, coded); } /** * Update a Workflow Step State. * * @param state * Workflow.StepState * @param coded * @throws WorkflowException */ protected void updateWorkflowState(Workflow.StepState state, ServiceCoded coded) throws WorkflowException { switch (state) { case SUSPENDED_ERROR: WorkflowStepCompleter.stepSuspendedError(getOpId(), coded); break; case SUSPENDED_NO_ERROR: WorkflowStepCompleter.stepSuspendedNoError(getOpId()); break; case ERROR: WorkflowStepCompleter.stepFailed(getOpId(), coded); break; case EXECUTING: WorkflowStepCompleter.stepExecuting(getOpId()); break; case SUCCESS: default: WorkflowStepCompleter.stepSucceded(getOpId()); } } /** * Update a Workflow Step by using the Operation.Status. * * @param status * Operation.Status * @param coded * @throws WorkflowException */ protected void updateWorkflowStatus(Operation.Status status, ServiceCoded coded) throws WorkflowException { switch (status) { case suspended_no_error: WorkflowStepCompleter.stepSuspendedNoError(getOpId()); break; case suspended_error: WorkflowStepCompleter.stepSuspendedError(getOpId(), coded); break; case error: WorkflowStepCompleter.stepFailed(getOpId(), coded); break; case pending: WorkflowStepCompleter.stepExecuting(getOpId()); break; default: WorkflowStepCompleter.stepSucceded(getOpId()); } } /** * Update the workflow step context * * @param context * context information */ public void updateWorkflowStepContext(Object context) { WorkflowService.getInstance().storeStepData(getOpId(), context); _logger.info("Storing rollback context to op: " + getOpId() + " context: " + context.toString()); } /** * Set the error status of the dataObject referred by the uri * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param uri * [in] - URI of clazz * @param coded * [in] - ServiceCoded containing error message reference */ protected void setErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, URI uri, ServiceCoded coded) { if (!NullColumnValueGetter.isNullURI(uri)) { dbClient.error(clazz, uri, getOpId(), coded); } } /** * Set the error status of the dataObject * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param dObject * [in] - DataObject of clazz * @param coded * [in] - ServiceCoded containing error message reference */ protected void setErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, DataObject dObject, ServiceCoded coded) { if (dObject != null) { dbClient.error(clazz, dObject.getId(), getOpId(), coded); } } /** * Set the ready status of the dataObject referred by the uri * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param uri * [in] - URI of clazz */ protected void setReadyOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, URI uri) { if (!NullColumnValueGetter.isNullURI(uri)) { dbClient.ready(clazz, uri, getOpId()); } } /** * Set the ready status on the dataObject * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param dObject * [in] - DataObject of clazz */ protected void setReadyOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, DataObject dObject) { if (dObject != null) { dbClient.ready(clazz, dObject.getId(), getOpId()); } } /** * Set the suspended error status of the dataObject referred by the uri * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param uri * [in] - URI of clazz */ protected void setSuspendedErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, URI uri, ServiceCoded coded) { if (!NullColumnValueGetter.isNullURI(uri)) { dbClient.suspended_error(clazz, uri, getOpId(), coded); } } /** * Set the suspended error status on the dataObject * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param dObject * [in] - DataObject of clazz */ protected void setSuspendedErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, DataObject dObject, ServiceCoded coded) { if (dObject != null) { dbClient.suspended_error(clazz, dObject.getId(), getOpId(), coded); } } /** * Set the suspended no-error status of the dataObject referred by the uri * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param uri * [in] - URI of clazz */ protected void setSuspendedNoErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, URI uri) { if (!NullColumnValueGetter.isNullURI(uri)) { dbClient.suspended_no_error(clazz, uri, getOpId()); } } /** * Set the suspended no-error status on the dataObject * * @param dbClient * [in] - Database Client * @param clazz * [in] - Class in DataObject hierarchy * @param dObject * [in] - DataObject of clazz */ protected void setSuspendedNoErrorOnDataObject(DbClient dbClient, Class<? extends DataObject> clazz, DataObject dObject) { if (dObject != null) { dbClient.suspended_no_error(clazz, dObject.getId(), getOpId()); } } protected void updateConsistencyGroupTasks(DbClient dbClient, Operation.Status status, ServiceCoded coded) { for (URI consistencyGroupId : getConsistencyGroupIds()) { _logger.info("Updating consistency group task: {}", consistencyGroupId); switch (status) { case error: setErrorOnDataObject(dbClient, BlockConsistencyGroup.class, consistencyGroupId, coded); break; case ready: setReadyOnDataObject(dbClient, BlockConsistencyGroup.class, consistencyGroupId); break; case suspended_error: setSuspendedErrorOnDataObject(dbClient, BlockConsistencyGroup.class, consistencyGroupId, coded); break; case suspended_no_error: setSuspendedNoErrorOnDataObject(dbClient, BlockConsistencyGroup.class, consistencyGroupId); break; } } } /** * clears all tasks by querying all tasks with the task id */ private void clearAllTasks(DbClient dbClient, Operation.Status status, ServiceCoded serviceCoded) { if (_opId != null) { List<Task> tasksForTaskId = TaskUtils.findTasksForRequestId(dbClient, _opId); Map<Class<? extends DataObject>, List<URI>> resourceMap = new HashMap<Class<? extends DataObject>, List<URI>>(); for (Task task : tasksForTaskId) { if (!task.getCompletedFlag()) { URI resourceId = task.getResource().getURI(); Class<? extends DataObject> resourceType = URIUtil.getModelClass(resourceId); if (resourceMap.get(resourceType) == null) { resourceMap.put(resourceType, new ArrayList<URI>()); } resourceMap.get(resourceType).add(resourceId); } } for (Entry<Class<? extends DataObject>, List<URI>> entry : resourceMap.entrySet()) { // if error, ready setStatus(entry.getKey(), entry.getValue(), dbClient, status, serviceCoded != null ? serviceCoded : DeviceControllerException.errors.unforeseen(), null); } } } @Override public String toString() { return Objects.toStringHelper(this) .add("_clazz", _clazz) .add("_opId", _opId) .add("_ids", _ids) .add("_consistencyGroupIds", _consistencyGroupIds) .add("_volumeGroupIds", _volumeGroupIds) .add("notifyWorkflow", notifyWorkflow) .add("asynchronous", asynchronous) .add("completed", completed) .toString(); } }