/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.component.workflow.execution.internal; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.api.CommunicationService; import de.rcenvironment.core.communication.common.LogicalNodeId; import de.rcenvironment.core.component.api.ComponentConstants; import de.rcenvironment.core.component.api.ComponentUtils; import de.rcenvironment.core.component.api.LoopComponentConstants; import de.rcenvironment.core.component.api.LoopComponentConstants.LoopBehaviorInCaseOfFailure; import de.rcenvironment.core.component.execution.api.ComponentExecutionContext; import de.rcenvironment.core.component.execution.api.ComponentExecutionContextBuilder; import de.rcenvironment.core.component.execution.api.ComponentExecutionException; import de.rcenvironment.core.component.execution.api.ComponentExecutionService; import de.rcenvironment.core.component.execution.api.ComponentState; import de.rcenvironment.core.component.execution.api.ConsoleRow; import de.rcenvironment.core.component.execution.api.ConsoleRow.Type; import de.rcenvironment.core.component.execution.api.ConsoleRow.WorkflowLifecyleEventType; import de.rcenvironment.core.component.execution.api.ConsoleRowBuilder; import de.rcenvironment.core.component.execution.api.ConsoleRowUtils; import de.rcenvironment.core.component.execution.api.ExecutionControllerException; import de.rcenvironment.core.component.execution.api.WorkflowGraph; import de.rcenvironment.core.component.execution.api.WorkflowGraphEdge; import de.rcenvironment.core.component.execution.api.WorkflowGraphNode; import de.rcenvironment.core.component.model.configuration.api.ConfigurationDescription; import de.rcenvironment.core.component.model.endpoint.api.EndpointDatumRecipient; import de.rcenvironment.core.component.model.endpoint.api.EndpointDatumRecipientFactory; import de.rcenvironment.core.component.model.endpoint.api.EndpointDescription; import de.rcenvironment.core.component.workflow.api.WorkflowConstants; import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionException; import de.rcenvironment.core.component.workflow.execution.api.WorkflowExecutionUtils; import de.rcenvironment.core.component.workflow.execution.api.WorkflowState; import de.rcenvironment.core.component.workflow.execution.internal.WorkflowExecutionStorageBridge.DataManagementIdsHolder; import de.rcenvironment.core.component.workflow.model.api.Connection; import de.rcenvironment.core.component.workflow.model.api.WorkflowDescription; import de.rcenvironment.core.component.workflow.model.api.WorkflowNode; import de.rcenvironment.core.datamodel.api.FinalWorkflowState; import de.rcenvironment.core.notification.DistributedNotificationService; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.core.utils.common.LogUtils; import de.rcenvironment.core.utils.common.StringUtils; import de.rcenvironment.core.utils.common.rpc.RemoteOperationException; import de.rcenvironment.core.utils.incubator.AbstractFixedTransitionsStateMachine; import de.rcenvironment.core.utils.incubator.AbstractStateMachine; import de.rcenvironment.core.utils.incubator.StateChangeException; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncExceptionListener; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; import de.rcenvironment.toolkit.modules.concurrency.api.CallablesGroup; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Workflow-specific implementation of {@link AbstractStateMachine}. * * @author Doreen Seider * @author Robert Mischke (tweaked notification setup) */ public class WorkflowStateMachine extends AbstractFixedTransitionsStateMachine<WorkflowState, WorkflowStateMachineEvent> implements ComponentStatesChangedEntirelyListener { private static final long WAIT_INTERVAL_TERMINATED_SEC = 60; private static final String CAUSE_WAITING_TIME_ELAPSED_SEC = "; cause: waiting time (%d seconds) elapsed"; private static final String CAUSE_WAITING_TIME_ELAPSED_HRS = "; cause: waiting time (%d hours) elapsed"; private static final Log LOG = LogFactory.getLog(WorkflowStateMachine.class); private static final WorkflowState[][] VALID_WORKFLOW_STATE_TRANSITIONS = new WorkflowState[][] { // standard lifecycle { WorkflowState.INIT, WorkflowState.PREPARING }, { WorkflowState.PREPARING, WorkflowState.STARTING }, { WorkflowState.PREPARING, WorkflowState.FINISHED }, { WorkflowState.STARTING, WorkflowState.RUNNING }, { WorkflowState.RUNNING, WorkflowState.FINISHED }, { WorkflowState.FINISHED, WorkflowState.DISPOSING }, { WorkflowState.DISPOSING, WorkflowState.DISPOSED }, // pause and resume { WorkflowState.RUNNING, WorkflowState.PAUSING }, { WorkflowState.PAUSING, WorkflowState.PAUSED }, { WorkflowState.PAUSING, WorkflowState.FINISHED }, { WorkflowState.PAUSED, WorkflowState.RESUMING }, { WorkflowState.RESUMING, WorkflowState.RUNNING }, // cancel { WorkflowState.RUNNING, WorkflowState.CANCELING }, { WorkflowState.PAUSED, WorkflowState.CANCELING }, { WorkflowState.CANCELING, WorkflowState.CANCELLED }, { WorkflowState.CANCELLED, WorkflowState.DISPOSING }, // failure { WorkflowState.PREPARING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.STARTING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.RUNNING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.PAUSING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.RESUMING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.CANCELING, WorkflowState.CANCELING_AFTER_FAILED }, { WorkflowState.CANCELING, WorkflowState.FAILED }, { WorkflowState.CANCELING_AFTER_FAILED, WorkflowState.FAILED }, { WorkflowState.RUNNING, WorkflowState.CANCELING_AFTER_RESULTS_REJECTED }, { WorkflowState.CANCELING_AFTER_RESULTS_REJECTED, WorkflowState.RESULTS_REJECTED }, { WorkflowState.RESULTS_REJECTED, WorkflowState.DISPOSING }, { WorkflowState.FAILED, WorkflowState.DISPOSING } }; private static CommunicationService communicationService; private static DistributedNotificationService notificationService; private static ComponentExecutionService componentExecutionService; private static WorkflowExecutionStatsService wfExeStatsService; // set visibility to protected for test purposes protected final Map<WorkflowStateMachineEventType, EventProcessor> eventProcessors = new HashMap<>(); private String wfNameAndIdMessagePart; private final AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService(); private WorkflowStateMachineContext wfStateMachineCtx; private WorkflowDescription fullWorkflowDescription; private Map<String, String> executionAuthTokens; private ScheduledFuture<?> heartbeatFuture; private final Map<String, LogicalNodeId> componentNodeIds = Collections .synchronizedMap(new HashMap<String, LogicalNodeId>()); private final Map<String, String> componentInstanceNames = Collections .synchronizedMap(new HashMap<String, String>()); private final CountDownLatch workflowTerminatedLatch = new CountDownLatch(2); private CountDownLatch pausedComonentStateLatch = new CountDownLatch(1); private CountDownLatch resumedComonentStateLatch = new CountDownLatch(1); private final CountDownLatch disposedComponentStateLatch = new CountDownLatch(1); private Future<?> currentTask = null; @Deprecated public WorkflowStateMachine() { super(WorkflowState.INIT, VALID_WORKFLOW_STATE_TRANSITIONS); } public WorkflowStateMachine(WorkflowStateMachineContext wfStateMachineCtx) { super(WorkflowState.INIT, VALID_WORKFLOW_STATE_TRANSITIONS); this.wfStateMachineCtx = wfStateMachineCtx; this.fullWorkflowDescription = wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().clone(); WorkflowExecutionUtils .removeDisabledWorkflowNodesWithoutNotify(wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription()); this.wfNameAndIdMessagePart = StringUtils.format("'%s' (%s)", wfStateMachineCtx.getWorkflowExecutionContext().getInstanceName(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()); initializeEventProcessors(); } // set visibility to protected for test purposes protected void initializeEventProcessors() { eventProcessors.put(WorkflowStateMachineEventType.START_REQUESTED, new StartRequestedEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.PREPARE_ATTEMPT_SUCCESSFUL, new PrepareAttemptSuccessfulEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.START_ATTEMPT_SUCCESSFUL, new StartAttemptSuccessfulEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.PAUSE_REQUESTED, new PauseRequestedEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.PAUSE_ATTEMPT_SUCCESSFUL, new PauseAttemptSuccessfulEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.RESUME_REQUESTED, new ResumeRequestedEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.RESUME_ATTEMPT_SUCCESSFUL, new ResumeAttemptSuccessfulEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_REQUESTED, new CancelRequestedEventProcessor(WorkflowState.CANCELING)); CancelRequestedEventProcessor cancelRequestedEventProcessor = new CancelRequestedEventProcessor(WorkflowState.CANCELING_AFTER_FAILED); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_AFTER_COMPONENT_LOST_REQUESTED, cancelRequestedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_AFTER_FAILED_REQUESTED, cancelRequestedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_AFTER_RESULTS_REJECTED_REQUESTED, new CancelRequestedEventProcessor(WorkflowState.CANCELING_AFTER_RESULTS_REJECTED)); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_ATTEMPT_SUCCESSFUL, new CancelAttemptSuccessufulEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.DISPOSE_REQUESTED, new DisposeRequestedEventProcessor()); DisposeAttemptSuccessfulOrFailedEventProcessor disposeAttemptSuccessfulOrFailedEventProcessor = new DisposeAttemptSuccessfulOrFailedEventProcessor(); eventProcessors.put(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_SUCCESSFUL, disposeAttemptSuccessfulOrFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_FAILED, disposeAttemptSuccessfulOrFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.ON_COMPONENTS_FINISHED, new OnComponentsFinishedEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.FINISHED, new FinishedEventProcessor()); PrepareStartPauseResumeFinishTimelineAttemptFailedEventProcessor variousAttemptsFailedEventProcessor = new PrepareStartPauseResumeFinishTimelineAttemptFailedEventProcessor(); eventProcessors.put(WorkflowStateMachineEventType.PREPARE_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.START_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.PAUSE_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.RESUME_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.VERIFICATION_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.FINISH_ATTEMPT_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.PROCESS_COMPONENT_TIMELINE_EVENTS_FAILED, variousAttemptsFailedEventProcessor); eventProcessors.put(WorkflowStateMachineEventType.COMPONENT_HEARTBEAT_LOST, new ComponentHeartbeatLostEventProcessor()); eventProcessors.put(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, new CancelAttemptFailedEventProcessor()); } @Override protected WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) throws StateChangeException { return eventProcessors.get(event.getType()).processEvent(currentState, event); } public void setComponentExecutionAuthTokens(Map<String, String> exeAuthTokens) { this.executionAuthTokens = exeAuthTokens; } private boolean checkStateChange(WorkflowState currentState, WorkflowState newState) { if (isStateChangeValid(currentState, newState)) { return true; } else { logInvalidStateChangeRequest(currentState, newState); return false; } } private void handleFailure(WorkflowStateMachineEvent event) { if (event.getThrowable() != null) { String errorMessagePrefix = StringUtils.format("Workflow %s failed and will be cancelled", wfNameAndIdMessagePart); String errorId = LogUtils.logExceptionAsSingleLineAndAssignUniqueMarker(LOG, errorMessagePrefix, event.getThrowable()); String errorMessage = ComponentUtils.createErrorLogMessage(event.getThrowable(), errorId); storeAndSendErrorLogMessage(ConsoleRow.Type.WORKFLOW_ERROR, errorMessage, "", ""); } else { final LogicalNodeId componentNode = componentNodeIds.get(event.getComponentExecutionId()); final String componentName = componentInstanceNames.get(event.getComponentExecutionId()); String errorMessagePrefix = StringUtils.format( "Workflow %s will be cancelled because component '%s' on %s failed", wfNameAndIdMessagePart, componentName, componentNode); if (event.getErrorMessage() != null) { String errorMessage = ComponentUtils.createErrorLogMessage(event.getErrorMessage(), event.getErrorId()); storeAndSendErrorLogMessage(ConsoleRow.Type.COMPONENT_ERROR, errorMessage, event.getComponentExecutionId(), componentName); } LOG.error(StringUtils.format("%s: for more information, look for the error marker %s in the log files of %s", errorMessagePrefix, event.getErrorId(), componentNode)); } } private void logInvalidStateChangeRequest(WorkflowState currentState, WorkflowState requestedState) { LOG.debug(StringUtils.format("Ignored workflow state change request for workflow %s as it will cause an invalid" + " state transition: %s -> %s", wfNameAndIdMessagePart, currentState, requestedState)); } @Override protected void onStateChanged(WorkflowState oldState, WorkflowState newState) { LOG.debug(StringUtils.format("Workflow %s is now %s (previous state: %s)", wfNameAndIdMessagePart, newState, oldState)); sendNewWorkflowStateAsConsoleRow(newState); notificationService.send(WorkflowConstants.STATE_NOTIFICATION_ID + wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier(), newState.name()); if (newState == WorkflowState.DISPOSED) { disposeNotificationBuffers(); } if (WorkflowConstants.FINAL_WORKFLOW_STATES.contains(newState)) { wfExeStatsService.addStatsAtWorkflowTermination(wfStateMachineCtx.getWorkflowExecutionContext(), newState); synchronized (wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier()) { if (heartbeatFuture != null && !heartbeatFuture.isCancelled()) { heartbeatFuture.cancel(false); } } sendLifeCycleEventAsConsoleRow(ConsoleRow.WorkflowLifecyleEventType.WORKFLOW_FINISHED); } } @Override protected void onStateChangeException(WorkflowStateMachineEvent event, StateChangeException e) { LOG.error(StringUtils.format("Invalid state change attempt for workflow %s, caused by event '%s'", wfNameAndIdMessagePart, event), e); } void prepareAsync() { currentTask = threadPool.submit(new AsyncPrepareTask()); } void startAsync() { currentTask = threadPool.submit(new AsyncStartTask()); } void cancelAsync() { threadPool.submit(new AsyncCancelTask(currentTask)); } void pauseAsync() { threadPool.submit(new AsyncPauseTask()); } void resumeAsync() { currentTask = threadPool.submit(new AsyncResumeTask()); } void disposeAsync() { currentTask = threadPool.submit(new AsyncDisposeTask()); } void waitForFinishAsync() { currentTask = threadPool.submit(new AsyncWaitForFinishTask()); } /** * Prepares the workflow. * * @author Doreen Seider */ private final class AsyncPrepareTask implements Runnable { @Override @TaskDescription("Prepare workflow") public void run() { wfExeStatsService.addStatsAtWorkflowStart(wfStateMachineCtx.getWorkflowExecutionContext()); notificationService.send(WorkflowConstants.NEW_WORKFLOW_NOTIFICATION_ID, wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()); initializeNotificationBuffers(); initializeConsoleLogWriting(); DataManagementIdsHolder dmIds; try { dmIds = wfStateMachineCtx.getWorkflowExecutionStorageBridge().addWorkflowExecution( wfStateMachineCtx.getWorkflowExecutionContext(), fullWorkflowDescription); } catch (WorkflowExecutionException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PREPARE_ATTEMPT_FAILED, e)); return; } finally { fullWorkflowDescription = null; } if (wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes().isEmpty()) { onComponentStatesChangedCompletelyToPrepared(); } final Map<String, ComponentExecutionContext> compExeCtxts; try { compExeCtxts = createComponentExecutionContexts(dmIds); checkForUnreachableComponentNodes(compExeCtxts); } catch (WorkflowExecutionException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PREPARE_ATTEMPT_FAILED, e)); return; } CallablesGroup<Throwable> callablesGroup = ConcurrencyUtils.getFactory().createCallablesGroup(Throwable.class); final Long referenceTimestamp = System.currentTimeMillis(); for (WorkflowNode wfNode : wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes()) { final WorkflowNode finalWfNode = wfNode; final ComponentExecutionContext compExeCtx = compExeCtxts.get(wfNode.getIdentifier()); callablesGroup.add(new Callable<Throwable>() { @Override @TaskDescription("Create component execution controller and perform prepare") public Exception call() throws Exception { try { String compExeId = componentExecutionService.init(compExeCtx, executionAuthTokens.get(finalWfNode.getIdentifier()), referenceTimestamp); componentNodeIds.put(compExeId, compExeCtx.getNodeId()); componentInstanceNames.put(compExeId, compExeCtx.getInstanceName()); initializeComponentConsoleLogWriting(compExeId); componentExecutionService.prepare(compExeId, compExeCtx.getNodeId()); LOG.debug(StringUtils.format("Created component '%s' (%s) on node %s", compExeCtx.getInstanceName(), compExeId, compExeCtx.getNodeId())); } catch (RemoteOperationException | RuntimeException e) { return e; } return null; } }, "Prepare component: " + compExeCtx.getExecutionIdentifier()); } List<Throwable> throwables = callablesGroup.executeParallel(new AsyncExceptionListener() { @Override public void onAsyncException(Exception e) { // should never happen } }); for (Throwable t : throwables) { if (t != null) { LOG.error(StringUtils.format("Failed to prepare workflow %s", wfNameAndIdMessagePart), t); } } for (Throwable t : throwables) { if (t != null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PREPARE_ATTEMPT_FAILED, new Throwable("Failed to prepare workflow", t))); return; } } LOG.debug(StringUtils.format("Workflow %s is prepared (%d component(s))", wfNameAndIdMessagePart, compExeCtxts.size())); } } private void checkForUnreachableComponentNodes(Map<String, ComponentExecutionContext> compExeCtxts) throws WorkflowExecutionException { Map<String, ComponentExecutionContext> notReachableCompExeIds = new HashMap<>(); Set<LogicalNodeId> reachableNodes = communicationService.getReachableLogicalNodes(); for (ComponentExecutionContext compExeCtx : compExeCtxts.values()) { // stored for logging purposes, see createMessageListingComponents; should be improved componentInstanceNames.put(compExeCtx.getExecutionIdentifier(), compExeCtx.getInstanceName()); componentNodeIds.put(compExeCtx.getExecutionIdentifier(), compExeCtx.getNodeId()); // TODO (p2) simply checking the reachability of the equivalent default logical node id for now; improve when needed if (!reachableNodes.contains(compExeCtx.getNodeId().convertToDefaultLogicalNodeId())) { notReachableCompExeIds.put(compExeCtx.getExecutionIdentifier(), compExeCtx); } } try { if (!notReachableCompExeIds.isEmpty()) { for (ComponentExecutionContext compExeCtx : compExeCtxts.values()) { String compExeId = compExeCtx.getExecutionIdentifier(); wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().accounceComponentInAnyFinalState(compExeId); wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().announceLastConsoleRow(compExeId); if (reachableNodes.contains(compExeCtx.getNodeId())) { sendComponentStateCanceled(compExeId); } else { sendComponentStateFailed(compExeId); } } throw new WorkflowExecutionException("Failed to execute workflow because component node(s) not reachable: " + createMessageListingComponents(notReachableCompExeIds.keySet())); } } finally { componentInstanceNames.clear(); componentNodeIds.clear(); } } private void initializeComponentConsoleLogWriting(String compExeId) { try { wfStateMachineCtx.getComponentsConsoleLogFileWriter().initializeComponentLogFile(compExeId); } catch (IOException e) { LOG.error(StringUtils.format("Failed to initialize console log file writers for workflow %s", wfNameAndIdMessagePart), e); } } private void initializeConsoleLogWriting() { try { wfStateMachineCtx.getComponentsConsoleLogFileWriter().initializeWorkflowLogFile(); } catch (IOException e) { LOG.error(StringUtils.format("Failed to initialize console log file writer for workflow %s", wfNameAndIdMessagePart), e); } } private void initializeNotificationBuffers() { final int bufferSize = 500; notificationService.setBufferSize(ConsoleRowUtils.composeConsoleNotificationId(wfStateMachineCtx.getWorkflowExecutionContext() .getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()), bufferSize); notificationService.setBufferSize(WorkflowConstants.STATE_NOTIFICATION_ID + wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier(), 1); notificationService.setBufferSize(StringUtils.format(ComponentConstants.NOTIFICATION_ID_PREFIX_PROCESSED_INPUT + "%s:%s", wfStateMachineCtx.getWorkflowExecutionContext().getNodeId().getLogicalNodeIdString(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()), bufferSize); for (WorkflowNode wfNode : wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes()) { String compExeId = wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wfNode.getIdentifier()); // set to 3 as the state before the component is disposing->disposed must be // accessible by the GUI notificationService.setBufferSize(ComponentConstants.STATE_NOTIFICATION_ID_PREFIX + compExeId, 3); notificationService.setBufferSize(ComponentConstants.ITERATION_COUNT_NOTIFICATION_ID_PREFIX + compExeId, 1); } } private Map<String, ComponentExecutionContext> createComponentExecutionContexts(DataManagementIdsHolder dmIds) throws WorkflowExecutionException { WorkflowDescription workflowDescription = wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription(); WorkflowGraph workflowGraph = createWorkflowGraph(workflowDescription); Map<String, ComponentExecutionContext> compExeCtxs = new HashMap<>(); for (WorkflowNode wfNode : workflowDescription.getWorkflowNodes()) { compExeCtxs.put(wfNode.getIdentifier(), createComponentExecutionContext(wfNode, workflowGraph, dmIds)); } return compExeCtxs; } private WorkflowGraph createWorkflowGraph(WorkflowDescription workflowDescription) throws WorkflowExecutionException { Map<String, WorkflowGraphNode> workflowGraphNodes = new HashMap<>(); Map<String, Set<WorkflowGraphEdge>> workflowGraphEdges = new HashMap<>(); for (WorkflowNode wn : workflowDescription.getWorkflowNodes()) { Map<String, String> endpointNames = new HashMap<>(); Set<String> inputIds = new HashSet<>(); for (EndpointDescription ep : wn.getInputDescriptionsManager().getEndpointDescriptions()) { inputIds.add(ep.getIdentifier()); endpointNames.put(ep.getIdentifier(), ep.getName()); } Set<String> outputIds = new HashSet<>(); for (EndpointDescription ep : wn.getOutputDescriptionsManager().getEndpointDescriptions()) { outputIds.add(ep.getIdentifier()); endpointNames.put(ep.getIdentifier(), ep.getName()); } String compExeId = wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wn.getIdentifier()); workflowGraphNodes.put(compExeId, new WorkflowGraphNode(compExeId, inputIds, outputIds, endpointNames, wn.getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getIsLoopDriver(), isDrivingFaultTolerantNode(wn), wn.getName())); } for (Connection cn : workflowDescription.getConnections()) { WorkflowGraphEdge edge = new WorkflowGraphEdge( wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(cn.getSourceNode().getIdentifier()), cn.getOutput().getIdentifier(), cn.getOutput().getEndpointDefinition().getEndpointCharacter(), wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(cn.getTargetNode().getIdentifier()), cn.getInput().getIdentifier(), cn.getInput().getEndpointDefinition().getEndpointCharacter()); String edgeKey = WorkflowGraph.createEdgeKey(edge); if (!workflowGraphEdges.containsKey(edgeKey)) { workflowGraphEdges.put(edgeKey, new HashSet<WorkflowGraphEdge>()); } workflowGraphEdges.get(edgeKey).add(edge); } WorkflowGraph workflowGraph = new WorkflowGraph(workflowGraphNodes, workflowGraphEdges); validatedNestedLoopDriverConfiguration(workflowDescription, workflowGraph); return workflowGraph; } private void validatedNestedLoopDriverConfiguration(WorkflowDescription workflowDescription, WorkflowGraph workflowGraph) throws WorkflowExecutionException { for (WorkflowNode wn : workflowDescription.getWorkflowNodes()) { boolean isDriverComp = wn.getComponentDescription().getComponentInstallation().getComponentRevision() .getComponentInterface().getIsLoopDriver(); if (isDriverComp) { String compExeId = wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wn.getIdentifier()); Boolean nestedLoop = Boolean.valueOf(wn.getConfigurationDescription() .getConfigurationValue(LoopComponentConstants.CONFIG_KEY_IS_NESTED_LOOP)); try { if (nestedLoop && workflowGraph.getLoopDriver(compExeId) == null) { storeAndSendErrorLogMessage(ConsoleRow.Type.WORKFLOW_ERROR, StringUtils.format( "Potential configuration error: '%s' is configured as a nested loop driver component but doesn't seem " + "to be part of a loop driven by an outer loop driver component", wn.getComponentDescription().getName()), "", ""); } else if (!nestedLoop && workflowGraph.getLoopDriver(compExeId) != null) { storeAndSendErrorLogMessage(ConsoleRow.Type.WORKFLOW_ERROR, StringUtils.format("Potential configuration error: '%s' is part of a loop driven by an outer loop driver " + "component but is not configured as a nested loop driver component", wn.getComponentDescription().getName()), "", ""); } } catch (ComponentExecutionException e) { throw new WorkflowExecutionException("Wokflow logic invalid", e); } } } } private boolean isDrivingFaultTolerantNode(WorkflowNode wn) { ConfigurationDescription configDesc = wn.getComponentDescription().getConfigurationDescription(); LoopBehaviorInCaseOfFailure behavior = LoopBehaviorInCaseOfFailure .fromString(configDesc.getConfigurationValue(LoopComponentConstants.CONFIG_KEY_LOOP_FAULT_TOLERANCE_COMP_FAILURE)); return behavior.equals(LoopBehaviorInCaseOfFailure.Discard); } private ComponentExecutionContext createComponentExecutionContext(WorkflowNode wfNode, WorkflowGraph workflowGraph, DataManagementIdsHolder dmIds) { String compExeId = wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wfNode.getIdentifier()); WorkflowDescription workflowDescription = wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription(); ComponentExecutionContextBuilder builder = new ComponentExecutionContextBuilder(); builder.setExecutionIdentifiers(compExeId, wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()); builder.setInstanceNames(wfNode.getName(), wfStateMachineCtx.getWorkflowExecutionContext().getInstanceName()); builder.setNodes(wfStateMachineCtx.getWorkflowExecutionContext().getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getDefaultStorageNodeId()); builder.setComponentDescription(wfNode.getComponentDescription()); boolean isConnectedToEndpointDatumSenders = false; for (Connection cn : workflowDescription.getConnections()) { if (cn.getTargetNode().getIdentifier().equals(wfNode.getIdentifier())) { isConnectedToEndpointDatumSenders = true; break; } } Map<String, List<EndpointDatumRecipient>> endpointDatumRecipients = new HashMap<>(); for (Connection cn : workflowDescription.getConnections()) { if (cn.getSourceNode().getIdentifier().equals(wfNode.getIdentifier())) { EndpointDatumRecipient endpointDatumRecipient = EndpointDatumRecipientFactory .createEndpointDatumRecipient(cn.getInput().getName(), wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(cn.getTargetNode().getIdentifier()), cn.getTargetNode().getName(), cn.getTargetNode().getComponentDescription().getNode()); if (!endpointDatumRecipients.containsKey(cn.getOutput().getName())) { endpointDatumRecipients.put(cn.getOutput().getName(), new ArrayList<EndpointDatumRecipient>()); } endpointDatumRecipients.get(cn.getOutput().getName()).add(endpointDatumRecipient); } } builder.setPredecessorAndSuccessorInformation(isConnectedToEndpointDatumSenders, endpointDatumRecipients); builder.setWorkflowGraph(workflowGraph); Long compInstanceDmId = dmIds.compInstDmIds.get(wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wfNode.getIdentifier())); Map<String, Long> inputDataManagementIds = dmIds.inputDmIds.get(wfStateMachineCtx.getWorkflowExecutionContext() .getCompExeIdByWfNodeId(wfNode.getIdentifier())); Map<String, Long> outputDataManagementIds = dmIds.outputDmIds.get(wfStateMachineCtx.getWorkflowExecutionContext() .getCompExeIdByWfNodeId(wfNode.getIdentifier())); builder.setDataManagementIds(wfStateMachineCtx.getWorkflowExecutionStorageBridge().getWorkflowInstanceDataManamagementId(), compInstanceDmId, inputDataManagementIds, outputDataManagementIds); return builder.build(); } /** * Starts the workflow. * * @author Doreen Seider */ private final class AsyncStartTask implements Runnable { @Override @TaskDescription("Start workflow") public void run() { sendLifeCycleEventAsConsoleRow(ConsoleRow.WorkflowLifecyleEventType.WORKFLOW_STARTING); ParallelComponentCaller ppc = new ParallelComponentCaller(componentNodeIds.keySet(), wfStateMachineCtx.getWorkflowExecutionContext()) { @Override public void onErrorInSingleComponentCall(String compExeId, Throwable t) { sendComponentStateFailed(compExeId); } @Override public void callSingleComponent(String compExeId) throws ExecutionControllerException, RemoteOperationException { componentExecutionService.start(compExeId, componentNodeIds.get(compExeId)); wfStateMachineCtx.getComponentLostWatcher().announceComponentHeartbeat(compExeId); } @Override public String getMethodToCallAsString() { return "start"; } }; // TODO direct method invocation causes an false positive checkstyle error Throwable throwable = ppc.callParallelAndWait(); if (throwable == null) { synchronized (wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier()) { heartbeatFuture = threadPool.scheduleAtFixedRate(wfStateMachineCtx.getComponentLostWatcher(), ComponentLostWatcher.DEFAULT_MAX_HEA7RTBEAT_INTERVAL_MSEC); } postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.START_ATTEMPT_SUCCESSFUL)); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.START_ATTEMPT_FAILED, throwable)); } if (wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes().isEmpty()) { onComponentStatesChangedCompletelyToFinished(); onComponentStatesChangedCompletelyToAnyFinalState(); onLastConsoleRowsReceived(); disposedComponentStateLatch.countDown(); } } } /** * Pauses the workflow. * * @author Doreen Seider */ private final class AsyncPauseTask implements Runnable { private static final int WAIT_INTERVAL_PAUSE_HRS = 10; @Override @TaskDescription("Pause workflow") public void run() { ParallelComponentCaller ppc = new ParallelComponentCaller(getComponentsToConsider(true), wfStateMachineCtx.getWorkflowExecutionContext()) { @Override public void onErrorInSingleComponentCall(String compExeId, Throwable t) { pausedComonentStateLatch.countDown(); sendComponentStateFailed(compExeId); } @Override public void callSingleComponent(String compExeId) throws ExecutionControllerException, RemoteOperationException { componentExecutionService.pause(compExeId, componentNodeIds.get(compExeId)); } @Override public String getMethodToCallAsString() { return "pause"; } }; // TODO direct method invocation causes an false positive checkstyle error Throwable throwable = ppc.callParallelAndWait(); try { // TODO review waiting time (note: when pausing a workflow, the components' runs are not interrupted or paused) boolean timeNotElapsed = pausedComonentStateLatch.await(WAIT_INTERVAL_PAUSE_HRS, TimeUnit.HOURS); if (!timeNotElapsed) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PAUSE_ATTEMPT_FAILED, new WorkflowExecutionException( StringUtils.format("Waiting for workflow %s to pause failed" + CAUSE_WAITING_TIME_ELAPSED_HRS, wfNameAndIdMessagePart, WAIT_INTERVAL_PAUSE_HRS)))); return; } } catch (InterruptedException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PAUSE_ATTEMPT_FAILED, new WorkflowExecutionException(StringUtils.format("Waiting for components to pause (workflow %s) was interrupted", wfNameAndIdMessagePart), e))); return; } if (throwable == null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PAUSE_ATTEMPT_SUCCESSFUL)); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PAUSE_ATTEMPT_FAILED, throwable)); } } } /** * Resumes the workflow. * * @author Doreen Seider */ private final class AsyncResumeTask implements Runnable { private static final int WAIT_INTERVAL_CANCEL_SEC = 60; @Override @TaskDescription("Resume workflow") public void run() { ParallelComponentCaller ppc = new ParallelComponentCaller(getComponentsToConsider(true), wfStateMachineCtx.getWorkflowExecutionContext()) { @Override public void onErrorInSingleComponentCall(String compExeId, Throwable t) { resumedComonentStateLatch.countDown(); sendComponentStateFailed(compExeId); } @Override public void callSingleComponent(String compExeId) throws ExecutionControllerException, RemoteOperationException { componentExecutionService.resume(compExeId, componentNodeIds.get(compExeId)); } @Override public String getMethodToCallAsString() { return "resume"; } }; // TODO direct method invocation causes an false positive checkstyle error Throwable throwable = ppc.callParallelAndWait(); try { boolean timeNotElapsed = resumedComonentStateLatch.await(WAIT_INTERVAL_CANCEL_SEC, TimeUnit.SECONDS); if (!timeNotElapsed) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.RESUME_ATTEMPT_FAILED, new WorkflowExecutionException( StringUtils.format("Waiting for workflow %s to resume failed" + CAUSE_WAITING_TIME_ELAPSED_SEC, wfNameAndIdMessagePart, WAIT_INTERVAL_CANCEL_SEC)))); return; } } catch (InterruptedException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.RESUME_ATTEMPT_FAILED, new WorkflowExecutionException(StringUtils.format("Waiting for components to resume (workflow %s) was interrupted", wfNameAndIdMessagePart), e))); return; } if (throwable == null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.RESUME_ATTEMPT_SUCCESSFUL)); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.RESUME_ATTEMPT_FAILED, throwable)); } } } /** * Cancels the workflow. * * @author Doreen Seider */ private final class AsyncCancelTask implements Runnable { private static final int WAIT_INTERVAL_CANCEL_SEC = 90; private final Future<?> future; protected AsyncCancelTask(Future<?> future) { this.future = future; } @Override @TaskDescription("Cancel workflow") public void run() { if (future != null) { try { future.get(WAIT_INTERVAL_CANCEL_SEC, TimeUnit.SECONDS); } catch (ExecutionException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, e)); return; } catch (TimeoutException | InterruptedException e) { future.cancel(true); postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, e)); return; } } Throwable throwable = null; if (!componentNodeIds.isEmpty()) { throwable = new ParallelComponentCaller(getComponentsToConsider(true), wfStateMachineCtx.getWorkflowExecutionContext()) { @Override public void callSingleComponent(String compExeId) throws ExecutionControllerException, RemoteOperationException { try { componentExecutionService.cancel(compExeId, componentNodeIds.get(compExeId)); } catch (ExecutionControllerException e) { LOG.debug(StringUtils.format("Failed to cancel component(s) of %s; cause: %s", getMethodToCallAsString(), e.toString())); } } @Override public void onErrorInSingleComponentCall(String compExeId, Throwable t) { wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().accounceComponentInAnyFinalState(compExeId); wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().announceLastConsoleRow(compExeId); sendComponentStateFailed(compExeId); } @Override public String getMethodToCallAsString() { return "cancel"; } }.callParallelAndWait(); int compsNotInitCount = wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes().size() - componentNodeIds.size(); for (int i = 0; i < compsNotInitCount; i++) { String pseudoCompExeId = String.valueOf(i); wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().accounceComponentInAnyFinalState(pseudoCompExeId); wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().announceLastConsoleRow(pseudoCompExeId); } try { boolean timeNotElapsed = workflowTerminatedLatch.await(WAIT_INTERVAL_TERMINATED_SEC, TimeUnit.SECONDS); if (!timeNotElapsed) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, new WorkflowExecutionException( StringUtils.format("Waiting for workflow %s to cancel failed" + CAUSE_WAITING_TIME_ELAPSED_SEC, wfNameAndIdMessagePart, WAIT_INTERVAL_CANCEL_SEC)))); return; } } catch (InterruptedException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, new WorkflowExecutionException(StringUtils.format("Waiting for components to cancel (workflow %s) was interrupted", wfNameAndIdMessagePart), e))); return; } } try { flushAndDisposeComponentLogFiles(); if (getState() == WorkflowState.CANCELING_AFTER_FAILED || throwable != null) { wfStateMachineCtx.getWorkflowExecutionStorageBridge().setWorkflowExecutionFinished(FinalWorkflowState.FAILED); } else if (getState() == WorkflowState.CANCELING_AFTER_RESULTS_REJECTED) { wfStateMachineCtx.getWorkflowExecutionStorageBridge() .setWorkflowExecutionFinished(FinalWorkflowState.RESULTS_REJECTED); } else if (getState() == WorkflowState.CANCELING) { wfStateMachineCtx.getWorkflowExecutionStorageBridge().setWorkflowExecutionFinished(FinalWorkflowState.CANCELLED); } } catch (WorkflowExecutionException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, e)); return; } if (throwable == null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_SUCCESSFUL)); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_ATTEMPT_FAILED, throwable)); } } } /** * Wait for the workflow to finish. * * @author Doreen Seider */ private final class AsyncWaitForFinishTask implements Runnable { @Override @TaskDescription("Wait for workflow to finish") public void run() { try { boolean timeNotElapsed = workflowTerminatedLatch.await(WAIT_INTERVAL_TERMINATED_SEC, TimeUnit.SECONDS); if (timeNotElapsed) { workflowExecutionFinished(); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.FINISH_ATTEMPT_FAILED, new WorkflowExecutionException(StringUtils.format("Waiting for workflow %s to finish failed", wfNameAndIdMessagePart) + " cause: waiting time elapsed"))); } } catch (InterruptedException e) { LOG.error(StringUtils.format("Waiting for workflow %s to finish failed", wfNameAndIdMessagePart) + "cause: waiting interrupted", e); postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.FINISH_ATTEMPT_FAILED, e)); } } } private void workflowExecutionFinished() { if (!wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes().isEmpty()) { flushAndDisposeComponentLogFiles(); } try { wfStateMachineCtx.getWorkflowExecutionStorageBridge().setWorkflowExecutionFinished(FinalWorkflowState.FINISHED); postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.FINISHED)); } catch (WorkflowExecutionException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.FINISH_ATTEMPT_FAILED, e)); } } private void flushAndDisposeComponentLogFiles() { wfStateMachineCtx.getComponentsConsoleLogFileWriter().addWorkflowConsoleRow(createConsoleRowForWorkflowLifeCycleEvent( WorkflowLifecyleEventType.WORKFLOW_LOG_FINISHED.name())); wfStateMachineCtx.getComponentsConsoleLogFileWriter().flushAndDisposeLogFiles(); } /** * Disposes the workflow. * * @author Doreen Seider */ private final class AsyncDisposeTask implements Runnable { private static final int WAIT_INTERVAL_DISPOSE_SEC = 60; @Override @TaskDescription("Dispose workflow") public void run() { notificationService.send(WorkflowConstants.STATE_DISPOSED_NOTIFICATION_ID, wfStateMachineCtx.getWorkflowExecutionContext() .getExecutionIdentifier()); ParallelComponentCaller ppc = new ParallelComponentCaller(getComponentsToConsider(false, true), wfStateMachineCtx.getWorkflowExecutionContext()) { @Override public void onErrorInSingleComponentCall(String compExeId, Throwable t) { wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().announceComponentState(compExeId, ComponentState.DISPOSED); } @Override public void callSingleComponent(String compExeId) throws ExecutionControllerException, RemoteOperationException { componentExecutionService.dispose(compExeId, componentNodeIds.get(compExeId)); } @Override public String getMethodToCallAsString() { return "dispose"; } }; // TODO direct method invocation causes an false positive checkstyle error Throwable throwable = ppc.callParallelAndWait(); try { boolean timeNotElapsed = disposedComponentStateLatch.await(WAIT_INTERVAL_DISPOSE_SEC, TimeUnit.SECONDS); if (!timeNotElapsed) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_FAILED, new WorkflowExecutionException( StringUtils.format("Waiting for workflow %s to dispose failed" + CAUSE_WAITING_TIME_ELAPSED_SEC, wfNameAndIdMessagePart, WAIT_INTERVAL_DISPOSE_SEC)))); } } catch (InterruptedException e) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_FAILED, new WorkflowExecutionException(StringUtils.format("Waiting for components to dispose (workflow %s) was interrupted", wfNameAndIdMessagePart), e))); return; } if (throwable == null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_SUCCESSFUL)); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.DISPOSE_ATTEMPT_FAILED, throwable)); } } } private void disposeNotificationBuffers() { notificationService.removePublisher(WorkflowConstants.STATE_NOTIFICATION_ID + wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()); notificationService.removePublisher(ConsoleRowUtils.composeConsoleNotificationId(wfStateMachineCtx.getWorkflowExecutionContext() .getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier())); notificationService.removePublisher(ConsoleRowUtils.composeConsoleNotificationId(wfStateMachineCtx.getWorkflowExecutionContext() .getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier())); for (WorkflowNode wfNode : wfStateMachineCtx.getWorkflowExecutionContext().getWorkflowDescription().getWorkflowNodes()) { String compExeId = wfStateMachineCtx.getWorkflowExecutionContext().getCompExeIdByWfNodeId(wfNode.getIdentifier()); notificationService.removePublisher(ComponentConstants.STATE_NOTIFICATION_ID_PREFIX + compExeId); notificationService.removePublisher(ComponentConstants.ITERATION_COUNT_NOTIFICATION_ID_PREFIX + compExeId); } } private Set<String> getComponentsToConsider(boolean ignoreCompsInFinalState, boolean ignoreDisposedComps) { Set<String> compsToConsider = new HashSet<>(componentNodeIds.keySet()); if (ignoreCompsInFinalState) { compsToConsider.removeAll(wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().getComponentsInFinalState()); } else if (ignoreDisposedComps) { compsToConsider.removeAll(wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().getDisposedComponents()); } return compsToConsider; } private String createMessageListingComponents(Set<String> compExeIds) { String message = ""; for (String compExeId : compExeIds) { if (!message.isEmpty()) { message += ", "; } message += StringUtils.format("'%s' (%s) at %s", componentInstanceNames.get(compExeId), compExeId, componentNodeIds.get(compExeId)); } return message; } private void sendLifeCycleEventAsConsoleRow(ConsoleRow.WorkflowLifecyleEventType type) { ConsoleRow consoleRow = createConsoleRowForWorkflowLifeCycleEvent(type.name()); sendConsoleRowAsNotification(consoleRow); } private void sendNewWorkflowStateAsConsoleRow(WorkflowState newState) { // send a LIFE_CYCLE_EVENT of subtype NEW_STATE with the new state's enum name attached String payload = StringUtils.escapeAndConcat(ConsoleRow.WorkflowLifecyleEventType.NEW_STATE.name(), newState.name()); sendConsoleRowAsNotification(createConsoleRowForWorkflowLifeCycleEvent(payload)); } private void sendConsoleRowAsNotification(ConsoleRow consoleRow) { notificationService.send(ConsoleRowUtils.composeConsoleNotificationId(wfStateMachineCtx.getWorkflowExecutionContext().getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()), consoleRow); } private ConsoleRow createConsoleRowForWorkflowLifeCycleEvent(String payload) { return createConsoleRow(ConsoleRow.Type.LIFE_CYCLE_EVENT, payload, "", ""); } private ConsoleRow createConsoleRow(Type type, String payload, String compExeId, String compInstanceName) { ConsoleRowBuilder consoleRowBuilder = new ConsoleRowBuilder(); consoleRowBuilder.setExecutionIdentifiers(wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier(), compExeId) .setInstanceNames(wfStateMachineCtx.getWorkflowExecutionContext().getInstanceName(), compInstanceName) .setType(type) .setPayload(payload); return consoleRowBuilder.build(); } private void storeAndSendErrorLogMessage(Type type, String message, String compExeId, String compInstanceName) { ConsoleRow consoleRow = createConsoleRow(type, message, compExeId, compInstanceName); wfStateMachineCtx.getComponentsConsoleLogFileWriter().addWorkflowConsoleRow(consoleRow); notificationService.send(ConsoleRowUtils.composeConsoleNotificationId(wfStateMachineCtx.getWorkflowExecutionContext().getNodeId(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier()), consoleRow); } @Override public void onComponentStatesChangedCompletelyToPrepared() { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.PREPARE_ATTEMPT_SUCCESSFUL)); } @Override public void onComponentStatesChangedCompletelyToPaused() { pausedComonentStateLatch.countDown(); } @Override public void onComponentStatesChangedCompletelyToResumed() { resumedComonentStateLatch.countDown(); } @Override public void onComponentStatesChangedCompletelyToFinished() { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.ON_COMPONENTS_FINISHED)); } @Override public void onComponentStatesChangedCompletelyToDisposed() { disposedComponentStateLatch.countDown(); } @Override public void onComponentStatesChangedCompletelyToAnyFinalState() { synchronized (wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier()) { if (heartbeatFuture != null && !heartbeatFuture.isCancelled()) { heartbeatFuture.cancel(false); } } workflowTerminatedLatch.countDown(); } @Override public void onLastConsoleRowsReceived() { workflowTerminatedLatch.countDown(); } private Set<String> getComponentsToConsider(boolean ignoreCompsInFinalState) { return getComponentsToConsider(ignoreCompsInFinalState, true); } private void sendComponentStateCanceled(String compExeId) { notificationService.send(ComponentConstants.STATE_NOTIFICATION_ID_PREFIX + compExeId, ComponentState.CANCELED.name()); } private void sendComponentStateFailed(String compExeId) { notificationService.send(ComponentConstants.STATE_NOTIFICATION_ID_PREFIX + compExeId, ComponentState.FAILED.name()); } protected void bindCommunicationService(CommunicationService newService) { communicationService = newService; } protected void bindComponentExecutionService(ComponentExecutionService newService) { componentExecutionService = newService; } protected void bindWorkflowExecutionStatsService(WorkflowExecutionStatsService newService) { wfExeStatsService = newService; } protected void bindDistributedNotificationService(DistributedNotificationService newService) { notificationService = newService; } @Override public void onComponentsLost(Set<String> compExeIdsLost) { for (String compExeIdLost : compExeIdsLost) { sendComponentStateFailed(compExeIdLost); } postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.COMPONENT_HEARTBEAT_LOST, new WorkflowExecutionException(StringUtils.format("Component(s) not reachable (anymore): " + createMessageListingComponents(compExeIdsLost))))); } /** * Processes {@link WorkflowStateMachineEventType}s. * * @author Doreen Seider */ private interface EventProcessor { WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event); } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class StartRequestedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.PREPARING)) { state = WorkflowState.PREPARING; prepareAsync(); } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class PrepareAttemptSuccessfulEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.STARTING)) { currentTask = null; startAsync(); state = WorkflowState.STARTING; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class StartAttemptSuccessfulEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.RUNNING)) { currentTask = null; state = WorkflowState.RUNNING; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class PauseRequestedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.PAUSING)) { wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().enablePausedComponentStateVerification(); pausedComonentStateLatch = new CountDownLatch(1); pauseAsync(); state = WorkflowState.PAUSING; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class PauseAttemptSuccessfulEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.PAUSED)) { state = WorkflowState.PAUSED; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class ResumeRequestedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.RESUMING)) { wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().enableResumedComponentStateVerification(); resumedComonentStateLatch = new CountDownLatch(1); resumeAsync(); state = WorkflowState.RESUMING; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class ResumeAttemptSuccessfulEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.RUNNING)) { state = WorkflowState.RUNNING; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class CancelRequestedEventProcessor implements EventProcessor { private final WorkflowState cancelingWfState; CancelRequestedEventProcessor(WorkflowState cancelingWfState) { this.cancelingWfState = cancelingWfState; } @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, cancelingWfState)) { state = cancelingWfState; if (cancelingWfState.equals(WorkflowState.CANCELING_AFTER_FAILED)) { handleFailure(event); } cancelAsync(); } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class CancelAttemptSuccessufulEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (currentState == WorkflowState.CANCELING) { if (checkStateChange(currentState, WorkflowState.CANCELLED)) { state = WorkflowState.CANCELLED; } } else if (currentState == WorkflowState.CANCELING_AFTER_FAILED) { if (checkStateChange(currentState, WorkflowState.FAILED)) { state = WorkflowState.FAILED; } } else if (currentState == WorkflowState.CANCELING_AFTER_RESULTS_REJECTED) { if (checkStateChange(currentState, WorkflowState.RESULTS_REJECTED)) { state = WorkflowState.RESULTS_REJECTED; } } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class DisposeRequestedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.DISPOSING)) { state = WorkflowState.DISPOSING; disposeAsync(); } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class DisposeAttemptSuccessfulOrFailedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.DISPOSED)) { currentTask = null; state = WorkflowState.DISPOSED; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class OnComponentsFinishedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { if (checkStateChange(currentState, WorkflowState.FINISHED)) { waitForFinishAsync(); } return currentState; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class FinishedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.FINISHED)) { state = WorkflowState.FINISHED; } return state; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class PrepareStartPauseResumeFinishTimelineAttemptFailedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { currentTask = null; if (event.getThrowable() != null) { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_AFTER_FAILED_REQUESTED, event.getThrowable())); } else { postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_AFTER_FAILED_REQUESTED, event.getErrorId(), event.getErrorMessage(), event.getComponentExecutionId())); } return currentState; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class ComponentHeartbeatLostEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { currentTask = null; wfStateMachineCtx.getComponentStatesChangedEntirelyVerifier().declareLostComponentsAsBeingInFinalStateAndDisposed(); postEvent(new WorkflowStateMachineEvent(WorkflowStateMachineEventType.CANCEL_AFTER_COMPONENT_LOST_REQUESTED, event.getThrowable())); return currentState; } } /** * Specific implementation of {@link EventProcessor}. * * @author Doreen Seider */ private class CancelAttemptFailedEventProcessor implements EventProcessor { @Override public WorkflowState processEvent(WorkflowState currentState, WorkflowStateMachineEvent event) { WorkflowState state = currentState; if (checkStateChange(currentState, WorkflowState.FAILED)) { if (event.getThrowable() != null) { LOG.error(StringUtils.format("Failed to cancel workflow %s", wfNameAndIdMessagePart), event.getThrowable()); } flushAndDisposeComponentLogFiles(); try { wfStateMachineCtx.getWorkflowExecutionStorageBridge().setWorkflowExecutionFinished(FinalWorkflowState.FAILED); } catch (WorkflowExecutionException e) { LOG.error(StringUtils.format("Failed to set final state of workflow %s (%s)", wfStateMachineCtx.getWorkflowExecutionContext().getInstanceName(), wfStateMachineCtx.getWorkflowExecutionContext().getExecutionIdentifier())); } state = WorkflowState.FAILED; } return state; } } }