package io.seqware.pipeline.plugins;
import com.google.common.collect.Lists;
import io.seqware.Engines;
import io.seqware.common.model.WorkflowRunStatus;
import io.seqware.pipeline.SqwKeys;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import joptsimple.ArgumentAcceptingOptionSpec;
import net.sourceforge.seqware.common.model.WorkflowRun;
import net.sourceforge.seqware.common.module.ReturnValue;
import net.sourceforge.seqware.common.module.ReturnValue.ExitStatus;
import net.sourceforge.seqware.common.util.Log;
import net.sourceforge.seqware.pipeline.plugin.Plugin;
import net.sourceforge.seqware.pipeline.plugin.PluginInterface;
import org.apache.commons.io.FileUtils;
import org.openide.util.lookup.ServiceProvider;
/**
* The Workflow Rescheduler can schedule a new workflow based on the configuration of a previously launched workflow.
*
* This will typically be used to re-schedule failed workflow runs that should be re-run totally from scratch. A new workflow will be
* re-scheduled using the same parameters that a specified workflow-run used.
*
* @author dyuen
* @version 1.1.0
*/
@ServiceProvider(service = PluginInterface.class)
public class WorkflowRescheduler extends Plugin {
public static final String INPUT_FILES = "input-files";
private final ArgumentAcceptingOptionSpec<String> workflowEngineSpec;
private final ArgumentAcceptingOptionSpec<String> hostSpec;
private final ArgumentAcceptingOptionSpec<String> outFileSpec;
private final ArgumentAcceptingOptionSpec<String> workflowRunSpec;
public WorkflowRescheduler() {
super();
parser.acceptsAll(Arrays.asList("help", "h", "?"), "Provides this help message.");
this.hostSpec = parser.acceptsAll(Arrays.asList("host", "ho"), "Used to schedule onto a specific host").withRequiredArg()
.ofType(String.class);
this.workflowRunSpec = parser
.acceptsAll(Arrays.asList("workflow-run", "wr"),
"Required: specify workflow-run(s) by swid, comma-delimited, to re-schedule").withRequiredArg().required()
.withValuesSeparatedBy(",");
this.workflowEngineSpec = WorkflowScheduler.createWorkflowEngineSpec(parser);
this.outFileSpec = parser.acceptsAll(Arrays.asList("out"), "Optional: Will output a workflow-run by sw_accession")
.withRequiredArg();
}
private String getEngineParam() {
String engine = options.valueOf(workflowEngineSpec);
if (engine == null) {
engine = config.get(SqwKeys.SW_DEFAULT_WORKFLOW_ENGINE.getSettingKey());
}
if (engine == null) {
engine = Engines.DEFAULT_ENGINE;
}
return engine;
}
@Override
public ReturnValue init() {
if (options.has(workflowEngineSpec)) {
return validateEngineString(options.valueOf(workflowEngineSpec));
}
return new ReturnValue(ExitStatus.SUCCESS);
}
public static ReturnValue validateEngineString(String engine) {
if (!Engines.ENGINES.contains(engine)) {
Log.error("Invalid workflow-engine value. Must be one of: " + Engines.ENGINES_LIST);
return new ReturnValue(ExitStatus.INVALIDARGUMENT);
}
return new ReturnValue(ExitStatus.SUCCESS);
}
/*
*/
@Override
public ReturnValue do_test() {
return new ReturnValue(ExitStatus.SUCCESS);
}
@Override
public ReturnValue clean_up() {
return new ReturnValue(ExitStatus.SUCCESS);
}
@Override
public String get_description() {
return "A plugin that lets you re-schedule previously launched workflow runs.";
}
@Override
public ReturnValue do_run() {
try {
File outputFile = null;
if (options.has(this.outFileSpec)) {
outputFile = new File(options.valueOf(this.outFileSpec));
}
if (options.has(workflowRunSpec)) {
List<String> workflowRunSWIDs = options.valuesOf(this.workflowRunSpec);
for (String runSWID : workflowRunSWIDs) {
WorkflowRun oldWorkflowRun = metadata.getWorkflowRun(Integer.parseInt(runSWID));
// extract a workflow ini from the previous run
String iniFile = oldWorkflowRun.getIniFile();
Path tempFile = Files.createTempFile("workflow", "ini");
Log.debug("Writing previous ini to " + tempFile.toString());
Files.write(tempFile, Lists.newArrayList(iniFile), Charset.defaultCharset());
// parent accessions should be already present in ini, so we do not need to extract this
// create a new workflow run
int newWorkflowRunID = this.metadata.add_workflow_run(oldWorkflowRun.getWorkflowAccession());
// this translation here is ugly, do we still need to do this?
int workflowRunAccessionInt = this.metadata.get_workflow_run_accession(newWorkflowRunID);
WorkflowRun newWorkflowRun = metadata.getWorkflowRun(workflowRunAccessionInt);
Log.stdout("Created workflow run with SWID: " + workflowRunAccessionInt);
// here, we could reverse-engineer parent links and re-create them
// but I'd rather just deprecate direct links from workflow runs to lane and ius though
// have the old workflow run mimic the new one and upload for re-launching
oldWorkflowRun.setWorkflowRunId(newWorkflowRunID);
oldWorkflowRun.setSwAccession(workflowRunAccessionInt);
oldWorkflowRun.setCreateTimestamp(newWorkflowRun.getCreateTimestamp());
oldWorkflowRun.setStatus(WorkflowRunStatus.submitted);
// null out stuff that doesn't make sense to copy over
oldWorkflowRun.setStatusCmd(null);
oldWorkflowRun.setCurrentWorkingDir(null);
oldWorkflowRun.setDax(null);
oldWorkflowRun.setStdOut(null);
oldWorkflowRun.setStdErr(null);
// override host and engine if needed
if (options.has(hostSpec)) {
String host = options.valueOf(hostSpec);
oldWorkflowRun.setHost(host);
}
if (options.has(workflowEngineSpec)) {
String engine = getEngineParam();
oldWorkflowRun.setWorkflowEngine(engine);
}
Log.info("You are re-scheduling workflow-run " + runSWID + " to " + workflowRunAccessionInt);
this.metadata.updateWorkflowRun(oldWorkflowRun);
if (options.has(outFileSpec)) {
FileUtils.write(outputFile, String.valueOf(newWorkflowRun) + "\n", true);
}
}
} else {
Log.error("I don't understand the combination of arguments you gave!");
Log.info(this.get_syntax());
return new ReturnValue(ExitStatus.INVALIDARGUMENT);
}
} catch (IOException ex) {
return new ReturnValue(ExitStatus.FILENOTWRITABLE);
}
return new ReturnValue();
}
}