/** * 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. */ package org.apache.hadoop.mapred; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.HashMap; import java.util.Map; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.TestCase; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.mapred.JobHistory.*; import org.apache.hadoop.mapred.QueueManager.QueueACL; import org.apache.hadoop.mapreduce.JobACL; import org.apache.hadoop.mapreduce.TaskType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authorize.AccessControlList; /** * Tests the JobHistory files - to catch any changes to JobHistory that can * cause issues for the execution of JobTracker.RecoveryManager, HistoryViewer. * * testJobHistoryFile * Run a job that will be succeeded and validate its history file format and * content. * * testJobHistoryUserLogLocation * Run jobs with the given values of hadoop.job.history.user.location as * (1)null(default case), (2)"none", and (3)some user specified dir. * Validate user history file location in each case. * * testJobHistoryJobStatus * Run jobs that will be (1) succeeded (2) failed (3) killed. * Validate job status read from history file in each case. * * Future changes to job history are to be reflected here in this file. */ public class TestJobHistory extends TestCase { private static final Log LOG = LogFactory.getLog(TestJobHistory.class); private static String TEST_ROOT_DIR = new File(System.getProperty( "test.build.data", "/tmp")).toURI().toString().replace(' ', '+'); private static final Pattern digitsPattern = Pattern.compile(JobHistory.DIGITS); // hostname like /default-rack/host1.foo.com OR host1.foo.com private static final Pattern hostNamePattern = Pattern.compile( "(/(([\\w\\-\\.]+)/)+)?([\\w\\-\\.]+)"); private static final String IP_ADDR = "\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\.\\d\\d?\\d?"; // hostname like /default-rack/host1.foo.com OR host1.foo.com private static final Pattern trackerNamePattern = Pattern.compile( "tracker_" + hostNamePattern + ":([\\w\\-\\.]+)/" + IP_ADDR + ":" + JobHistory.DIGITS); private static final Pattern splitsPattern = Pattern.compile( hostNamePattern + "(," + hostNamePattern + ")*"); private static Map<String, List<String>> taskIDsToAttemptIDs = new HashMap<String, List<String>>(); //Each Task End seen from history file is added here private static List<String> taskEnds = new ArrayList<String>(); // List of tasks that appear in history file after JT reatart. This is to // allow START_TIME=0 for these tasks. private static List<String> ignoreStartTimeOfTasks = new ArrayList<String>(); // List of potential tasks whose start time can be 0 because of JT restart private static List<String> tempIgnoreStartTimeOfTasks = new ArrayList<String>(); /** * Listener for history log file, it populates JobHistory.JobInfo * object with data from log file and validates the data. */ static class TestListener extends DefaultJobHistoryParser.JobTasksParseListener { int lineNum;//line number of history log file boolean isJobLaunched; boolean isJTRestarted; TestListener(JobHistory.JobInfo job) { super(job); lineNum = 0; isJobLaunched = false; isJTRestarted = false; } // TestListener implementation public void handle(RecordTypes recType, Map<Keys, String> values) throws IOException { lineNum++; // Check if the record is of type Meta if (recType == JobHistory.RecordTypes.Meta) { long version = Long.parseLong(values.get(Keys.VERSION)); assertTrue("Unexpected job history version ", (version >= 0 && version <= JobHistory.VERSION)); } else if (recType.equals(RecordTypes.Job)) { String jobid = values.get(Keys.JOBID); assertTrue("record type 'Job' is seen without JOBID key" + " in history file at line " + lineNum, jobid != null); JobID id = JobID.forName(jobid); assertTrue("JobID in history file is in unexpected format " + "at line " + lineNum, id != null); String time = values.get(Keys.LAUNCH_TIME); if (time != null) { if (isJobLaunched) { // We assume that if we see LAUNCH_TIME again, it is because of JT restart isJTRestarted = true; } else {// job launched first time isJobLaunched = true; } } time = values.get(Keys.FINISH_TIME); if (time != null) { assertTrue ("Job FINISH_TIME is seen in history file at line " + lineNum + " before LAUNCH_TIME is seen", isJobLaunched); } } else if (recType.equals(RecordTypes.Task)) { String taskid = values.get(Keys.TASKID); assertTrue("record type 'Task' is seen without TASKID key" + " in history file at line " + lineNum, taskid != null); TaskID id = TaskID.forName(taskid); assertTrue("TaskID in history file is in unexpected format " + "at line " + lineNum, id != null); String time = values.get(Keys.START_TIME); if (time != null) { List<String> attemptIDs = taskIDsToAttemptIDs.get(taskid); assertTrue("Duplicate START_TIME seen for task " + taskid + " in history file at line " + lineNum, attemptIDs == null); attemptIDs = new ArrayList<String>(); taskIDsToAttemptIDs.put(taskid, attemptIDs); if (isJTRestarted) { // This maintains a potential ignoreStartTimeTasks list tempIgnoreStartTimeOfTasks.add(taskid); } } time = values.get(Keys.FINISH_TIME); if (time != null) { String s = values.get(Keys.TASK_STATUS); if (s != null) { List<String> attemptIDs = taskIDsToAttemptIDs.get(taskid); assertTrue ("Task FINISH_TIME is seen in history file at line " + lineNum + " before START_TIME is seen", attemptIDs != null); // Check if all the attemptIDs of this task are finished assertTrue("TaskId " + taskid + " is finished at line " + lineNum + " but its attemptID is not finished.", (attemptIDs.size() <= 1)); // Check if at least 1 attempt of this task is seen assertTrue("TaskId " + taskid + " is finished at line " + lineNum + " but no attemptID is seen before this.", attemptIDs.size() == 1); if (s.equals("KILLED") || s.equals("FAILED")) { // Task End with KILLED/FAILED status in history file is // considered as TaskEnd, TaskStart. This is useful in checking // the order of history lines. attemptIDs = new ArrayList<String>(); taskIDsToAttemptIDs.put(taskid, attemptIDs); } else { taskEnds.add(taskid); } } else { // This line of history file could be just an update to finish time } } } else if (recType.equals(RecordTypes.MapAttempt) || recType.equals(RecordTypes.ReduceAttempt)) { String taskid = values.get(Keys.TASKID); assertTrue("record type " + recType + " is seen without TASKID key" + " in history file at line " + lineNum, taskid != null); String attemptId = values.get(Keys.TASK_ATTEMPT_ID); TaskAttemptID id = TaskAttemptID.forName(attemptId); assertTrue("AttemptID in history file is in unexpected format " + "at line " + lineNum, id != null); String time = values.get(Keys.START_TIME); if (time != null) { List<String> attemptIDs = taskIDsToAttemptIDs.get(taskid); assertTrue ("TaskAttempt is seen in history file at line " + lineNum + " before Task is seen", attemptIDs != null); assertFalse ("Duplicate TaskAttempt START_TIME is seen in history " + "file at line " + lineNum, attemptIDs.remove(attemptId)); if (attemptIDs.isEmpty()) { //just a boolean whether any attempt is seen or not attemptIDs.add("firstAttemptIsSeen"); } attemptIDs.add(attemptId); if (tempIgnoreStartTimeOfTasks.contains(taskid) && (id.getId() < 1000)) { // If Task line of this attempt is seen in history file after // JT restart and if this attempt is < 1000(i.e. attempt is noti // started after JT restart) - assuming single JT restart happened ignoreStartTimeOfTasks.add(taskid); } } time = values.get(Keys.FINISH_TIME); if (time != null) { List<String> attemptIDs = taskIDsToAttemptIDs.get(taskid); assertTrue ("TaskAttempt FINISH_TIME is seen in history file at line " + lineNum + " before Task is seen", attemptIDs != null); assertTrue ("TaskAttempt FINISH_TIME is seen in history file at line " + lineNum + " before TaskAttempt START_TIME is seen", attemptIDs.remove(attemptId)); } } super.handle(recType, values); } } // Check if the time is in the expected format private static boolean isTimeValid(String time) { Matcher m = digitsPattern.matcher(time); return m.matches() && (Long.parseLong(time) > 0); } private static boolean areTimesInOrder(String time1, String time2) { return (Long.parseLong(time1) <= Long.parseLong(time2)); } // Validate Format of Job Level Keys, Values read from history file private static void validateJobLevelKeyValuesFormat(Map<Keys, String> values, String status) { String time = values.get(Keys.SUBMIT_TIME); assertTrue("Job SUBMIT_TIME is in unexpected format:" + time + " in history file", isTimeValid(time)); time = values.get(Keys.LAUNCH_TIME); assertTrue("Job LAUNCH_TIME is in unexpected format:" + time + " in history file", isTimeValid(time)); String time1 = values.get(Keys.FINISH_TIME); assertTrue("Job FINISH_TIME is in unexpected format:" + time1 + " in history file", isTimeValid(time1)); assertTrue("Job FINISH_TIME is < LAUNCH_TIME in history file", areTimesInOrder(time, time1)); String stat = values.get(Keys.JOB_STATUS); assertTrue("Unexpected JOB_STATUS \"" + stat + "\" is seen in" + " history file", (status.equals(stat))); String priority = values.get(Keys.JOB_PRIORITY); assertTrue("Unknown priority for the job in history file", (priority.equals("HIGH") || priority.equals("LOW") || priority.equals("NORMAL") || priority.equals("VERY_HIGH") || priority.equals("VERY_LOW"))); } // Validate Format of Task Level Keys, Values read from history file private static void validateTaskLevelKeyValuesFormat(JobHistory.JobInfo job, boolean splitsCanBeEmpty) { Map<String, JobHistory.Task> tasks = job.getAllTasks(); // validate info of each task for (JobHistory.Task task : tasks.values()) { String tid = task.get(Keys.TASKID); String time = task.get(Keys.START_TIME); // We allow START_TIME=0 for tasks seen in history after JT restart if (!ignoreStartTimeOfTasks.contains(tid) || (Long.parseLong(time) != 0)) { assertTrue("Task START_TIME of " + tid + " is in unexpected format:" + time + " in history file", isTimeValid(time)); } String time1 = task.get(Keys.FINISH_TIME); assertTrue("Task FINISH_TIME of " + tid + " is in unexpected format:" + time1 + " in history file", isTimeValid(time1)); assertTrue("Task FINISH_TIME is < START_TIME in history file", areTimesInOrder(time, time1)); // Make sure that the Task type exists and it is valid String type = task.get(Keys.TASK_TYPE); assertTrue("Unknown Task type \"" + type + "\" is seen in " + "history file for task " + tid, (type.equals("MAP") || type.equals("REDUCE") || type.equals("SETUP") || type.equals("CLEANUP"))); if (type.equals("MAP")) { String splits = task.get(Keys.SPLITS); //order in the condition OR check is important here if (!splitsCanBeEmpty || splits.length() != 0) { Matcher m = splitsPattern.matcher(splits); assertTrue("Unexpected format of SPLITS \"" + splits + "\" is seen" + " in history file for task " + tid, m.matches()); } } // Validate task status String status = task.get(Keys.TASK_STATUS); assertTrue("Unexpected TASK_STATUS \"" + status + "\" is seen in" + " history file for task " + tid, (status.equals("SUCCESS") || status.equals("FAILED") || status.equals("KILLED"))); } } // Validate foramt of Task Attempt Level Keys, Values read from history file private static void validateTaskAttemptLevelKeyValuesFormat(JobHistory.JobInfo job) { Map<String, JobHistory.Task> tasks = job.getAllTasks(); // For each task for (JobHistory.Task task : tasks.values()) { // validate info of each attempt for (JobHistory.TaskAttempt attempt : task.getTaskAttempts().values()) { String id = attempt.get(Keys.TASK_ATTEMPT_ID); String time = attempt.get(Keys.START_TIME); assertTrue("START_TIME of task attempt " + id + " is in unexpected format:" + time + " in history file", isTimeValid(time)); String time1 = attempt.get(Keys.FINISH_TIME); assertTrue("FINISH_TIME of task attempt " + id + " is in unexpected format:" + time1 + " in history file", isTimeValid(time1)); assertTrue("Task FINISH_TIME is < START_TIME in history file", areTimesInOrder(time, time1)); // Make sure that the Task type exists and it is valid String type = attempt.get(Keys.TASK_TYPE); assertTrue("Unknown Task type \"" + type + "\" is seen in " + "history file for task attempt " + id, (type.equals("MAP") || type.equals("REDUCE") || type.equals("SETUP") || type.equals("CLEANUP"))); // Validate task status String status = attempt.get(Keys.TASK_STATUS); assertTrue("Unexpected TASK_STATUS \"" + status + "\" is seen in" + " history file for task attempt " + id, (status.equals("SUCCESS") || status.equals("FAILED") || status.equals("KILLED"))); // Validate task Avataar String avataar = attempt.get(Keys.AVATAAR); assertTrue("Unexpected LOCALITY \"" + avataar + "\" is seen in " + " history file for task attempt " + id, (avataar.equals("VIRGIN") || avataar.equals("SPECULATIVE")) ); // Map Task Attempts should have valid LOCALITY if (type.equals("MAP")) { String locality = attempt.get(Keys.LOCALITY); assertTrue("Unexpected LOCALITY \"" + locality + "\" is seen in " + " history file for task attempt " + id, (locality.equals("NODE_LOCAL") || locality.equals("GROUP_LOCAL") || locality.equals("RACK_LOCAL") || locality.equals("OFF_SWITCH")) ); } // Reduce Task Attempts should have valid SHUFFLE_FINISHED time and // SORT_FINISHED time if (type.equals("REDUCE") && status.equals("SUCCESS")) { time1 = attempt.get(Keys.SHUFFLE_FINISHED); assertTrue("SHUFFLE_FINISHED time of task attempt " + id + " is in unexpected format:" + time1 + " in history file", isTimeValid(time1)); assertTrue("Reduce Task SHUFFLE_FINISHED time is < START_TIME " + "in history file", areTimesInOrder(time, time1)); time = attempt.get(Keys.SORT_FINISHED); assertTrue("SORT_FINISHED of task attempt " + id + " is in unexpected format:" + time + " in history file", isTimeValid(time)); assertTrue("Reduce Task SORT_FINISHED time is < SORT_FINISHED time" + " in history file", areTimesInOrder(time1, time)); } // check if hostname is valid String hostname = attempt.get(Keys.HOSTNAME); Matcher m = hostNamePattern.matcher(hostname); assertTrue("Unexpected Host name of task attempt " + id, m.matches()); // check if trackername is valid String trackerName = attempt.get(Keys.TRACKER_NAME); m = trackerNamePattern.matcher(trackerName); assertTrue("Unexpected tracker name of task attempt " + id, m.matches()); if (!status.equals("KILLED")) { // check if http port is valid String httpPort = attempt.get(Keys.HTTP_PORT); m = digitsPattern.matcher(httpPort); assertTrue("Unexpected http port of task attempt " + id, m.matches()); } // check if counters are parsable String counters = attempt.get(Keys.COUNTERS); try { Counters readCounters = Counters.fromEscapedCompactString(counters); assertTrue("Counters of task attempt " + id + " are not parsable", readCounters != null); } catch (ParseException pe) { LOG.warn("While trying to parse counters of task attempt " + id + ", " + pe); } } } } /** * Returns the conf file name in the same * @param path path of the jobhistory file * @param running whether the job is running or completed */ private static Path getPathForConf(Path path) { return JobHistory.confPathFromLogFilePath(path); } /** * Validates the format of contents of history file * (1) history file exists and in correct location * (2) Verify if the history file is parsable * (3) Validate the contents of history file * (a) Format of all TIMEs are checked against a regex * (b) validate legality/format of job level key, values * (c) validate legality/format of task level key, values * (d) validate legality/format of attempt level key, values * (e) check if all the TaskAttempts, Tasks started are finished. * Check finish of each TaskAttemptID against its start to make sure * that all TaskAttempts, Tasks started are indeed finished and the * history log lines are in the proper order. * We want to catch ordering of history lines like * Task START * Attempt START * Task FINISH * Attempt FINISH * (speculative execution is turned off for this). * @param id job id * @param conf job conf */ static void validateJobHistoryFileFormat(JobID id, JobConf conf, String status, boolean splitsCanBeEmpty) throws IOException { // Get the history file name Path dir = JobHistory.getCompletedJobHistoryLocation(); String logFileName = getDoneFile(conf, id, dir); // Framework history log file location Path logFile = new Path(dir, logFileName); FileSystem fileSys = logFile.getFileSystem(conf); // Check if the history file exists assertTrue("History file does not exist", fileSys.exists(logFile)); // Check that the log file name includes a directory level for the version number assertTrue("History filename does not include a directory level " + "for the version number.", logFile.toString() .contains("/" + JobHistory.DONE_DIRECTORY_FORMAT_DIRNAME + "/")); // check if the history file is parsable String[] jobDetails = JobHistory.JobInfo.decodeJobHistoryFileName( logFileName).split("_"); String jobId = jobDetails[2] + "_" + jobDetails[3] + "_" + jobDetails[4]; JobHistory.JobInfo jobInfo = new JobHistory.JobInfo(jobId); TestListener l = new TestListener(jobInfo); JobHistory.parseHistoryFromFS(logFile.toString().substring(5), l, fileSys); // validate format of job level key, values validateJobLevelKeyValuesFormat(jobInfo.getValues(), status); // validate format of task level key, values validateTaskLevelKeyValuesFormat(jobInfo, splitsCanBeEmpty); // validate format of attempt level key, values validateTaskAttemptLevelKeyValuesFormat(jobInfo); // check if all the TaskAttempts, Tasks started are finished for // successful jobs if (status.equals("SUCCESS")) { // Make sure that the lists in taskIDsToAttemptIDs are empty. for(Iterator<String> it = taskIDsToAttemptIDs.keySet().iterator();it.hasNext();) { String taskid = it.next(); assertTrue("There are some Tasks which are not finished in history " + "file.", taskEnds.contains(taskid)); List<String> attemptIDs = taskIDsToAttemptIDs.get(taskid); if(attemptIDs != null) { assertTrue("Unexpected. TaskID " + taskid + " has task attempt(s)" + " that are not finished.", (attemptIDs.size() == 1)); } } } } // Validate Job Level Keys, Values read from history file by // comparing them with the actual values from JT. private static void validateJobLevelKeyValues(MiniMRCluster mr, RunningJob job, JobHistory.JobInfo jobInfo, JobConf conf) throws IOException { JobTracker jt = mr.getJobTrackerRunner().getJobTracker(); JobInProgress jip = jt.getJob(job.getID()); Map<Keys, String> values = jobInfo.getValues(); assertTrue("SUBMIT_TIME of job obtained from history file did not " + "match the expected value", jip.getStartTime() == Long.parseLong(values.get(Keys.SUBMIT_TIME))); assertTrue("LAUNCH_TIME of job obtained from history file did not " + "match the expected value", jip.getLaunchTime() == Long.parseLong(values.get(Keys.LAUNCH_TIME))); assertTrue("FINISH_TIME of job obtained from history file did not " + "match the expected value", jip.getFinishTime() == Long.parseLong(values.get(Keys.FINISH_TIME))); assertTrue("Job Status of job obtained from history file did not " + "match the expected value", values.get(Keys.JOB_STATUS).equals("SUCCESS")); assertTrue("Job Priority of job obtained from history file did not " + "match the expected value", jip.getPriority().toString().equals( values.get(Keys.JOB_PRIORITY))); assertTrue("Job Name of job obtained from history file did not " + "match the expected value", JobHistory.JobInfo.getJobName(conf).equals( values.get(Keys.JOBNAME))); assertTrue("User Name of job obtained from history file did not " + "match the expected value", JobHistory.JobInfo.getUserName(conf).equals( values.get(Keys.USER))); // Validate job counters Counters c = new Counters(); jip.getCounters(c); assertTrue("Counters of job obtained from history file did not " + "match the expected value", c.makeEscapedCompactString().equals(values.get(Keys.COUNTERS))); Counters m = new Counters(); jip.getMapCounters(m); assertTrue("Map Counters of job obtained from history file did not " + "match the expected value", m.makeEscapedCompactString(). equals(values.get(Keys.MAP_COUNTERS))); Counters r = new Counters(); jip.getReduceCounters(r); assertTrue("Reduce Counters of job obtained from history file did not " + "match the expected value", r.makeEscapedCompactString(). equals(values.get(Keys.REDUCE_COUNTERS))); // Validate number of total maps, total reduces, finished maps, // finished reduces, failed maps, failed recudes String totalMaps = values.get(Keys.TOTAL_MAPS); assertTrue("Unexpected number of total maps in history file", Integer.parseInt(totalMaps) == jip.desiredMaps()); String totalReduces = values.get(Keys.TOTAL_REDUCES); assertTrue("Unexpected number of total reduces in history file", Integer.parseInt(totalReduces) == jip.desiredReduces()); String finMaps = values.get(Keys.FINISHED_MAPS); assertTrue("Unexpected number of finished maps in history file", Integer.parseInt(finMaps) == jip.finishedMaps()); String finReduces = values.get(Keys.FINISHED_REDUCES); assertTrue("Unexpected number of finished reduces in history file", Integer.parseInt(finReduces) == jip.finishedReduces()); String failedMaps = values.get(Keys.FAILED_MAPS); assertTrue("Unexpected number of failed maps in history file", Integer.parseInt(failedMaps) == jip.failedMapTasks); String failedReduces = values.get(Keys.FAILED_REDUCES); assertTrue("Unexpected number of failed reduces in history file", Integer.parseInt(failedReduces) == jip.failedReduceTasks); } // Validate Task Level Keys, Values read from history file by // comparing them with the actual values from JT. private static void validateTaskLevelKeyValues(MiniMRCluster mr, RunningJob job, JobHistory.JobInfo jobInfo) throws IOException { JobTracker jt = mr.getJobTrackerRunner().getJobTracker(); JobInProgress jip = jt.getJob(job.getID()); // Get the 1st map, 1st reduce, cleanup & setup taskIDs and // validate their history info TaskID mapTaskId = new TaskID(job.getID(), true, 0); TaskID reduceTaskId = new TaskID(job.getID(), false, 0); TaskInProgress cleanups[] = jip.getTasks(TaskType.JOB_CLEANUP); TaskID cleanupTaskId; if (cleanups[0].isComplete()) { cleanupTaskId = cleanups[0].getTIPId(); } else { cleanupTaskId = cleanups[1].getTIPId(); } TaskInProgress setups[] = jip.getTasks(TaskType.JOB_SETUP); TaskID setupTaskId; if (setups[0].isComplete()) { setupTaskId = setups[0].getTIPId(); } else { setupTaskId = setups[1].getTIPId(); } Map<String, JobHistory.Task> tasks = jobInfo.getAllTasks(); // validate info of the 4 tasks(cleanup, setup, 1st map, 1st reduce) for (JobHistory.Task task : tasks.values()) { String tid = task.get(Keys.TASKID); if (tid.equals(mapTaskId.toString()) || tid.equals(reduceTaskId.toString()) || tid.equals(cleanupTaskId.toString()) || tid.equals(setupTaskId.toString())) { TaskID taskId = null; if (tid.equals(mapTaskId.toString())) { taskId = mapTaskId; } else if (tid.equals(reduceTaskId.toString())) { taskId = reduceTaskId; } else if (tid.equals(cleanupTaskId.toString())) { taskId = cleanupTaskId; } else if (tid.equals(setupTaskId.toString())) { taskId = setupTaskId; } TaskInProgress tip = jip.getTaskInProgress(taskId); assertTrue("START_TIME of Task " + tid + " obtained from history " + "file did not match the expected value", tip.getExecStartTime() == Long.parseLong(task.get(Keys.START_TIME))); assertTrue("FINISH_TIME of Task " + tid + " obtained from history " + "file did not match the expected value", tip.getExecFinishTime() == Long.parseLong(task.get(Keys.FINISH_TIME))); if (taskId == mapTaskId) {//check splits only for map task assertTrue("Splits of Task " + tid + " obtained from history file " + " did not match the expected value", tip.getSplitNodes().equals(task.get(Keys.SPLITS))); } TaskAttemptID attemptId = tip.getSuccessfulTaskid(); TaskStatus ts = tip.getTaskStatus(attemptId); // Validate task counters Counters c = ts.getCounters(); assertTrue("Counters of Task " + tid + " obtained from history file " + " did not match the expected value", c.makeEscapedCompactString().equals(task.get(Keys.COUNTERS))); } } } // Validate Task Attempt Level Keys, Values read from history file by // comparing them with the actual values from JT. private static void validateTaskAttemptLevelKeyValues(MiniMRCluster mr, RunningJob job, JobHistory.JobInfo jobInfo) throws IOException { JobTracker jt = mr.getJobTrackerRunner().getJobTracker(); JobInProgress jip = jt.getJob(job.getID()); Map<String, JobHistory.Task> tasks = jobInfo.getAllTasks(); // For each task for (JobHistory.Task task : tasks.values()) { // validate info of each attempt for (JobHistory.TaskAttempt attempt : task.getTaskAttempts().values()) { String idStr = attempt.get(Keys.TASK_ATTEMPT_ID); TaskAttemptID attemptId = TaskAttemptID.forName(idStr); TaskID tid = attemptId.getTaskID(); // Validate task id assertTrue("Task id of Task Attempt " + idStr + " obtained from " + "history file did not match the expected value", tid.toString().equals(attempt.get(Keys.TASKID))); TaskInProgress tip = jip.getTaskInProgress(tid); TaskStatus ts = tip.getTaskStatus(attemptId); // Validate task attempt start time assertTrue("START_TIME of Task attempt " + idStr + " obtained from " + "history file did not match the expected value", ts.getStartTime() == Long.parseLong(attempt.get(Keys.START_TIME))); // Validate task attempt finish time assertTrue("FINISH_TIME of Task attempt " + idStr + " obtained from " + "history file did not match the expected value", ts.getFinishTime() == Long.parseLong(attempt.get(Keys.FINISH_TIME))); TaskTrackerStatus ttStatus = jt.getTaskTrackerStatus(ts.getTaskTracker()); if (ttStatus != null) { assertTrue("http port of task attempt " + idStr + " obtained from " + "history file did not match the expected value", ttStatus.getHttpPort() == Integer.parseInt(attempt.get(Keys.HTTP_PORT))); if (attempt.get(Keys.TASK_STATUS).equals("SUCCESS")) { String ttHostname = jt.getNode(ttStatus.getHost()).toString(); // check if hostname is valid assertTrue("Host name of task attempt " + idStr + " obtained from" + " history file did not match the expected value", ttHostname.equals(attempt.get(Keys.HOSTNAME))); } } if (attempt.get(Keys.TASK_STATUS).equals("SUCCESS")) { // Validate SHUFFLE_FINISHED time and SORT_FINISHED time of // Reduce Task Attempts if (attempt.get(Keys.TASK_TYPE).equals("REDUCE")) { assertTrue("SHUFFLE_FINISHED time of task attempt " + idStr + " obtained from history file did not match the expected" + " value", ts.getShuffleFinishTime() == Long.parseLong(attempt.get(Keys.SHUFFLE_FINISHED))); assertTrue("SORT_FINISHED time of task attempt " + idStr + " obtained from history file did not match the expected" + " value", ts.getSortFinishTime() == Long.parseLong(attempt.get(Keys.SORT_FINISHED))); } //Validate task counters Counters c = ts.getCounters(); assertTrue("Counters of Task Attempt " + idStr + " obtained from " + "history file did not match the expected value", c.makeEscapedCompactString().equals(attempt.get(Keys.COUNTERS))); } // check if tracker name is valid assertTrue("Tracker name of task attempt " + idStr + " obtained from " + "history file did not match the expected value", ts.getTaskTracker().equals(attempt.get(Keys.TRACKER_NAME))); } } } /** * Checks if the history file content is as expected comparing with the * actual values obtained from JT. * Job Level, Task Level and Task Attempt Level Keys, Values are validated. * @param job RunningJob object of the job whose history is to be validated * @param conf job conf */ static void validateJobHistoryFileContent(MiniMRCluster mr, RunningJob job, JobConf conf) throws IOException { JobID id = job.getID(); Path doneDir = JobHistory.getCompletedJobHistoryLocation(); // Get the history file name String logFileName = getDoneFile(conf, id, doneDir); // Framework history log file location Path logFile = new Path(doneDir, logFileName); FileSystem fileSys = logFile.getFileSystem(conf); // Check if the history file exists assertTrue("History file does not exist", fileSys.exists(logFile)); // check if the history file is parsable String[] jobDetails = JobHistory.JobInfo.decodeJobHistoryFileName( logFileName).split("_"); String jobId = jobDetails[2] + "_" + jobDetails[3] + "_" + jobDetails[4]; JobHistory.JobInfo jobInfo = new JobHistory.JobInfo(jobId); DefaultJobHistoryParser.JobTasksParseListener l = new DefaultJobHistoryParser.JobTasksParseListener(jobInfo); JobHistory.parseHistoryFromFS(logFile.toString().substring(5), l, fileSys); // Now the history file contents are available in jobInfo. Let us compare // them with the actual values from JT. validateJobLevelKeyValues(mr, job, jobInfo, conf); validateTaskLevelKeyValues(mr, job, jobInfo); validateTaskAttemptLevelKeyValues(mr, job, jobInfo); // Also JobACLs should be correct if (mr.getJobTrackerRunner().getJobTracker().areACLsEnabled()) { AccessControlList acl = new AccessControlList( conf.get(JobACL.VIEW_JOB.getAclName(), " ")); assertTrue(acl.toString().equals( jobInfo.getJobACLs().get(JobACL.VIEW_JOB).toString())); acl = new AccessControlList( conf.get(JobACL.MODIFY_JOB.getAclName(), " ")); assertTrue(acl.toString().equals( jobInfo.getJobACLs().get(JobACL.MODIFY_JOB).toString())); } // Validate the job queue name assertTrue(jobInfo.getJobQueue().equals(conf.getQueueName())); // Validate the workflow properties assertTrue(jobInfo.get(Keys.WORKFLOW_ID).equals( conf.get(JobConf.WORKFLOW_ID, ""))); assertTrue(jobInfo.get(Keys.WORKFLOW_NAME).equals( conf.get(JobConf.WORKFLOW_NAME, ""))); assertTrue(jobInfo.get(Keys.WORKFLOW_NODE_NAME).equals( conf.get(JobConf.WORKFLOW_NODE_NAME, ""))); assertTrue(jobInfo.get(Keys.WORKFLOW_ADJACENCIES).equals( JobHistory.JobInfo.getWorkflowAdjacencies(conf))); assertTrue(jobInfo.get(Keys.WORKFLOW_TAGS).equals( conf.get(JobConf.WORKFLOW_TAGS, ""))); } public void testDoneFolderOnHDFS() throws IOException { MiniMRCluster mr = null; try { JobConf conf = new JobConf(); // keep for less time conf.setLong("mapred.jobtracker.retirejob.check", 1000); conf.setLong("mapred.jobtracker.retirejob.interval", 100000); //set the done folder location String doneFolder = "history_done"; conf.set("mapred.job.tracker.history.completed.location", doneFolder); MiniDFSCluster dfsCluster = new MiniDFSCluster(conf, 2, true, null); mr = new MiniMRCluster(2, dfsCluster.getFileSystem().getUri().toString(), 3, null, null, conf); // run the TCs conf = mr.createJobConf(); FileSystem fs = FileSystem.get(conf); // clean up fs.delete(new Path("succeed"), true); Path inDir = new Path("succeed/input"); Path outDir = new Path("succeed/output"); //Disable speculative execution conf.setSpeculativeExecution(false); // Make sure that the job is not removed from memory until we do finish // the validation of history file content conf.setInt("mapred.jobtracker.completeuserjobs.maximum", 10); conf.set("user.name", UserGroupInformation.getCurrentUser().getUserName()); // Run a job that will be succeeded and validate its history file RunningJob job = UtilsForTests.runJobSucceed(conf, inDir, outDir); Path doneDir = JobHistory.getCompletedJobHistoryLocation(); assertEquals("History DONE folder not correct", doneFolder, doneDir.getName()); JobID id = job.getID(); String logFileName = getDoneFile(conf, id, doneDir); assertNotNull(logFileName); System.err.println("testDoneFolderOnHDFS -- seeking " + logFileName); // Framework history log file location Path logFile = new Path(doneDir, logFileName); FileSystem fileSys = logFile.getFileSystem(conf); // Check if the history file exists assertTrue("History file does not exist", fileSys.exists(logFile)); // check if the corresponding conf file exists Path confFile = getPathForConf(logFile); assertTrue("Config for completed jobs doesnt exist: " + confFile, fileSys.exists(confFile)); // check if the file exists under a done folder assertTrue("Completed job config doesnt exist under the done folder", confFile.toString().startsWith(doneDir.toString())); // check if the file exists in a done folder assertTrue("Completed jobs doesnt exist under the done folder", logFile.toString().startsWith(doneDir.toString())); assertTrue("Completed job and config file aren't in the same directory", confFile.getParent().toString().equals(logFile.getParent().toString())); // Test that all of the ancestors of the log file have the same // permissions as the done directory Path cursor = logFile.getParent(); Path doneParent = doneDir.getParent(); FsPermission donePermission = getStatus(fileSys, doneDir).getPermission(); System.err.println("testDoneFolderOnHDFS: done dir permission = " + donePermission); while (!cursor.equals(doneParent)) { FileStatus cursorStatus = getStatus(fileSys, cursor); FsPermission cursorPermission = cursorStatus.getPermission(); assertEquals("testDoneFolderOnHDFS: A done directory descendant, " + cursor + " does not have the same permisison as the done directory, " + doneDir, donePermission, cursorPermission); cursor = cursor.getParent(); } // check if the job file is removed from the history location Path runningJobsHistoryFolder = logFile.getParent().getParent(); Path runningJobHistoryFilename = new Path(runningJobsHistoryFolder, logFile.getName()); Path runningJobConfFilename = new Path(runningJobsHistoryFolder, confFile.getName()); assertFalse("History file not deleted from the running folder", fileSys.exists(runningJobHistoryFilename)); assertFalse("Config for completed jobs not deleted from running folder", fileSys.exists(runningJobConfFilename)); validateJobHistoryFileFormat(job.getID(), conf, "SUCCESS", false); validateJobHistoryFileContent(mr, job, conf); // get the job conf filename } finally { if (mr != null) { cleanupLocalFiles(mr); mr.shutdown(); } } } private static FileStatus getStatus(FileSystem fs, final Path path) { Path pathParent = path.getParent(); try { FileStatus[] statuses = fs.listStatus(pathParent, new PathFilter() { @Override public boolean accept(Path filterPath) { return filterPath.getName().equals(path.getName()); } } ); return statuses[0]; } catch (IOException e) { return null; } } /** Run a job that will be succeeded and validate its history file format * and its content. */ public void testJobHistoryFile() throws IOException { MiniMRCluster mr = null; try { JobConf conf = new JobConf(); // keep for less time conf.setLong("mapred.jobtracker.retirejob.check", 1000); conf.setLong("mapred.jobtracker.retirejob.interval", 100000); //set the done folder location String doneFolder = TEST_ROOT_DIR + "history_done"; conf.set("mapred.job.tracker.history.completed.location", doneFolder); // Enable ACLs so that they are logged to history conf.setBoolean(JobConf.MR_ACLS_ENABLED, true); // no queue admins for default queue conf.set(QueueManager.toFullPropertyName( "default", QueueACL.ADMINISTER_JOBS.getAclName()), " "); // set workflow properties conf.set(JobConf.WORKFLOW_ID, "workflowId1"); conf.set(JobConf.WORKFLOW_NAME, "workflowName1"); String workflowNodeName = "A"; conf.set(JobConf.WORKFLOW_NODE_NAME, workflowNodeName); conf.set(JobConf.WORKFLOW_ADJACENCY_PREFIX_STRING + workflowNodeName, "BC"); conf.set(JobConf.WORKFLOW_ADJACENCY_PREFIX_STRING + workflowNodeName, "DEF"); conf.set(JobConf.WORKFLOW_ADJACENCY_PREFIX_STRING + "DEF", "G"); conf.set(JobConf.WORKFLOW_ADJACENCY_PREFIX_STRING + "Z", workflowNodeName); conf.set(JobConf.WORKFLOW_TAGS, "tag1,tag2"); mr = new MiniMRCluster(2, "file:///", 3, null, null, conf); // run the TCs conf = mr.createJobConf(); FileSystem fs = FileSystem.get(conf); // clean up fs.delete(new Path(TEST_ROOT_DIR + "/succeed"), true); Path inDir = new Path(TEST_ROOT_DIR + "/succeed/input"); Path outDir = new Path(TEST_ROOT_DIR + "/succeed/output"); //Disable speculative execution conf.setSpeculativeExecution(false); conf.set(JobACL.VIEW_JOB.getAclName(), "user1,user2 group1,group2"); conf.set(JobACL.MODIFY_JOB.getAclName(), "user3,user4 group3,group4"); // Make sure that the job is not removed from memory until we do finish // the validation of history file content conf.setInt("mapred.jobtracker.completeuserjobs.maximum", 10); conf.set("user.name", UserGroupInformation.getCurrentUser().getUserName()); // Run a job that will be succeeded and validate its history file RunningJob job = UtilsForTests.runJobSucceed(conf, inDir, outDir); Path doneDir = JobHistory.getCompletedJobHistoryLocation(); assertEquals("History DONE folder not correct", doneFolder, doneDir.toString()); JobID id = job.getID(); String logFileName = getDoneFile(conf, id, doneDir); // Framework history log file location Path logFile = new Path(doneDir, logFileName); FileSystem fileSys = logFile.getFileSystem(conf); // Check if the history file exists System.err.println("testJobHistoryFile -- seeking " + logFile); assertTrue("History file does not exist", fileSys.exists(logFile)); // check if the corresponding conf file exists Path confFile = getPathForConf(logFile); assertTrue("Config for completed jobs doesnt exist: " + confFile, fileSys.exists(confFile)); // check if the file exists in a done folder assertTrue("Completed job config doesnt exist under the done folder", confFile.toString().startsWith(doneDir.toString())); // check if the file exists in a done folder assertTrue("Completed jobs doesnt exist in the done folder", logFile.toString().startsWith(doneDir.toString())); assertTrue("Completed job and config file aren't in the same directory", confFile.getParent().toString().equals(logFile.getParent().toString())); // check if the job file is removed from the history location Path runningJobsHistoryFolder = logFile.getParent().getParent(); Path runningJobHistoryFilename = new Path(runningJobsHistoryFolder, logFile.getName()); Path runningJobConfFilename = new Path(runningJobsHistoryFolder, confFile.getName()); assertFalse("History file not deleted from the running folder", fileSys.exists(runningJobHistoryFilename)); assertFalse("Config for completed jobs not deleted from running folder", fileSys.exists(runningJobConfFilename)); validateJobHistoryFileFormat(job.getID(), conf, "SUCCESS", false); validateJobHistoryFileContent(mr, job, conf); // get the job conf filename String name = JobHistory.JobInfo.getLocalJobFilePath(job.getID()); File file = new File(name); // check if the file get deleted while (file.exists()) { LOG.info("Waiting for " + file + " to be deleted"); UtilsForTests.waitFor(100); } } finally { if (mr != null) { cleanupLocalFiles(mr); mr.shutdown(); } } } //Returns the file in the done folder //Waits for sometime to get the file moved to done static String getDoneFile(JobConf conf, JobID id, Path doneDir) throws IOException { String name = null; for (int i = 0; name == null && i < 20; i++) { name = JobHistory.JobInfo.getDoneJobHistoryFileName(conf, id); UtilsForTests.waitFor(1000); } return name; } // Returns the output path where user history log file is written to with // default configuration setting for hadoop.job.history.user.location private static Path getLogLocationInOutputPath (String logFileName, JobConf conf) { JobConf jobConf = new JobConf(true);//default JobConf FileOutputFormat.setOutputPath(jobConf, FileOutputFormat.getOutputPath(conf)); Path result = JobHistory.JobInfo.getJobHistoryLogLocationForUser (logFileName, jobConf); return result; } static private String coreLogLocation(String subdirLogLocation) { return subdirLogLocation.substring (subdirLogLocation.lastIndexOf(Path.SEPARATOR_CHAR) + 1); } /** * Checks if the user history file exists in the correct dir * @param id job id * @param conf job conf */ private static void validateJobHistoryUserLogLocation(JobID id, JobConf conf) throws IOException { // Get the history file name Path doneDir = JobHistory.getCompletedJobHistoryLocation(); String logFileName = getDoneFile(conf, id, doneDir); // User history log file location Path logFile = JobHistory.JobInfo.getJobHistoryLogLocationForUser( coreLogLocation(logFileName), conf); if(logFile == null) { // get the output path where history file is written to when // hadoop.job.history.user.location is not set logFile = getLogLocationInOutputPath(coreLogLocation(logFileName), conf); } FileSystem fileSys = null; fileSys = logFile.getFileSystem(conf); // Check if the user history file exists in the correct dir if (conf.get("hadoop.job.history.user.location") == null) { assertTrue("User log file " + logFile + " does not exist", fileSys.exists(logFile)); } else if ("none".equals(conf.get("hadoop.job.history.user.location"))) { // history file should not exist in the output path assertFalse("Unexpected. User log file exists in output dir when " + "hadoop.job.history.user.location is set to \"none\"", fileSys.exists(logFile)); } else { //hadoop.job.history.user.location is set to a specific location. // User log file should exist in that location assertTrue("User log file " + logFile + " does not exist", fileSys.exists(logFile)); // User log file should not exist in output path. // get the output path where history file is written to when // hadoop.job.history.user.location is not set Path logFile1 = getLogLocationInOutputPath(logFileName, conf); if (logFile != logFile1) { fileSys = logFile1.getFileSystem(conf); assertFalse("Unexpected. User log file exists in output dir when " + "hadoop.job.history.user.location is set to a different location", fileSys.exists(logFile1)); } } } // Validate user history file location for the given values of // hadoop.job.history.user.location as // (1)null(default case), (2)"none", and (3)some user specified dir. public void testJobHistoryUserLogLocation() throws IOException { MiniMRCluster mr = null; try { mr = new MiniMRCluster(2, "file:///", 3); // run the TCs JobConf conf = mr.createJobConf(); FileSystem fs = FileSystem.get(conf); // clean up fs.delete(new Path(TEST_ROOT_DIR + "/succeed"), true); Path inDir = new Path(TEST_ROOT_DIR + "/succeed/input1"); Path outDir = new Path(TEST_ROOT_DIR + "/succeed/output1"); conf.set("user.name", UserGroupInformation.getCurrentUser().getUserName()); // validate for the case of null(default) RunningJob job = UtilsForTests.runJobSucceed(conf, inDir, outDir); validateJobHistoryUserLogLocation(job.getID(), conf); inDir = new Path(TEST_ROOT_DIR + "/succeed/input2"); outDir = new Path(TEST_ROOT_DIR + "/succeed/output2"); // validate for the case of "none" conf.set("hadoop.job.history.user.location", "none"); job = UtilsForTests.runJobSucceed(conf, inDir, outDir); validateJobHistoryUserLogLocation(job.getID(), conf); inDir = new Path(TEST_ROOT_DIR + "/succeed/input3"); outDir = new Path(TEST_ROOT_DIR + "/succeed/output3"); // validate for the case of any dir conf.set("hadoop.job.history.user.location", TEST_ROOT_DIR + "/succeed"); job = UtilsForTests.runJobSucceed(conf, inDir, outDir); validateJobHistoryUserLogLocation(job.getID(), conf); } finally { if (mr != null) { cleanupLocalFiles(mr); mr.shutdown(); } } } private void cleanupLocalFiles(MiniMRCluster mr) throws IOException { Configuration conf = mr.createJobConf(); JobTracker jt = mr.getJobTrackerRunner().getJobTracker(); Path sysDir = new Path(jt.getSystemDir()); FileSystem fs = sysDir.getFileSystem(conf); fs.delete(sysDir, true); Path jobHistoryDir = JobHistory.getJobHistoryLocation(); fs = jobHistoryDir.getFileSystem(conf); fs.delete(jobHistoryDir, true); } /** * Checks if the history file has expected job status * @param id job id * @param conf job conf */ private static void validateJobHistoryJobStatus(JobID id, JobConf conf, String status) throws IOException { // Get the history file name Path doneDir = JobHistory.getCompletedJobHistoryLocation(); String logFileName = getDoneFile(conf, id, doneDir); // Framework history log file location Path logFile = new Path(doneDir, logFileName); FileSystem fileSys = logFile.getFileSystem(conf); // Check if the history file exists System.err.println("validateJobHistoryJobStatus -- seeking " + logFile); assertTrue("History file does not exist", fileSys.exists(logFile)); // check history file permission assertTrue("History file permissions does not match", fileSys.getFileStatus(logFile).getPermission().equals( new FsPermission(JobHistory.HISTORY_FILE_PERMISSION))); // check if the history file is parsable String[] jobDetails = JobHistory.JobInfo.decodeJobHistoryFileName( logFileName).split("_"); String jobId = jobDetails[2] + "_" + jobDetails[3] + "_" + jobDetails[4]; JobHistory.JobInfo jobInfo = new JobHistory.JobInfo(jobId); DefaultJobHistoryParser.JobTasksParseListener l = new DefaultJobHistoryParser.JobTasksParseListener(jobInfo); JobHistory.parseHistoryFromFS(logFile.toString().substring(5), l, fileSys); assertTrue("Job Status read from job history file is not the expected" + " status", status.equals(jobInfo.getValues().get(Keys.JOB_STATUS))); } // run jobs that will be (1) succeeded (2) failed (3) killed // and validate job status read from history file in each case public void testJobHistoryJobStatus() throws IOException { MiniMRCluster mr = null; try { mr = new MiniMRCluster(2, "file:///", 3); // run the TCs JobConf conf = mr.createJobConf(); FileSystem fs = FileSystem.get(conf); // clean up fs.delete(new Path(TEST_ROOT_DIR + "/succeedfailkilljob"), true); Path inDir = new Path(TEST_ROOT_DIR + "/succeedfailkilljob/input"); Path outDir = new Path(TEST_ROOT_DIR + "/succeedfailkilljob/output"); conf.set("user.name", UserGroupInformation.getCurrentUser().getUserName()); // Run a job that will be succeeded and validate its job status // existing in history file RunningJob job = UtilsForTests.runJobSucceed(conf, inDir, outDir); validateJobHistoryJobStatus(job.getID(), conf, "SUCCESS"); long historyCleanerRanAt = JobHistory.HistoryCleaner.getLastRan(); assertTrue(historyCleanerRanAt != 0); // Run a job that will be failed and validate its job status // existing in history file job = UtilsForTests.runJobFail(conf, inDir, outDir); validateJobHistoryJobStatus(job.getID(), conf, "FAILED"); assertTrue(historyCleanerRanAt == JobHistory.HistoryCleaner.getLastRan()); // Run a job that will be killed and validate its job status // existing in history file job = UtilsForTests.runJobKill(conf, inDir, outDir); validateJobHistoryJobStatus(job.getID(), conf, "KILLED"); assertTrue(historyCleanerRanAt == JobHistory.HistoryCleaner.getLastRan()); } finally { if (mr != null) { cleanupLocalFiles(mr); mr.shutdown(); } } } public void testJobHistoryCleaner() throws Exception { JobConf conf = new JobConf(); FileSystem fs = FileSystem.get(conf); JobHistory.DONEDIR_FS = fs; JobHistory.DONE = new Path(TEST_ROOT_DIR + "/done"); Path histDirOld = new Path(JobHistory.DONE, "version-1/jtinstid/2013/02/05/000000/"); Path histDirOnLine = new Path(JobHistory.DONE, "version-1/jtinstid/2013/02/06/000000/"); final int dayMillis = 1000 * 60 * 60 * 24; try { Calendar runTime = Calendar.getInstance(); runTime.clear(); runTime.set(2013, 1, 8, 12, 0); long runTimeMillis = runTime.getTimeInMillis(); fs.mkdirs(histDirOld); fs.mkdirs(histDirOnLine); Path histFileOldDir = new Path(histDirOld, "jobfile1.txt"); Path histFileOnLineDir = new Path(histDirOnLine, "jobfile1.txt"); Path histFileDontDelete = new Path(histDirOnLine, "jobfile2.txt"); fs.create(histFileOldDir).close(); fs.create(histFileOnLineDir).close(); fs.create(histFileDontDelete).close(); new File(histFileOnLineDir.toUri()).setLastModified( runTimeMillis - dayMillis * 5 / 2); new File(histFileDontDelete.toUri()).setLastModified( runTimeMillis - dayMillis * 3 / 2); HistoryCleaner.maxAgeOfHistoryFiles = dayMillis * 2; // two days HistoryCleaner historyCleaner = new HistoryCleaner(); historyCleaner.clean(runTimeMillis); assertFalse(fs.exists(histDirOld)); assertTrue(fs.exists(histDirOnLine)); assertFalse(fs.exists(histFileOldDir)); assertFalse(fs.exists(histFileOnLineDir)); assertTrue(fs.exists(histFileDontDelete)); } finally { fs.delete(JobHistory.DONE, true); } } }