Author: rkanter
Date: Thu Nov 14 23:08:09 2013
New Revision: 1542114

URL: http://svn.apache.org/r1542114
Log:
OOZIE-1550 Create a safeguard to kill errant recursive workflows before they 
bring down oozie (rkanter)

Modified:
    
oozie/trunk/core/src/main/java/org/apache/oozie/action/oozie/SubWorkflowActionExecutor.java
    oozie/trunk/core/src/main/resources/oozie-default.xml
    
oozie/trunk/core/src/test/java/org/apache/oozie/action/hadoop/ActionExecutorTestCase.java
    
oozie/trunk/core/src/test/java/org/apache/oozie/action/oozie/TestSubWorkflowActionExecutor.java
    oozie/trunk/docs/src/site/twiki/WorkflowFunctionalSpec.twiki
    oozie/trunk/release-log.txt

Modified: 
oozie/trunk/core/src/main/java/org/apache/oozie/action/oozie/SubWorkflowActionExecutor.java
URL: 
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/java/org/apache/oozie/action/oozie/SubWorkflowActionExecutor.java?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- 
oozie/trunk/core/src/main/java/org/apache/oozie/action/oozie/SubWorkflowActionExecutor.java
 (original)
+++ 
oozie/trunk/core/src/main/java/org/apache/oozie/action/oozie/SubWorkflowActionExecutor.java
 Thu Nov 14 23:08:09 2013
@@ -48,6 +48,8 @@ public class SubWorkflowActionExecutor e
     public static final String ACTION_TYPE = "sub-workflow";
     public static final String LOCAL = "local";
     public static final String PARENT_ID = "oozie.wf.parent.id";
+    public static final String SUBWORKFLOW_MAX_DEPTH = 
"oozie.action.subworkflow.max.depth";
+    private static final String SUBWORKFLOW_DEPTH = 
"oozie.action.subworkflow.depth";
 
     private static final Set<String> DISALLOWED_DEFAULT_PROPERTIES = new 
HashSet<String>();
 
@@ -119,6 +121,16 @@ public class SubWorkflowActionExecutor e
         conf.set(PARENT_ID, parentId);
     }
 
+    protected void verifyAndInjectSubworkflowDepth(Configuration parentConf, 
Configuration conf) throws ActionExecutorException {
+        int depth = conf.getInt(SUBWORKFLOW_DEPTH, 0);
+        int maxDepth = Services.get().getConf().getInt(SUBWORKFLOW_MAX_DEPTH, 
50);
+        if (depth >= maxDepth) {
+            throw new 
ActionExecutorException(ActionExecutorException.ErrorType.ERROR, "SUBWF001",
+                    "Depth [{0}] cannot exceed maximum subworkflow depth 
[{1}]", (depth + 1), maxDepth);
+        }
+        conf.setInt(SUBWORKFLOW_DEPTH, depth + 1);
+    }
+
     protected String checkIfRunning(OozieClient oozieClient, String extId) 
throws OozieClientException {
         String jobId = oozieClient.getJobId(extId);
         if (jobId.equals("")) {
@@ -161,6 +173,7 @@ public class SubWorkflowActionExecutor e
                 injectCallback(context, subWorkflowConf);
                 injectRecovery(extId, subWorkflowConf);
                 injectParent(context.getWorkflow().getId(), subWorkflowConf);
+                verifyAndInjectSubworkflowDepth(parentConf, subWorkflowConf);
 
                 //TODO: this has to be refactored later to be done in a single 
place for REST calls and this
                 JobUtils.normalizeAppPath(context.getWorkflow().getUser(), 
context.getWorkflow().getGroup(),

Modified: oozie/trunk/core/src/main/resources/oozie-default.xml
URL: 
http://svn.apache.org/viewvc/oozie/trunk/core/src/main/resources/oozie-default.xml?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- oozie/trunk/core/src/main/resources/oozie-default.xml (original)
+++ oozie/trunk/core/src/main/resources/oozie-default.xml Thu Nov 14 23:08:09 
2013
@@ -1519,6 +1519,18 @@
         </description>
     </property>
 
+    <!-- SubworkflowActionExecutor -->
+
+    <property>
+        <name>oozie.action.subworkflow.max.depth</name>
+        <value>50</value>
+        <description>
+            The maximum depth for subworkflows.  For example, if set to 3, 
then a workflow can start subwf1, which can start subwf2,
+            which can start subwf3; but if subwf3 tries to start subwf4, then 
the action will fail.  This is helpful in preventing
+            errant workflows from starting infintely recursive subworkflows.
+        </description>
+    </property>
+
     <!-- HadoopAccessorService -->
 
     <property>

Modified: 
oozie/trunk/core/src/test/java/org/apache/oozie/action/hadoop/ActionExecutorTestCase.java
URL: 
http://svn.apache.org/viewvc/oozie/trunk/core/src/test/java/org/apache/oozie/action/hadoop/ActionExecutorTestCase.java?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- 
oozie/trunk/core/src/test/java/org/apache/oozie/action/hadoop/ActionExecutorTestCase.java
 (original)
+++ 
oozie/trunk/core/src/test/java/org/apache/oozie/action/hadoop/ActionExecutorTestCase.java
 Thu Nov 14 23:08:09 2013
@@ -71,7 +71,9 @@ public abstract class ActionExecutorTest
 
     @Override
     protected void tearDown() throws Exception {
-        Services.get().destroy();
+        if (Services.get() != null) {
+            Services.get().destroy();
+        }
         super.tearDown();
     }
 

Modified: 
oozie/trunk/core/src/test/java/org/apache/oozie/action/oozie/TestSubWorkflowActionExecutor.java
URL: 
http://svn.apache.org/viewvc/oozie/trunk/core/src/test/java/org/apache/oozie/action/oozie/TestSubWorkflowActionExecutor.java?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- 
oozie/trunk/core/src/test/java/org/apache/oozie/action/oozie/TestSubWorkflowActionExecutor.java
 (original)
+++ 
oozie/trunk/core/src/test/java/org/apache/oozie/action/oozie/TestSubWorkflowActionExecutor.java
 Thu Nov 14 23:08:09 2013
@@ -33,6 +33,9 @@ import java.io.File;
 import java.io.StringReader;
 import java.io.Writer;
 import java.io.OutputStreamWriter;
+import java.util.Properties;
+import org.apache.oozie.local.LocalOozie;
+import org.apache.oozie.service.XLogService;
 
 public class TestSubWorkflowActionExecutor extends ActionExecutorTestCase {
     private static final int JOB_TIMEOUT = 100 * 1000;
@@ -406,4 +409,62 @@ public class TestSubWorkflowActionExecut
         childConf = wps.createProtoActionConf(childConf, true);
         assertEquals(childConf.get(WorkflowAppService.APP_LIB_PATH_LIST), 
subwfLibJar.toString());
     }
+
+    public void testSubworkflowDepth() throws Exception {
+        Path subWorkflowAppPath = getFsTestCaseDir();
+        FileSystem fs = getFileSystem();
+        Writer writer = new OutputStreamWriter(fs.create(new 
Path(subWorkflowAppPath, "workflow.xml")));
+        // Infinitly recursive workflow
+        String appStr = "<workflow-app xmlns=\"uri:oozie:workflow:0.4\" 
name=\"workflow\">" +
+                "<start to=\"subwf\"/>" +
+                "<action name=\"subwf\">" +
+                "     <sub-workflow xmlns='uri:oozie:workflow:0.4'>" +
+                "          <app-path>" + subWorkflowAppPath.toString() + 
"</app-path>" +
+                "     </sub-workflow>" +
+                "     <ok to=\"end\"/>" +
+                "     <error to=\"fail\"/>" +
+                "</action>" +
+                "<kill name=\"fail\">" +
+                "     <message>Sub workflow failed, error 
message[${wf:errorMessage(wf:lastErrorNode())}]</message>" +
+                "</kill>" +
+                "<end name=\"end\"/>" +
+                "</workflow-app>";
+        writer.write(appStr);
+        writer.close();
+
+        try {
+            Services.get().destroy();
+            setSystemProperty(XLogService.LOG4J_FILE, 
"oozie-log4j.properties");
+            LocalOozie.start();
+            // Set the max depth to 3
+            
Services.get().getConf().setInt("oozie.action.subworkflow.max.depth", 3);
+            final OozieClient wfClient = LocalOozie.getClient();
+            Properties conf = wfClient.createConfiguration();
+            conf.setProperty(OozieClient.APP_PATH, 
subWorkflowAppPath.toString());
+            conf.setProperty(OozieClient.USER_NAME, getTestUser());
+            final String jobId0 = wfClient.submit(conf);
+            wfClient.start(jobId0);
+
+            waitFor(20 * 1000, new Predicate() {
+                @Override
+                public boolean evaluate() throws Exception {
+                    return wfClient.getJobInfo(jobId0).getStatus() == 
WorkflowJob.Status.KILLED;
+                }
+            });
+            // All should be KILLED because our infinitly recusrive workflow 
hit the max
+            assertEquals(WorkflowJob.Status.KILLED, 
wfClient.getJobInfo(jobId0).getStatus());
+            String jobId1 = 
wfClient.getJobInfo(jobId0).getActions().get(1).getExternalId();
+            assertEquals(WorkflowJob.Status.KILLED, 
wfClient.getJobInfo(jobId1).getStatus());
+            String jobId2 = 
wfClient.getJobInfo(jobId1).getActions().get(1).getExternalId();
+            assertEquals(WorkflowJob.Status.KILLED, 
wfClient.getJobInfo(jobId2).getStatus());
+            String jobId3 = 
wfClient.getJobInfo(jobId2).getActions().get(1).getExternalId();
+            assertEquals(WorkflowJob.Status.KILLED, 
wfClient.getJobInfo(jobId3).getStatus());
+            String jobId4 = 
wfClient.getJobInfo(jobId3).getActions().get(1).getExternalId();
+            // A fourth subworkflow shouldn't have been created because we set 
the max to 3
+            assertNull(jobId4);
+        }
+        finally {
+            LocalOozie.stop();
+        }
+    }
 }

Modified: oozie/trunk/docs/src/site/twiki/WorkflowFunctionalSpec.twiki
URL: 
http://svn.apache.org/viewvc/oozie/trunk/docs/src/site/twiki/WorkflowFunctionalSpec.twiki?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- oozie/trunk/docs/src/site/twiki/WorkflowFunctionalSpec.twiki (original)
+++ oozie/trunk/docs/src/site/twiki/WorkflowFunctionalSpec.twiki Thu Nov 14 
23:08:09 2013
@@ -1311,6 +1311,9 @@ in oozie-site.xml or on a per-job basis 
 If both are specified, =oozie.wf.subworkflow.classpath.inheritance= has 
priority.  If the subworkflow and the parent have
 conflicting jars, the subworkflow's jar has priority.  By default, 
=oozie.wf.subworkflow.classpath.inheritance= is set to false.
 
+To prevent errant workflows from starting infinitely recursive subworkflows, 
=oozie.action.subworkflow.max.depth= can be specified
+in oozie-site.xml to set the maximum depth of subworkflow calls.  For example, 
if set to 3, then a workflow can start subwf1, which
+can start subwf2, which can start subwf3; but if subwf3 tries to start subwf4, 
then the action will fail.  The default is 50.
 
 #JavaAction
 ---++++ 3.2.7 Java Action

Modified: oozie/trunk/release-log.txt
URL: 
http://svn.apache.org/viewvc/oozie/trunk/release-log.txt?rev=1542114&r1=1542113&r2=1542114&view=diff
==============================================================================
--- oozie/trunk/release-log.txt (original)
+++ oozie/trunk/release-log.txt Thu Nov 14 23:08:09 2013
@@ -1,5 +1,6 @@
 -- Oozie 4.1.0 release (trunk - unreleased)
 
+OOZIE-1550 Create a safeguard to kill errant recursive workflows before they 
bring down oozie (rkanter)
 OOZIE-1314 IllegalArgumentException: wfId cannot be empty (shwethags via virag)
 OOZIE-1606 Update Curator to 2.3.0 and fix some misc minor ZK related things 
(rkanter)
 OOZIE-1544 Support variables for coord data-in/data-out dataset (puru via 
rohini)


Reply via email to