/** * 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.mapreduce.test.system; import java.io.IOException; import junit.framework.Assert; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.Path; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.JobStatus; import org.apache.hadoop.mapred.JobTracker; import org.apache.hadoop.mapred.RunningJob; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.JobID; import org.apache.hadoop.test.system.process.RemoteProcess; import org.apache.hadoop.mapred.TaskStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.mapreduce.test.system.TaskInfo; import static org.junit.Assert.*; /** * JobTracker client for system tests. */ public class JTClient extends MRDaemonClient<JTProtocol> { static final Log LOG = LogFactory.getLog(JTClient.class); private JobClient client; private static final String HADOOP_JT_OPTS_ENV = "HADOOP_JOBTRACKER_OPTS"; /** * Create JobTracker client to talk to {@link JobTracker} specified in the * configuration. <br/> * * @param conf * configuration used to create a client. * @param daemon * the process management instance for the {@link JobTracker} * @throws IOException */ public JTClient(Configuration conf, RemoteProcess daemon) throws IOException { super(conf, daemon); } @Override public synchronized void connect() throws IOException { if (isConnected()) { return; } client = new JobClient(new JobConf(getConf())); setConnected(true); } @Override public synchronized void disconnect() throws IOException { client.close(); } @Override public synchronized JTProtocol getProxy() { return (JTProtocol) client.getProtocol(); } /** * Gets the {@link JobClient} which can be used for job submission. JobClient * which is returned would not contain the decorated API's. To be used for * submitting of the job. * * @return client handle to the JobTracker */ public JobClient getClient() { return client; } /** * Gets the configuration which the JobTracker is currently running.<br/> * * @return configuration of JobTracker. * * @throws IOException */ public Configuration getJobTrackerConfig() throws IOException { return getProxy().getDaemonConf(); } /** * Kills the job. <br/> * * @param id * of the job to be killed. * @throws IOException */ public void killJob(JobID id) throws IOException { try { getClient().killJob(id); } catch (InterruptedException e) { throw new IOException(e); } } /** * Verification API to check running jobs and running job states. users have * to ensure that their jobs remain running state while verification is * called. <br/> * * @param jobId * of the job to be verified. * * @throws Exception */ public void verifyRunningJob(JobID jobId) throws Exception { } private JobInfo getJobInfo(JobID jobId) throws IOException { JobInfo info = getProxy().getJobInfo(jobId); if (info == null && !getProxy().isJobRetired(jobId)) { Assert.fail("Job id : " + jobId + " has never been submitted to JT"); } return info; } /** * Verification API to wait till job retires and verify all the retired state * is correct. <br/> * * @param job * of the job used for completion * @return job handle * @throws Exception */ public Job submitAndVerifyJob(Job job) throws Exception { job.submit(); JobID jobId = job.getJobID(); verifyRunningJob(jobId); verifyCompletedJob(jobId); return job; } /** * Verification API to check if the job completion state is correct. <br/> * * @param id * id of the job to be verified. */ public void verifyCompletedJob(JobID id) throws Exception { RunningJob rJob = getClient().getJob(org.apache.hadoop.mapred.JobID.downgrade(id)); while (!rJob.isComplete()) { LOG.info("waiting for job :" + id + " to retire"); Thread.sleep(1000); rJob = getClient().getJob(org.apache.hadoop.mapred.JobID.downgrade(id)); } verifyJobDetails(id); JobInfo jobInfo = getJobInfo(id); if (jobInfo != null) { while (!jobInfo.isHistoryFileCopied()) { Thread.sleep(1000); LOG.info(id + " waiting for history file to copied"); jobInfo = getJobInfo(id); if (jobInfo == null) { break; } } } verifyJobHistory(id); } /** * Verification API to check if the job details are semantically correct.<br/> * * @param jobId * jobID of the job * @return true if all the job verifications are verified to be true * @throws Exception */ public void verifyJobDetails(JobID jobId) throws Exception { // wait till the setup is launched and finished. JobInfo jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } LOG.info("waiting for the setup to be finished"); while (!jobInfo.isSetupFinished()) { Thread.sleep(2000); jobInfo = getJobInfo(jobId); if (jobInfo == null) { break; } } // verify job id. assertTrue(jobId.toString().startsWith("job_")); LOG.info("verified job id and is : " + jobId.toString()); // verify the number of map/reduce tasks. verifyNumTasks(jobId); // should verify job progress. verifyJobProgress(jobId); jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } if (jobInfo.getStatus().getRunState() == JobStatus.SUCCEEDED) { // verify if map/reduce progress reached 1. jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } assertEquals(1.0, jobInfo.getStatus().mapProgress(), 0.001); assertEquals(1.0, jobInfo.getStatus().reduceProgress(), 0.001); // verify successful finish of tasks. verifyAllTasksSuccess(jobId); } if (jobInfo.getStatus().isJobComplete()) { // verify if the cleanup is launched. jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } assertTrue(jobInfo.isCleanupLaunched()); LOG.info("Verified launching of cleanup"); } } public void verifyAllTasksSuccess(JobID jobId) throws IOException { JobInfo jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } TaskInfo[] taskInfos = getProxy().getTaskInfo(jobId); if (taskInfos.length == 0 && getProxy().isJobRetired(jobId)) { LOG.info("Job has been retired from JT memory : " + jobId); return; } for (TaskInfo taskInfo : taskInfos) { TaskStatus[] taskStatus = taskInfo.getTaskStatus(); if (taskStatus != null && taskStatus.length > 0) { int i; for (i = 0; i < taskStatus.length; i++) { if (TaskStatus.State.SUCCEEDED.equals(taskStatus[i].getRunState())) { break; } } assertFalse(i == taskStatus.length); } } LOG.info("verified that none of the tasks failed."); } public void verifyJobProgress(JobID jobId) throws IOException { JobInfo jobInfo; jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } assertTrue(jobInfo.getStatus().mapProgress() >= 0 && jobInfo.getStatus().mapProgress() <= 1); LOG.info("verified map progress and is " + jobInfo.getStatus().mapProgress()); assertTrue(jobInfo.getStatus().reduceProgress() >= 0 && jobInfo.getStatus().reduceProgress() <= 1); LOG.info("verified reduce progress and is " + jobInfo.getStatus().reduceProgress()); } public void verifyNumTasks(JobID jobId) throws IOException { JobInfo jobInfo; jobInfo = getJobInfo(jobId); if (jobInfo == null) { return; } assertEquals(jobInfo.numMaps(), (jobInfo.runningMaps() + jobInfo.waitingMaps() + jobInfo.finishedMaps())); LOG.info("verified number of map tasks and is " + jobInfo.numMaps()); assertEquals(jobInfo.numReduces(), (jobInfo.runningReduces() + jobInfo.waitingReduces() + jobInfo.finishedReduces())); LOG.info("verified number of reduce tasks and is " + jobInfo.numReduces()); } /** * Verification API to check if the job history file is semantically correct. <br/> * * * @param jobId * of the job to be verified. * @throws IOException */ public void verifyJobHistory(JobID jobId) throws IOException { JobInfo info = getJobInfo(jobId); String url = ""; if (info == null) { LOG.info("Job has been retired from JT memory : " + jobId); url = getProxy().getJobHistoryLocationForRetiredJob(jobId); } else { url = info.getHistoryUrl(); } Path p = new Path(url); if (p.toUri().getScheme().equals("file:/")) { FileStatus st = getFileStatus(url, true); Assert.assertNotNull("Job History file for " + jobId + " not present " + "when job is completed", st); } else { FileStatus st = getFileStatus(url, false); Assert.assertNotNull("Job History file for " + jobId + " not present " + "when job is completed", st); } LOG.info("Verified the job history for the jobId : " + jobId); } @Override public String getHadoopOptsEnvName() { return HADOOP_JT_OPTS_ENV; } /** * Concrete implementation of abstract super class method * * @param attributeName name of the attribute to be retrieved * @return Object value of the given attribute * @throws IOException is thrown in case of communication errors */ @Override public Object getDaemonAttribute(String attributeName) throws IOException { return getJmxAttribute("JobTracker", "JobTrackerInfo", attributeName); } }