001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.action.hadoop; 020 021import java.io.BufferedReader; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.InputStreamReader; 025import java.io.OutputStream; 026import java.math.BigInteger; 027import java.security.MessageDigest; 028import java.security.NoSuchAlgorithmException; 029import java.security.PrivilegedExceptionAction; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Properties; 036 037import org.apache.hadoop.conf.Configuration; 038import org.apache.hadoop.fs.FileSystem; 039import org.apache.hadoop.fs.Path; 040import org.apache.hadoop.io.SequenceFile; 041import org.apache.hadoop.io.Text; 042import org.apache.hadoop.mapred.JobConf; 043import org.apache.hadoop.mapred.RunningJob; 044import org.apache.hadoop.mapred.Counters; 045import org.apache.hadoop.security.UserGroupInformation; 046import org.apache.oozie.client.OozieClient; 047import org.apache.oozie.client.WorkflowAction; 048import org.apache.oozie.service.HadoopAccessorException; 049import org.apache.oozie.service.HadoopAccessorService; 050import org.apache.oozie.service.Services; 051import org.apache.oozie.service.URIHandlerService; 052import org.apache.oozie.service.UserGroupInformationService; 053import org.apache.oozie.util.IOUtils; 054import org.apache.oozie.util.PropertiesUtils; 055 056public class LauncherMapperHelper { 057 058 public static String getRecoveryId(Configuration launcherConf, Path actionDir, String recoveryId) 059 throws HadoopAccessorException, IOException { 060 String jobId = null; 061 Path recoveryFile = new Path(actionDir, recoveryId); 062 FileSystem fs = Services.get().get(HadoopAccessorService.class) 063 .createFileSystem(launcherConf.get("user.name"),recoveryFile.toUri(), launcherConf); 064 065 if (fs.exists(recoveryFile)) { 066 InputStream is = fs.open(recoveryFile); 067 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 068 jobId = reader.readLine(); 069 reader.close(); 070 } 071 return jobId; 072 073 } 074 075 public static void setupMainClass(Configuration launcherConf, String javaMainClass) { 076 // Only set the javaMainClass if its not null or empty string, this way the user can override the action's main class via 077 // <configuration> property 078 if (javaMainClass != null && !javaMainClass.equals("")) { 079 launcherConf.set(LauncherMapper.CONF_OOZIE_ACTION_MAIN_CLASS, javaMainClass); 080 } 081 } 082 083 public static void setupLauncherURIHandlerConf(Configuration launcherConf) { 084 for(Map.Entry<String, String> entry : Services.get().get(URIHandlerService.class).getLauncherConfig()) { 085 launcherConf.set(entry.getKey(), entry.getValue()); 086 } 087 } 088 089 public static void setupMainArguments(Configuration launcherConf, String[] args) { 090 launcherConf.setInt(LauncherMapper.CONF_OOZIE_ACTION_MAIN_ARG_COUNT, args.length); 091 for (int i = 0; i < args.length; i++) { 092 launcherConf.set(LauncherMapper.CONF_OOZIE_ACTION_MAIN_ARG_PREFIX + i, args[i]); 093 } 094 } 095 096 public static void setupMaxOutputData(Configuration launcherConf, int maxOutputData) { 097 launcherConf.setInt(LauncherMapper.CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, maxOutputData); 098 } 099 100 /** 101 * Set the maximum value of stats data 102 * 103 * @param launcherConf the oozie launcher configuration 104 * @param maxStatsData the maximum allowed size of stats data 105 */ 106 public static void setupMaxExternalStatsSize(Configuration launcherConf, int maxStatsData){ 107 launcherConf.setInt(LauncherMapper.CONF_OOZIE_EXTERNAL_STATS_MAX_SIZE, maxStatsData); 108 } 109 110 /** 111 * Set the maximum number of globbed files/dirs 112 * 113 * @param launcherConf the oozie launcher configuration 114 * @param fsGlobMax the maximum number of files/dirs for FS operation 115 */ 116 public static void setupMaxFSGlob(Configuration launcherConf, int fsGlobMax){ 117 launcherConf.setInt(LauncherMapper.CONF_OOZIE_ACTION_FS_GLOB_MAX, fsGlobMax); 118 } 119 120 public static void setupLauncherInfo(JobConf launcherConf, String jobId, String actionId, Path actionDir, 121 String recoveryId, Configuration actionConf, String prepareXML) throws IOException, HadoopAccessorException { 122 123 launcherConf.setMapperClass(LauncherMapper.class); 124 launcherConf.setSpeculativeExecution(false); 125 launcherConf.setNumMapTasks(1); 126 launcherConf.setNumReduceTasks(0); 127 128 launcherConf.set(LauncherMapper.OOZIE_JOB_ID, jobId); 129 launcherConf.set(LauncherMapper.OOZIE_ACTION_ID, actionId); 130 launcherConf.set(LauncherMapper.OOZIE_ACTION_DIR_PATH, actionDir.toString()); 131 launcherConf.set(LauncherMapper.OOZIE_ACTION_RECOVERY_ID, recoveryId); 132 launcherConf.set(LauncherMapper.ACTION_PREPARE_XML, prepareXML); 133 134 actionConf.set(LauncherMapper.OOZIE_JOB_ID, jobId); 135 actionConf.set(LauncherMapper.OOZIE_ACTION_ID, actionId); 136 137 if (Services.get().getConf().getBoolean("oozie.hadoop-2.0.2-alpha.workaround.for.distributed.cache", false)) { 138 List<String> purgedEntries = new ArrayList<String>(); 139 Collection<String> entries = actionConf.getStringCollection("mapreduce.job.cache.files"); 140 for (String entry : entries) { 141 if (entry.contains("#")) { 142 purgedEntries.add(entry); 143 } 144 } 145 actionConf.setStrings("mapreduce.job.cache.files", purgedEntries.toArray(new String[purgedEntries.size()])); 146 launcherConf.setBoolean("oozie.hadoop-2.0.2-alpha.workaround.for.distributed.cache", true); 147 } 148 149 FileSystem fs = 150 Services.get().get(HadoopAccessorService.class).createFileSystem(launcherConf.get("user.name"), 151 actionDir.toUri(), launcherConf); 152 fs.mkdirs(actionDir); 153 154 OutputStream os = fs.create(new Path(actionDir, LauncherMapper.ACTION_CONF_XML)); 155 try { 156 actionConf.writeXml(os); 157 } finally { 158 IOUtils.closeSafely(os); 159 } 160 161 launcherConf.setInputFormat(OozieLauncherInputFormat.class); 162 launcherConf.set("mapred.output.dir", new Path(actionDir, "output").toString()); 163 } 164 165 public static void setupYarnRestartHandling(JobConf launcherJobConf, Configuration actionConf, String launcherTag) 166 throws NoSuchAlgorithmException { 167 launcherJobConf.setLong(LauncherMainHadoopUtils.OOZIE_JOB_LAUNCH_TIME, System.currentTimeMillis()); 168 // Tags are limited to 100 chars so we need to hash them to make sure (the actionId otherwise doesn't have a max length) 169 String tag = getTag(launcherTag); 170 // keeping the oozie.child.mapreduce.job.tags instead of mapreduce.job.tags to avoid killing launcher itself. 171 // mapreduce.job.tags should only go to child job launch by launcher. 172 actionConf.set(LauncherMainHadoopUtils.CHILD_MAPREDUCE_JOB_TAGS, tag); 173 } 174 175 private static String getTag(String launcherTag) throws NoSuchAlgorithmException { 176 MessageDigest digest = MessageDigest.getInstance("MD5"); 177 digest.update(launcherTag.getBytes(), 0, launcherTag.length()); 178 String md5 = "oozie-" + new BigInteger(1, digest.digest()).toString(16); 179 return md5; 180 } 181 182 public static boolean isMainDone(RunningJob runningJob) throws IOException { 183 return runningJob.isComplete(); 184 } 185 186 public static boolean isMainSuccessful(RunningJob runningJob) throws IOException { 187 boolean succeeded = runningJob.isSuccessful(); 188 if (succeeded) { 189 Counters counters = runningJob.getCounters(); 190 if (counters != null) { 191 Counters.Group group = counters.getGroup(LauncherMapper.COUNTER_GROUP); 192 if (group != null) { 193 succeeded = group.getCounter(LauncherMapper.COUNTER_LAUNCHER_ERROR) == 0; 194 } 195 } 196 } 197 return succeeded; 198 } 199 200 /** 201 * Determine whether action has external child jobs or not 202 * @param actionData 203 * @return true/false 204 * @throws IOException 205 */ 206 public static boolean hasExternalChildJobs(Map<String, String> actionData) throws IOException { 207 return actionData.containsKey(LauncherMapper.ACTION_DATA_EXTERNAL_CHILD_IDS); 208 } 209 210 /** 211 * Determine whether action has output data or not 212 * @param actionData 213 * @return true/false 214 * @throws IOException 215 */ 216 public static boolean hasOutputData(Map<String, String> actionData) throws IOException { 217 return actionData.containsKey(LauncherMapper.ACTION_DATA_OUTPUT_PROPS); 218 } 219 220 /** 221 * Determine whether action has external stats or not 222 * @param actionData 223 * @return true/false 224 * @throws IOException 225 */ 226 public static boolean hasStatsData(Map<String, String> actionData) throws IOException{ 227 return actionData.containsKey(LauncherMapper.ACTION_DATA_STATS); 228 } 229 230 /** 231 * Determine whether action has new id (id swap) or not 232 * @param actionData 233 * @return true/false 234 * @throws IOException 235 */ 236 public static boolean hasIdSwap(Map<String, String> actionData) throws IOException { 237 return actionData.containsKey(LauncherMapper.ACTION_DATA_NEW_ID); 238 } 239 240 /** 241 * Get the sequence file path storing all action data 242 * @param actionDir 243 * @return 244 */ 245 public static Path getActionDataSequenceFilePath(Path actionDir) { 246 return new Path(actionDir, LauncherMapper.ACTION_DATA_SEQUENCE_FILE); 247 } 248 249 /** 250 * Utility function to load the contents of action data sequence file into 251 * memory object 252 * 253 * @param fs Action Filesystem 254 * @param actionDir Path 255 * @param conf Configuration 256 * @return Map action data 257 * @throws IOException 258 * @throws InterruptedException 259 */ 260 public static Map<String, String> getActionData(final FileSystem fs, final Path actionDir, final Configuration conf) 261 throws IOException, InterruptedException { 262 UserGroupInformationService ugiService = Services.get().get(UserGroupInformationService.class); 263 UserGroupInformation ugi = ugiService.getProxyUser(conf.get(OozieClient.USER_NAME)); 264 265 return ugi.doAs(new PrivilegedExceptionAction<Map<String, String>>() { 266 @Override 267 public Map<String, String> run() throws IOException { 268 Map<String, String> ret = new HashMap<String, String>(); 269 Path seqFilePath = getActionDataSequenceFilePath(actionDir); 270 if (fs.exists(seqFilePath)) { 271 SequenceFile.Reader seqFile = new SequenceFile.Reader(fs, seqFilePath, conf); 272 Text key = new Text(), value = new Text(); 273 while (seqFile.next(key, value)) { 274 ret.put(key.toString(), value.toString()); 275 } 276 seqFile.close(); 277 } 278 else { // maintain backward-compatibility. to be deprecated 279 org.apache.hadoop.fs.FileStatus[] files = fs.listStatus(actionDir); 280 InputStream is; 281 BufferedReader reader = null; 282 Properties props; 283 if (files != null && files.length > 0) { 284 for (int x = 0; x < files.length; x++) { 285 Path file = files[x].getPath(); 286 if (file.equals(new Path(actionDir, "externalChildIds.properties"))) { 287 is = fs.open(file); 288 reader = new BufferedReader(new InputStreamReader(is)); 289 ret.put(LauncherMapper.ACTION_DATA_EXTERNAL_CHILD_IDS, 290 IOUtils.getReaderAsString(reader, -1)); 291 } 292 else if (file.equals(new Path(actionDir, "newId.properties"))) { 293 is = fs.open(file); 294 reader = new BufferedReader(new InputStreamReader(is)); 295 props = PropertiesUtils.readProperties(reader, -1); 296 ret.put(LauncherMapper.ACTION_DATA_NEW_ID, props.getProperty("id")); 297 } 298 else if (file.equals(new Path(actionDir, LauncherMapper.ACTION_DATA_OUTPUT_PROPS))) { 299 int maxOutputData = conf.getInt(LauncherMapper.CONF_OOZIE_ACTION_MAX_OUTPUT_DATA, 300 2 * 1024); 301 is = fs.open(file); 302 reader = new BufferedReader(new InputStreamReader(is)); 303 ret.put(LauncherMapper.ACTION_DATA_OUTPUT_PROPS, PropertiesUtils 304 .propertiesToString(PropertiesUtils.readProperties(reader, maxOutputData))); 305 } 306 else if (file.equals(new Path(actionDir, LauncherMapper.ACTION_DATA_STATS))) { 307 int statsMaxOutputData = conf.getInt(LauncherMapper.CONF_OOZIE_EXTERNAL_STATS_MAX_SIZE, 308 Integer.MAX_VALUE); 309 is = fs.open(file); 310 reader = new BufferedReader(new InputStreamReader(is)); 311 ret.put(LauncherMapper.ACTION_DATA_STATS, PropertiesUtils 312 .propertiesToString(PropertiesUtils.readProperties(reader, statsMaxOutputData))); 313 } 314 else if (file.equals(new Path(actionDir, LauncherMapper.ACTION_DATA_ERROR_PROPS))) { 315 is = fs.open(file); 316 reader = new BufferedReader(new InputStreamReader(is)); 317 ret.put(LauncherMapper.ACTION_DATA_ERROR_PROPS, IOUtils.getReaderAsString(reader, -1)); 318 } 319 } 320 } 321 } 322 return ret; 323 } 324 }); 325 } 326}