/** * 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.net.URI; import java.net.InetAddress; import junit.framework.TestCase; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.io.IntWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.lib.IdentityMapper; import org.apache.hadoop.mapred.lib.IdentityReducer; import org.apache.hadoop.mapreduce.server.jobtracker.JTConfig; import org.apache.hadoop.mapreduce.server.tasktracker.TTConfig; /** * A JUnit test to test Map-Reduce empty jobs. */ public class TestEmptyJob extends TestCase { private static final Log LOG = LogFactory.getLog(TestEmptyJob.class.getName()); private static String TEST_ROOT_DIR = new File(System.getProperty("test.build.data", "/tmp")).toURI() .toString().replace(' ', '+'); MiniMRCluster mr = null; /** Committer with commit waiting on a signal */ static class CommitterWithDelayCommit extends FileOutputCommitter { @Override public void commitJob(JobContext context) throws IOException { Configuration conf = context.getConfiguration(); Path share = new Path(conf.get("share")); FileSystem fs = FileSystem.get(conf); while (true) { if (fs.exists(share)) { break; } UtilsForTests.waitFor(100); } super.commitJob(context); } } /** * Simple method running a MapReduce job with no input data. Used to test that * such a job is successful. * * @param fileSys * @param numMaps * @param numReduces * @return true if the MR job is successful, otherwise false * @throws IOException */ private boolean launchEmptyJob(URI fileSys, int numMaps, int numReduces) throws IOException { // create an empty input dir final Path inDir = new Path(TEST_ROOT_DIR, "testing/empty/input"); final Path outDir = new Path(TEST_ROOT_DIR, "testing/empty/output"); final Path inDir2 = new Path(TEST_ROOT_DIR, "testing/dummy/input"); final Path outDir2 = new Path(TEST_ROOT_DIR, "testing/dummy/output"); final Path share = new Path(TEST_ROOT_DIR, "share"); JobConf conf = mr.createJobConf(); FileSystem fs = FileSystem.get(fileSys, conf); fs.delete(new Path(TEST_ROOT_DIR), true); fs.delete(outDir, true); if (!fs.mkdirs(inDir)) { LOG.warn("Can't create " + inDir); return false; } // use WordCount example FileSystem.setDefaultUri(conf, fileSys); conf.setJobName("empty"); // use an InputFormat which returns no split conf.setInputFormat(EmptyInputFormat.class); conf.setOutputCommitter(CommitterWithDelayCommit.class); conf.setOutputKeyClass(Text.class); conf.setOutputValueClass(IntWritable.class); conf.setMapperClass(IdentityMapper.class); conf.setReducerClass(IdentityReducer.class); FileInputFormat.setInputPaths(conf, inDir); FileOutputFormat.setOutputPath(conf, outDir); conf.setNumMapTasks(numMaps); conf.setNumReduceTasks(numReduces); conf.set("share", share.toString()); // run job and wait for completion JobClient jc = new JobClient(conf); RunningJob runningJob = jc.submitJob(conf); JobInProgress job = mr.getJobTrackerRunner().getJobTracker().getJob(runningJob.getID()); InetAddress ip = InetAddress.getLocalHost(); if (ip != null) { assertTrue(job.getJobSubmitHostAddress().equalsIgnoreCase( ip.getHostAddress())); assertTrue(job.getJobSubmitHostName().equalsIgnoreCase(ip.getHostName())); } while (true) { if (job.isCleanupLaunched()) { LOG.info("Waiting for cleanup to be launched for job " + runningJob.getID()); break; } UtilsForTests.waitFor(100); } // submit another job so that the map load increases and scheduling happens LOG.info("Launching dummy job "); RunningJob dJob = null; try { JobConf dConf = new JobConf(conf); dConf.setOutputCommitter(FileOutputCommitter.class); dJob = UtilsForTests.runJob(dConf, inDir2, outDir2, 2, 0); } catch (Exception e) { LOG.info("Exception ", e); throw new IOException(e); } while (true) { LOG.info("Waiting for job " + dJob.getID() + " to complete"); try { Thread.sleep(100); } catch (InterruptedException e) { } if (dJob.isComplete()) { break; } } // check if the second job is successful assertTrue(dJob.isSuccessful()); // signal the cleanup fs.create(share).close(); while (true) { LOG.info("Waiting for job " + runningJob.getID() + " to complete"); try { Thread.sleep(100); } catch (InterruptedException e) { } if (runningJob.isComplete()) { break; } } assertTrue(runningJob.isComplete()); assertTrue(runningJob.isSuccessful()); JobID jobID = runningJob.getID(); TaskReport[] jobSetupTasks = jc.getSetupTaskReports(jobID); assertTrue("Number of job-setup tips is not 2!", jobSetupTasks.length == 2); assertTrue("Setup progress is " + runningJob.setupProgress() + " and not 1.0", runningJob.setupProgress() == 1.0); assertTrue("Setup task is not finished!", mr.getJobTrackerRunner() .getJobTracker().getJob(jobID).isSetupFinished()); assertTrue("Number of maps is not zero!", jc.getMapTaskReports(runningJob .getID()).length == 0); assertTrue( "Map progress is " + runningJob.mapProgress() + " and not 1.0!", runningJob.mapProgress() == 1.0); assertTrue("Reduce progress is " + runningJob.reduceProgress() + " and not 1.0!", runningJob.reduceProgress() == 1.0); assertTrue("Number of reduces is not " + numReduces, jc .getReduceTaskReports(runningJob.getID()).length == numReduces); TaskReport[] jobCleanupTasks = jc.getCleanupTaskReports(jobID); assertTrue("Number of job-cleanup tips is not 2!", jobCleanupTasks.length == 2); assertTrue("Cleanup progress is " + runningJob.cleanupProgress() + " and not 1.0", runningJob.cleanupProgress() == 1.0); assertTrue("Job output directory doesn't exit!", fs.exists(outDir)); FileStatus[] list = fs.listStatus(outDir, new Utils.OutputFileUtils.OutputFilesFilter()); assertTrue("Number of part-files is " + list.length + " and not " + numReduces, list.length == numReduces); // cleanup fs.delete(outDir, true); // return job result LOG.info("job is complete: " + runningJob.isSuccessful()); return (runningJob.isSuccessful()); } /** * Test that a job with no input data (and thus with no input split and no map * task to execute) is successful. * * @throws IOException */ public void testEmptyJob() throws IOException { FileSystem fileSys = null; try { final int taskTrackers = 2; JobConf conf = new JobConf(); fileSys = FileSystem.get(conf); conf.set(JTConfig.JT_IPC_HANDLER_COUNT, "1"); conf.set(JTConfig.JT_IPC_ADDRESS, "127.0.0.1:0"); conf.set(JTConfig.JT_HTTP_ADDRESS, "127.0.0.1:0"); conf.set(TTConfig.TT_HTTP_ADDRESS, "127.0.0.1:0"); mr = new MiniMRCluster(taskTrackers, fileSys.getUri().toString(), 1, null, null, conf); assertTrue(launchEmptyJob(fileSys.getUri(), 3, 1)); assertTrue(launchEmptyJob(fileSys.getUri(), 0, 0)); } finally { if (fileSys != null) { fileSys.close(); } if (mr != null) { mr.shutdown(); } } } }