PageRenderTime 1948ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/test/java/org/apache/oozie/action/hadoop/TestShellActionExecutor.java

https://github.com/apache/oozie
Java | 400 lines | 261 code | 43 blank | 96 comment | 8 complexity | a391ae53f497a8c28b8b5ea57aacf04d MD5 | raw file
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.apache.oozie.action.hadoop;
  19. import java.io.File;
  20. import java.io.OutputStreamWriter;
  21. import java.io.Writer;
  22. import java.nio.charset.StandardCharsets;
  23. import java.util.Map;
  24. import org.apache.hadoop.conf.Configuration;
  25. import org.apache.hadoop.fs.FileSystem;
  26. import org.apache.hadoop.fs.Path;
  27. import org.apache.hadoop.util.Shell;
  28. import org.apache.oozie.WorkflowActionBean;
  29. import org.apache.oozie.WorkflowJobBean;
  30. import org.apache.oozie.client.WorkflowAction;
  31. import org.apache.oozie.service.Services;
  32. import org.apache.oozie.service.WorkflowAppService;
  33. import org.apache.oozie.util.PropertiesUtils;
  34. import org.apache.oozie.util.XConfiguration;
  35. import org.apache.oozie.util.XmlUtils;
  36. import org.jdom2.Element;
  37. import org.junit.Assert;
  38. public class TestShellActionExecutor extends ActionExecutorTestCase {
  39. private static final String SHELL_EXEC = Shell.WINDOWS ? "cmd.exe" : "sh";
  40. private static final String SHELL_PARAM = Shell.WINDOWS ? "/c" : "-c";
  41. private static final String SHELL_SCRIPTNAME = Shell.WINDOWS ? "script.cmd" : "script.sh";
  42. private static final String SHELL_SCRIPT_CONTENT = Shell.WINDOWS
  43. ? "dir /s /b\necho %1 %2\necho %PATH%\ntype %0"
  44. : "ls -ltr\necho $1 $2\necho $PATH\npwd\ntype sh";
  45. private static final String SHELL_SCRIPT_CONTENT_ENVVAR = Shell.WINDOWS
  46. ? "dir /s /b\necho var1=%var1%\necho var2=%var2%"
  47. : "ls -ltr\necho var1=$var1\necho var2=$var2";
  48. private static final String SHELL_SCRIPT_CONTENT_ERROR = Shell.WINDOWS
  49. ? "dir /s /b\necho %1 %2\nexit 1"
  50. : "ls -ltr\necho $1 $2\nexit 1";
  51. private static final String PERL_SCRIPT_CONTENT = "print \"MY_VAR=TESTING\";";
  52. private static final String SHELL_SCRIPT_HADOOP_CONF_DIR_CONTENT = Shell.WINDOWS
  53. ? "echo OOZIE_ACTION_CONF_XML=%OOZIE_ACTION_CONF_XML%\necho HADOOP_CONF_DIR=%HADOOP_CONF_DIR%\n"
  54. : "echo OOZIE_ACTION_CONF_XML=$OOZIE_ACTION_CONF_XML\necho HADOOP_CONF_DIR=$HADOOP_CONF_DIR\n";
  55. private static final String SHELL_SCRIPT_YARN_CONF_DIR_CONTENT = Shell.WINDOWS
  56. ? "echo YARN_CONF_DIR=%YARN_CONF_DIR%\n"
  57. : "echo YARN_CONF_DIR=$YARN_CONF_DIR\n";
  58. private static final String SHELL_SCRIPT_LOG4J_EXISTENCE_CHECKER = Shell.WINDOWS
  59. ? "IF EXIST %HADOOP_CONF_DIR%\\log4j.properties echo L4J_EXISTS=yes\n"
  60. : "if [ -f $HADOOP_CONF_DIR/log4j.properties ]; then echo L4J_EXISTS=yes; fi\n";
  61. private static final String SHELL_SCRIPT_LOG4J_CONTENT_COUNTER = Shell.WINDOWS
  62. ? "FOR /f %%i IN ('TYPE %HADOOP_CONF_DIR%\\log4j.properties " +
  63. "^| FIND /c /v \"~DOESNOTMATCH~\"') DO " +
  64. "SET L4J_LC=%%i\necho L4J_LC=%L4J_LC%\n" +
  65. "FOR /f %%i IN ('FINDSTR \"CLA CLRA\" %HADOOP_CONF_DIR%\\log4j.properties " +
  66. "^| FIND /c /v \"~DOESNOTMATCH~\"') DO " +
  67. "SET L4J_APPENDER=%%i\necho L4J_APPENDER=%L4J_APPENDER%\n"
  68. : "echo L4J_LC=$(cat $HADOOP_CONF_DIR/log4j.properties | wc -l)\n" +
  69. "echo L4J_APPENDER=$(grep -e 'CLA' -e 'CLRA' -c " +
  70. "$HADOOP_CONF_DIR/log4j.properties)\n";
  71. /**
  72. * Verify if the ShellActionExecutor indeed setups the basic stuffs
  73. *
  74. * @throws Exception
  75. */
  76. public void testSetupMethods() throws Exception {
  77. ShellActionExecutor ae = new ShellActionExecutor();
  78. assertNull(ae.getLauncherClasses());
  79. Element actionXml = XmlUtils.parseXml("<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>"
  80. + "<name-node>" + getNameNodeUri() + "</name-node>" + "<exec>SCRIPT</exec>"
  81. + "<argument>a=A</argument>" + "<argument>b=B</argument>" + "</shell>");
  82. XConfiguration protoConf = new XConfiguration();
  83. protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
  84. WorkflowJobBean wf = createBaseWorkflow(protoConf, "pig-action");
  85. WorkflowActionBean action = (WorkflowActionBean) wf.getActions().get(0);
  86. action.setType(ae.getType());
  87. Context context = new Context(wf, action);
  88. Configuration conf = ae.createBaseHadoopConf(context, actionXml);
  89. ae.setupActionConf(conf, context, actionXml, getFsTestCaseDir());
  90. assertEquals("SCRIPT", conf.get("oozie.shell.exec"));
  91. assertEquals("2", conf.get("oozie.shell.args.size"));
  92. assertEquals("a=A", conf.get("oozie.shell.args.0"));
  93. assertEquals("b=B", conf.get("oozie.shell.args.1"));
  94. assertEquals("Expected HADOOP_CONF_DIR setup switch to be disabled",
  95. "false", conf.get("oozie.action.shell.setup.hadoop.conf.dir"));
  96. assertEquals("Expected log4j.properties write switch to be enabled",
  97. "true", conf.get("oozie.action.shell.setup.hadoop.conf.dir.write.log4j.properties"));
  98. assertNotNull("Expected a default config to exist for log4j.properties",
  99. conf.get("oozie.action.shell.setup.hadoop.conf.dir.log4j.content"));
  100. }
  101. /**
  102. * test if a sample shell script could run successfully
  103. *
  104. * @throws Exception
  105. */
  106. public void testShellScript() throws Exception {
  107. FileSystem fs = getFileSystem();
  108. // Create the script file with canned shell command
  109. Path script = new Path(getAppPath(), SHELL_SCRIPTNAME);
  110. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  111. w.write(SHELL_SCRIPT_CONTENT);
  112. w.close();
  113. // Create sample Shell action xml
  114. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  115. + getNameNodeUri() + "</name-node>" + "<exec>" + SHELL_EXEC + "</exec>" + "<argument>" + SHELL_PARAM + "</argument>"
  116. + "<argument>" + SHELL_SCRIPTNAME + "</argument>" + "<argument>A</argument>" + "<argument>B</argument>"
  117. + "<env-var>var1=val1</env-var>" + "<env-var>var2=val2</env-var>" + "<file>" + script.toString()
  118. + "#" + script.getName() + "</file>" + "</shell>";
  119. // Submit and verify the job's status
  120. _testSubmit(actionXml, true, "");
  121. }
  122. /**
  123. * Test if a shell script could run successfully with {@link ShellMain#CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR} enabled.
  124. *
  125. * @throws Exception
  126. */
  127. public void testShellScriptHadoopConfDir() throws Exception {
  128. FileSystem fs = getFileSystem();
  129. // Create the script file with canned shell command
  130. Path script = new Path(getAppPath(), SHELL_SCRIPTNAME);
  131. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  132. w.write(SHELL_SCRIPT_HADOOP_CONF_DIR_CONTENT);
  133. w.write(SHELL_SCRIPT_YARN_CONF_DIR_CONTENT);
  134. w.write(SHELL_SCRIPT_LOG4J_EXISTENCE_CHECKER);
  135. w.write(SHELL_SCRIPT_LOG4J_CONTENT_COUNTER);
  136. w.close();
  137. // Create sample Shell action xml
  138. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  139. + getNameNodeUri() + "</name-node>" + "<configuration>"
  140. + "<property><name>oozie.action.shell.setup.hadoop.conf.dir</name><value>true</value></property>"
  141. + "</configuration>" + "<exec>" + SHELL_EXEC + "</exec>" + "<argument>" + SHELL_PARAM + "</argument>"
  142. + "<argument>" + SHELL_SCRIPTNAME + "</argument>" + "<file>" + script.toString()
  143. + "#" + script.getName() + "</file>" + "<capture-output/>" + "</shell>";
  144. // Submit and verify the job's status
  145. WorkflowAction action = _testSubmit(actionXml, true, "");
  146. String oozieActionConfXml = PropertiesUtils.stringToProperties(action.getData()).getProperty("OOZIE_ACTION_CONF_XML");
  147. String hadoopConfDir = PropertiesUtils.stringToProperties(action.getData()).getProperty("HADOOP_CONF_DIR");
  148. String yarnConfDir = PropertiesUtils.stringToProperties(action.getData()).getProperty("YARN_CONF_DIR");
  149. String log4jExists = PropertiesUtils.stringToProperties(action.getData()).getProperty("L4J_EXISTS");
  150. String log4jFileLineCount = PropertiesUtils.stringToProperties(action.getData()).getProperty("L4J_LC");
  151. String log4BadAppenderCount = PropertiesUtils.stringToProperties(action.getData()).getProperty("L4J_APPENDER");
  152. assertNotNull(oozieActionConfXml);
  153. assertNotNull(hadoopConfDir);
  154. String s = new File(oozieActionConfXml).getParent() + File.separator + "oozie-hadoop-conf-";
  155. Assert.assertTrue(
  156. "Expected HADOOP_CONF_DIR to start with " + s + " but was " + hadoopConfDir,
  157. hadoopConfDir.startsWith(s));
  158. Assert.assertTrue(
  159. "Expected YARN_CONF_DIR to start with " + s + " but was " + yarnConfDir,
  160. yarnConfDir.startsWith(s));
  161. Assert.assertEquals(
  162. "Expected log4j.properties file to exist", "yes", log4jExists);
  163. Assert.assertTrue(
  164. "Expected log4j.properties to have non-zero line count, but has: " + log4jFileLineCount,
  165. Integer.parseInt(log4jFileLineCount) > 0);
  166. Assert.assertEquals(
  167. "Expected log4j.properties to have no container appender references (CLA/CLRA)",
  168. 0, Integer.parseInt(log4BadAppenderCount));
  169. }
  170. /**
  171. * Test if a shell script could run successfully with {@link ShellMain#CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR} enabled.
  172. * Run with {@link ShellMain#CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_WRITE_LOG4J_PROPERTIES} disabled.
  173. *
  174. * @throws Exception
  175. */
  176. public void testShellScriptHadoopConfDirWithNoL4J() throws Exception {
  177. FileSystem fs = getFileSystem();
  178. // Create the script file with canned shell command
  179. Path script = new Path(getAppPath(), SHELL_SCRIPTNAME);
  180. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  181. w.write(SHELL_SCRIPT_LOG4J_EXISTENCE_CHECKER);
  182. w.close();
  183. // Create sample Shell action xml
  184. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  185. + getNameNodeUri() + "</name-node>" + "<configuration>"
  186. + "<property><name>oozie.action.shell.setup.hadoop.conf.dir</name><value>true</value></property>"
  187. + "<property><name>oozie.action.shell.setup.hadoop.conf.dir.write.log4j.properties"
  188. + "</name><value>false</value></property>"
  189. + "</configuration>" + "<exec>" + SHELL_EXEC + "</exec>" + "<argument>" + SHELL_PARAM + "</argument>"
  190. + "<argument>" + SHELL_SCRIPTNAME + "</argument>" + "<file>" + script.toString()
  191. + "#" + script.getName() + "</file>" + "<capture-output/>" + "</shell>";
  192. // Submit and verify the job's status
  193. WorkflowAction action = _testSubmit(actionXml, true, "");
  194. String log4jExists = PropertiesUtils.stringToProperties(action.getData()).getProperty("L4J_EXISTS");
  195. Assert.assertNull(
  196. "Expected no log4j.properties file to exist", log4jExists);
  197. }
  198. /**
  199. * test if a sample shell script could run with error when the script return
  200. * non-zero exit code
  201. *
  202. * @throws Exception
  203. */
  204. public void testShellScriptError() throws Exception {
  205. FileSystem fs = getFileSystem();
  206. // Create the script file with canned shell command
  207. Path script = new Path(getAppPath(), SHELL_SCRIPTNAME);
  208. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  209. w.write(SHELL_SCRIPT_CONTENT_ERROR);
  210. w.close();
  211. // Create sample shell action xml
  212. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  213. + getNameNodeUri() + "</name-node>" + "<exec>" + SHELL_EXEC + "</exec>" + "<argument>" + SHELL_PARAM + "</argument>"
  214. + "<argument>" + SHELL_SCRIPTNAME + "</argument>" + "<argument>A</argument>" + "<argument>B</argument>" + "<file>"
  215. + script.toString() + "#" + script.getName() + "</file>" + "</shell>";
  216. // Submit and verify the job's status
  217. _testSubmit(actionXml, false, "");
  218. }
  219. /**
  220. * test if a perl script could run successfully
  221. *
  222. * @throws Exception
  223. */
  224. public void testPerlScript() throws Exception {
  225. if (Shell.WINDOWS) {
  226. System.out.println("Windows cannot natively execute perl. Skipping test");
  227. return;
  228. }
  229. FileSystem fs = getFileSystem();
  230. // Create a sample perl script
  231. Path script = new Path(getAppPath(), "script.pl");
  232. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  233. w.write(PERL_SCRIPT_CONTENT);
  234. w.close();
  235. // Create a Sample Shell action using the perl script
  236. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  237. + getNameNodeUri() + "</name-node>" + "<exec>perl</exec>" + "<argument>script.pl</argument>"
  238. + "<argument>A</argument>" + "<argument>B</argument>" + "<env-var>my_var1=my_val1</env-var>" + "<file>"
  239. + script.toString() + "#" + script.getName() + "</file>" + "<capture-output/>" + "</shell>";
  240. _testSubmit(actionXml, true, "TESTING");
  241. }
  242. /**
  243. * Test that env variable can contain '=' symbol within value
  244. *
  245. * @throws Exception
  246. */
  247. public void testEnvVar() throws Exception {
  248. Services.get().destroy();
  249. Services services = new Services();
  250. services.getConf().setInt(LauncherAMUtils.CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, 8 * 1042);
  251. services.init();
  252. FileSystem fs = getFileSystem();
  253. // Create the script file with canned shell command
  254. Path script = new Path(getAppPath(), SHELL_SCRIPTNAME);
  255. Writer w = new OutputStreamWriter(fs.create(script), StandardCharsets.UTF_8);
  256. w.write(SHELL_SCRIPT_CONTENT_ENVVAR);
  257. w.close();
  258. String envValueHavingEqualSign = "a=b;c=d";
  259. // Create sample shell action xml
  260. String actionXml = "<shell>" + "<job-tracker>" + getJobTrackerUri() + "</job-tracker>" + "<name-node>"
  261. + getNameNodeUri() + "</name-node>" + "<exec>" + SHELL_EXEC + "</exec>" + "<argument>" + SHELL_PARAM + "</argument>"
  262. + "<argument>" + SHELL_SCRIPTNAME + "</argument>" + "<argument>A</argument>" + "<argument>B</argument>"
  263. + "<env-var>var1=val1</env-var>" + "<env-var>var2=" + envValueHavingEqualSign + "</env-var>" + "<file>"
  264. + script.toString()
  265. + "#" + script.getName() + "</file>" + "<capture-output />" + "</shell>";
  266. Context context = createContext(actionXml);
  267. // Submit the action
  268. final String launcherId = submitAction(context);
  269. waitUntilYarnAppDoneAndAssertSuccess(launcherId);
  270. ShellActionExecutor ae = new ShellActionExecutor();
  271. WorkflowAction action = context.getAction();
  272. ae.check(context, action);
  273. ae.end(context, action);
  274. // Checking action data from shell script output
  275. assertEquals(envValueHavingEqualSign, PropertiesUtils.stringToProperties(action.getData())
  276. .getProperty("var2"));
  277. }
  278. /**
  279. * Submit the WF with a Shell action and very of the job succeeds
  280. *
  281. * @param actionXml
  282. * @param checkForSuccess
  283. * @throws Exception
  284. */
  285. private WorkflowAction _testSubmit(String actionXml, boolean checkForSuccess, String capture_output) throws Exception {
  286. Context context = createContext(actionXml);
  287. final String launcherId = submitAction(context);// Submit the action
  288. waitUntilYarnAppDoneAndAssertSuccess(launcherId);
  289. Configuration conf = new XConfiguration();
  290. conf.set("user.name", getTestUser());
  291. Map<String, String> actionData = LauncherHelper.getActionData(getFileSystem(), context.getActionDir(),
  292. conf);
  293. assertFalse(LauncherHelper.hasIdSwap(actionData));
  294. ShellActionExecutor ae = new ShellActionExecutor();
  295. ae.check(context, context.getAction());
  296. ae.end(context, context.getAction());
  297. assertTrue(launcherId.equals(context.getAction().getExternalId()));
  298. if (checkForSuccess) { // Postive test cases
  299. assertEquals("SUCCEEDED", context.getAction().getExternalStatus());
  300. // Testing capture output
  301. if (capture_output != null && capture_output.length() > 0) {
  302. assertEquals(capture_output, PropertiesUtils.stringToProperties(context.getAction().getData())
  303. .getProperty("MY_VAR"));
  304. }
  305. }
  306. else { // Negative test cases
  307. assertEquals("FAILED/KILLED", context.getAction().getExternalStatus());
  308. assertNotNull(context.getAction().getErrorMessage());
  309. }
  310. if (checkForSuccess) { // Positive test cases
  311. assertEquals(WorkflowAction.Status.OK, context.getAction().getStatus());
  312. }
  313. else {// Negative test cases
  314. assertEquals(WorkflowAction.Status.ERROR, context.getAction().getStatus());
  315. }
  316. return context.getAction();
  317. }
  318. /**
  319. * Create WF using the Shell action and return the corresponding contest
  320. * structure
  321. *
  322. * @param actionXml :Shell action
  323. * @return :Context
  324. * @throws Exception
  325. */
  326. private Context createContext(String actionXml) throws Exception {
  327. ShellActionExecutor ae = new ShellActionExecutor();
  328. XConfiguration protoConf = new XConfiguration();
  329. protoConf.set(WorkflowAppService.HADOOP_USER, getTestUser());
  330. // Make sure Kerbores prinicpal is in the conf
  331. WorkflowJobBean wf = createBaseWorkflow(protoConf, "shell-action");
  332. WorkflowActionBean action = (WorkflowActionBean) wf.getActions().get(0);
  333. action.setType(ae.getType());
  334. action.setConf(actionXml);
  335. return new Context(wf, action);
  336. }
  337. /**
  338. * Submit the Shell action using Shell ActionExecutor
  339. *
  340. * @param context
  341. * @return The RunningJob of the Launcher Mapper
  342. * @throws Exception
  343. */
  344. private String submitAction(Context context) throws Exception {
  345. ShellActionExecutor ae = new ShellActionExecutor();
  346. WorkflowAction action = context.getAction();
  347. ae.prepareActionDir(getFileSystem(), context);
  348. ae.submitLauncher(getFileSystem(), context, action); // Submit the action
  349. String jobId = action.getExternalId();
  350. String jobTracker = action.getTrackerUri();
  351. String consoleUrl = action.getConsoleUrl();
  352. assertNotNull(jobId);
  353. assertNotNull(jobTracker);
  354. assertNotNull(consoleUrl);
  355. return jobId;
  356. }
  357. }