PageRenderTime 75ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/sharelib/oozie/src/main/java/org/apache/oozie/action/hadoop/ShellMain.java

https://github.com/apache/oozie
Java | 385 lines | 234 code | 39 blank | 112 comment | 31 complexity | a317143bf6f7fea90ef213ca71f2d75c 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.BufferedReader;
  20. import java.io.BufferedWriter;
  21. import java.io.File;
  22. import java.io.FileOutputStream;
  23. import java.io.IOException;
  24. import java.io.InputStreamReader;
  25. import java.io.OutputStreamWriter;
  26. import java.io.PrintWriter;
  27. import java.io.StringReader;
  28. import java.nio.charset.StandardCharsets;
  29. import java.util.ArrayList;
  30. import java.util.List;
  31. import java.util.Map;
  32. import org.apache.hadoop.conf.Configuration;
  33. import org.apache.hadoop.util.Shell;
  34. public class ShellMain extends LauncherMain {
  35. public static final String CONF_OOZIE_SHELL_ARGS = "oozie.shell.args";
  36. public static final String CONF_OOZIE_SHELL_EXEC = "oozie.shell.exec";
  37. public static final String CONF_OOZIE_SHELL_ENVS = "oozie.shell.envs";
  38. public static final String CONF_OOZIE_SHELL_CAPTURE_OUTPUT = "oozie.shell.capture-output";
  39. public static final String CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR = "oozie.action.shell.setup.hadoop.conf.dir";
  40. public static final String CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_WRITE_LOG4J_PROPERTIES =
  41. "oozie.action.shell.setup.hadoop.conf.dir.write.log4j.properties";
  42. public static final String CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_LOG4J_CONTENT =
  43. "oozie.action.shell.setup.hadoop.conf.dir.log4j.content";
  44. public static final String CONF_OOZIE_SHELL_MAX_SCRIPT_SIZE_TO_PRINT_KB = "oozie.action.shell.max-print-size-kb";
  45. private static final int DEFAULT_MAX_SRIPT_SIZE_TO_PRINT_KB = 128;
  46. public static final String OOZIE_ACTION_CONF_XML = "OOZIE_ACTION_CONF_XML";
  47. private static final String HADOOP_CONF_DIR = "HADOOP_CONF_DIR";
  48. private static final String YARN_CONF_DIR = "YARN_CONF_DIR";
  49. private static String LOG4J_PROPERTIES = "log4j.properties";
  50. /**
  51. * @param args Invoked from LauncherAMUtils:map()
  52. * @throws Exception in case of error when running the application
  53. */
  54. public static void main(String[] args) throws Exception {
  55. run(ShellMain.class, args);
  56. }
  57. @Override
  58. protected void run(String[] args) throws Exception {
  59. Configuration actionConf = loadActionConf();
  60. setYarnTag(actionConf);
  61. setApplicationTags(actionConf, TEZ_APPLICATION_TAGS);
  62. setApplicationTags(actionConf, SPARK_YARN_TAGS);
  63. int exitCode = execute(actionConf);
  64. if (exitCode != 0) {
  65. // Shell command failed. therefore make the action failed
  66. throw new LauncherMainException(1);
  67. }
  68. }
  69. /**
  70. * Execute the shell command
  71. *
  72. * @param actionConf the action configuration
  73. * @return command exit value
  74. * @throws Exception in case of error during action execution
  75. */
  76. private int execute(Configuration actionConf) throws Exception {
  77. String exec = getExec(actionConf);
  78. List<String> args = getShellArguments(actionConf);
  79. ArrayList<String> cmdArray = getCmdList(exec, args.toArray(new String[args.size()]));
  80. ProcessBuilder builder = new ProcessBuilder(cmdArray);
  81. Map<String, String> envp = getEnvMap(builder.environment(), actionConf);
  82. // Getting the Current working dir and setting it to processbuilder
  83. File currDir = new File("dummy").getAbsoluteFile().getParentFile();
  84. System.out.println("Current working dir " + currDir);
  85. builder.directory(currDir);
  86. // Setup Hadoop *-site files in case the user runs a Hadoop-type program (e.g. hive)
  87. prepareHadoopConfigs(actionConf, envp, currDir);
  88. printCommand(actionConf, cmdArray, envp); // For debugging purpose
  89. System.out.println("=================================================================");
  90. System.out.println();
  91. System.out.println(">>> Invoking Shell command line now >>");
  92. System.out.println();
  93. System.out.flush();
  94. boolean captureOutput = actionConf.getBoolean(CONF_OOZIE_SHELL_CAPTURE_OUTPUT, false);
  95. // Execute the Command
  96. Process p = builder.start();
  97. Thread[] thrArray = handleShellOutput(p, captureOutput);
  98. int exitValue = p.waitFor();
  99. // Wait for both the thread to exit
  100. if (thrArray != null) {
  101. for (Thread thr : thrArray) {
  102. thr.join();
  103. }
  104. }
  105. System.out.println("Exit code of the Shell command " + exitValue);
  106. System.out.println("<<< Invocation of Shell command completed <<<");
  107. System.out.println();
  108. return exitValue;
  109. }
  110. /**
  111. * This method takes the OOZIE_ACTION_CONF_XML and copies it to Hadoop *-site files in a new directory; it then sets the
  112. * HADOOP/YARN_CONF_DIR to point there. This should allow most Hadoop ecosystem CLI programs to have the proper configuration,
  113. * propagated from Oozie's copy and including anything set in the Workflow's configuration section as well. Otherwise,
  114. * HADOOP/YARN_CONF_DIR points to the NodeManager's *-site files, which are likely not suitable for client programs.
  115. * It will only do this if {@link #CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR} is set to true.
  116. *
  117. * @param actionConf The action configuration
  118. * @param envp The environment for the Shell process
  119. * @param currDir The current working dir
  120. * @throws IOException in case of error writing config or property file
  121. */
  122. private void prepareHadoopConfigs(Configuration actionConf, Map<String, String> envp, File currDir) throws IOException {
  123. if (actionConf.getBoolean(CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR, false)) {
  124. String actionXml = envp.get(OOZIE_ACTION_CONF_XML);
  125. if (actionXml != null) {
  126. File confDir = new File(currDir, "oozie-hadoop-conf-" + System.currentTimeMillis());
  127. writeHadoopConfig(actionXml, confDir);
  128. if (actionConf.getBoolean(CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_WRITE_LOG4J_PROPERTIES, true)) {
  129. System.out.println("Writing " + LOG4J_PROPERTIES + " to " + confDir);
  130. writeLoggerProperties(actionConf, confDir);
  131. }
  132. System.out.println("Setting " + HADOOP_CONF_DIR + " and " + YARN_CONF_DIR
  133. + " to " + confDir.getAbsolutePath());
  134. envp.put(HADOOP_CONF_DIR, confDir.getAbsolutePath());
  135. envp.put(YARN_CONF_DIR, confDir.getAbsolutePath());
  136. }
  137. }
  138. }
  139. /**
  140. * Write a {@link #LOG4J_PROPERTIES} file into the provided directory.
  141. * Content of the log4j.properties sourced from the default value of property
  142. * {@link #CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_LOG4J_CONTENT}, defined in oozie-default.xml.
  143. * This is required for properly redirecting command outputs to stderr.
  144. * Otherwise, logging from commands may go into stdout and make it to the Shell's captured output.
  145. * @param actionConf the action's configuration, to source log4j contents from
  146. * @param confDir the directory to write a {@link #LOG4J_PROPERTIES} file under
  147. * @throws IOException in case of write error
  148. */
  149. private static void writeLoggerProperties(Configuration actionConf, File confDir) throws IOException {
  150. String log4jContents = actionConf.get(
  151. CONF_OOZIE_SHELL_SETUP_HADOOP_CONF_DIR_LOG4J_CONTENT);
  152. File log4jPropertiesFile = new File(confDir, LOG4J_PROPERTIES);
  153. FileOutputStream log4jFileOutputStream = new FileOutputStream(log4jPropertiesFile, false);
  154. PrintWriter log4jWriter = new PrintWriter(new OutputStreamWriter(log4jFileOutputStream, StandardCharsets.UTF_8));
  155. BufferedReader lineReader = new BufferedReader(new StringReader(log4jContents));
  156. String line = lineReader.readLine();
  157. while (line != null) {
  158. // Trim the line (both preceding and trailing whitespaces) before writing it as a line in file
  159. log4jWriter.println(line.trim());
  160. line = lineReader.readLine();
  161. }
  162. log4jWriter.close();
  163. }
  164. /**
  165. * Return the environment variable to pass to in shell command execution.
  166. *
  167. * @param envp the environment map to fill
  168. * @param actionConf the config to work from
  169. * @return the filled up map
  170. */
  171. private Map<String, String> getEnvMap(Map<String, String> envp, Configuration actionConf) {
  172. // Adding user-specified environments
  173. String[] envs = ActionUtils.getStrings(actionConf, CONF_OOZIE_SHELL_ENVS);
  174. for (String env : envs) {
  175. String[] varValue = env.split("=",2); // Error case is handled in
  176. // ShellActionExecutor
  177. envp.put(varValue[0], varValue[1]);
  178. }
  179. // Adding action.xml to env
  180. envp.put(OOZIE_ACTION_CONF_XML, System.getProperty("oozie.action.conf.xml", ""));
  181. return envp;
  182. }
  183. /**
  184. * Get the shell commands with the arguments
  185. *
  186. * @param exec command to execute
  187. * @param args ars of the command
  188. * @return command and list of args
  189. */
  190. private ArrayList<String> getCmdList(String exec, String[] args) {
  191. ArrayList<String> cmdArray = new ArrayList<String>();
  192. cmdArray.add(exec); // Main executable
  193. for (String arg : args) { // Adding rest of the arguments
  194. cmdArray.add(arg);
  195. }
  196. return cmdArray;
  197. }
  198. /**
  199. * Print the output written by the Shell execution in its stdout/stderr.
  200. * Also write the stdout output to a file for capturing.
  201. *
  202. * @param p process
  203. * @param captureOutput indicates if STDOUT should be captured or not.
  204. * @return Array of threads (one for stdout and another one for stderr
  205. * processing
  206. * @throws IOException thrown if an IO error occurrs.
  207. */
  208. protected Thread[] handleShellOutput(Process p, boolean captureOutput)
  209. throws IOException {
  210. BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream(),
  211. StandardCharsets.UTF_8));
  212. BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream(),
  213. StandardCharsets.UTF_8));
  214. OutputWriteThread thrStdout = new OutputWriteThread(input, true, captureOutput);
  215. thrStdout.setDaemon(true);
  216. thrStdout.start();
  217. OutputWriteThread thrStderr = new OutputWriteThread(error, false, false);
  218. thrStderr.setDaemon(true);
  219. thrStderr.start();
  220. return new Thread[]{ thrStdout, thrStderr };
  221. }
  222. /**
  223. * Thread to write output to LM stdout/stderr. Also write the content for
  224. * capture-output.
  225. */
  226. class OutputWriteThread extends Thread {
  227. BufferedReader reader = null;
  228. boolean isStdout = false;
  229. boolean needCaptured = false;
  230. public OutputWriteThread(BufferedReader reader, boolean isStdout, boolean needCaptured) {
  231. this.reader = reader;
  232. this.isStdout = isStdout;
  233. this.needCaptured = needCaptured;
  234. }
  235. @Override
  236. public void run() {
  237. String line;
  238. BufferedWriter os = null;
  239. try {
  240. if (needCaptured) {
  241. File file = new File(System.getProperty(OUTPUT_PROPERTIES));
  242. os = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),
  243. StandardCharsets.UTF_8));
  244. }
  245. while ((line = reader.readLine()) != null) {
  246. if (isStdout) { // For stdout
  247. // 1. Writing to LM STDOUT
  248. System.out.println("Stdoutput " + line);
  249. // 2. Writing for capture output
  250. if (os != null) {
  251. if (Shell.WINDOWS) {
  252. line = line.replace("\\u", "\\\\u");
  253. }
  254. os.write(line);
  255. os.newLine();
  256. }
  257. }
  258. else {
  259. System.err.println(line); // 1. Writing to LM STDERR
  260. }
  261. }
  262. }
  263. catch (IOException e) {
  264. e.printStackTrace();
  265. throw new RuntimeException("Stdout/Stderr read/write error :" + e);
  266. }finally {
  267. try {
  268. reader.close();
  269. }
  270. catch (IOException ex) {
  271. //NOP ignoring error on close of STDOUT/STDERR
  272. }
  273. if (os != null) {
  274. try {
  275. os.close();
  276. }
  277. catch (IOException e) {
  278. e.printStackTrace();
  279. throw new RuntimeException("Unable to close the file stream :" + e);
  280. }
  281. }
  282. }
  283. }
  284. }
  285. /**
  286. * Print the command including the arguments as well as the environment
  287. * setup
  288. *
  289. * @param cmdArray :Command Array
  290. * @param envp :Environment array
  291. * @param config :Hadoop configuration
  292. */
  293. protected void printCommand(Configuration config, ArrayList<String> cmdArray, Map<String, String> envp) {
  294. int i = 0;
  295. System.out.println("Full Command .. ");
  296. System.out.println("-------------------------");
  297. for (String arg : cmdArray) {
  298. System.out.println(i++ + ":" + arg + ":");
  299. }
  300. if (!cmdArray.isEmpty()) {
  301. ShellContentWriter writer = new ShellContentWriter(
  302. config.getInt(CONF_OOZIE_SHELL_MAX_SCRIPT_SIZE_TO_PRINT_KB, DEFAULT_MAX_SRIPT_SIZE_TO_PRINT_KB),
  303. System.out,
  304. System.err,
  305. cmdArray.get(0)
  306. );
  307. writer.print();
  308. }
  309. if (envp != null) {
  310. System.out.println("List of passing environment");
  311. System.out.println("-------------------------");
  312. for (Map.Entry<String, String> entry : envp.entrySet()) {
  313. System.out.println(entry.getKey() + "=" + entry.getValue() + ":");
  314. }
  315. }
  316. }
  317. /**
  318. * Retrieve the list of arguments that were originally specified to
  319. * Workflow.xml.
  320. *
  321. * @param actionConf the configuration
  322. * @return argument list
  323. */
  324. protected List<String> getShellArguments(Configuration actionConf) {
  325. List<String> arguments = new ArrayList<String>();
  326. String[] scrArgs = ActionUtils.getStrings(actionConf, CONF_OOZIE_SHELL_ARGS);
  327. for (String scrArg : scrArgs) {
  328. arguments.add(scrArg);
  329. }
  330. return arguments;
  331. }
  332. /**
  333. * Retrieve the executable name that was originally specified to
  334. * Workflow.xml.
  335. *
  336. * @param actionConf the configuration
  337. * @return executable
  338. */
  339. protected String getExec(Configuration actionConf) {
  340. String exec = actionConf.get(CONF_OOZIE_SHELL_EXEC);
  341. if (exec == null) {
  342. throw new RuntimeException("Action Configuration does not have " + CONF_OOZIE_SHELL_EXEC + " property");
  343. }
  344. return exec;
  345. }
  346. }