code: private void jobComplete() {
      final JobTrackerInstrumentation metrics = jobtracker.getInstrumentation();
      //
      // All tasks are complete, then the job is done!
      //
      if (this.status.getRunState() == JobStatus.RUNNING ||
          this.status.getRunState() == JobStatus.PREP) {
        changeStateTo(JobStatus.SUCCEEDED);
        this.status.setCleanupProgress(1.0f);
        if (maps.length == 0) {
          this.status.setMapProgress(1.0f);
        }
        if (reduces.length == 0) {
          this.status.setReduceProgress(1.0f);
        }
        this.finishTime = JobTracker.getClock().getTime();
        LOG.info("Job " + this.status.getJobID() +
                 " has completed successfully.");
  
        // Log the job summary (this should be done prior to logging to
        // job-history to ensure job-counters are in-sync
        JobSummary.logJobSummary(this, jobtracker.getClusterStatus(false));
  
        Counters counters = getCounters();
        // Log job-history
        JobHistory.JobInfo.logFinished(this.status.getJobID(), finishTime,
                                       this.finishedMapTasks,
                                       this.finishedReduceTasks, failedMapTasks,
                                       failedReduceTasks, killedMapTasks,
                                       killedReduceTasks, getMapCounters(),
                                       getReduceCounters(), counters);
        // Note that finalize will close the job history handles which garbage collect
        // might try to finalize
        garbageCollect();
  
        metrics.completeJob(this.conf, this.status.getJobID());
      }
    }
docs: The job is done since all it's component tasks are either
  successful or have failed.