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 */
018package org.apache.hadoop.mapred;
019
020import java.io.FileNotFoundException;
021import java.io.IOException;
022import java.net.InetSocketAddress;
023import java.net.URL;
024import java.security.PrivilegedExceptionAction;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.List;
028
029import org.apache.hadoop.classification.InterfaceAudience;
030import org.apache.hadoop.classification.InterfaceStability;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.FileStatus;
033import org.apache.hadoop.fs.FileSystem;
034import org.apache.hadoop.fs.Path;
035import org.apache.hadoop.io.Text;
036import org.apache.hadoop.mapred.ClusterStatus.BlackListInfo;
037import org.apache.hadoop.mapreduce.Cluster;
038import org.apache.hadoop.mapreduce.ClusterMetrics;
039import org.apache.hadoop.mapreduce.Job;
040import org.apache.hadoop.mapreduce.MRJobConfig;
041import org.apache.hadoop.mapreduce.QueueInfo;
042import org.apache.hadoop.mapreduce.TaskTrackerInfo;
043import org.apache.hadoop.mapreduce.TaskType;
044import org.apache.hadoop.mapreduce.counters.Limits;
045import org.apache.hadoop.mapreduce.filecache.DistributedCache;
046import org.apache.hadoop.mapreduce.security.token.delegation.DelegationTokenIdentifier;
047import org.apache.hadoop.mapreduce.tools.CLI;
048import org.apache.hadoop.mapreduce.util.ConfigUtil;
049import org.apache.hadoop.security.UserGroupInformation;
050import org.apache.hadoop.security.token.SecretManager.InvalidToken;
051import org.apache.hadoop.security.token.Token;
052import org.apache.hadoop.security.token.TokenRenewer;
053import org.apache.hadoop.util.Tool;
054import org.apache.hadoop.util.ToolRunner;
055
056/**
057 * <code>JobClient</code> is the primary interface for the user-job to interact
058 * with the cluster.
059 * 
060 * <code>JobClient</code> provides facilities to submit jobs, track their 
061 * progress, access component-tasks' reports/logs, get the Map-Reduce cluster
062 * status information etc.
063 * 
064 * <p>The job submission process involves:
065 * <ol>
066 *   <li>
067 *   Checking the input and output specifications of the job.
068 *   </li>
069 *   <li>
070 *   Computing the {@link InputSplit}s for the job.
071 *   </li>
072 *   <li>
073 *   Setup the requisite accounting information for the {@link DistributedCache} 
074 *   of the job, if necessary.
075 *   </li>
076 *   <li>
077 *   Copying the job's jar and configuration to the map-reduce system directory 
078 *   on the distributed file-system. 
079 *   </li>
080 *   <li>
081 *   Submitting the job to the cluster and optionally monitoring
082 *   it's status.
083 *   </li>
084 * </ol></p>
085 *  
086 * Normally the user creates the application, describes various facets of the
087 * job via {@link JobConf} and then uses the <code>JobClient</code> to submit 
088 * the job and monitor its progress.
089 * 
090 * <p>Here is an example on how to use <code>JobClient</code>:</p>
091 * <p><blockquote><pre>
092 *     // Create a new JobConf
093 *     JobConf job = new JobConf(new Configuration(), MyJob.class);
094 *     
095 *     // Specify various job-specific parameters     
096 *     job.setJobName("myjob");
097 *     
098 *     job.setInputPath(new Path("in"));
099 *     job.setOutputPath(new Path("out"));
100 *     
101 *     job.setMapperClass(MyJob.MyMapper.class);
102 *     job.setReducerClass(MyJob.MyReducer.class);
103 *
104 *     // Submit the job, then poll for progress until the job is complete
105 *     JobClient.runJob(job);
106 * </pre></blockquote></p>
107 * 
108 * <h4 id="JobControl">Job Control</h4>
109 * 
110 * <p>At times clients would chain map-reduce jobs to accomplish complex tasks 
111 * which cannot be done via a single map-reduce job. This is fairly easy since 
112 * the output of the job, typically, goes to distributed file-system and that 
113 * can be used as the input for the next job.</p>
114 * 
115 * <p>However, this also means that the onus on ensuring jobs are complete 
116 * (success/failure) lies squarely on the clients. In such situations the 
117 * various job-control options are:
118 * <ol>
119 *   <li>
120 *   {@link #runJob(JobConf)} : submits the job and returns only after 
121 *   the job has completed.
122 *   </li>
123 *   <li>
124 *   {@link #submitJob(JobConf)} : only submits the job, then poll the 
125 *   returned handle to the {@link RunningJob} to query status and make 
126 *   scheduling decisions.
127 *   </li>
128 *   <li>
129 *   {@link JobConf#setJobEndNotificationURI(String)} : setup a notification
130 *   on job-completion, thus avoiding polling.
131 *   </li>
132 * </ol></p>
133 * 
134 * @see JobConf
135 * @see ClusterStatus
136 * @see Tool
137 * @see DistributedCache
138 */
139@InterfaceAudience.Public
140@InterfaceStability.Stable
141public class JobClient extends CLI implements AutoCloseable {
142
143  @InterfaceAudience.Private
144  public static final String MAPREDUCE_CLIENT_RETRY_POLICY_ENABLED_KEY =
145      "mapreduce.jobclient.retry.policy.enabled";
146  @InterfaceAudience.Private
147  public static final boolean MAPREDUCE_CLIENT_RETRY_POLICY_ENABLED_DEFAULT =
148      false;
149  @InterfaceAudience.Private
150  public static final String MAPREDUCE_CLIENT_RETRY_POLICY_SPEC_KEY =
151      "mapreduce.jobclient.retry.policy.spec";
152  @InterfaceAudience.Private
153  public static final String MAPREDUCE_CLIENT_RETRY_POLICY_SPEC_DEFAULT =
154      "10000,6,60000,10"; // t1,n1,t2,n2,...
155
156  public static enum TaskStatusFilter { NONE, KILLED, FAILED, SUCCEEDED, ALL }
157  private TaskStatusFilter taskOutputFilter = TaskStatusFilter.FAILED; 
158  
159  private int maxRetry = MRJobConfig.DEFAULT_MR_CLIENT_JOB_MAX_RETRIES;
160  private long retryInterval =
161      MRJobConfig.DEFAULT_MR_CLIENT_JOB_RETRY_INTERVAL;
162
163  static{
164    ConfigUtil.loadResources();
165  }
166
167  /**
168   * A NetworkedJob is an implementation of RunningJob.  It holds
169   * a JobProfile object to provide some info, and interacts with the
170   * remote service to provide certain functionality.
171   */
172  static class NetworkedJob implements RunningJob {
173    Job job;
174    /**
175     * We store a JobProfile and a timestamp for when we last
176     * acquired the job profile.  If the job is null, then we cannot
177     * perform any of the tasks.  The job might be null if the cluster
178     * has completely forgotten about the job.  (eg, 24 hours after the
179     * job completes.)
180     */
181    public NetworkedJob(JobStatus status, Cluster cluster) throws IOException {
182      this(status, cluster, new JobConf(status.getJobFile()));
183    }
184    
185    private NetworkedJob(JobStatus status, Cluster cluster, JobConf conf)
186        throws IOException {
187      this(Job.getInstance(cluster, status, conf));
188    }
189
190    public NetworkedJob(Job job) throws IOException {
191      this.job = job;
192    }
193
194    public Configuration getConfiguration() {
195      return job.getConfiguration();
196    }
197
198    /**
199     * An identifier for the job
200     */
201    public JobID getID() {
202      return JobID.downgrade(job.getJobID());
203    }
204    
205    /** @deprecated This method is deprecated and will be removed. Applications should 
206     * rather use {@link #getID()}.*/
207    @Deprecated
208    public String getJobID() {
209      return getID().toString();
210    }
211    
212    /**
213     * The user-specified job name
214     */
215    public String getJobName() {
216      return job.getJobName();
217    }
218
219    /**
220     * The name of the job file
221     */
222    public String getJobFile() {
223      return job.getJobFile();
224    }
225
226    /**
227     * A URL where the job's status can be seen
228     */
229    public String getTrackingURL() {
230      return job.getTrackingURL();
231    }
232
233    /**
234     * A float between 0.0 and 1.0, indicating the % of map work
235     * completed.
236     */
237    public float mapProgress() throws IOException {
238      return job.mapProgress();
239    }
240
241    /**
242     * A float between 0.0 and 1.0, indicating the % of reduce work
243     * completed.
244     */
245    public float reduceProgress() throws IOException {
246      return job.reduceProgress();
247    }
248
249    /**
250     * A float between 0.0 and 1.0, indicating the % of cleanup work
251     * completed.
252     */
253    public float cleanupProgress() throws IOException {
254      try {
255        return job.cleanupProgress();
256      } catch (InterruptedException ie) {
257        throw new IOException(ie);
258      }
259    }
260
261    /**
262     * A float between 0.0 and 1.0, indicating the % of setup work
263     * completed.
264     */
265    public float setupProgress() throws IOException {
266      return job.setupProgress();
267    }
268
269    /**
270     * Returns immediately whether the whole job is done yet or not.
271     */
272    public synchronized boolean isComplete() throws IOException {
273      return job.isComplete();
274    }
275
276    /**
277     * True iff job completed successfully.
278     */
279    public synchronized boolean isSuccessful() throws IOException {
280      return job.isSuccessful();
281    }
282
283    /**
284     * Blocks until the job is finished
285     */
286    public void waitForCompletion() throws IOException {
287      try {
288        job.waitForCompletion(false);
289      } catch (InterruptedException ie) {
290        throw new IOException(ie);
291      } catch (ClassNotFoundException ce) {
292        throw new IOException(ce);
293      }
294    }
295
296    /**
297     * Tells the service to get the state of the current job.
298     */
299    public synchronized int getJobState() throws IOException {
300      try {
301        return job.getJobState().getValue();
302      } catch (InterruptedException ie) {
303        throw new IOException(ie);
304      }
305    }
306    
307    /**
308     * Tells the service to terminate the current job.
309     */
310    public synchronized void killJob() throws IOException {
311      job.killJob();
312    }
313   
314    
315    /** Set the priority of the job.
316    * @param priority new priority of the job. 
317    */
318    public synchronized void setJobPriority(String priority) 
319                                                throws IOException {
320      try {
321        job.setPriority(
322          org.apache.hadoop.mapreduce.JobPriority.valueOf(priority));
323      } catch (InterruptedException ie) {
324        throw new IOException(ie);
325      }
326    }
327    
328    /**
329     * Kill indicated task attempt.
330     * @param taskId the id of the task to kill.
331     * @param shouldFail if true the task is failed and added to failed tasks list, otherwise
332     * it is just killed, w/o affecting job failure status.
333     */
334    public synchronized void killTask(TaskAttemptID taskId,
335        boolean shouldFail) throws IOException {
336      if (shouldFail) {
337        job.failTask(taskId);
338      } else {
339        job.killTask(taskId);
340      }
341    }
342
343    /** @deprecated Applications should rather use {@link #killTask(TaskAttemptID, boolean)}*/
344    @Deprecated
345    public synchronized void killTask(String taskId, boolean shouldFail) throws IOException {
346      killTask(TaskAttemptID.forName(taskId), shouldFail);
347    }
348    
349    /**
350     * Fetch task completion events from cluster for this job. 
351     */
352    public synchronized TaskCompletionEvent[] getTaskCompletionEvents(
353        int startFrom) throws IOException {
354      try {
355        org.apache.hadoop.mapreduce.TaskCompletionEvent[] acls = 
356          job.getTaskCompletionEvents(startFrom, 10);
357        TaskCompletionEvent[] ret = new TaskCompletionEvent[acls.length];
358        for (int i = 0 ; i < acls.length; i++ ) {
359          ret[i] = TaskCompletionEvent.downgrade(acls[i]);
360        }
361        return ret;
362      } catch (InterruptedException ie) {
363        throw new IOException(ie);
364      }
365    }
366
367    /**
368     * Dump stats to screen
369     */
370    @Override
371    public String toString() {
372      return job.toString();
373    }
374        
375    /**
376     * Returns the counters for this job
377     */
378    public Counters getCounters() throws IOException {
379      Counters result = null;
380      org.apache.hadoop.mapreduce.Counters temp = job.getCounters();
381      if(temp != null) {
382        result = Counters.downgrade(temp);
383      }
384      return result;
385    }
386    
387    @Override
388    public String[] getTaskDiagnostics(TaskAttemptID id) throws IOException {
389      try { 
390        return job.getTaskDiagnostics(id);
391      } catch (InterruptedException ie) {
392        throw new IOException(ie);
393      }
394    }
395
396    public String getHistoryUrl() throws IOException {
397      try {
398        return job.getHistoryUrl();
399      } catch (InterruptedException ie) {
400        throw new IOException(ie);
401      }
402    }
403
404    public boolean isRetired() throws IOException {
405      try {
406        return job.isRetired();
407      } catch (InterruptedException ie) {
408        throw new IOException(ie);
409      }
410    }
411    
412    boolean monitorAndPrintJob() throws IOException, InterruptedException {
413      return job.monitorAndPrintJob();
414    }
415    
416    @Override
417    public String getFailureInfo() throws IOException {
418      try {
419        return job.getStatus().getFailureInfo();
420      } catch (InterruptedException ie) {
421        throw new IOException(ie);
422      }
423    }
424
425    @Override
426    public JobStatus getJobStatus() throws IOException {
427      try {
428        return JobStatus.downgrade(job.getStatus());
429      } catch (InterruptedException ie) {
430        throw new IOException(ie);
431      }
432    }
433  }
434
435  /**
436   * Ugi of the client. We store this ugi when the client is created and 
437   * then make sure that the same ugi is used to run the various protocols.
438   */
439  UserGroupInformation clientUgi;
440  
441  /**
442   * Create a job client.
443   */
444  public JobClient() {
445  }
446    
447  /**
448   * Build a job client with the given {@link JobConf}, and connect to the 
449   * default cluster
450   * 
451   * @param conf the job configuration.
452   * @throws IOException
453   */
454  public JobClient(JobConf conf) throws IOException {
455    init(conf);
456  }
457
458  /**
459   * Build a job client with the given {@link Configuration}, 
460   * and connect to the default cluster
461   * 
462   * @param conf the configuration.
463   * @throws IOException
464   */
465  public JobClient(Configuration conf) throws IOException {
466    init(new JobConf(conf));
467  }
468
469  /**
470   * Connect to the default cluster
471   * @param conf the job configuration.
472   * @throws IOException
473   */
474  public void init(JobConf conf) throws IOException {
475    setConf(conf);
476    Limits.init(conf);
477    cluster = new Cluster(conf);
478    clientUgi = UserGroupInformation.getCurrentUser();
479
480    maxRetry = conf.getInt(MRJobConfig.MR_CLIENT_JOB_MAX_RETRIES,
481      MRJobConfig.DEFAULT_MR_CLIENT_JOB_MAX_RETRIES);
482
483    retryInterval =
484      conf.getLong(MRJobConfig.MR_CLIENT_JOB_RETRY_INTERVAL,
485        MRJobConfig.DEFAULT_MR_CLIENT_JOB_RETRY_INTERVAL);
486
487  }
488
489  /**
490   * Build a job client, connect to the indicated job tracker.
491   * 
492   * @param jobTrackAddr the job tracker to connect to.
493   * @param conf configuration.
494   */
495  public JobClient(InetSocketAddress jobTrackAddr, 
496                   Configuration conf) throws IOException {
497    cluster = new Cluster(jobTrackAddr, conf);
498    clientUgi = UserGroupInformation.getCurrentUser();
499  }
500
501  /**
502   * Close the <code>JobClient</code>.
503   */
504  @Override
505  public synchronized void close() throws IOException {
506    cluster.close();
507  }
508
509  /**
510   * Get a filesystem handle.  We need this to prepare jobs
511   * for submission to the MapReduce system.
512   * 
513   * @return the filesystem handle.
514   */
515  public synchronized FileSystem getFs() throws IOException {
516    try { 
517      return cluster.getFileSystem();
518    } catch (InterruptedException ie) {
519      throw new IOException(ie);
520    }
521  }
522  
523  /**
524   * Get a handle to the Cluster
525   */
526  public Cluster getClusterHandle() {
527    return cluster;
528  }
529  
530  /**
531   * Submit a job to the MR system.
532   * 
533   * This returns a handle to the {@link RunningJob} which can be used to track
534   * the running-job.
535   * 
536   * @param jobFile the job configuration.
537   * @return a handle to the {@link RunningJob} which can be used to track the
538   *         running-job.
539   * @throws FileNotFoundException
540   * @throws InvalidJobConfException
541   * @throws IOException
542   */
543  public RunningJob submitJob(String jobFile) throws FileNotFoundException, 
544                                                     InvalidJobConfException, 
545                                                     IOException {
546    // Load in the submitted job details
547    JobConf job = new JobConf(jobFile);
548    return submitJob(job);
549  }
550    
551  /**
552   * Submit a job to the MR system.
553   * This returns a handle to the {@link RunningJob} which can be used to track
554   * the running-job.
555   * 
556   * @param conf the job configuration.
557   * @return a handle to the {@link RunningJob} which can be used to track the
558   *         running-job.
559   * @throws FileNotFoundException
560   * @throws IOException
561   */
562  public RunningJob submitJob(final JobConf conf) throws FileNotFoundException,
563                                                  IOException {
564    return submitJobInternal(conf);
565  }
566
567  @InterfaceAudience.Private
568  public RunningJob submitJobInternal(final JobConf conf)
569      throws FileNotFoundException, IOException {
570    try {
571      conf.setBooleanIfUnset("mapred.mapper.new-api", false);
572      conf.setBooleanIfUnset("mapred.reducer.new-api", false);
573      Job job = clientUgi.doAs(new PrivilegedExceptionAction<Job> () {
574        @Override
575        public Job run() throws IOException, ClassNotFoundException, 
576          InterruptedException {
577          Job job = Job.getInstance(conf);
578          job.submit();
579          return job;
580        }
581      });
582      // update our Cluster instance with the one created by Job for submission
583      // (we can't pass our Cluster instance to Job, since Job wraps the config
584      // instance, and the two configs would then diverge)
585      cluster = job.getCluster();
586      return new NetworkedJob(job);
587    } catch (InterruptedException ie) {
588      throw new IOException("interrupted", ie);
589    }
590  }
591
592  private Job getJobUsingCluster(final JobID jobid) throws IOException,
593  InterruptedException {
594    return clientUgi.doAs(new PrivilegedExceptionAction<Job>() {
595      public Job run() throws IOException, InterruptedException  {
596       return cluster.getJob(jobid);
597      }
598    });
599  }
600
601  protected RunningJob getJobInner(final JobID jobid) throws IOException {
602    try {
603      
604      Job job = getJobUsingCluster(jobid);
605      if (job != null) {
606        JobStatus status = JobStatus.downgrade(job.getStatus());
607        if (status != null) {
608          return new NetworkedJob(status, cluster,
609              new JobConf(job.getConfiguration()));
610        } 
611      }
612    } catch (InterruptedException ie) {
613      throw new IOException(ie);
614    }
615    return null;
616  }
617
618  /**
619   * Get an {@link RunningJob} object to track an ongoing job.  Returns
620   * null if the id does not correspond to any known job.
621   *
622   * @param jobid the jobid of the job.
623   * @return the {@link RunningJob} handle to track the job, null if the
624   *         <code>jobid</code> doesn't correspond to any known job.
625   * @throws IOException
626   */
627  public RunningJob getJob(final JobID jobid) throws IOException {
628     for (int i = 0;i <= maxRetry;i++) {
629       if (i > 0) {
630         try {
631           Thread.sleep(retryInterval);
632         } catch (Exception e) { }
633       }
634       RunningJob job = getJobInner(jobid);
635       if (job != null) {
636         return job;
637       }
638     }
639     return null;
640  }
641
642  /**@deprecated Applications should rather use {@link #getJob(JobID)}.
643   */
644  @Deprecated
645  public RunningJob getJob(String jobid) throws IOException {
646    return getJob(JobID.forName(jobid));
647  }
648  
649  private static final TaskReport[] EMPTY_TASK_REPORTS = new TaskReport[0];
650  
651  /**
652   * Get the information of the current state of the map tasks of a job.
653   * 
654   * @param jobId the job to query.
655   * @return the list of all of the map tips.
656   * @throws IOException
657   */
658  public TaskReport[] getMapTaskReports(JobID jobId) throws IOException {
659    return getTaskReports(jobId, TaskType.MAP);
660  }
661  
662  private TaskReport[] getTaskReports(final JobID jobId, TaskType type) throws 
663    IOException {
664    try {
665      Job j = getJobUsingCluster(jobId);
666      if(j == null) {
667        return EMPTY_TASK_REPORTS;
668      }
669      return TaskReport.downgradeArray(j.getTaskReports(type));
670    } catch (InterruptedException ie) {
671      throw new IOException(ie);
672    }
673  }
674  
675  /**@deprecated Applications should rather use {@link #getMapTaskReports(JobID)}*/
676  @Deprecated
677  public TaskReport[] getMapTaskReports(String jobId) throws IOException {
678    return getMapTaskReports(JobID.forName(jobId));
679  }
680  
681  /**
682   * Get the information of the current state of the reduce tasks of a job.
683   * 
684   * @param jobId the job to query.
685   * @return the list of all of the reduce tips.
686   * @throws IOException
687   */    
688  public TaskReport[] getReduceTaskReports(JobID jobId) throws IOException {
689    return getTaskReports(jobId, TaskType.REDUCE);
690  }
691
692  /**
693   * Get the information of the current state of the cleanup tasks of a job.
694   * 
695   * @param jobId the job to query.
696   * @return the list of all of the cleanup tips.
697   * @throws IOException
698   */    
699  public TaskReport[] getCleanupTaskReports(JobID jobId) throws IOException {
700    return getTaskReports(jobId, TaskType.JOB_CLEANUP);
701  }
702
703  /**
704   * Get the information of the current state of the setup tasks of a job.
705   * 
706   * @param jobId the job to query.
707   * @return the list of all of the setup tips.
708   * @throws IOException
709   */    
710  public TaskReport[] getSetupTaskReports(JobID jobId) throws IOException {
711    return getTaskReports(jobId, TaskType.JOB_SETUP);
712  }
713
714  
715  /**@deprecated Applications should rather use {@link #getReduceTaskReports(JobID)}*/
716  @Deprecated
717  public TaskReport[] getReduceTaskReports(String jobId) throws IOException {
718    return getReduceTaskReports(JobID.forName(jobId));
719  }
720  
721  /**
722   * Display the information about a job's tasks, of a particular type and
723   * in a particular state
724   * 
725   * @param jobId the ID of the job
726   * @param type the type of the task (map/reduce/setup/cleanup)
727   * @param state the state of the task 
728   * (pending/running/completed/failed/killed)
729   */
730  public void displayTasks(final JobID jobId, String type, String state) 
731  throws IOException {
732    try {
733      Job job = getJobUsingCluster(jobId);
734      super.displayTasks(job, type, state);
735    } catch (InterruptedException ie) {
736      throw new IOException(ie);
737    }
738  }
739  
740  /**
741   * Get status information about the Map-Reduce cluster.
742   *  
743   * @return the status information about the Map-Reduce cluster as an object
744   *         of {@link ClusterStatus}.
745   * @throws IOException
746   */
747  public ClusterStatus getClusterStatus() throws IOException {
748    try {
749      return clientUgi.doAs(new PrivilegedExceptionAction<ClusterStatus>() {
750        public ClusterStatus run() throws IOException, InterruptedException {
751          ClusterMetrics metrics = cluster.getClusterStatus();
752          return new ClusterStatus(metrics.getTaskTrackerCount(), metrics
753            .getBlackListedTaskTrackerCount(), cluster
754            .getTaskTrackerExpiryInterval(), metrics.getOccupiedMapSlots(),
755            metrics.getOccupiedReduceSlots(), metrics.getMapSlotCapacity(),
756            metrics.getReduceSlotCapacity(), cluster.getJobTrackerStatus(),
757            metrics.getDecommissionedTaskTrackerCount(), metrics
758              .getGrayListedTaskTrackerCount());
759        }
760      });
761    } catch (InterruptedException ie) {
762      throw new IOException(ie);
763    }
764  }
765
766  private  Collection<String> arrayToStringList(TaskTrackerInfo[] objs) {
767    Collection<String> list = new ArrayList<String>();
768    for (TaskTrackerInfo info: objs) {
769      list.add(info.getTaskTrackerName());
770    }
771    return list;
772  }
773
774  private  Collection<BlackListInfo> arrayToBlackListInfo(TaskTrackerInfo[] objs) {
775    Collection<BlackListInfo> list = new ArrayList<BlackListInfo>();
776    for (TaskTrackerInfo info: objs) {
777      BlackListInfo binfo = new BlackListInfo();
778      binfo.setTrackerName(info.getTaskTrackerName());
779      binfo.setReasonForBlackListing(info.getReasonForBlacklist());
780      binfo.setBlackListReport(info.getBlacklistReport());
781      list.add(binfo);
782    }
783    return list;
784  }
785
786  /**
787   * Get status information about the Map-Reduce cluster.
788   *  
789   * @param  detailed if true then get a detailed status including the
790   *         tracker names
791   * @return the status information about the Map-Reduce cluster as an object
792   *         of {@link ClusterStatus}.
793   * @throws IOException
794   */
795  public ClusterStatus getClusterStatus(boolean detailed) throws IOException {
796    try {
797      return clientUgi.doAs(new PrivilegedExceptionAction<ClusterStatus>() {
798        public ClusterStatus run() throws IOException, InterruptedException {
799        ClusterMetrics metrics = cluster.getClusterStatus();
800        return new ClusterStatus(arrayToStringList(cluster.getActiveTaskTrackers()),
801          arrayToBlackListInfo(cluster.getBlackListedTaskTrackers()),
802          cluster.getTaskTrackerExpiryInterval(), metrics.getOccupiedMapSlots(),
803          metrics.getOccupiedReduceSlots(), metrics.getMapSlotCapacity(),
804          metrics.getReduceSlotCapacity(), 
805          cluster.getJobTrackerStatus());
806        }
807      });
808    } catch (InterruptedException ie) {
809      throw new IOException(ie);
810    }
811  }
812    
813
814  /** 
815   * Get the jobs that are not completed and not failed.
816   * 
817   * @return array of {@link JobStatus} for the running/to-be-run jobs.
818   * @throws IOException
819   */
820  public JobStatus[] jobsToComplete() throws IOException {
821    List<JobStatus> stats = new ArrayList<JobStatus>();
822    for (JobStatus stat : getAllJobs()) {
823      if (!stat.isJobComplete()) {
824        stats.add(stat);
825      }
826    }
827    return stats.toArray(new JobStatus[0]);
828  }
829
830  /** 
831   * Get the jobs that are submitted.
832   * 
833   * @return array of {@link JobStatus} for the submitted jobs.
834   * @throws IOException
835   */
836  public JobStatus[] getAllJobs() throws IOException {
837    try {
838      org.apache.hadoop.mapreduce.JobStatus[] jobs = 
839          clientUgi.doAs(new PrivilegedExceptionAction<
840              org.apache.hadoop.mapreduce.JobStatus[]> () {
841            public org.apache.hadoop.mapreduce.JobStatus[] run() 
842                throws IOException, InterruptedException {
843              return cluster.getAllJobStatuses();
844            }
845          });
846      JobStatus[] stats = new JobStatus[jobs.length];
847      for (int i = 0; i < jobs.length; i++) {
848        stats[i] = JobStatus.downgrade(jobs[i]);
849      }
850      return stats;
851    } catch (InterruptedException ie) {
852      throw new IOException(ie);
853    }
854  }
855  
856  /** 
857   * Utility that submits a job, then polls for progress until the job is
858   * complete.
859   * 
860   * @param job the job configuration.
861   * @throws IOException if the job fails
862   */
863  public static RunningJob runJob(JobConf job) throws IOException {
864    JobClient jc = new JobClient(job);
865    RunningJob rj = jc.submitJob(job);
866    try {
867      if (!jc.monitorAndPrintJob(job, rj)) {
868        throw new IOException("Job failed!");
869      }
870    } catch (InterruptedException ie) {
871      Thread.currentThread().interrupt();
872    }
873    return rj;
874  }
875  
876  /**
877   * Monitor a job and print status in real-time as progress is made and tasks 
878   * fail.
879   * @param conf the job's configuration
880   * @param job the job to track
881   * @return true if the job succeeded
882   * @throws IOException if communication to the JobTracker fails
883   */
884  public boolean monitorAndPrintJob(JobConf conf, 
885                                    RunningJob job
886  ) throws IOException, InterruptedException {
887    return ((NetworkedJob)job).monitorAndPrintJob();
888  }
889
890  static String getTaskLogURL(TaskAttemptID taskId, String baseUrl) {
891    return (baseUrl + "/tasklog?plaintext=true&attemptid=" + taskId); 
892  }
893  
894  static Configuration getConfiguration(String jobTrackerSpec)
895  {
896    Configuration conf = new Configuration();
897    if (jobTrackerSpec != null) {        
898      if (jobTrackerSpec.indexOf(":") >= 0) {
899        conf.set("mapred.job.tracker", jobTrackerSpec);
900      } else {
901        String classpathFile = "hadoop-" + jobTrackerSpec + ".xml";
902        URL validate = conf.getResource(classpathFile);
903        if (validate == null) {
904          throw new RuntimeException(classpathFile + " not found on CLASSPATH");
905        }
906        conf.addResource(classpathFile);
907      }
908    }
909    return conf;
910  }
911
912  /**
913   * Sets the output filter for tasks. only those tasks are printed whose
914   * output matches the filter. 
915   * @param newValue task filter.
916   */
917  @Deprecated
918  public void setTaskOutputFilter(TaskStatusFilter newValue){
919    this.taskOutputFilter = newValue;
920  }
921    
922  /**
923   * Get the task output filter out of the JobConf.
924   * 
925   * @param job the JobConf to examine.
926   * @return the filter level.
927   */
928  public static TaskStatusFilter getTaskOutputFilter(JobConf job) {
929    return TaskStatusFilter.valueOf(job.get("jobclient.output.filter", 
930                                            "FAILED"));
931  }
932    
933  /**
934   * Modify the JobConf to set the task output filter.
935   * 
936   * @param job the JobConf to modify.
937   * @param newValue the value to set.
938   */
939  public static void setTaskOutputFilter(JobConf job, 
940                                         TaskStatusFilter newValue) {
941    job.set("jobclient.output.filter", newValue.toString());
942  }
943    
944  /**
945   * Returns task output filter.
946   * @return task filter. 
947   */
948  @Deprecated
949  public TaskStatusFilter getTaskOutputFilter(){
950    return this.taskOutputFilter; 
951  }
952
953  protected long getCounter(org.apache.hadoop.mapreduce.Counters cntrs,
954      String counterGroupName, String counterName) throws IOException {
955    Counters counters = Counters.downgrade(cntrs);
956    return counters.findCounter(counterGroupName, counterName).getValue();
957  }
958
959  /**
960   * Get status information about the max available Maps in the cluster.
961   *  
962   * @return the max available Maps in the cluster
963   * @throws IOException
964   */
965  public int getDefaultMaps() throws IOException {
966    try {
967      return clientUgi.doAs(new PrivilegedExceptionAction<Integer>() {
968        @Override
969        public Integer run() throws IOException, InterruptedException {
970          return cluster.getClusterStatus().getMapSlotCapacity();
971        }
972      });
973    } catch (InterruptedException ie) {
974      throw new IOException(ie);
975    }
976  }
977
978  /**
979   * Get status information about the max available Reduces in the cluster.
980   *  
981   * @return the max available Reduces in the cluster
982   * @throws IOException
983   */
984  public int getDefaultReduces() throws IOException {
985    try {
986      return clientUgi.doAs(new PrivilegedExceptionAction<Integer>() {
987        @Override
988        public Integer run() throws IOException, InterruptedException {
989          return cluster.getClusterStatus().getReduceSlotCapacity();
990        }
991      });
992    } catch (InterruptedException ie) {
993      throw new IOException(ie);
994    }
995  }
996
997  /**
998   * Grab the jobtracker system directory path where job-specific files are to be placed.
999   * 
1000   * @return the system directory where job-specific files are to be placed.
1001   */
1002  public Path getSystemDir() {
1003    try {
1004      return clientUgi.doAs(new PrivilegedExceptionAction<Path>() {
1005        @Override
1006        public Path run() throws IOException, InterruptedException {
1007          return cluster.getSystemDir();
1008        }
1009      });
1010      } catch (IOException ioe) {
1011      return null;
1012    } catch (InterruptedException ie) {
1013      return null;
1014    }
1015  }
1016
1017  /**
1018   * Checks if the job directory is clean and has all the required components
1019   * for (re) starting the job
1020   */
1021  public static boolean isJobDirValid(Path jobDirPath, FileSystem fs)
1022      throws IOException {
1023    FileStatus[] contents = fs.listStatus(jobDirPath);
1024    int matchCount = 0;
1025    if (contents != null && contents.length >= 2) {
1026      for (FileStatus status : contents) {
1027        if ("job.xml".equals(status.getPath().getName())) {
1028          ++matchCount;
1029        }
1030        if ("job.split".equals(status.getPath().getName())) {
1031          ++matchCount;
1032        }
1033      }
1034      if (matchCount == 2) {
1035        return true;
1036      }
1037    }
1038    return false;
1039  }
1040
1041  /**
1042   * Fetch the staging area directory for the application
1043   * 
1044   * @return path to staging area directory
1045   * @throws IOException
1046   */
1047  public Path getStagingAreaDir() throws IOException {
1048    try {
1049      return clientUgi.doAs(new PrivilegedExceptionAction<Path>() {
1050        @Override
1051        public Path run() throws IOException, InterruptedException {
1052          return cluster.getStagingAreaDir();
1053        }
1054      });
1055    } catch (InterruptedException ie) {
1056      // throw RuntimeException instead for compatibility reasons
1057      throw new RuntimeException(ie);
1058    }
1059  }
1060
1061  private JobQueueInfo getJobQueueInfo(QueueInfo queue) {
1062    JobQueueInfo ret = new JobQueueInfo(queue);
1063    // make sure to convert any children
1064    if (queue.getQueueChildren().size() > 0) {
1065      List<JobQueueInfo> childQueues = new ArrayList<JobQueueInfo>(queue
1066          .getQueueChildren().size());
1067      for (QueueInfo child : queue.getQueueChildren()) {
1068        childQueues.add(getJobQueueInfo(child));
1069      }
1070      ret.setChildren(childQueues);
1071    }
1072    return ret;
1073  }
1074
1075  private JobQueueInfo[] getJobQueueInfoArray(QueueInfo[] queues)
1076      throws IOException {
1077    JobQueueInfo[] ret = new JobQueueInfo[queues.length];
1078    for (int i = 0; i < queues.length; i++) {
1079      ret[i] = getJobQueueInfo(queues[i]);
1080    }
1081    return ret;
1082  }
1083
1084  /**
1085   * Returns an array of queue information objects about root level queues
1086   * configured
1087   *
1088   * @return the array of root level JobQueueInfo objects
1089   * @throws IOException
1090   */
1091  public JobQueueInfo[] getRootQueues() throws IOException {
1092    try {
1093      return clientUgi.doAs(new PrivilegedExceptionAction<JobQueueInfo[]>() {
1094        public JobQueueInfo[] run() throws IOException, InterruptedException {
1095          return getJobQueueInfoArray(cluster.getRootQueues());
1096        }
1097      });
1098    } catch (InterruptedException ie) {
1099      throw new IOException(ie);
1100    }
1101  }
1102
1103  /**
1104   * Returns an array of queue information objects about immediate children
1105   * of queue queueName.
1106   * 
1107   * @param queueName
1108   * @return the array of immediate children JobQueueInfo objects
1109   * @throws IOException
1110   */
1111  public JobQueueInfo[] getChildQueues(final String queueName) throws IOException {
1112    try {
1113      return clientUgi.doAs(new PrivilegedExceptionAction<JobQueueInfo[]>() {
1114        public JobQueueInfo[] run() throws IOException, InterruptedException {
1115          return getJobQueueInfoArray(cluster.getChildQueues(queueName));
1116        }
1117      });
1118    } catch (InterruptedException ie) {
1119      throw new IOException(ie);
1120    }
1121  }
1122  
1123  /**
1124   * Return an array of queue information objects about all the Job Queues
1125   * configured.
1126   * 
1127   * @return Array of JobQueueInfo objects
1128   * @throws IOException
1129   */
1130  public JobQueueInfo[] getQueues() throws IOException {
1131    try {
1132      return clientUgi.doAs(new PrivilegedExceptionAction<JobQueueInfo[]>() {
1133        public JobQueueInfo[] run() throws IOException, InterruptedException {
1134          return getJobQueueInfoArray(cluster.getQueues());
1135        }
1136      });
1137    } catch (InterruptedException ie) {
1138      throw new IOException(ie);
1139    }
1140  }
1141  
1142  /**
1143   * Gets all the jobs which were added to particular Job Queue
1144   * 
1145   * @param queueName name of the Job Queue
1146   * @return Array of jobs present in the job queue
1147   * @throws IOException
1148   */
1149  
1150  public JobStatus[] getJobsFromQueue(final String queueName) throws IOException {
1151    try {
1152      QueueInfo queue = clientUgi.doAs(new PrivilegedExceptionAction<QueueInfo>() {
1153        @Override
1154        public QueueInfo run() throws IOException, InterruptedException {
1155          return cluster.getQueue(queueName);
1156        }
1157      });
1158      if (queue == null) {
1159        return null;
1160      }
1161      org.apache.hadoop.mapreduce.JobStatus[] stats = 
1162        queue.getJobStatuses();
1163      JobStatus[] ret = new JobStatus[stats.length];
1164      for (int i = 0 ; i < stats.length; i++ ) {
1165        ret[i] = JobStatus.downgrade(stats[i]);
1166      }
1167      return ret;
1168    } catch (InterruptedException ie) {
1169      throw new IOException(ie);
1170    }
1171  }
1172  
1173  /**
1174   * Gets the queue information associated to a particular Job Queue
1175   * 
1176   * @param queueName name of the job queue.
1177   * @return Queue information associated to particular queue.
1178   * @throws IOException
1179   */
1180  public JobQueueInfo getQueueInfo(final String queueName) throws IOException {
1181    try {
1182      QueueInfo queueInfo = clientUgi.doAs(new 
1183          PrivilegedExceptionAction<QueueInfo>() {
1184        public QueueInfo run() throws IOException, InterruptedException {
1185          return cluster.getQueue(queueName);
1186        }
1187      });
1188      if (queueInfo != null) {
1189        return new JobQueueInfo(queueInfo);
1190      }
1191      return null;
1192    } catch (InterruptedException ie) {
1193      throw new IOException(ie);
1194    }
1195  }
1196  
1197  /**
1198   * Gets the Queue ACLs for current user
1199   * @return array of QueueAclsInfo object for current user.
1200   * @throws IOException
1201   */
1202  public QueueAclsInfo[] getQueueAclsForCurrentUser() throws IOException {
1203    try {
1204      org.apache.hadoop.mapreduce.QueueAclsInfo[] acls = 
1205        clientUgi.doAs(new 
1206            PrivilegedExceptionAction
1207            <org.apache.hadoop.mapreduce.QueueAclsInfo[]>() {
1208              public org.apache.hadoop.mapreduce.QueueAclsInfo[] run() 
1209              throws IOException, InterruptedException {
1210                return cluster.getQueueAclsForCurrentUser();
1211              }
1212        });
1213      QueueAclsInfo[] ret = new QueueAclsInfo[acls.length];
1214      for (int i = 0 ; i < acls.length; i++ ) {
1215        ret[i] = QueueAclsInfo.downgrade(acls[i]);
1216      }
1217      return ret;
1218    } catch (InterruptedException ie) {
1219      throw new IOException(ie);
1220    }
1221  }
1222
1223  /**
1224   * Get a delegation token for the user from the JobTracker.
1225   * @param renewer the user who can renew the token
1226   * @return the new token
1227   * @throws IOException
1228   */
1229  public Token<DelegationTokenIdentifier> 
1230    getDelegationToken(final Text renewer) throws IOException, InterruptedException {
1231    return clientUgi.doAs(new 
1232        PrivilegedExceptionAction<Token<DelegationTokenIdentifier>>() {
1233      public Token<DelegationTokenIdentifier> run() throws IOException, 
1234      InterruptedException {
1235        return cluster.getDelegationToken(renewer);
1236      }
1237    });
1238  }
1239
1240  /**
1241   * Renew a delegation token
1242   * @param token the token to renew
1243   * @return true if the renewal went well
1244   * @throws InvalidToken
1245   * @throws IOException
1246   * @deprecated Use {@link Token#renew} instead
1247   */
1248  public long renewDelegationToken(Token<DelegationTokenIdentifier> token
1249                                   ) throws InvalidToken, IOException, 
1250                                            InterruptedException {
1251    return token.renew(getConf());
1252  }
1253
1254  /**
1255   * Cancel a delegation token from the JobTracker
1256   * @param token the token to cancel
1257   * @throws IOException
1258   * @deprecated Use {@link Token#cancel} instead
1259   */
1260  public void cancelDelegationToken(Token<DelegationTokenIdentifier> token
1261                                    ) throws InvalidToken, IOException, 
1262                                             InterruptedException {
1263    token.cancel(getConf());
1264  }
1265
1266  /**
1267   */
1268  public static void main(String argv[]) throws Exception {
1269    int res = ToolRunner.run(new JobClient(), argv);
1270    System.exit(res);
1271  }
1272}
1273