/** * 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.IOException; import java.util.ArrayList; import java.util.List; import org.apache.hadoop.mapred.FakeObjectUtilities.FakeJobInProgress; import org.apache.hadoop.mapred.FakeObjectUtilities.FakeTaskInProgress; import org.apache.hadoop.mapred.FakeObjectUtilities.FakeJobTracker; import org.apache.hadoop.mapreduce.server.jobtracker.JTConfig; import org.apache.hadoop.mapreduce.split.JobSplit; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.TaskType; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import junit.framework.TestCase; public class TestSetupTaskScheduling extends TestCase { public static final Log LOG = LogFactory.getLog(TestSetupTaskScheduling.class); static String[] trackers = new String[] { "tracker_tracker1:1000", "tracker_tracker2:1000", "tracker_tracker3:1000" }; private static FakeJobTracker jobTracker; /** * Fake JobInProgress that can return a hardcoded setup or * cleanup task depending on the slot type passed in. */ static class FakeJobWithSetupTask extends FakeObjectUtilities.FakeJobInProgress { FakeJobWithSetupTask(JobConf jobConf, JobTracker tracker) throws IOException { super(jobConf, tracker); } /** * Initialize tasks, including setup. */ @Override public synchronized void initTasks() throws IOException { super.initTasks(); JobSplit.TaskSplitMetaInfo emptySplit = new JobSplit.TaskSplitMetaInfo(); setup = new TaskInProgress[2]; setup[0] = new TaskInProgress(getJobID(), "test", emptySplit, jobtracker, getJobConf(), this, numMapTasks + 1, 1); setup[1] = new TaskInProgress(getJobID(), "test", numMapTasks, numReduceTasks + 1, jobtracker, getJobConf(), this, 1); } /** * Obtain a setup task on a map slot or reduce slot * depending on what is free. * * Every call to this will return either a map or reduce * setup task. No check is done to see if the task is already * returned */ @Override public Task obtainJobSetupTask(TaskTrackerStatus tts, int clusterSize, int numUniqueHosts, boolean isMapSlot) throws IOException{ TaskInProgress tip = null; if (isMapSlot) { tip = setup[0]; } else { tip = setup[1]; } Task t = tip.getTaskToRun(tts.getHost()); t.setJobSetupTask(); return t; } } static class FakeJobWithTaskCleanupTask extends FakeObjectUtilities.FakeJobInProgress { FakeJobWithTaskCleanupTask(JobConf jobConf, JobTracker tracker) throws IOException { super(jobConf, tracker); } /** * Initialize tasks(1 map and 1 reduce task each needs 2 slots, similar to * tasks of a high RAM job). */ @Override public synchronized void initTasks() throws IOException { super.initTasks(); final int numSlotsPerTask = 2; maps = new TaskInProgress[1]; reduces = new TaskInProgress[1]; maps[0] = new FakeTaskInProgress(getJobID(), "test", JobSplit.EMPTY_TASK_SPLIT, jobtracker, getJobConf(), this, 0, numSlotsPerTask); TaskAttemptID attemptId = new TaskAttemptID(maps[0].getTIPId(), 0); // make this task a taskCleanup task of a map task mapCleanupTasks.add(attemptId); TaskStatus stat = new MapTaskStatus(attemptId, 0.01f, 2, TaskStatus.State.FAILED_UNCLEAN, "", "", trackers[0], TaskStatus.Phase.MAP, new Counters()); maps[0].updateStatus(stat); //similarly for reduce task's taskCleanup task reduces[0] = new FakeTaskInProgress(getJobID(), "test", 1, 0, jobtracker, getJobConf(), this, numSlotsPerTask); attemptId = new TaskAttemptID(reduces[0].getTIPId(), 0); // make this task a taskCleanup task of a reduce task reduceCleanupTasks.add(attemptId); stat = new ReduceTaskStatus(attemptId, 0.01f, 2, TaskStatus.State.FAILED_UNCLEAN, "", "", trackers[0], TaskStatus.Phase.REDUCE, new Counters()); reduces[0].updateStatus(stat); } } public void setUp() throws Exception { JobConf conf = new JobConf(); conf.set(JTConfig.JT_IPC_ADDRESS, "localhost:0"); conf.set(JTConfig.JT_HTTP_ADDRESS, "0.0.0.0:0"); jobTracker = new FakeJobTracker(conf, new Clock(), trackers); for (String tracker : trackers) { FakeObjectUtilities.establishFirstContact(jobTracker, tracker); } } // create a job for testing setup tasks and reservations FakeJobInProgress createJob(TaskType taskType) throws IOException { JobConf conf = new JobConf(); conf.setSpeculativeExecution(false); conf.setNumMapTasks(2); conf.setNumReduceTasks(2); conf.set(JobContext.REDUCE_FAILURES_MAXPERCENT, ".70"); conf.set(JobContext.MAP_FAILURES_MAX_PERCENT, ".70"); FakeJobInProgress job = null; if (taskType == null) { conf.setBoolean(JobContext.SETUP_CLEANUP_NEEDED, false); job = new FakeJobInProgress(conf, jobTracker); } else if (taskType == TaskType.JOB_SETUP) { job = new FakeJobWithSetupTask(conf, jobTracker); } else if (taskType == TaskType.TASK_CLEANUP) { job = new FakeJobWithTaskCleanupTask(conf, jobTracker); } job.setClusterSize(trackers.length); job.initTasks(); return job; } // create a new TaskStatus and add to a list of status objects. // useMapSlot param is needed only when taskType is TASK_CLEANUP. void addNewTaskStatus(FakeJobInProgress job, TaskType taskType, boolean useMapSlot, String tracker, List<TaskStatus> reports) throws IOException { TaskAttemptID task = null; TaskStatus status = null; if (taskType == TaskType.MAP) { task = job.findMapTask(tracker); status = new MapTaskStatus(task, 0.01f, 2, TaskStatus.State.RUNNING, "", "", tracker, TaskStatus.Phase.MAP, new Counters()); } else if (taskType == TaskType.TASK_CLEANUP) { if (useMapSlot) { status = job.maps[0].taskStatuses.get( new TaskAttemptID(job.maps[0].getTIPId(), 0)); } else { status = job.reduces[0].taskStatuses.get( new TaskAttemptID(job.reduces[0].getTIPId(), 0)); } } else { task = job.findReduceTask(tracker); status = new ReduceTaskStatus(task, 0.01f, 2, TaskStatus.State.RUNNING, "", "", tracker, TaskStatus.Phase.REDUCE, new Counters()); } reports.add(status); } // create a TaskTrackerStatus TaskTrackerStatus createTaskTrackerStatus(String tracker, List<TaskStatus> reports) { TaskTrackerStatus ttStatus = new TaskTrackerStatus(tracker, JobInProgress.convertTrackerNameToHostName(tracker), 0, reports, 0, 2, 2); return ttStatus; } /** * Test that a setup task can be run against a map slot * if it is free. * @throws IOException */ public void testSetupTaskReturnedForFreeMapSlots() throws IOException { // create a job with a setup task. FakeJobInProgress job = createJob(TaskType.JOB_SETUP); jobTracker.jobs.put(job.getJobID(), job); // create a status simulating a free tasktracker List<TaskStatus> reports = new ArrayList<TaskStatus>(); TaskTrackerStatus ttStatus = createTaskTrackerStatus(trackers[2], reports); // verify that a setup task can be assigned to a map slot. List<Task> tasks = jobTracker.getSetupAndCleanupTasks(ttStatus); assertEquals(1, tasks.size()); assertTrue(tasks.get(0).isJobSetupTask()); assertTrue(tasks.get(0).isMapTask()); jobTracker.jobs.clear(); } /** * Test to check that map slots are counted when returning * a setup task. * @throws IOException */ public void testMapSlotsCountedForSetup() throws IOException { // create a job with a setup task. FakeJobInProgress job = createJob(TaskType.JOB_SETUP); jobTracker.jobs.put(job.getJobID(), job); // create another job for reservation FakeJobInProgress job1 = createJob(null); jobTracker.jobs.put(job1.getJobID(), job1); // create TT status for testing getSetupAndCleanupTasks List<TaskStatus> taskStatuses = new ArrayList<TaskStatus>(); addNewTaskStatus(job, TaskType.MAP, true, trackers[0], taskStatuses); TaskTrackerStatus ttStatus = createTaskTrackerStatus(trackers[0], taskStatuses); // test that there should be no map setup task returned. List<Task> tasks = jobTracker.getSetupAndCleanupTasks(ttStatus); assertEquals(1, tasks.size()); assertTrue(tasks.get(0).isJobSetupTask()); assertFalse(tasks.get(0).isMapTask()); jobTracker.jobs.clear(); } /** * Test to check that reduce slots are also counted when returning * a setup task. * @throws IOException */ public void testReduceSlotsCountedForSetup() throws IOException { // create a job with a setup task. FakeJobInProgress job = createJob(TaskType.JOB_SETUP); jobTracker.jobs.put(job.getJobID(), job); // create another job for reservation FakeJobInProgress job1 = createJob(null); jobTracker.jobs.put(job1.getJobID(), job1); // create TT status for testing getSetupAndCleanupTasks List<TaskStatus> reports = new ArrayList<TaskStatus>(); // because free map slots are checked first in code, // we fill up map slots also. addNewTaskStatus(job1, TaskType.MAP, true, trackers[1], reports); addNewTaskStatus(job1, TaskType.REDUCE, false,trackers[1], reports); TaskTrackerStatus ttStatus = createTaskTrackerStatus(trackers[1], reports); // test that there should be no setup task returned, // as both map and reduce slots are occupied. List<Task> tasks = jobTracker.getSetupAndCleanupTasks(ttStatus); assertNull(tasks); jobTracker.jobs.clear(); } void validateNumSlotsUsedForTaskCleanup(TaskTrackerStatus ttStatus) throws IOException { List<Task> tasks = jobTracker.getSetupAndCleanupTasks(ttStatus); assertEquals("Actual number of taskCleanup tasks is not same as expected", 1, tasks.size()); LOG.info("taskCleanup task is " + tasks.get(0)); assertTrue(tasks.get(0).isTaskCleanupTask()); // slots needed for taskCleanup task should be 1(even for high RAM jobs) assertEquals("TaskCleanup task should not need more than 1 slot.", 1, tasks.get(0).getNumSlotsRequired()); } /** * Test to check that map slots are counted when returning * a taskCleanup task. * @throws IOException */ public void testNumSlotsUsedForTaskCleanup() throws IOException { // Create a high RAM job with a map task's cleanup task and a reduce task's // cleanup task. Make this Fake job a high RAM job by setting the slots // required for map/reduce task to 2. FakeJobInProgress job = createJob(TaskType.TASK_CLEANUP); jobTracker.jobs.put(job.getJobID(), job); // create TT status for testing getSetupAndCleanupTasks List<TaskStatus> taskStatuses = new ArrayList<TaskStatus>(); TaskTrackerStatus ttStatus = createTaskTrackerStatus(trackers[0], taskStatuses);//create dummy status // validate mapTaskCleanup task validateNumSlotsUsedForTaskCleanup(ttStatus); // validate reduceTaskCleanup task validateNumSlotsUsedForTaskCleanup(ttStatus); jobTracker.jobs.clear(); } }