use of org.apache.oozie.workflow.WorkflowException in project oozie by apache.
the class LiteWorkflowValidator method checkActionNode.
private void checkActionNode(NodeDef node) throws WorkflowException {
try {
Element action = XmlUtils.parseXml(node.getConf());
ActionService actionService = Services.get().get(ActionService.class);
boolean supportedAction = actionService.hasActionType(action.getName());
if (!supportedAction) {
throw new WorkflowException(ErrorCode.E0723, node.getName(), action.getName());
}
} catch (JDOMException ex) {
throw new WorkflowException(ErrorCode.E0700, "JDOMException: " + ex.getMessage());
}
}
use of org.apache.oozie.workflow.WorkflowException in project oozie by apache.
the class LiteWorkflowValidator method validateForkJoin.
/**
* This method recursively validates two things:
* - fork/join methods are properly paired
* - there are no multiple "okTo" paths to a given node
*
* Important: this method assumes that the workflow is not acyclic - therefore this must run after performBasicValidation()
*
* @param app The WorkflowApp
* @param node Current node we're checking
* @param currentFork Current fork node (null if we are not under a fork path)
* @param topDecisionParent The top (eldest) decision node along the path to this node, or null if there isn't one
* @param okPath false if node (or an ancestor of node) was gotten to via an "error to" transition or via a join node that has
* already been visited at least once before
* @param forkJoins Map that contains a mapping of fork-join node pairs.
* @param nodeAndDecisionParents Map that contains a mapping of nodes and their eldest decision node
* @throws WorkflowException If there is any of the constraints described above is violated
*/
private void validateForkJoin(LiteWorkflowApp app, NodeDef node, NodeDef currentFork, String topDecisionParent, boolean okPath, Deque<String> path, Map<String, String> forkJoins, Map<String, Optional<String>> nodeAndDecisionParents) throws WorkflowException {
final String nodeName = node.getName();
path.addLast(nodeName);
/* If we're walking an "okTo" path and the nodes are not Kill/Join/End, we have to make sure that only a single
* "okTo" path exists to the current node.
*
* The "topDecisionParent" represents the eldest decision in the chain that we've gone through. For example, let's assume
* that D1, D2, D3 are decision nodes and A is an action node.
*
* D1-->D2-->D3---> ... (rest of the WF)
* | | |
* | | |
* | | +----> +---+
* | +---------> | A |
* +-------------> +---+
*
* In this case, there are three "okTo" paths to "A" but it's still a valid workflow because the eldest decision node
* is D1 and during every run, there is only one possible execution path that leads to A (D1->A, D1->D2->A or
* (D1->D2->D3->A). In the code, if we encounter a decision node and we already have one, we don't update it. If it's null
* then we set it to the current decision node we're under.
*
* If the "current" and "top" parents are null, it means that we reached the node from two separate "okTo" paths, which is
* not acceptable.
*
* Also, if we have two distinct top decision parents it means that the node is reachable from two decision paths which
* are not "chained" (like in the example).
*
* It's worth noting that the last two examples can only occur in case of fork-join when we start to execute at least
* two separate paths in parallel. Without fork-join, multiple parents or two null parents would mean that there is a loop
* in the workflow but that should not happen since it has been validated.
*/
if (okPath && !(node instanceof KillNodeDef) && !(node instanceof JoinNodeDef) && !(node instanceof EndNodeDef)) {
// using Optional here so we can distinguish between "non-visited" and "visited - no parent" state.
Optional<String> decisionParentOpt = nodeAndDecisionParents.get(nodeName);
if (decisionParentOpt == null) {
nodeAndDecisionParents.put(node.getName(), Optional.fromNullable(topDecisionParent));
} else {
String decisionParent = decisionParentOpt.isPresent() ? decisionParentOpt.get() : null;
if ((decisionParent == null && topDecisionParent == null) || !Objects.equal(decisionParent, topDecisionParent)) {
throw new WorkflowException(ErrorCode.E0743, nodeName);
}
}
}
/* Fork-Join validation logic:
*
* At each Fork node, we recurse to every possible paths, changing the "currentFork" variable to the Fork node. We stop
* walking as soon as we encounter a Join node. At the Join node, we update the forkJoin mapping, which maintains
* the relationship between every fork-join pair (actually it's join->fork mapping). We check whether the join->fork
* mapping already contains another Fork node, which means that the Join is reachable from at least two distinct
* Fork nodes, so we terminate the validation.
*
* From the Join node, we don't recurse further. Therefore, all recursive calls return back to the point where we called
* validateForkJoin() from the Fork node in question.
*
* At this point, we have to check how many different Join nodes we've found at each different paths. We collect them to
* a set, then we make sure that we have only a single Join node for all Fork paths. Otherwise the workflow is broken.
*
* If we have only a single Join, then we get the transition node from the Join and go on with the recursive validation -
* this time we use the original "currentFork" variable that we have on the stack. With this approach, nested
* Fork-Joins are handled correctly.
*/
if (node instanceof ForkNodeDef) {
final List<String> transitions = node.getTransitions();
checkForkTransitions(app, transitions, node);
for (String t : transitions) {
NodeDef transition = app.getNode(t);
validateForkJoin(app, transition, node, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
}
// get the Join node for this ForkNode & validate it (we must have only one)
Set<String> joins = new HashSet<String>();
collectJoins(app, forkJoins, nodeName, joins);
checkJoins(joins, nodeName);
List<String> joinTransitions = app.getNode(joins.iterator().next()).getTransitions();
NodeDef next = app.getNode(joinTransitions.get(0));
validateForkJoin(app, next, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
} else if (node instanceof JoinNodeDef) {
if (currentFork == null) {
throw new WorkflowException(ErrorCode.E0742, node.getName());
}
// join --> fork mapping
String forkNode = forkJoins.get(nodeName);
if (forkNode == null) {
forkJoins.put(nodeName, currentFork.getName());
} else if (!forkNode.equals(currentFork.getName())) {
throw new WorkflowException(ErrorCode.E0758, node.getName(), forkNode + "," + currentFork);
}
} else if (node instanceof DecisionNodeDef) {
List<String> transitions = node.getTransitions();
// see explanation above - if we already have a topDecisionParent, we don't update it
String parentDecisionNode = topDecisionParent;
if (parentDecisionNode == null) {
parentDecisionNode = nodeName;
}
for (String t : transitions) {
NodeDef transition = app.getNode(t);
validateForkJoin(app, transition, currentFork, parentDecisionNode, okPath, path, forkJoins, nodeAndDecisionParents);
}
} else if (node instanceof KillNodeDef) {
// no op
} else if (node instanceof EndNodeDef) {
// is the current "End") and look at last node again so we know where we came from
if (currentFork != null) {
path.removeLast();
String previous = path.peekLast();
throw new WorkflowException(ErrorCode.E0737, previous, node.getName());
}
} else if (node instanceof ActionNodeDef) {
// "ok to" transition
String transition = node.getTransitions().get(0);
NodeDef okNode = app.getNode(transition);
validateForkJoin(app, okNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
// "error to" transition
transition = node.getTransitions().get(1);
NodeDef errorNode = app.getNode(transition);
validateForkJoin(app, errorNode, currentFork, topDecisionParent, false, path, forkJoins, nodeAndDecisionParents);
} else if (node instanceof StartNodeDef) {
// start always has only 1 transition
String transition = node.getTransitions().get(0);
NodeDef tranNode = app.getNode(transition);
validateForkJoin(app, tranNode, currentFork, topDecisionParent, okPath, path, forkJoins, nodeAndDecisionParents);
} else {
throw new WorkflowException(ErrorCode.E0740, node.getClass());
}
path.remove(nodeName);
}
use of org.apache.oozie.workflow.WorkflowException in project oozie by apache.
the class TestLiteWorkflowAppParser method testParserDefaultNameNodeFail.
public void testParserDefaultNameNodeFail() throws Exception {
LiteWorkflowAppParser parser = newLiteWorkflowAppParser();
// No default NN is set
try {
parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-namenode.xml", -1), new Configuration());
fail();
} catch (WorkflowException e) {
assertEquals(ErrorCode.E0701, e.getErrorCode());
assertTrue(e.getMessage().contains("No name-node defined"));
}
}
use of org.apache.oozie.workflow.WorkflowException in project oozie by apache.
the class TestLiteWorkflowAppParser method testParserDefaultJobTrackerFail.
public void testParserDefaultJobTrackerFail() throws Exception {
LiteWorkflowAppParser parser = newLiteWorkflowAppParser();
// No default NN is set
try {
parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-jobtracker.xml", -1), new Configuration());
fail();
} catch (WorkflowException e) {
assertEquals(ErrorCode.E0701, e.getErrorCode());
assertTrue(e.getMessage().contains("E0701: XML schema error, No job-tracker or resource-manager defined"));
}
}
use of org.apache.oozie.workflow.WorkflowException in project oozie by apache.
the class TestLiteWorkflowLib method testEmptyWorkflow.
public void testEmptyWorkflow() throws WorkflowException {
LiteWorkflowApp def = new LiteWorkflowApp("wf", "<worklfow-app/>", new StartNodeDef(TestControlNodeHandler.class, "end")).addNode(new EndNodeDef("end", TestControlNodeHandler.class));
final LiteWorkflowInstance job = new LiteWorkflowInstance(def, new XConfiguration(), "1");
assertEquals(WorkflowInstance.Status.PREP, job.getStatus());
job.start();
waitFor(5 * 1000, new Predicate() {
@Override
public boolean evaluate() throws Exception {
return job.getStatus() == WorkflowInstance.Status.SUCCEEDED;
}
});
assertEquals(WorkflowInstance.Status.SUCCEEDED, job.getStatus());
}
Aggregations