You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@oozie.apache.org by rk...@apache.org on 2015/07/15 01:33:01 UTC

oozie git commit: OOZIE-2187 Add a way to specify a default JT/RM and NN (rkanter)

Repository: oozie
Updated Branches:
  refs/heads/master 6a731f992 -> a762991ab


OOZIE-2187 Add a way to specify a default JT/RM and NN (rkanter)


Project: http://git-wip-us.apache.org/repos/asf/oozie/repo
Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/a762991a
Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/a762991a
Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/a762991a

Branch: refs/heads/master
Commit: a762991ab00df5e60d5e8b7f430b89861abe0671
Parents: 6a731f9
Author: Robert Kanter <rk...@cloudera.com>
Authored: Tue Jul 14 16:31:43 2015 -0700
Committer: Robert Kanter <rk...@cloudera.com>
Committed: Tue Jul 14 16:31:43 2015 -0700

----------------------------------------------------------------------
 .../org/apache/oozie/action/ActionExecutor.java |  23 +-
 .../oozie/action/hadoop/JavaActionExecutor.java |   9 +-
 .../workflow/lite/LiteWorkflowAppParser.java    | 357 ++++++++++---------
 core/src/main/resources/oozie-default.xml       |  21 ++
 .../lite/TestLiteWorkflowAppParser.java         | 270 ++++++++++++--
 .../wf-schema-no-jobtracker-global.xml          |  61 ++++
 .../test/resources/wf-schema-no-jobtracker.xml  |  57 +++
 .../resources/wf-schema-no-namenode-global.xml  |  61 ++++
 .../test/resources/wf-schema-no-namenode.xml    |  57 +++
 .../resources/wf-schema-valid-global-ext.xml    |  16 +-
 release-log.txt                                 |   1 +
 11 files changed, 738 insertions(+), 195 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/main/java/org/apache/oozie/action/ActionExecutor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/action/ActionExecutor.java b/core/src/main/java/org/apache/oozie/action/ActionExecutor.java
index 07c6d26..2be4549 100644
--- a/core/src/main/java/org/apache/oozie/action/ActionExecutor.java
+++ b/core/src/main/java/org/apache/oozie/action/ActionExecutor.java
@@ -60,8 +60,6 @@ public abstract class ActionExecutor {
      * Error code used by {@link #convertException} when there is not register error information for an exception.
      */
     public static final String ERROR_OTHER = "OTHER";
-    
-    public boolean requiresNNJT = false;
 
     public static enum RETRYPOLICY {
         EXPONENTIAL, PERIODIC
@@ -562,4 +560,25 @@ public abstract class ActionExecutor {
      */
     public abstract boolean isCompleted(String externalStatus);
 
+    /**
+     * Returns true if this action type requires a NameNode and JobTracker.  These can either be specified directly in the action
+     * via &lt;name-node&gt; and &lt;job-tracker&gt;, from the fields in the global section, or from their default values.  If
+     * false, Oozie won't ensure (i.e. won't throw an Exception if non-existant) that this action type has these values.
+     *
+     * @return true if a NameNode and JobTracker are required; false if not
+     */
+    public boolean requiresNameNodeJobTracker() {
+        return false;
+    }
+
+    /**
+     * Returns true if this action type supports a Configuration and JobXML.  In this case, Oozie will include the
+     * &lt;configuration&gt; and &lt;job-xml&gt; elements from the global section (if provided) with the action.  If false, Oozie
+     * won't add these.
+     *
+     * @return true if the global section's Configuration and JobXML should be given; false if not
+     */
+    public boolean supportsConfigurationJobXML() {
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/main/java/org/apache/oozie/action/hadoop/JavaActionExecutor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/action/hadoop/JavaActionExecutor.java b/core/src/main/java/org/apache/oozie/action/hadoop/JavaActionExecutor.java
index 492ceaf..6e959df 100644
--- a/core/src/main/java/org/apache/oozie/action/hadoop/JavaActionExecutor.java
+++ b/core/src/main/java/org/apache/oozie/action/hadoop/JavaActionExecutor.java
@@ -135,7 +135,6 @@ public class JavaActionExecutor extends ActionExecutor {
 
     protected JavaActionExecutor(String type) {
         super(type);
-        requiresNNJT = true;
     }
 
     public static List<Class> getCommonLauncherClasses() {
@@ -1628,4 +1627,12 @@ public class JavaActionExecutor extends ActionExecutor {
             }
         }
     }
+
+    public boolean requiresNameNodeJobTracker() {
+        return true;
+    }
+
+    public boolean supportsConfigurationJobXML() {
+        return true;
+    }
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowAppParser.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowAppParser.java b/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowAppParser.java
index c857011..d3a6523 100644
--- a/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowAppParser.java
+++ b/core/src/main/java/org/apache/oozie/workflow/lite/LiteWorkflowAppParser.java
@@ -93,6 +93,14 @@ public class LiteWorkflowAppParser {
     public static final String VALIDATE_FORK_JOIN = "oozie.validate.ForkJoin";
     public static final String WF_VALIDATE_FORK_JOIN = "oozie.wf.validate.ForkJoin";
 
+    public static final String DEFAULT_NAME_NODE = "oozie.actions.default.name-node";
+    public static final String DEFAULT_JOB_TRACKER = "oozie.actions.default.job-tracker";
+
+    private static final String JOB_TRACKER = "job-tracker";
+    private static final String NAME_NODE = "name-node";
+    private static final String JOB_XML = "job-xml";
+    private static final String CONFIGURATION = "configuration";
+
     private Schema schema;
     private Class<? extends ControlNodeHandler> controlNodeHandler;
     private Class<? extends DecisionNodeHandler> decisionHandlerClass;
@@ -121,6 +129,9 @@ public class LiteWorkflowAppParser {
     private List<NodeAndTopDecisionParent> visitedOkNodes = new ArrayList<NodeAndTopDecisionParent>();
     private List<String> visitedJoinNodes = new ArrayList<String>();
 
+    private String defaultNameNode;
+    private String defaultJobTracker;
+
     public LiteWorkflowAppParser(Schema schema,
                                  Class<? extends ControlNodeHandler> controlNodeHandler,
                                  Class<? extends DecisionNodeHandler> decisionHandlerClass,
@@ -129,6 +140,21 @@ public class LiteWorkflowAppParser {
         this.controlNodeHandler = controlNodeHandler;
         this.decisionHandlerClass = decisionHandlerClass;
         this.actionHandlerClass = actionHandlerClass;
+
+        defaultNameNode = ConfigurationService.get(DEFAULT_NAME_NODE);
+        if (defaultNameNode != null) {
+            defaultNameNode = defaultNameNode.trim();
+            if (defaultNameNode.isEmpty()) {
+                defaultNameNode = null;
+            }
+        }
+        defaultJobTracker = ConfigurationService.get(DEFAULT_JOB_TRACKER);
+        if (defaultJobTracker != null) {
+            defaultJobTracker = defaultJobTracker.trim();
+            if (defaultJobTracker.isEmpty()) {
+                defaultJobTracker = null;
+            }
+        }
     }
 
     public LiteWorkflowApp validateAndParse(Reader reader, Configuration jobConf) throws WorkflowException {
@@ -391,115 +417,78 @@ public class LiteWorkflowAppParser {
             throws WorkflowException {
         Namespace ns = root.getNamespace();
         LiteWorkflowApp def = null;
-        Element global = null;
+        GlobalSectionData gData = null;
         for (Element eNode : (List<Element>) root.getChildren()) {
             if (eNode.getName().equals(START_E)) {
                 def = new LiteWorkflowApp(root.getAttributeValue(NAME_A), strDef,
                                           new StartNodeDef(controlNodeHandler, eNode.getAttributeValue(TO_A)));
-            }
-            else {
-                if (eNode.getName().equals(END_E)) {
-                    def.addNode(new EndNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler));
+            } else if (eNode.getName().equals(END_E)) {
+                def.addNode(new EndNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler));
+            } else if (eNode.getName().equals(KILL_E)) {
+                def.addNode(new KillNodeDef(eNode.getAttributeValue(NAME_A),
+                                            eNode.getChildText(KILL_MESSAGE_E, ns), controlNodeHandler));
+            } else if (eNode.getName().equals(FORK_E)) {
+                List<String> paths = new ArrayList<String>();
+                for (Element tran : (List<Element>) eNode.getChildren(FORK_PATH_E, ns)) {
+                    paths.add(tran.getAttributeValue(FORK_START_A));
                 }
-                else {
-                    if (eNode.getName().equals(KILL_E)) {
-                        def.addNode(new KillNodeDef(eNode.getAttributeValue(NAME_A),
-                                                    eNode.getChildText(KILL_MESSAGE_E, ns), controlNodeHandler));
+                def.addNode(new ForkNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler, paths));
+            } else if (eNode.getName().equals(JOIN_E)) {
+                def.addNode(new JoinNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler, eNode.getAttributeValue(TO_A)));
+            } else if (eNode.getName().equals(DECISION_E)) {
+                Element eSwitch = eNode.getChild(DECISION_SWITCH_E, ns);
+                List<String> transitions = new ArrayList<String>();
+                for (Element e : (List<Element>) eSwitch.getChildren(DECISION_CASE_E, ns)) {
+                    transitions.add(e.getAttributeValue(TO_A));
+                }
+                transitions.add(eSwitch.getChild(DECISION_DEFAULT_E, ns).getAttributeValue(TO_A));
+
+                String switchStatement = XmlUtils.prettyPrint(eSwitch).toString();
+                def.addNode(new DecisionNodeDef(eNode.getAttributeValue(NAME_A), switchStatement, decisionHandlerClass,
+                                                transitions));
+            } else if (ACTION_E.equals(eNode.getName())) {
+                String[] transitions = new String[2];
+                Element eActionConf = null;
+                for (Element elem : (List<Element>) eNode.getChildren()) {
+                    if (ACTION_OK_E.equals(elem.getName())) {
+                        transitions[0] = elem.getAttributeValue(TO_A);
+                    } else if (ACTION_ERROR_E.equals(elem.getName())) {
+                        transitions[1] = elem.getAttributeValue(TO_A);
+                    } else if (SLA_INFO.equals(elem.getName()) || CREDENTIALS.equals(elem.getName())) {
+                        continue;
+                    } else {
+                        eActionConf = elem;
+                        handleDefaultsAndGlobal(gData, configDefault, elem);
                     }
-                    else {
-                        if (eNode.getName().equals(FORK_E)) {
-                            List<String> paths = new ArrayList<String>();
-                            for (Element tran : (List<Element>) eNode.getChildren(FORK_PATH_E, ns)) {
-                                paths.add(tran.getAttributeValue(FORK_START_A));
-                            }
-                            def.addNode(new ForkNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler, paths));
-                        }
-                        else {
-                            if (eNode.getName().equals(JOIN_E)) {
-                                def.addNode(new JoinNodeDef(eNode.getAttributeValue(NAME_A), controlNodeHandler,
-                                                            eNode.getAttributeValue(TO_A)));
-                            }
-                            else {
-                                if (eNode.getName().equals(DECISION_E)) {
-                                    Element eSwitch = eNode.getChild(DECISION_SWITCH_E, ns);
-                                    List<String> transitions = new ArrayList<String>();
-                                    for (Element e : (List<Element>) eSwitch.getChildren(DECISION_CASE_E, ns)) {
-                                        transitions.add(e.getAttributeValue(TO_A));
-                                    }
-                                    transitions.add(eSwitch.getChild(DECISION_DEFAULT_E, ns).getAttributeValue(TO_A));
-
-                                    String switchStatement = XmlUtils.prettyPrint(eSwitch).toString();
-                                    def.addNode(new DecisionNodeDef(eNode.getAttributeValue(NAME_A), switchStatement, decisionHandlerClass,
-                                                                    transitions));
-                                }
-                                else {
-                                    if (ACTION_E.equals(eNode.getName())) {
-                                        String[] transitions = new String[2];
-                                        Element eActionConf = null;
-                                        for (Element elem : (List<Element>) eNode.getChildren()) {
-                                            if (ACTION_OK_E.equals(elem.getName())) {
-                                                transitions[0] = elem.getAttributeValue(TO_A);
-                                            }
-                                            else {
-                                                if (ACTION_ERROR_E.equals(elem.getName())) {
-                                                    transitions[1] = elem.getAttributeValue(TO_A);
-                                                }
-                                                else {
-                                                    if (SLA_INFO.equals(elem.getName()) || CREDENTIALS.equals(elem.getName())) {
-                                                        continue;
-                                                    }
-                                                    else {
-                                                        eActionConf = elem;
-                                                        handleGlobal(ns, global, configDefault, elem);
-                                                        }
-                                                }
-                                            }
-                                        }
-
-                                        String credStr = eNode.getAttributeValue(CRED_A);
-                                        String userRetryMaxStr = eNode.getAttributeValue(USER_RETRY_MAX_A);
-                                        String userRetryIntervalStr = eNode.getAttributeValue(USER_RETRY_INTERVAL_A);
-                                        try {
-                                            if (!StringUtils.isEmpty(userRetryMaxStr)) {
-                                                userRetryMaxStr = ELUtils.resolveAppName(userRetryMaxStr, jobConf);
-                                            }
-                                            if (!StringUtils.isEmpty(userRetryIntervalStr)) {
-                                                userRetryIntervalStr = ELUtils.resolveAppName(userRetryIntervalStr,
-                                                        jobConf);
-                                            }
-                                        }
-                                        catch (Exception e) {
-                                            throw new WorkflowException(ErrorCode.E0703, e.getMessage());
-                                        }
-
-                                        String actionConf = XmlUtils.prettyPrint(eActionConf).toString();
-                                        def.addNode(new ActionNodeDef(eNode.getAttributeValue(NAME_A), actionConf, actionHandlerClass,
-                                                                      transitions[0], transitions[1], credStr,
-                                                                      userRetryMaxStr, userRetryIntervalStr));
-                                    }
-                                    else {
-                                        if (SLA_INFO.equals(eNode.getName()) || CREDENTIALS.equals(eNode.getName())) {
-                                            // No operation is required
-                                        }
-                                        else {
-                                            if (eNode.getName().equals(GLOBAL)) {
-                                                global = eNode;
-                                            }
-                                            else {
-                                                if (eNode.getName().equals(PARAMETERS)) {
-                                                    // No operation is required
-                                                }
-                                                else {
-                                                    throw new WorkflowException(ErrorCode.E0703, eNode.getName());
-                                                }
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
+                }
+
+                String credStr = eNode.getAttributeValue(CRED_A);
+                String userRetryMaxStr = eNode.getAttributeValue(USER_RETRY_MAX_A);
+                String userRetryIntervalStr = eNode.getAttributeValue(USER_RETRY_INTERVAL_A);
+                try {
+                    if (!StringUtils.isEmpty(userRetryMaxStr)) {
+                        userRetryMaxStr = ELUtils.resolveAppName(userRetryMaxStr, jobConf);
                     }
+                    if (!StringUtils.isEmpty(userRetryIntervalStr)) {
+                        userRetryIntervalStr = ELUtils.resolveAppName(userRetryIntervalStr, jobConf);
+                    }
+                }
+                catch (Exception e) {
+                    throw new WorkflowException(ErrorCode.E0703, e.getMessage());
                 }
+
+                String actionConf = XmlUtils.prettyPrint(eActionConf).toString();
+                def.addNode(new ActionNodeDef(eNode.getAttributeValue(NAME_A), actionConf, actionHandlerClass,
+                                              transitions[0], transitions[1], credStr,
+                                              userRetryMaxStr, userRetryIntervalStr));
+            } else if (SLA_INFO.equals(eNode.getName()) || CREDENTIALS.equals(eNode.getName())) {
+                // No operation is required
+            } else if (eNode.getName().equals(GLOBAL)) {
+                gData = parseGlobalSection(ns, eNode);
+            } else if (eNode.getName().equals(PARAMETERS)) {
+                // No operation is required
+            } else {
+                throw new WorkflowException(ErrorCode.E0703, eNode.getName());
             }
         }
         return def;
@@ -575,85 +564,138 @@ public class LiteWorkflowAppParser {
         traversed.put(node.getName(), VisitStatus.VISITED);
     }
 
-    /**
-     * Handle the global section
-     *
-     * @param ns
-     * @param global
-     * @param eActionConf
-     * @throws WorkflowException
-     */
-
-    @SuppressWarnings("unchecked")
-    private void handleGlobal(Namespace ns, Element global, Configuration configDefault, Element eActionConf)
-            throws WorkflowException {
+    private void addChildElement(Element parent, Namespace ns, String childName, String childValue) {
+        Element child = new Element(childName, ns);
+        child.setText(childValue);
+        parent.addContent(child);
+    }
 
-        // Use the action's namespace when getting children of the action (will
-        // be different than ns for extension actions)
-        Namespace actionNs = eActionConf.getNamespace();
+    private class GlobalSectionData {
+        final String jobTracker;
+        final String nameNode;
+        final List<String> jobXmls;
+        final Configuration conf;
+
+        public GlobalSectionData(String jobTracker, String nameNode, List<String> jobXmls, Configuration conf) {
+            this.jobTracker = jobTracker;
+            this.nameNode = nameNode;
+            this.jobXmls = jobXmls;
+            this.conf = conf;
+        }
+    }
 
+    private GlobalSectionData parseGlobalSection(Namespace ns, Element global) throws WorkflowException {
+        GlobalSectionData gData = null;
         if (global != null) {
-            Element globalJobTracker = global.getChild("job-tracker", ns);
-            Element globalNameNode = global.getChild("name-node", ns);
-            List<Element> globalJobXml = global.getChildren("job-xml", ns);
-            Element globalConfiguration = global.getChild("configuration", ns);
+            String globalJobTracker = null;
+            Element globalJobTrackerElement = global.getChild(JOB_TRACKER, ns);
+            if (globalJobTrackerElement != null) {
+                globalJobTracker = globalJobTrackerElement.getValue();
+            }
 
-            if (globalJobTracker != null && eActionConf.getChild("job-tracker", actionNs) == null) {
-                Element jobTracker = new Element("job-tracker", actionNs);
-                jobTracker.setText(globalJobTracker.getText());
-                eActionConf.addContent(jobTracker);
+            String globalNameNode = null;
+            Element globalNameNodeElement = global.getChild(NAME_NODE, ns);
+            if (globalNameNodeElement != null) {
+                globalNameNode = globalNameNodeElement.getValue();
             }
 
-            if (globalNameNode != null && eActionConf.getChild("name-node", actionNs) == null) {
-                Element nameNode = new Element("name-node", actionNs);
-                nameNode.setText(globalNameNode.getText());
-                eActionConf.addContent(nameNode);
+            List<String> globalJobXmls = null;
+            @SuppressWarnings("unchecked")
+            List<Element> globalJobXmlElements = global.getChildren(JOB_XML, ns);
+            if (!globalJobXmlElements.isEmpty()) {
+                globalJobXmls = new ArrayList<String>(globalJobXmlElements.size());
+                for(Element jobXmlElement: globalJobXmlElements) {
+                    globalJobXmls.add(jobXmlElement.getText());
+                }
             }
 
-            if (!globalJobXml.isEmpty()) {
-                List<Element> actionJobXml = eActionConf.getChildren("job-xml", actionNs);
-                for(Element jobXml: globalJobXml){
+            Configuration globalConf = null;
+            Element globalConfigurationElement = global.getChild(CONFIGURATION, ns);
+            if (globalConfigurationElement != null) {
+                try {
+                    globalConf = new XConfiguration(new StringReader(XmlUtils.prettyPrint(globalConfigurationElement).toString()));
+                } catch (IOException ioe) {
+                    throw new WorkflowException(ErrorCode.E0700, "Error while processing global section conf");
+                }
+            }
+            gData = new GlobalSectionData(globalJobTracker, globalNameNode, globalJobXmls, globalConf);
+        }
+        return gData;
+    }
+
+    private void handleDefaultsAndGlobal(GlobalSectionData gData, Configuration configDefault, Element actionElement)
+            throws WorkflowException {
+        ActionExecutor ae = Services.get().get(ActionService.class).getExecutor(actionElement.getName());
+        if (ae == null) {
+            throw new WorkflowException(ErrorCode.E0723, actionElement.getName(), ActionService.class.getName());
+        }
+
+        Namespace actionNs = actionElement.getNamespace();
+
+        if (ae.requiresNameNodeJobTracker()) {
+            if (actionElement.getChild(NAME_NODE, actionNs) == null) {
+                if (gData != null && gData.nameNode != null) {
+                    addChildElement(actionElement, actionNs, NAME_NODE, gData.nameNode);
+                } else if (defaultNameNode != null) {
+                    addChildElement(actionElement, actionNs, NAME_NODE, defaultNameNode);
+                } else {
+                    throw new WorkflowException(ErrorCode.E0701, "No " + NAME_NODE + " defined");
+                }
+            }
+            if (actionElement.getChild(JOB_TRACKER, actionNs) == null) {
+                if (gData != null && gData.jobTracker != null) {
+                    addChildElement(actionElement, actionNs, JOB_TRACKER, gData.jobTracker);
+                } else if (defaultJobTracker != null) {
+                    addChildElement(actionElement, actionNs, JOB_TRACKER, defaultJobTracker);
+                } else {
+                    throw new WorkflowException(ErrorCode.E0701, "No " + JOB_TRACKER + " defined");
+                }
+            }
+        }
+
+        if (ae.supportsConfigurationJobXML()) {
+            @SuppressWarnings("unchecked")
+            List<Element> actionJobXmls = actionElement.getChildren(JOB_XML, actionNs);
+            if (gData != null && gData.jobXmls != null) {
+                for(String gJobXml : gData.jobXmls) {
                     boolean alreadyExists = false;
-                    for(Element actionXml: actionJobXml){
-                        if(jobXml.getText().equals(actionXml.getText())){
+                    for (Element actionXml : actionJobXmls) {
+                        if (gJobXml.equals(actionXml.getText())) {
                             alreadyExists = true;
                             break;
                         }
                     }
-
-                    if (!alreadyExists){
-                        Element ejobXml = new Element("job-xml", actionNs);
-                        ejobXml.setText(jobXml.getText());
-                        eActionConf.addContent(ejobXml);
+                    if (!alreadyExists) {
+                        Element ejobXml = new Element(JOB_XML, actionNs);
+                        ejobXml.setText(gJobXml);
+                        actionElement.addContent(ejobXml);
                     }
-
                 }
             }
+
             try {
                 XConfiguration actionConf = new XConfiguration();
                 if (configDefault != null)
                     XConfiguration.copy(configDefault, actionConf);
-                if (globalConfiguration != null) {
-                    Configuration globalConf = new XConfiguration(new StringReader(XmlUtils.prettyPrint(
-                            globalConfiguration).toString()));
-                    XConfiguration.copy(globalConf, actionConf);
+                if (gData != null && gData.conf != null) {
+                    XConfiguration.copy(gData.conf, actionConf);
                 }
-                Element actionConfiguration = eActionConf.getChild("configuration", actionNs);
+                Element actionConfiguration = actionElement.getChild(CONFIGURATION, actionNs);
                 if (actionConfiguration != null) {
                     //copy and override
-                    XConfiguration.copy(new XConfiguration(new StringReader(XmlUtils.prettyPrint(
-                            actionConfiguration).toString())), actionConf);
+                    XConfiguration.copy(new XConfiguration(new StringReader(XmlUtils.prettyPrint(actionConfiguration).toString())),
+                            actionConf);
                 }
-                int position = eActionConf.indexOf(actionConfiguration);
-                eActionConf.removeContent(actionConfiguration); //replace with enhanced one
+                int position = actionElement.indexOf(actionConfiguration);
+                actionElement.removeContent(actionConfiguration); //replace with enhanced one
                 Element eConfXml = XmlUtils.parseXml(actionConf.toXmlString(false));
                 eConfXml.detach();
                 eConfXml.setNamespace(actionNs);
                 if (position > 0) {
-                    eActionConf.addContent(position, eConfXml);
+                    actionElement.addContent(position, eConfXml);
                 }
                 else {
-                    eActionConf.addContent(eConfXml);
+                    actionElement.addContent(eConfXml);
                 }
             }
             catch (IOException e) {
@@ -663,21 +705,6 @@ public class LiteWorkflowAppParser {
                 throw new WorkflowException(ErrorCode.E0700, "Error while processing action conf");
             }
         }
-        else {
-            ActionExecutor ae = Services.get().get(ActionService.class).getExecutor(eActionConf.getName());
-            if (ae == null) {
-                throw new WorkflowException(ErrorCode.E0723, eActionConf.getName(), ActionService.class.getName());
-            }
-            if (ae.requiresNNJT) {
-
-                if (eActionConf.getChild("name-node", actionNs) == null) {
-                    throw new WorkflowException(ErrorCode.E0701, "No name-node defined");
-                }
-                if (eActionConf.getChild("job-tracker", actionNs) == null) {
-                    throw new WorkflowException(ErrorCode.E0701, "No job-tracker defined");
-                }
-            }
-        }
     }
 
 }

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/main/resources/oozie-default.xml
----------------------------------------------------------------------
diff --git a/core/src/main/resources/oozie-default.xml b/core/src/main/resources/oozie-default.xml
index 4dc127e..2f44827 100644
--- a/core/src/main/resources/oozie-default.xml
+++ b/core/src/main/resources/oozie-default.xml
@@ -2543,4 +2543,25 @@
             Set it false if there is any security concern.
         </description>
     </property>
+
+    <property>
+        <name>oozie.actions.default.name-node</name>
+        <value> </value>
+        <description>
+            The default value to use for the &lt;name-node&gt; element in applicable action types.  This value will be used when
+            neither the action itself nor the global section specifies a &lt;name-node&gt;.  As expected, it should be of the form
+            "hdfs://HOST:PORT".
+        </description>
+    </property>
+
+    <property>
+        <name>oozie.actions.default.job-tracker</name>
+        <value> </value>
+        <description>
+            The default value to use for the &lt;job-tracker&gt; element in applicable action types.  This value will be used when
+            neither the action itself nor the global section specifies a &lt;job-tracker&gt;.  As expected, it should be of the form
+            "HOST:PORT".
+        </description>
+    </property>
+
 </configuration>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java b/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
index 0eb1ee0..1fc1736 100644
--- a/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
+++ b/core/src/test/java/org/apache/oozie/workflow/lite/TestLiteWorkflowAppParser.java
@@ -27,6 +27,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.oozie.service.ActionService;
+import org.apache.oozie.service.ConfigurationService;
 import org.apache.oozie.service.LiteWorkflowStoreService;
 import org.apache.oozie.service.SchemaService;
 import org.apache.oozie.service.Services;
@@ -47,7 +48,7 @@ public class TestLiteWorkflowAppParser extends XTestCase {
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        setSystemProperty("oozie.service.SchemaService.wf.ext.schemas", "hive-action-0.2.xsd");
+        setSystemProperty("oozie.service.SchemaService.wf.ext.schemas", "hive-action-0.2.xsd,email-action-0.2.xsd");
         new Services().init();
     }
 
@@ -57,6 +58,13 @@ public class TestLiteWorkflowAppParser extends XTestCase {
         super.tearDown();
     }
 
+    private String cleanupXml(String xml) {
+        xml = xml.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
+        xml = xml.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
+        xml = xml.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
+        return xml;
+    }
+
     public void testParserGlobal() throws Exception {
         LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
             LiteWorkflowStoreService.LiteControlNodeHandler.class,
@@ -79,8 +87,8 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "  </streaming>\r\n" +
              "  <file>/tmp</file>\r\n" +
              "  <archive>/tmp</archive>\r\n" +
-             "  <job-tracker>${foo}</job-tracker>\r\n" +
              "  <name-node>bar</name-node>\r\n" +
+             "  <job-tracker>${foo}</job-tracker>\r\n" +
              "  <configuration>\r\n" +
              "    <property>\r\n" +
              "      <name>b</name>\r\n" +
@@ -92,11 +100,8 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "    </property>\r\n" +
              "  </configuration>\r\n" +
              "</map-reduce>";
-        d = d.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
-        d = d.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
-        d = d.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
-        System.out.println("\n" + d +"\n");
-        assertEquals(expectedD.replaceAll(" ",""), d.replaceAll(" ", ""));
+        d = cleanupXml(d);
+        assertEquals(expectedD.replaceAll(" ", ""), d.replaceAll(" ", ""));
 
     }
 
@@ -123,8 +128,8 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "  <job-xml>/tmp</job-xml>\r\n" +
              "  <file>/tmp</file>\r\n" +
              "  <archive>/tmp</archive>\r\n" +
-             "  <job-tracker>foo</job-tracker>\r\n" +
              "  <name-node>bar</name-node>\r\n" +
+             "  <job-tracker>foo</job-tracker>\r\n" +
              "  <job-xml>/spam1</job-xml>\r\n" +
              "  <job-xml>/spam2</job-xml>\r\n" +
              "  <configuration>\r\n" +
@@ -138,10 +143,8 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "    </property>\r\n" +
              "  </configuration>\r\n" +
              "</map-reduce>";
-        d = d.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
-        d = d.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
-        d = d.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
-        assertEquals(expectedD.replaceAll(" ",""), d.replaceAll(" ", ""));
+        d = cleanupXml(d);
+        assertEquals(expectedD.replaceAll(" ", ""), d.replaceAll(" ", ""));
 
     }
 
@@ -175,12 +178,10 @@ public class TestLiteWorkflowAppParser extends XTestCase {
                 "  <param>x</param>\r\n" +
                 "  <file>/tmp</file>\r\n" +
                 "  <file>/tmp</file>\r\n" +
-                "  <job-tracker>${foo}</job-tracker>\r\n" +
                 "  <name-node>bar</name-node>\r\n" +
+                "  <job-tracker>${foo}</job-tracker>\r\n" +
                 "</pig>";
-        e = e.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
-        e = e.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
-        e = e.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
+        e = cleanupXml(e);
         assertEquals(expectedE.replaceAll(" ", ""), e.replaceAll(" ", ""));
 
     }
@@ -218,12 +219,10 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "  <script>script.q</script>\r\n" +
              "  <param>INPUT=/tmp/table</param>\r\n" +
              "  <param>OUTPUT=/tmp/hive</param>\r\n" +
-             "  <job-tracker>foo</job-tracker>\r\n" +
              "  <name-node>bar</name-node>\r\n" +
+             "  <job-tracker>foo</job-tracker>\r\n" +
              "</hive>";
-        a = a.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
-        a = a.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
-        a = a.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
+        a = cleanupXml(a);
         assertEquals(expectedA.replaceAll(" ",""), a.replaceAll(" ", ""));
     }
 
@@ -258,10 +257,29 @@ public class TestLiteWorkflowAppParser extends XTestCase {
              "  <arg>/tmp/data.txt</arg>\r\n" +
              "  <arg>/tmp2/data.txt</arg>\r\n" +
              "</distcp>";
-        b = b.replaceAll(" xmlns=?(\"|\')(\"|\')", "");
-        b = b.replaceAll("\\s*<source>.*</source>", "");    // remove the <source> added by Hadoop 2
-        b = b.replaceAll("\\s*<!--Loaded from Unknown-->", "");   // remove the <!--LoadedfromUnknown--> added by Hadoop 1.2.1
-        assertEquals(expectedB.replaceAll(" ",""), b.replaceAll(" ", ""));
+        b = cleanupXml(b);
+        assertEquals(expectedB.replaceAll(" ", ""), b.replaceAll(" ", ""));
+    }
+
+    public void testParserGlobalExtensionActionsNotApplicable() throws Exception {
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        // Not all actions want a JT, NN, conf, or jobxml (e.g. email action)
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-valid-global-ext.xml", -1),
+                new Configuration());
+
+        String c1 = app.getNode("c1").getConf();
+        String expectedC1 =
+                "<email xmlns=\"uri:oozie:email-action:0.2\">\r\n" +
+                "  <to>foo@bar.com</to>\r\n" +
+                "  <subject>foo</subject>\r\n" +
+                "  <body>bar</body>\r\n" +
+                "</email>";
+        c1 = cleanupXml(c1);
+        assertEquals(expectedC1.replaceAll(" ", ""), c1.replaceAll(" ", ""));
     }
 
     public void testParserGlobalExtensionActionsNoGlobal() throws Exception {
@@ -288,6 +306,210 @@ public class TestLiteWorkflowAppParser extends XTestCase {
         }
     }
 
+    public void testParserDefaultNameNode() throws Exception {
+        ConfigurationService.set("oozie.actions.default.name-node", "default-nn");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-namenode.xml", -1),
+                new Configuration());
+        String a = app.getNode("a").getConf();
+        String expectedA =
+                "<hive xmlns=\"uri:oozie:hive-action:0.2\">\r\n" +
+                        "  <prepare>\r\n" +
+                        "    <delete path=\"/tmp\" />\r\n" +
+                        "    <mkdir path=\"/tmp\" />\r\n" +
+                        "  </prepare>\r\n" +
+                        "  <job-tracker>foo</job-tracker>\r\n" +
+                        "  <configuration>\r\n" +
+                        "    <property>\r\n" +
+                        "      <name>c</name>\r\n" +
+                        "      <value>C</value>\r\n" +
+                        "    </property>\r\n" +
+                        "  </configuration>\r\n" +
+                        "  <script>script.q</script>\r\n" +
+                        "  <param>INPUT=/tmp/table</param>\r\n" +
+                        "  <param>OUTPUT=/tmp/hive</param>\r\n" +
+                        "  <name-node>default-nn</name-node>\r\n" +
+                        "</hive>";
+        a = cleanupXml(a);
+        assertEquals(expectedA.replaceAll(" ", ""), a.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultNameNodeWithGlobal() throws Exception {
+        ConfigurationService.set("oozie.actions.default.name-node", "default-nn");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-namenode-global.xml", -1),
+                new Configuration());
+        String a = app.getNode("a").getConf();
+        String expectedA =
+                "<hive xmlns=\"uri:oozie:hive-action:0.2\">\r\n" +
+                        "  <prepare>\r\n" +
+                        "    <delete path=\"/tmp\" />\r\n" +
+                        "    <mkdir path=\"/tmp\" />\r\n" +
+                        "  </prepare>\r\n" +
+                        "  <job-tracker>foo</job-tracker>\r\n" +
+                        "  <configuration>\r\n" +
+                        "    <property>\r\n" +
+                        "      <name>c</name>\r\n" +
+                        "      <value>C</value>\r\n" +
+                        "    </property>\r\n" +
+                        "  </configuration>\r\n" +
+                        "  <script>script.q</script>\r\n" +
+                        "  <param>INPUT=/tmp/table</param>\r\n" +
+                        "  <param>OUTPUT=/tmp/hive</param>\r\n" +
+                        "  <name-node>global-nn</name-node>\r\n" +
+                        "</hive>";
+        a = cleanupXml(a);
+        assertEquals(expectedA.replaceAll(" ", ""), a.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultNameNodeNotApplicable() throws Exception {
+        ConfigurationService.set("oozie.actions.default.name-node", "default-nn");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        // Not all actions want a NN (e.g. email action)
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-namenode.xml", -1),
+                new Configuration());
+        String b1 = app.getNode("b1").getConf();
+        String expectedB1 =
+                "<email xmlns=\"uri:oozie:email-action:0.2\">\r\n" +
+                        "  <to>foo@bar.com</to>\r\n" +
+                        "  <subject>foo</subject>\r\n" +
+                        "  <body>bar</body>\r\n" +
+                        "</email>";
+        b1 = cleanupXml(b1);
+        assertEquals(expectedB1.replaceAll(" ", ""), b1.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultNameNodeFail() throws Exception {
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        // No default NN is set
+        try {
+            LiteWorkflowApp app = 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"));
+        }
+    }
+
+    public void testParserDefaultJobTracker() throws Exception {
+        ConfigurationService.set("oozie.actions.default.job-tracker", "default-jt");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-jobtracker.xml", -1),
+                new Configuration());
+        String a = app.getNode("a").getConf();
+        String expectedA =
+                "<hive xmlns=\"uri:oozie:hive-action:0.2\">\r\n" +
+                        "  <prepare>\r\n" +
+                        "    <delete path=\"/tmp\" />\r\n" +
+                        "    <mkdir path=\"/tmp\" />\r\n" +
+                        "  </prepare>\r\n" +
+                        "  <name-node>bar</name-node>\r\n" +
+                        "  <configuration>\r\n" +
+                        "    <property>\r\n" +
+                        "      <name>c</name>\r\n" +
+                        "      <value>C</value>\r\n" +
+                        "    </property>\r\n" +
+                        "  </configuration>\r\n" +
+                        "  <script>script.q</script>\r\n" +
+                        "  <param>INPUT=/tmp/table</param>\r\n" +
+                        "  <param>OUTPUT=/tmp/hive</param>\r\n" +
+                        "  <job-tracker>default-jt</job-tracker>\r\n" +
+                        "</hive>";
+        a = cleanupXml(a);
+        assertEquals(expectedA.replaceAll(" ", ""), a.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultJobTrackerWithGlobal() throws Exception {
+        ConfigurationService.set("oozie.actions.default.job-tracker", "default-jt");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-jobtracker-global.xml", -1),
+                new Configuration());
+        String a = app.getNode("a").getConf();
+        String expectedA =
+                "<hive xmlns=\"uri:oozie:hive-action:0.2\">\r\n" +
+                        "  <prepare>\r\n" +
+                        "    <delete path=\"/tmp\" />\r\n" +
+                        "    <mkdir path=\"/tmp\" />\r\n" +
+                        "  </prepare>\r\n" +
+                        "  <name-node>bar</name-node>\r\n" +
+                        "  <configuration>\r\n" +
+                        "    <property>\r\n" +
+                        "      <name>c</name>\r\n" +
+                        "      <value>C</value>\r\n" +
+                        "    </property>\r\n" +
+                        "  </configuration>\r\n" +
+                        "  <script>script.q</script>\r\n" +
+                        "  <param>INPUT=/tmp/table</param>\r\n" +
+                        "  <param>OUTPUT=/tmp/hive</param>\r\n" +
+                        "  <job-tracker>global-jt</job-tracker>\r\n" +
+                        "</hive>";
+        a = cleanupXml(a);
+        assertEquals(expectedA.replaceAll(" ", ""), a.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultJobTrackerNotApplicable() throws Exception {
+        ConfigurationService.set("oozie.actions.default.job-tracker", "default-jt");
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        // Not all actions want a NN (e.g. email action)
+        LiteWorkflowApp app = parser.validateAndParse(IOUtils.getResourceAsReader("wf-schema-no-jobtracker.xml", -1),
+                new Configuration());
+        String b1 = app.getNode("b1").getConf();
+        String expectedB1 =
+                "<email xmlns=\"uri:oozie:email-action:0.2\">\r\n" +
+                        "  <to>foo@bar.com</to>\r\n" +
+                        "  <subject>foo</subject>\r\n" +
+                        "  <body>bar</body>\r\n" +
+                        "</email>";
+        b1 = cleanupXml(b1);
+        assertEquals(expectedB1.replaceAll(" ", ""), b1.replaceAll(" ", ""));
+    }
+
+    public void testParserDefaultJobTrackerFail() throws Exception {
+        LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
+                LiteWorkflowStoreService.LiteControlNodeHandler.class,
+                LiteWorkflowStoreService.LiteDecisionHandler.class,
+                LiteWorkflowStoreService.LiteActionHandler.class);
+
+        // No default NN is set
+        try {
+            LiteWorkflowApp app = 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("No job-tracker defined"));
+        }
+    }
+
     public void testParser() throws Exception {
         LiteWorkflowAppParser parser = new LiteWorkflowAppParser(null,
                                                                  LiteWorkflowStoreService.LiteControlNodeHandler.class,

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/resources/wf-schema-no-jobtracker-global.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/wf-schema-no-jobtracker-global.xml b/core/src/test/resources/wf-schema-no-jobtracker-global.xml
new file mode 100644
index 0000000..433b6b8
--- /dev/null
+++ b/core/src/test/resources/wf-schema-no-jobtracker-global.xml
@@ -0,0 +1,61 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.4" name="test-wf">
+    <global>
+        <job-tracker>global-jt</job-tracker>
+    </global>
+
+    <start to="a"/>
+
+    <action name="a">
+        <hive xmlns="uri:oozie:hive-action:0.2">
+            <prepare>
+                <delete path="/tmp"/>
+                <mkdir path="/tmp"/>
+            </prepare>
+            <name-node>bar</name-node>
+            <configuration>
+                <property>
+                    <name>c</name>
+                    <value>C</value>
+                </property>
+            </configuration>
+            <script>script.q</script>
+            <param>INPUT=/tmp/table</param>
+            <param>OUTPUT=/tmp/hive</param>
+        </hive>
+        <ok to="b2"/>
+        <error to="b1"/>
+    </action>
+
+    <action name="b1">
+        <email xmlns="uri:oozie:email-action:0.2">
+            <to>foo@bar.com</to>
+            <subject>foo</subject>
+            <body>bar</body>
+        </email>
+        <ok to="b2"/>
+        <error to="b2"/>
+    </action>
+
+    <kill name="b2">
+        <message>fail</message>
+    </kill>
+
+    <end name="c"/>
+</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/resources/wf-schema-no-jobtracker.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/wf-schema-no-jobtracker.xml b/core/src/test/resources/wf-schema-no-jobtracker.xml
new file mode 100644
index 0000000..2eea734
--- /dev/null
+++ b/core/src/test/resources/wf-schema-no-jobtracker.xml
@@ -0,0 +1,57 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.4" name="test-wf">
+    <start to="a"/>
+
+    <action name="a">
+        <hive xmlns="uri:oozie:hive-action:0.2">
+            <prepare>
+                <delete path="/tmp"/>
+                <mkdir path="/tmp"/>
+            </prepare>
+            <name-node>bar</name-node>
+            <configuration>
+                <property>
+                    <name>c</name>
+                    <value>C</value>
+                </property>
+            </configuration>
+            <script>script.q</script>
+            <param>INPUT=/tmp/table</param>
+            <param>OUTPUT=/tmp/hive</param>
+        </hive>
+        <ok to="b2"/>
+        <error to="b1"/>
+    </action>
+
+    <action name="b1">
+        <email xmlns="uri:oozie:email-action:0.2">
+            <to>foo@bar.com</to>
+            <subject>foo</subject>
+            <body>bar</body>
+        </email>
+        <ok to="b2"/>
+        <error to="b2"/>
+    </action>
+
+    <kill name="b2">
+        <message>fail</message>
+    </kill>
+
+    <end name="c"/>
+</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/resources/wf-schema-no-namenode-global.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/wf-schema-no-namenode-global.xml b/core/src/test/resources/wf-schema-no-namenode-global.xml
new file mode 100644
index 0000000..874ec52
--- /dev/null
+++ b/core/src/test/resources/wf-schema-no-namenode-global.xml
@@ -0,0 +1,61 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.4" name="test-wf">
+    <global>
+        <name-node>global-nn</name-node>
+    </global>
+
+    <start to="a"/>
+
+    <action name="a">
+        <hive xmlns="uri:oozie:hive-action:0.2">
+            <prepare>
+                <delete path="/tmp"/>
+                <mkdir path="/tmp"/>
+            </prepare>
+            <job-tracker>foo</job-tracker>
+            <configuration>
+                <property>
+                    <name>c</name>
+                    <value>C</value>
+                </property>
+            </configuration>
+            <script>script.q</script>
+            <param>INPUT=/tmp/table</param>
+            <param>OUTPUT=/tmp/hive</param>
+        </hive>
+        <ok to="b2"/>
+        <error to="b1"/>
+    </action>
+
+    <action name="b1">
+        <email xmlns="uri:oozie:email-action:0.2">
+            <to>foo@bar.com</to>
+            <subject>foo</subject>
+            <body>bar</body>
+        </email>
+        <ok to="b2"/>
+        <error to="b2"/>
+    </action>
+
+    <kill name="b2">
+        <message>fail</message>
+    </kill>
+
+    <end name="c"/>
+</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/resources/wf-schema-no-namenode.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/wf-schema-no-namenode.xml b/core/src/test/resources/wf-schema-no-namenode.xml
new file mode 100644
index 0000000..14ff8d4
--- /dev/null
+++ b/core/src/test/resources/wf-schema-no-namenode.xml
@@ -0,0 +1,57 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<workflow-app xmlns="uri:oozie:workflow:0.4" name="test-wf">
+    <start to="a"/>
+
+    <action name="a">
+        <hive xmlns="uri:oozie:hive-action:0.2">
+            <prepare>
+                <delete path="/tmp"/>
+                <mkdir path="/tmp"/>
+            </prepare>
+            <job-tracker>foo</job-tracker>
+            <configuration>
+                <property>
+                    <name>c</name>
+                    <value>C</value>
+                </property>
+            </configuration>
+            <script>script.q</script>
+            <param>INPUT=/tmp/table</param>
+            <param>OUTPUT=/tmp/hive</param>
+        </hive>
+        <ok to="b2"/>
+        <error to="b1"/>
+    </action>
+
+    <action name="b1">
+        <email xmlns="uri:oozie:email-action:0.2">
+            <to>foo@bar.com</to>
+            <subject>foo</subject>
+            <body>bar</body>
+        </email>
+        <ok to="b2"/>
+        <error to="b2"/>
+    </action>
+
+    <kill name="b2">
+        <message>fail</message>
+    </kill>
+
+    <end name="c"/>
+</workflow-app>

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/core/src/test/resources/wf-schema-valid-global-ext.xml
----------------------------------------------------------------------
diff --git a/core/src/test/resources/wf-schema-valid-global-ext.xml b/core/src/test/resources/wf-schema-valid-global-ext.xml
index 86d274d..88bf6c7 100644
--- a/core/src/test/resources/wf-schema-valid-global-ext.xml
+++ b/core/src/test/resources/wf-schema-valid-global-ext.xml
@@ -50,7 +50,7 @@
             <param>OUTPUT=/tmp/hive</param>
         </hive>
         <ok to="b"/>
-        <error to="c"/>
+        <error to="c1"/>
     </action>
 
     <action name="b">
@@ -75,10 +75,20 @@
             <arg>/tmp2/data.txt</arg>
         </distcp>
         <ok to="d"/>
-        <error to="c"/>
+        <error to="c1"/>
     </action>
 
-    <kill name="c">
+    <action name="c1">
+        <email xmlns="uri:oozie:email-action:0.2">
+            <to>foo@bar.com</to>
+            <subject>foo</subject>
+            <body>bar</body>
+        </email>
+        <ok to="c2"/>
+        <error to="c2"/>
+    </action>
+
+    <kill name="c2">
         <message>fail</message>
     </kill>
 

http://git-wip-us.apache.org/repos/asf/oozie/blob/a762991a/release-log.txt
----------------------------------------------------------------------
diff --git a/release-log.txt b/release-log.txt
index b19a913..4ffc302 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
 -- Oozie 4.3.0 release (trunk - unreleased)
 
+OOZIE-2187 Add a way to specify a default JT/RM and NN (rkanter)
 OOZIE-2272 Use Hadoop's CredentialProvider for passwords in oozie-site (rkanter)
 OOZIE-2287 Add support for deleting hcat partitions in fs action delete (kailongs via rohini)
 OOZIE-2285 Change in concurrency should trigger coord action ready command (kailongs via rohini)