/* * ProActive Parallel Suite(TM): * The Open Source library for parallel and distributed * Workflows & Scheduling, Orchestration, Cloud Automation * and Big Data Analysis on Enterprise Grids & Clouds. * * Copyright (c) 2007 - 2017 ActiveEon * Contact: contact@activeeon.com * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation: version 3 of * the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * If needed, contact us to obtain a release under GPL Version 2 or 3 * or a different license than the AGPL. */ package org.ow2.proactive.scheduler.common.task; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import org.objectweb.proactive.annotation.PublicAPI; import org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector; import org.ow2.proactive.scheduler.common.SchedulerConstants; import org.ow2.proactive.scheduler.common.job.Job; import org.ow2.proactive.scheduler.common.task.dataspaces.InputAccessMode; import org.ow2.proactive.scheduler.common.task.dataspaces.InputSelector; import org.ow2.proactive.scheduler.common.task.dataspaces.OutputAccessMode; import org.ow2.proactive.scheduler.common.task.dataspaces.OutputSelector; import org.ow2.proactive.scheduler.common.task.flow.FlowAction; import org.ow2.proactive.scheduler.common.task.flow.FlowBlock; import org.ow2.proactive.scheduler.common.task.flow.FlowScript; import org.ow2.proactive.scripting.Script; import org.ow2.proactive.scripting.SelectionScript; import com.google.common.base.Joiner; /** * This class is the super class of the every task that can be integrated in a job.<br> * A task contains some properties that can be set but also : <ul> * <li>A selection script that can be used to select a specific execution node for this task.</li> * <li>A preScript that will be launched before the real task (can be used to set environment vars).</li> * <li>A postScript that will be launched just after the end of the real task. * (this can be used to transfer files that have been created by the task).</li> * <li>A CleaningScript that will be launched by the resource manager to perform some cleaning. (deleting files or resources).</li> * </ul> * You will also be able to add dependences (if necessary) to * this task. The dependences mechanism are best describe below. * * @see #addDependence(Task) * * @author The ProActive Team * @since ProActive Scheduling 0.9 */ @PublicAPI @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name = "task") public abstract class Task extends CommonAttribute { /** Name of the task. */ protected String name = SchedulerConstants.TASK_DEFAULT_NAME; /** block declaration : syntactic scopes used for workflows * string representation of a FlowBlock enum */ protected String flowBlock = FlowBlock.NONE.toString(); /** Description of the task. */ protected String description = null; /** tag of the task */ protected String tag = null; /** DataSpace inputFiles */ protected List<InputSelector> inputFiles = null; /** DataSpace outputFiles */ protected List<OutputSelector> outputFiles = null; /** * The parallel environment of the task included number of nodes and topology definition * required to execute this task. */ protected ParallelEnvironment parallelEnvironment = null; /** * selection script : can be launched before getting a node in order to * verify some computer specificity. */ protected List<SelectionScript> sScripts; /** * PreScript : can be used to launch script just before the task * execution. */ protected Script<?> preScript; /** * PostScript : can be used to launch script just after the task */ protected Script<?> postScript; /** * CleaningScript : can be used to launch script just after the task or the postScript (if set) * Started even if a problem occurs. */ protected Script<?> cScript; /** * FlowScript: Control Flow Action Script executed after the task, * returns a {@link FlowAction} which will perform flow actions on the job */ protected FlowScript flowScript; /** Tell whether this task has a precious result or not. */ protected boolean preciousResult; /** If true, the stdout/err of this tasks are stored in a file in LOCALSPACE and copied back to USERSPACE */ protected boolean preciousLogs; protected boolean runAsMe; /** List of dependences if necessary. */ @XmlTransient private List<Task> dependences = null; /** maximum execution time of the task (in milliseconds), the variable is only valid if isWallTime is true */ protected long wallTime = 0; protected ForkEnvironment forkEnvironment; /** A map to hold task variables */ protected Map<String, TaskVariable> variables = Collections.synchronizedMap(new LinkedHashMap()); /** * Add a dependence to the task. <font color="red">Warning : the dependence order is very * important.</font> * <p> * In fact, it is in this order that you will get back the result in the children task. * <p> * For example: if you add to the task t3, the dependences t1 then t2, the parents of t3 will be t1 and t2 in this order * and the parameters of t3 will be the results of t1 and t2 in this order. * * @param task * the parent task to add to this task. */ public void addDependence(Task task) { if (dependences == null) { dependences = new ArrayList<Task>(); } dependences.add(task); } /** * Same as the {@link #addDependence(Task)} method, but for a list of dependences. * * @param tasks * the parent list of tasks to add to this task. */ public void addDependences(List<Task> tasks) { if (dependences == null) { dependences = new ArrayList<Task>(); } for (Task t : tasks) { addDependence(t); } } /** * To get the description of this task. * * @return the description of this task. */ public String getDescription() { return description; } /** * To set the description of this task. * * @param description * the description to set. */ public void setDescription(String description) { this.description = description; } /** * To know if the result of this task is precious. * * @return true if the result is precious, false if not. */ public boolean isPreciousResult() { return preciousResult; } /** * Set if the result of this task is precious. * * @param preciousResult true if the result of this task is precious, false if not. */ public void setPreciousResult(boolean preciousResult) { this.preciousResult = preciousResult; } /** * To know if the logs of this task are precious. * * @return true if the logs are precious, false if not. */ public boolean isPreciousLogs() { return preciousLogs; } /** * Set if the logs of this task are precious. * * @param preciousLogs true if the logs of this task are precious, false if not. */ public void setPreciousLogs(boolean preciousLogs) { this.preciousLogs = preciousLogs; } /** * To know if the task will be executed under the user identity or not. * * @return true if the task will be executed as the user identity. */ public boolean isRunAsMe() { return this.runAsMe; } /** * Set if this task will be run under user identity. * This could impact the performance. * * @param runAsMe true if this task will be run under user identity. */ public void setRunAsMe(boolean runAsMe) { this.runAsMe = runAsMe; } /** * To get the name of this task. * * @return the name of this task. */ public String getName() { return name; } /** * To set the name of this task. * * @param name * the name to set. */ public void setName(String name) { if (name == null) { return; } if (name.length() > 255) { throw new IllegalArgumentException("The name is too long, it must have 255 chars length max : " + name); } this.name = name; } /** * Get the tag of this task. * Return null if this task has no tag. * * @return the tag of this task */ public String getTag() { return this.tag; } /** * Set the tag of this task. * @param tag tag value */ public void setTag(String tag) { this.tag = tag; } /** * Defining Control Flow Blocks with pairs of {@link FlowBlock#START} and {@link FlowBlock#END} * is a semantic requirement to be able to create specific control flow constructs. * <p> * Refer to the documentation for detailed instructions. * * @return the FlowBlock attribute of this task indicating if it is the beginning or the ending * of a new block scope */ public FlowBlock getFlowBlock() { return FlowBlock.parse(this.flowBlock); } /** * Defining Control Flow Blocks using {@link FlowBlock#START} and {@link FlowBlock#END} * is a semantic requirement to be able to create specific control flow constructs. * <p> * Refer to the documentation for detailed instructions. * * @param fb the FlowBlock attribute of this task indicating if it is the beginning * or the ending of a new block scope */ public void setFlowBlock(FlowBlock fb) { this.flowBlock = fb.toString(); } /** * To get the preScript of this task. * * @return the preScript of this task. */ public Script<?> getPreScript() { return preScript; } /** * To set the preScript of this task. * * @param preScript * the preScript to set. */ public void setPreScript(Script<?> preScript) { this.preScript = preScript; this.preScript.overrideDefaultScriptName("PreScript"); } /** * To get the postScript of this task. * * @return the postScript of this task. */ public Script<?> getPostScript() { return postScript; } /** * To perform complex Control Flow Actions in a TaskFlowJob, * a FlowScript needs to be attached to specific tasks. * <p> * Some Control Flow constructs need to be used within the bounds of * a {@link FlowBlock}, which is defined a the level of the Task using * {@link Task#setFlowBlock(FlowBlock)}. * <p> * Refer to the user documentation for further instructions. * * @return the Control Flow Script used for workflow actions */ public FlowScript getFlowScript() { return flowScript; } /** * To perform complex Control Flow Actions in a TaskFlowJob, * a FlowScript needs to be attached to specific tasks. * <p> * Some Control Flow constructs need to be used within the bounds of * a {@link FlowBlock}, which is defined a the level of the Task using * {@link Task#setFlowBlock(FlowBlock)}. * <p> * Refer to the user documentation for further instructions. * * @param s the Control Flow Script used for workflow actions */ public void setFlowScript(FlowScript s) { this.flowScript = s; } /** * To set the postScript of this task. * * @param postScript * the postScript to set. */ public void setPostScript(Script<?> postScript) { this.postScript = postScript; this.postScript.overrideDefaultScriptName("PostScript"); } /** * To get the cleaningScript of this task. * * @return the cleaningScript of this task. */ public Script<?> getCleaningScript() { return cScript; } /** * To set the cleaningScript of this task. * * @param cleaningScript * the cleaningScript to set. */ public void setCleaningScript(Script<?> cleaningScript) { this.cScript = cleaningScript; this.cScript.overrideDefaultScriptName("CleanScript"); } /** * Checks if the task is parallel. * @return true if the task is parallel, false otherwise. */ public boolean isParallel() { return parallelEnvironment != null && parallelEnvironment.getNodesNumber() >= 1; } /** * Returns the parallel environment of the task. * @return the parallel environment of the task. */ public ParallelEnvironment getParallelEnvironment() { return parallelEnvironment; } /** * Sets new parallel environment of the task. * @param parallelEnvironment new parallel environment of the task. */ public void setParallelEnvironment(ParallelEnvironment parallelEnvironment) { this.parallelEnvironment = parallelEnvironment; } /** * To get the number of execution for this task. * * @return the number of times this task can be executed. */ /** * To get the selection script. This is the script that will select a node. * * @return the selection Script. */ public List<SelectionScript> getSelectionScripts() { if (sScripts == null || sScripts.size() == 0) { return null; } else { return sScripts; } } /** * Set a selection script. It is the script that will be in charge of selecting a node. */ public void setSelectionScript(SelectionScript selScript) { if (selScript == null) { throw new IllegalArgumentException("The given selection script cannot be null !"); } List<SelectionScript> selScriptsList = new ArrayList<SelectionScript>(); selScriptsList.add(selScript); setSelectionScripts(selScriptsList); } /** * Set a list of selection scripts. These are the scripts that will be in charge of selecting a node. */ public void setSelectionScripts(List<SelectionScript> selScriptsList) { this.sScripts = selScriptsList; } /** * To add a selection script to the list of selection script. * * @param selectionScript * the selectionScript to add. */ public void addSelectionScript(SelectionScript selectionScript) { if (selectionScript == null) { throw new IllegalArgumentException("The given selection script cannot be null !"); } if (this.sScripts == null) { this.sScripts = new ArrayList<SelectionScript>(); } this.sScripts.add(selectionScript); } /** * To get the list of dependencies of the task. * * @return the the list of dependencies of the task. */ @XmlTransient public List<Task> getDependencesList() { return dependences; } /** * Get the number of nodes needed for this task (by default: 1). * * @return the number of Nodes Needed */ public int getNumberOfNodesNeeded() { return isParallel() ? getParallelEnvironment().getNodesNumber() : 1; } /** * @return the walltime */ public long getWallTime() { return wallTime; } /** * Return the working Directory. * * @return the working Directory. */ public String getWorkingDir() { if (forkEnvironment == null) { return null; } return forkEnvironment.getWorkingDir(); } /** * Set the wall time to the task in millisecond. * * @param walltime the walltime to set in millisecond. */ public void setWallTime(long walltime) { if (walltime < 0) { throw new IllegalArgumentException("The walltime must be a positive or nul integer value (>=0) !"); } this.wallTime = walltime; } /** * Return true if wallTime is set. * * @return the isWallTime */ public boolean isWallTimeSet() { return wallTime > 0; } /** * Set the number of nodes needed for this task. * <p> * This number represents the total number of nodes that you need. You may remember that * default number is 1. * * @param numberOfNodesNeeded the number Of Nodes Needed to set. */ @Deprecated public void setNumberOfNeededNodes(int numberOfNodesNeeded) { if (parallelEnvironment != null) { throw new IllegalStateException("Cannot set numberOfNodesNeeded as it could be inconsistent with the parallel environment"); } this.parallelEnvironment = new ParallelEnvironment(numberOfNodesNeeded); } /** * Add the files value to the given files value * according to the provided access mode. * <p> * Mode define the way the files will be bring to LOCAL space. * * @param files the input Files to add * @param mode the way to provide files to LOCAL space */ public void addInputFiles(FileSelector files, InputAccessMode mode) { if (files == null) { throw new IllegalArgumentException("Argument files is null"); } if (inputFiles == null) { inputFiles = new ArrayList<InputSelector>(); } inputFiles.add(new InputSelector(files, mode)); } /** * Add the files value to the given files value * according to the provided access mode. * <p> * Mode define the way the files will be send to OUTPUT space. * * @param files the output Files to add * @param mode the way to send files to OUTPUT space */ public void addOutputFiles(FileSelector files, OutputAccessMode mode) { if (files == null) { throw new IllegalArgumentException("Argument files is null"); } if (outputFiles == null) { outputFiles = new ArrayList<OutputSelector>(); } outputFiles.add(new OutputSelector(files, mode)); } /** * Add the files to the given filesToInclude value * according to the provided access mode. * <p> * Mode define the way the files will be bring to LOCAL space. * filesToInclude can represent one file or many files defined by a regular expression. * @see FileSelector for details * * @param filesToInclude the input files to add * @param mode the way to provide files to LOCAL space */ public void addInputFiles(String filesToInclude, InputAccessMode mode) { if (filesToInclude == null) { throw new IllegalArgumentException("Argument filesToInclude is null"); } if (inputFiles == null) { inputFiles = new ArrayList<InputSelector>(); } inputFiles.add(new InputSelector(new FileSelector(filesToInclude), mode)); } /** * Add the files to the given filesToInclude value * according to the provided access mode. * <p> * Mode define the way the files will be send to OUTPUT space. * filesToInclude can represent one file or many files defined by a regular expression. * @see FileSelector for details * * @param filesToInclude the output files to add * @param mode the way to send files to OUTPUT space */ public void addOutputFiles(String filesToInclude, OutputAccessMode mode) { if (filesToInclude == null) { throw new IllegalArgumentException("Argument filesToInclude is null"); } if (outputFiles == null) { outputFiles = new ArrayList<OutputSelector>(); } outputFiles.add(new OutputSelector(new FileSelector(filesToInclude), mode)); } /** * Get the input file selectors list. * This list represents every couple of input FileSelector and its associated access mode * The first element is the first added couple.<br> * This method returns null if nothing was added to the inputFiles. * * @return the input file selectors list */ public List<InputSelector> getInputFilesList() { return inputFiles; } /** * Get the output file selectors list. * This list represents every couple of output FileSelector and its associated access mode * The first element is the first added couple.<br> * This method returns null if nothing was added to the outputFiles. * * @return the output file selectors list */ public List<OutputSelector> getOutputFilesList() { return outputFiles; } /** * Sets the variable map for this task. * * @param variables the variables map */ public void setVariables(Map<String, TaskVariable> variables) { Job.verifyVariableMap(variables); this.variables = Collections.synchronizedMap(new LinkedHashMap(variables)); } /** * Returns the variable map of this task. * * @return a variable map */ public Map<String, TaskVariable> getVariables() { return this.variables; } /** * Returns the variable map of this task. * * @return a variable map */ public Map<String, String> getVariablesOverriden(Job job) { Map<String, String> taskVariables = new LinkedHashMap<>(); if (job != null) { taskVariables.putAll(job.getVariablesAsReplacementMap()); } for (TaskVariable variable : getVariables().values()) { if (!variable.isJobInherited()) { taskVariables.put(variable.getName(), variable.getValue()); } } return taskVariables; } @Override public String toString() { return name; } public String display() { String nl = System.lineSeparator(); return "Task \'" + name + "\' : " + nl + "\tDescription = '" + description + "'" + nl + (restartTaskOnError.isSet() ? "\trestartTaskOnError = '" + restartTaskOnError.getValue() + '\'' + nl : "") + (onTaskError.isSet() ? "\tonTaskError = '" + onTaskError.getValue() + '\'' + nl : "") + (maxNumberOfExecution.isSet() ? "\tmaxNumberOfExecution = '" + maxNumberOfExecution.getValue().getIntegerValue() + '\'' + nl : "") + "\ttag = " + tag + nl + "\tvariables = {" + nl + Joiner.on('\n').withKeyValueSeparator("=").join(variables) + nl + "}" + nl + "\tgenericInformation = {" + nl + Joiner.on('\n').withKeyValueSeparator("=").join(genericInformation) + nl + "}" + nl + "\tInputFiles = " + inputFiles + nl + "\tOutputFiles = " + outputFiles + nl + "\tParallelEnvironment = " + parallelEnvironment + nl + "\tFlowBlock = '" + flowBlock + "'" + nl + "\tSelectionScripts = " + displaySelectionScripts() + nl + "\tForkEnvironment = " + forkEnvironment + nl + "\tPreScript = " + ((preScript != null) ? preScript.display() : null) + nl + "\tPostScript = " + ((postScript != null) ? postScript.display() : null) + nl + "\tCleanScript = " + ((cScript != null) ? cScript.display() : null) + nl + "\tFlowScript = " + ((flowScript != null) ? flowScript.display() : null) + nl + "\tPreciousResult = " + preciousResult + nl + "\tPreciousLogs = " + preciousLogs + nl + "\tRunAsMe = " + runAsMe + nl + "\tWallTime = " + wallTime + nl + "\tDependences = " + dependences; } private String displaySelectionScripts() { if (sScripts == null) return null; String answer = "{ "; for (int i = 0; i < sScripts.size(); i++) { SelectionScript ss = sScripts.get(i); if (ss != null) { answer += ss.display() + ((i < sScripts.size() - 1) ? " , " : ""); } } answer += " }"; return answer; } public ForkEnvironment getForkEnvironment() { return forkEnvironment; } public void setForkEnvironment(ForkEnvironment forkEnvironment) { this.forkEnvironment = forkEnvironment; } }