/*
* Eoulsan development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public License version 2.1 or
* later and CeCILL-C. This should be distributed with the code.
* If you do not have a copy, see:
*
* http://www.gnu.org/licenses/lgpl-2.1.txt
* http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt
*
* Copyright for this code is held jointly by the Genomic platform
* of the Institut de Biologie de l'École normale supérieure and
* the individual authors. These should be listed in @author doc
* comments.
*
* For more information on the Eoulsan project and its aims,
* or to join the Eoulsan Google group, visit the home page
* at:
*
* http://outils.genomique.biologie.ens.fr/eoulsan
*
*/
package fr.ens.biologie.genomique.eoulsan.core.schedulers.clusters;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import fr.ens.biologie.genomique.eoulsan.EoulsanException;
import fr.ens.biologie.genomique.eoulsan.Settings;
/**
* This class allow to submit, stop and get the status of jobs using Bpipe
* scheduler wrappers.
* @author Laurent Jourdren
* @since 2.0
*/
public abstract class BpipeTaskScheduler extends AbstractClusterTaskScheduler {
/**
* Get the path to the Bpipe command wrapper.
* @return the File object with the path to the Bpipe command wrapper
*/
protected abstract File getBpipeCommandWrapper();
//
// ClusterTaskScheduler methods
//
@Override
public void configure(final Settings settings) throws EoulsanException {
}
@Override
public synchronized String submitJob(final String jobName,
final List<String> jobCommand, final File jobDirectory, final int taskId,
final int requiredMemory, final int requiredProcessors)
throws IOException {
checkNotNull(jobName, "jobName argument cannot be null");
checkNotNull(jobCommand, "jobCommand argument cannot be null");
checkNotNull(jobDirectory, "jobDirectory argument cannot be null");
checkArgument(jobDirectory.isDirectory(),
"The job directory does not exists or is not a directory: "
+ jobDirectory);
final String jobCommandString = Joiner.on(' ').join(jobCommand);
try {
final Process process = startJobProcess(jobName, jobCommandString,
jobDirectory, taskId, requiredMemory, requiredProcessors);
// Read output of the submit command
final BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
final String jobId = reader.readLine();
reader.close();
final int exitCode = process.waitFor();
if (exitCode == 0) {
getLogger().fine("Job "
+ jobId + " submitted to " + getSchedulerName()
+ " scheduler. Job name: " + jobName + " Job command: "
+ jobCommand);
// Add the cluster job to the list of job to kill if workflow fails
ClusterJobEmergencyStopTask.addHadoopJobEmergencyStopTask(this, jobId);
return jobId;
} else {
getLogger().warning("Job submission failed with "
+ getSchedulerName() + " scheduler. Job name: " + jobName
+ " Job command: " + jobCommand);
throw new IOException("Job submission failed, exit code: " + exitCode);
}
} catch (InterruptedException e) {
throw new IOException(e);
}
}
@Override
public void stopJob(final String jobId) throws IOException {
checkNotNull(jobId, "jobId argument cannot be null");
try {
final Process process = stopJobProcess(jobId);
final int exitCode = process.waitFor();
if (exitCode == 0) {
getLogger().fine("Job "
+ jobId + " removed from " + getSchedulerName() + " cluster");
} else {
getLogger().warning("Job "
+ jobId + " not removed from " + getSchedulerName() + " cluster");
}
} catch (InterruptedException e) {
getLogger().severe(e.getMessage());
}
}
@Override
public StatusResult statusJob(final String jobId) throws IOException {
checkNotNull(jobId, "jobId argument cannot be null");
try {
final Process process = statusJobProcess(jobId);
// Read output of the submit command
final BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
final String jobStatus = reader.readLine();
reader.close();
final int exitCode = process.waitFor();
if (exitCode == 0) {
getLogger().fine("Job "
+ jobId + " status on " + getSchedulerName()
+ " scheduler. Job status: " + jobStatus);
final List<String> fields =
Lists.newArrayList(Splitter.on(' ').split(jobStatus.trim()));
switch (fields.get(0)) {
case "WAITING":
return new StatusResult(StatusValue.WAITING);
case "RUNNING":
return new StatusResult(StatusValue.RUNNING);
case "COMPLETE":
// Remove the cluster job to the list of job to kill if workflow fails
ClusterJobEmergencyStopTask.removeHadoopJobEmergencyStopTask(this,
jobId);
if (fields.size() != 2) {
throw new IOException("Invalid complete string: " + jobStatus);
}
try {
return new StatusResult(StatusValue.COMPLETE,
Integer.parseInt(fields.get(1)));
} catch (NumberFormatException e) {
throw new IOException("Invalid complete string: " + jobStatus, e);
}
case "UNKNOWN":
return new StatusResult(StatusValue.UNKNOWN);
default:
throw new IOException("Unknown status: " + jobStatus);
}
} else {
getLogger()
.warning("Job status command failed. Exit code: " + exitCode);
throw new IOException("Job status failed, exit code: " + exitCode);
}
} catch (InterruptedException e) {
throw new IOException(e);
}
}
@Override
public void cleanupJob(final String jobId) throws IOException {
// Nothing to do
}
//
// Process builders methods
//
/**
* Create process to submit a job.
* @param jobName job name
* @param jobCommand job command
* @param jobDirectory job directory
* @param taskId task id
* @param requiredMemory required memory
* @param requiredProcessors required processors
* @return a Process object
* @throws IOException if an error occurs while creating the process
*/
private Process startJobProcess(final String jobName, final String jobCommand,
final File jobDirectory, final int taskId, final int requiredMemory,
final int requiredProcessors) throws IOException {
final List<String> command = new ArrayList<>();
command.add(getBpipeCommandWrapperPath());
command.add("start");
final ProcessBuilder builder = new ProcessBuilder(command);
builder.environment().put("NAME", jobName);
builder.environment().put("COMMAND", jobCommand);
builder.environment().put("JOBDIR", jobDirectory.getAbsolutePath());
builder.environment().put("EOULSAN_TASK_ID", "" + taskId);
if (requiredMemory > 0) {
final int memory =
requiredMemory / 1024 + (requiredMemory % 1024 == 0 ? 0 : 1);
builder.environment().put("MEMORY", "" + memory);
}
if (requiredProcessors > 0) {
builder.environment().put("PROCS", "" + requiredProcessors);
}
return builder.start();
}
/**
* Create process to stop a job.
* @param jobId job id
* @return a Process object
* @throws IOException if an error occurs while creating the process
*/
private Process stopJobProcess(final String jobId) throws IOException {
final List<String> command = new ArrayList<>();
command.add(getBpipeCommandWrapperPath());
command.add("stop");
command.add(jobId);
final ProcessBuilder builder = new ProcessBuilder(command);
return builder.start();
}
/**
* Create process to get the status of a job.
* @param jobId job id
* @return a Process object
* @throws IOException if an error occurs while creating the process
*/
private Process statusJobProcess(final String jobId) throws IOException {
final List<String> command = new ArrayList<>();
command.add(getBpipeCommandWrapperPath());
command.add("status");
command.add(jobId);
final ProcessBuilder builder = new ProcessBuilder(command);
return builder.start();
}
//
// Other methods
//
/**
* This method get the absolute path of the Bpipe wrapper and check if the
* wrapper exists and if it is executable.
* @return the wrapper absolute path.
* @throws IOException if the wrapper does not exists or cannot be executed
*/
private String getBpipeCommandWrapperPath() throws IOException {
final File f = getBpipeCommandWrapper();
if (f == null) {
throw new IOException("No Bpipe command wrapper defined for scheduler "
+ getSchedulerName());
}
if (!f.exists()) {
throw new IOException("The Bpipe command wrapper defines for scheduler "
+ getSchedulerName() + " does not exist: " + f);
}
if (!f.canExecute()) {
throw new IOException("The Bpipe command wrapper defines for scheduler "
+ getSchedulerName() + " does not exist: " + f);
}
return f.getAbsolutePath();
}
}