/** * Copyright 2007-2008 University Of Southern California * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.isi.pegasus.planner.code.generator.condor; import edu.isi.pegasus.common.logging.LoggingKeys; import edu.isi.pegasus.planner.catalog.site.classes.SiteCatalogEntry; import edu.isi.pegasus.planner.catalog.site.classes.SiteStore; import edu.isi.pegasus.planner.catalog.transformation.TransformationCatalogEntry; import edu.isi.pegasus.planner.catalog.transformation.classes.TCType; import edu.isi.pegasus.planner.catalog.TransformationCatalog; import edu.isi.pegasus.planner.code.CodeGeneratorException; import edu.isi.pegasus.planner.code.GridStart; import edu.isi.pegasus.planner.code.POSTScript; import edu.isi.pegasus.planner.code.GridStartFactory; import edu.isi.pegasus.planner.code.generator.Abstract; import edu.isi.pegasus.planner.code.generator.Braindump; import edu.isi.pegasus.planner.code.generator.NetloggerJobMapper; import edu.isi.pegasus.common.logging.LogManager; import edu.isi.pegasus.common.util.CondorVersion; import edu.isi.pegasus.planner.classes.ADag; import edu.isi.pegasus.planner.classes.Job; import edu.isi.pegasus.planner.classes.PegasusBag; import edu.isi.pegasus.planner.classes.DAGJob; import edu.isi.pegasus.planner.common.PegasusProperties; import edu.isi.pegasus.common.util.Boolean; import edu.isi.pegasus.planner.classes.AggregatedJob; import edu.isi.pegasus.planner.code.generator.MonitordNotify; import edu.isi.pegasus.planner.namespace.Condor; import edu.isi.pegasus.planner.namespace.Dagman; import edu.isi.pegasus.planner.namespace.Globus; import edu.isi.pegasus.planner.namespace.Pegasus; import edu.isi.pegasus.planner.namespace.ENV; import edu.isi.pegasus.planner.partitioner.graph.GraphNode; import org.griphyn.vdl.euryale.VTorInUseException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import edu.isi.pegasus.planner.catalog.classes.Profiles; import edu.isi.pegasus.planner.classes.PegasusFile; import edu.isi.pegasus.planner.namespace.Metadata; import java.io.BufferedWriter; import java.io.File; import java.io.Writer; import java.io.FileWriter; import java.io.PrintWriter; import java.io.IOException; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import java.util.Collection; import java.util.List; import java.util.Properties; import java.util.HashMap; /** * This class generates the condor submit files for the DAG which has to * be submitted to the Condor DagMan. * * @author Gaurang Mehta * @author Karan Vahi * @version $Revision$ */ public class CondorGenerator extends Abstract { /** * The default category for the sub dax jobs. */ public static final String DEFAULT_SUBDAG_CATEGORY_KEY = "subwf"; /** * The default category for the sub dax jobs. */ public static final String SUBMIT_FILE_SUFFIX = ".sub"; /** * The nice separator, define once, use often. */ public static final String mSeparator = "######################################################################"; /** * Default value for the periodic_release for a job */ public static final String DEFAULT_PERIODIC_RELEASE_VALUE = "False"; /** * Default value for the periodic_remove for a job */ public static final String DEFAULT_PERIODIC_REMOVE_VALUE = "(JobStatus == 5) && ((CurrentTime - EnteredCurrentStatus) > 1800)"; /** * The namespace to use for condor dagman. */ public static final String CONDOR_DAGMAN_NAMESPACE = "condor"; /** * The logical name with which to query the transformation catalog for the * condor_dagman executable, that ends up running the mini dag as one * job. */ public static final String CONDOR_DAGMAN_LOGICAL_NAME = "dagman"; /** * The prefix for DAGMan specific properties */ public static final String DAGMAN_PROPERTIES_PREFIX = "dagman."; /** * The default priority key associated with the stagein jobs. */ public static final int DEFAULT_STAGE_IN_PRIORITY_KEY = 700; /** * The default priority key associated with the inter site transfer jobs. */ public static final int DEFAULT_INTER_SITE_PRIORITY_KEY = 700; /** * The default priority key associated with the create dir jobs. */ public static final int DEFAULT_CREATE_DIR_PRIORITY_KEY = 800; /** * The default priority key associated with chmod jobs. */ public static final int DEFAULT_CHMOD_PRIORITY_KEY = 800; /** * The default priority key associated with the stage out jobs. */ public static final int DEFAULT_STAGE_OUT_PRIORITY_KEY = 900; /** * The default priority key associated with the replica registration jobs. */ public static final int DEFAULT_REPLICA_REG_PRIORITY_KEY = 900; /** * The default priority key associated with the cleanup jobs. */ public static final int DEFAULT_CLEANUP_PRIORITY_KEY = 1000; /** * the environment variable key populated with all jobs to have the * condor job id set in the environment. */ public static final String CONDOR_JOB_ID_ENV_KEY = "CONDOR_JOBID"; /** * default value for CONDOR_JOBID env variable */ public static final String DEFAULT_CONDOR_JOB_ID_ENV_VALUE = "$(cluster).$(process)"; /** * Map that maps job type to corresponding Condor Concurrency limit */ private static Map<Integer,String> mJobTypeToCondorConcurrencyLimits = null; /** * Map that maps job type to corresponding condor concurrency limits */ private static Map<Integer, String> jobTypeToCondorConcurrencyLimits(){ if( mJobTypeToCondorConcurrencyLimits == null ){ //PM-933 mJobTypeToCondorConcurrencyLimits = new HashMap(); //pegasus_transfer is our Condor Concurrency Group for all transfer jobs mJobTypeToCondorConcurrencyLimits.put( Job.STAGE_IN_JOB, "pegasus_transfer.stagein"); mJobTypeToCondorConcurrencyLimits.put( Job.STAGE_OUT_JOB, "pegasus_transfer.stageout"); mJobTypeToCondorConcurrencyLimits.put( Job.INTER_POOL_JOB, "pegasus_transfer.inter"); mJobTypeToCondorConcurrencyLimits.put( Job.STAGE_IN_WORKER_PACKAGE_JOB, "pegasus_transfer.worker"); //pegasus_auxillary is our Condor Concurrency Group for all other auxillary jobs mJobTypeToCondorConcurrencyLimits.put( Job.CREATE_DIR_JOB, "pegasus_auxillary.createdir"); mJobTypeToCondorConcurrencyLimits.put( Job.CLEANUP_JOB, "pegasus_auxillary.cleanup"); mJobTypeToCondorConcurrencyLimits.put( Job.REPLICA_REG_JOB, "pegasus_auxillary.registration"); mJobTypeToCondorConcurrencyLimits.put( Job.CHMOD_JOB, "pegasus_auxillary.chmod"); //compute, dax, dag jobs are not placed in any groups as we don't want any throttling per se mJobTypeToCondorConcurrencyLimits.put( Job.COMPUTE_JOB, "pegasus_compute"); mJobTypeToCondorConcurrencyLimits.put( Job.DAX_JOB, "pegasus_dax"); mJobTypeToCondorConcurrencyLimits.put( Job.DAG_JOB, "pegasus_dag"); } return mJobTypeToCondorConcurrencyLimits; } /** * Handle to the Transformation Catalog. */ protected TransformationCatalog mTCHandle; /** * Handle to the pool provider. */ //private PoolInfoProvider mPoolHandle; /** * The handle to the site catalog store. */ private SiteStore mSiteStore; /** * Specifies the implementing class for the pool interface. Contains * the name of the class that implements the pool interface the user has * asked at runtime. */ protected String mPoolClass; /** * The file handle to the .dag file. A part of the dag file is printed * as we write the submit files, to insert the appropriate postscripts * for handling exit codes. */ protected PrintWriter mDagWriter; /** * The name of the log file in the /tmp directory */ protected String mTempLogFile; /** * A boolean indicating whether the files have been generated or not. */ protected boolean mDone; /** * The workflow for which the code has to be generated. */ protected ADag mConcreteWorkflow; /** * Handle to the Style factory, that is used for this workflow. */ protected CondorStyleFactory mStyleFactory; /** * The handle to the GridStart Factory. */ protected GridStartFactory mGridStartFactory; /** * A boolean indicating whether grid start has been initialized or not. */ protected boolean mInitializeGridStart; /** * Handle to escaping class for environment variables */ protected CondorEnvironmentEscape mEnvEscape; /** * The long value of condor version. */ private long mCondorVersion; /** * Boolean indicating whether to assign job priorities or not. */ private boolean mAssignDefaultJobPriorities; /** * Boolean indicating whether to assign concurrency limits or not. */ private boolean mAssociateConcurrencyLimits; /** * The default constructor. */ public CondorGenerator(){ super(); mInitializeGridStart = true; mStyleFactory = new CondorStyleFactory(); mGridStartFactory = new GridStartFactory(); mEnvEscape = new CondorEnvironmentEscape(); } /** * Initializes the Code Generator implementation. Initializes the various * writers. * * @param bag the bag of initialization objects. * * @throws CodeGeneratorException in case of any error occuring code generation. */ public void initialize( PegasusBag bag ) throws CodeGeneratorException{ super.initialize( bag ); //create the base directory recovery File wdir = new File(mSubmitFileDir); wdir.mkdirs(); mTCHandle = bag.getHandleToTransformationCatalog(); mSiteStore = bag.getHandleToSiteStore(); mAssignDefaultJobPriorities = mProps.assignDefaultJobPriorities(); mAssociateConcurrencyLimits = mProps.associateCondorConcurrencyLimits(); //instantiate and intialize the style factory mStyleFactory.initialize( bag ); //determine the condor version mCondorVersion = CondorVersion.getInstance( mLogger ).numericValue(); if( mCondorVersion == -1 ){ mLogger.log( "Unable to determine the version of condor " , LogManager.WARNING_MESSAGE_LEVEL ); } else{ mLogger.log( "Condor Version detected is " + mCondorVersion , LogManager.DEBUG_MESSAGE_LEVEL ); } } /** * Generates the code for the concrete workflow in Condor DAGMAN and CondorG * input format. Returns only the File object for the DAG file that is written * out. * * @param dag the concrete workflow. * * @return the Collection of <code>File</code> objects for the files written * out. * * @throws CodeGeneratorException in case of any error occuring code generation. */ public Collection<File> generateCode( ADag dag ) throws CodeGeneratorException{ if ( mInitializeGridStart ){ mConcreteWorkflow = dag; mGridStartFactory.initialize( mBag, dag, this.getDAGFilename(mConcreteWorkflow, POSTSCRIPT_LOG_SUFFIX ) ); mInitializeGridStart = false; } String orgDAGFileName = getDAGFilename( dag, ".dag" ); File orgDAGFile = new File ( mSubmitFileDir, orgDAGFileName ); //PM-966 we need to write out first to a tmp dag file //and then do atomic rename String dagFileName = orgDAGFileName + ".tmp"; mDone = false; File dagFile = new File ( mSubmitFileDir, dagFileName );; Collection<File> result = new ArrayList(1); if ( dag.isEmpty() ) { //call the callout before returns concreteDagEmpty( orgDAGFileName, dag ); return result ; } if( mProps.symlinkCommonLog() ){ //figure out the logs directory for condor logs String dir = mProps.getSubmitLogsDirectory(); File directory = null; if( dir != null ){ directory = new File( dir ); //try to create this directory if it does not exist if( !directory.exists() && !directory.mkdirs() ){ //directory does not exist and cannot be created directory = null; } } mLogger.log( "Condor logs directory to be used is " + directory, LogManager.DEBUG_MESSAGE_LEVEL ); //Create a file in the submit logs directory for the log //and symlink it to the submit directory. try{ File f = File.createTempFile( dag.getLabel() + "-" + dag.getIndex(), ".log", directory ); mTempLogFile=f.getAbsolutePath(); } catch (IOException ioe) { mLogger.log( "Error while creating an empty log file in " + "the local temp directory " + ioe.getMessage(), LogManager.ERROR_MESSAGE_LEVEL); } } mLogger.logEventStart( LoggingKeys.EVENT_PEGASUS_CODE_GENERATION, LoggingKeys.DAX_ID, dag.getAbstractWorkflowName(), LogManager.DEBUG_MESSAGE_LEVEL); //convert the dax to a graph representation and walk it //in a top down manner //PM-747 no need for conversion as ADag now implements Graph interface //Graph workflow = dag; SUBDAXGenerator subdaxGen = new SUBDAXGenerator(); subdaxGen.initialize( mBag, dag, mDagWriter ); //we should initialize the .dag file only when we are done //with the conversion //initialize the file handle to the dag //file and print it's header initializeDagFileWriter( dagFile , dag ); result.add( dagFile ); //write out any category based dagman knobs to the dagman file printDagString( this.getCategoryDAGManKnobs( mProps ) ); for( Iterator it = dag.iterator(); it.hasNext(); ){ GraphNode node = ( GraphNode )it.next(); Job job = (Job)node.getContent(); //only apply priority if job is not associated with a priority //beforehand and assign priorities by default is true if( !job.condorVariables.containsKey( Condor.PRIORITY_KEY ) && this.mAssignDefaultJobPriorities ){ int priority = getJobPriority( job, node.getDepth() ); //apply a priority to the job overwriting any preexisting priority job.condorVariables.construct( Condor.PRIORITY_KEY, new Integer(priority).toString() ); //log to debug StringBuffer sb = new StringBuffer(); sb.append( "Applying priority of " ).append( priority ). append( " to " ).append( job.getID() ); mLogger.log( sb.toString(), LogManager.DEBUG_MESSAGE_LEVEL ); } // HTCondor ticket 5749 . We can assign DAG priorities only if // detected condor version is greater than 8.5.6 if( mCondorVersion >= CondorVersion.v_8_5_6 ){ //PM-1105 assign a DAGMAN priority that mirrors the condor //job priority if set, only if DAGMAN priority is not already set if( !job.dagmanVariables.containsKey( Dagman.PRIORITY_KEY) ){ //check again if condor priority is set and mirror it if( job.condorVariables.containsKey( Condor.PRIORITY_KEY)){ job.dagmanVariables.construct( Dagman.PRIORITY_KEY, (String)job.condorVariables.get(Condor.PRIORITY_KEY) ); } } } if( job instanceof DAGJob ){ //SUBDAG EXTERNAL B inner.dag DAGJob djob = ( DAGJob )job; //djob.dagmanVariables.checkKeyInNS( Dagman.SUBDAG_EXTERNAL_KEY, // djob.getDAGFile() ); StringBuffer sb = new StringBuffer(); sb.append( Dagman.SUBDAG_EXTERNAL_KEY ).append( " " ).append( job.getName() ). append( " " ).append( djob.getDAGFile() ); //check if dag needs to run in a specific directory String dagDir = djob.getDirectory(); if( dagDir != null){ sb.append( " " ).append( Dagman.DIRECTORY_EXTERNAL_KEY ). append( " " ).append( dagDir ); } //if no category is associated with the job, add a default //category if( !job.dagmanVariables.containsKey( Dagman.CATEGORY_KEY ) ){ job.dagmanVariables.construct( Dagman.CATEGORY_KEY, DEFAULT_SUBDAG_CATEGORY_KEY ); } printDagString( sb.toString() ); printDagString( job.dagmanVariables.toString( job.getName()) ); } else{ //normal jobs and subdax jobs if( job.typeRecursive() ){ Job daxJob = job; job = subdaxGen.generateCode( job ); //set the arguments to the DAX job to the ones //in the generated DAGJob to ensure stampede event //is generated correctly daxJob.setRemoteExecutable( job.getRemoteExecutable() ); daxJob.setArguments( job.getArguments() ); } if( job != null ){ //the submit file for the job needs to be written out //write out a condor submit file generateCode( dag, job ); } //write out all the dagman profile variables associated //with the job to the .dag file. printDagString( job.dagmanVariables.toString( job.getName()) ); } mLogger.log("Written Submit file : " + job.getFileFullPath( this.mSubmitFileDir, SUBMIT_FILE_SUFFIX), LogManager.DEBUG_MESSAGE_LEVEL); } mLogger.logEventCompletion( LogManager.DEBUG_MESSAGE_LEVEL ); //writing the tail of .dag file //that contains the relation pairs this.writeDagFileTail( dag ); mLogger.log("Written Dag File : " + dagFileName, LogManager.DEBUG_MESSAGE_LEVEL); //symlink the log file to a file in the temp directory if possible if( mProps.symlinkCommonLog() ){ this.generateLogFileSymlink( this.getCondorLogInTmpDirectory(), this.getCondorLogInSubmitDirectory( dag ) ); } //write out the DOT file mLogger.log( "Writing out the DOT file ", LogManager.DEBUG_MESSAGE_LEVEL ); this.writeDOTFile( getDAGFilename( dag, ".dot"), dag ); this.writeMetadataFile( getDAGFilename( dag, ".metadata"), dag ); /* //we no longer write out the job.map file //write out the netlogger file mLogger.log( "Written out job.map file", LogManager.DEBUG_MESSAGE_LEVEL ); this.writeJobMapFile( getDAGFilename( dag, ".job.map"), dag ); */ //write out the notifications input file this.writeOutNotifications( dag ); //the dax replica store this.writeOutDAXReplicaStore( dag ); //write out the nelogger file this.writeOutStampedeEvents( dag ); //write out the metrics file // this.writeOutWorkflowMetrics(dag); //write out the braindump file this.writeOutBraindump( dag ); //PM-966 rename the tmp dag file back to the original name //before we write out the dag.condor.sub file dagFile.renameTo( orgDAGFile ); mLogger.log("Renamed temporary dag file to : " + orgDAGFile, LogManager.DEBUG_MESSAGE_LEVEL); //write out the dag.condor.sub file this.writeOutDAGManSubmitFile( dag, orgDAGFile ); //we are donedirectory mDone = true; return result; } /** * Generates the code (condor submit file) for a single job. * * @param dag the dag of which the job is a part of. * @param job the <code>Job</code> object holding the information about * that particular job. * * @throws CodeGeneratorException in case of any error occuring code generation. */ public void generateCode( ADag dag, Job job ) throws CodeGeneratorException{ String dagname = dag.getLabel(); String dagindex = dag.getIndex(); String dagcount = dag.getCount(); String subfilename = job.getFileBaseName( SUBMIT_FILE_SUFFIX ); String envStr = null; //initialize GridStart if required. if ( mInitializeGridStart ){ mConcreteWorkflow = dag; mGridStartFactory.initialize( mBag, dag, this.getDAGFilename(mConcreteWorkflow, POSTSCRIPT_LOG_SUFFIX ) ); mInitializeGridStart = false; } //for recursive dax's trigger partition and plan and exit. //Commented out to be replaced with SUBDAG rendering. //Karan September 10th 2009 /* if ( job.typeRecursive() ){ String args = job.getArguments(); PartitionAndPlan pap = new PartitionAndPlan(); pap.initialize( mBag ); Collection<File> files = pap.doPartitionAndPlan( args ); File dagFile = null; for( Iterator it = files.iterator(); it.hasNext(); ){ File f = (File) it.next(); if ( f.getName().endsWith( ".dag" ) ){ dagFile = f; break; } } mLogger.log( "The DAG for the recursive job created is " + dagFile, LogManager.DEBUG_MESSAGE_LEVEL ); //translate the current job into DAGMan submit file Job dagCondorJob = this.constructDAGJob( job.getName(), dagFile.getParent(), dagFile.getName() ); //write out the dagCondorJob for it mLogger.log( "Generating submit file for DAG " , LogManager.DEBUG_MESSAGE_LEVEL ); this.generateCode( dag, dagCondorJob ); //setting the dagman variables of dagCondorJob to original job //so that the right information is printed in the .dag file job.dagmanVariables = dagCondorJob.dagmanVariables; return; } */ // intialize the print stream to the file PrintWriter writer = null; try{ writer = getWriter(job , SUBMIT_FILE_SUFFIX); }catch(IOException ioe ){ throw new CodeGeneratorException( "IOException while writing submit file for job " + job.getName(), ioe); } //handle the globus rsl parameters //for the job from various resources handleGlobusRSLForJob( job ); StringBuffer fragment = new StringBuffer(); //add the header to the fragment fragment.append(CondorGenerator.mSeparator).append( "\n" ); fragment.append("# PEGASUS WMS GENERATED SUBMIT FILE").append( "\n" ); fragment.append("# DAG : " + dagname + ", Index = " + dagindex + ", Count = " + dagcount).append( "\n" ); fragment.append("# SUBMIT FILE NAME : " + subfilename).append( "\n" ); fragment.append(CondorGenerator.mSeparator); writer.println( fragment ); // handle environment settings //before we apply any styles //allows for glite to escape environment values handleEnvVarForJob( dag, job ); //figure out the style to apply for a job applyStyle( job, writer ); //PM-934 environment variables are also printed //in the new format String env = mEnvEscape.escape(job.envVariables ); writer.println( "environment = " + env ); // handle Condor variables handleCondorVarForJob( job ); //write the classad's that have the information regarding //which Pegasus super node is a node part of, in addition to the //release version of Chimera/Pegasus, the jobClass and the //workflow id StringWriter classADWriter = new StringWriter(); PrintWriter pwClassADWriter = new PrintWriter( classADWriter ); ClassADSGenerator.generate( pwClassADWriter, dag, job ); if( mAssociateConcurrencyLimits ){ //PM-933, PM-1000 associate the corresponding concurrency limits job.condorVariables.construct( Condor.CONCURRENCY_LIMITS_KEY, getConcurrencyLimit(job) ); } //PM-796 we print all the condor variables after the classad //generator has generated the user classads writer.print( job.condorVariables ); writer.print( classADWriter.getBuffer() ); // DONE fragment = new StringBuffer(); fragment.append("queue").append( "\n" ); fragment.append(CondorGenerator.mSeparator).append( "\n" ); fragment.append("# END OF SUBMIT FILE").append( "\n" ); fragment.append(CondorGenerator.mSeparator); writer.println( fragment ); /* writer.println("queue"); writer.println(this.mSeparator); writer.println("# END OF SUBMIT FILE"); writer.println(this.mSeparator); */ // close the print stream to the file (flush) writer.close(); return; } /** * Starts monitoring of the workflow by invoking a workflow monitor daemon * tailstatd. The tailstatd is picked up from the default path of * $PEGASUS_HOME/bin/tailstatd. * * @return boolean indicating whether could successfully start the monitor * daemon or not. * * @throws VTorInUseException in case the method is called before the * submit files have been generated. */ public boolean startMonitoring() throws VTorInUseException{ //do nothing. //earlier the braindump file was generated when this function //was called. return true; } /** * Resets the Code Generator implementation. * * @throws CodeGeneratorException in case of any error occuring code generation. */ public void reset( )throws CodeGeneratorException{ super.reset(); mDone = false; mInitializeGridStart = true; } /** * Constructs a map with the numbers/values to be passed in the RSL handle * for certain pools. The user ends up specifying these through the * properties file. The value of the property is of the form * poolname1=value,poolname2=value.... * * @param propValue the value of the property got from the properties file. * * @return Map */ private Map constructMap(String propValue) { Map map = new java.util.TreeMap(); if (propValue != null) { StringTokenizer st = new StringTokenizer(propValue, ","); while (st.hasMoreTokens()) { String raw = st.nextToken(); int pos = raw.indexOf('='); if (pos > 0) { map.put(raw.substring(0, pos).trim(), raw.substring(pos + 1).trim()); } } } return map; } /** * Constructs a job that plans and submits the partitioned workflow, * referred to by a Partition. The main job itself is a condor dagman job * that submits the concrete workflow. The concrete workflow is generated by * running the planner in the prescript for the job. * * @param name the name to be assigned to the job. * @param directory the submit directory where the submit files for the * partition should reside. this is where the dag file is * created * @param dagBasename the basename of the dag file created. * * @return the constructed DAG job. */ protected Job constructDAGJob( String name, String directory, String dagBasename){ //for time being use the old functions. Job job = new Job(); //set the logical transformation job.setTransformation( CONDOR_DAGMAN_NAMESPACE, CONDOR_DAGMAN_LOGICAL_NAME, null); //set the logical derivation attributes of the job. job.setDerivation( CONDOR_DAGMAN_NAMESPACE, CONDOR_DAGMAN_LOGICAL_NAME, null ); //always runs on the submit host job.setSiteHandle( "local" ); //set the partition id only as the unique id //for the time being. // job.setName(partition.getID()); //set the logical id for the job same as the partition id. job.setName( name ); List entries; TransformationCatalogEntry entry = null; //get the path to condor dagman try{ //try to construct the path from the environment entry = constructTCEntryFromEnvironment( ); //try to construct from the TC if( entry == null ){ entries = mTCHandle.lookup(job.namespace, job.logicalName, job.version, job.getSiteHandle(), TCType.INSTALLED); entry = (entries == null) ? defaultTCEntry( "local" ) ://construct from site catalog //Gaurang assures that if no record is found then //TC Mechanism returns null (TransformationCatalogEntry) entries.get(0); } } catch(Exception e){ throw new RuntimeException( "ERROR: While accessing the Transformation Catalog",e); } if(entry == null){ //throw appropriate error throw new RuntimeException("ERROR: Entry not found in tc for job " + job.getCompleteTCName() + " on site " + job.getSiteHandle()); } //set the path to the executable and environment string job.setRemoteExecutable( entry.getPhysicalTransformation() ); //the job itself is the main job of the super node //construct the classad specific information job.jobID = job.getName(); job.jobClass = Job.COMPUTE_JOB; //directory where all the dagman related files for the nested dagman //reside. Same as the directory passed as an input parameter String dir = directory; //make the initial dir point to the submit file dir for the partition //we can do this as we are running this job both on local host, and scheduler //universe. Hence, no issues of shared filesystem or anything. job.condorVariables.construct( "initialdir", dir ); //construct the argument string, with all the dagman files //being generated in the partition directory. Using basenames as //initialdir has been specified for the job. StringBuffer sb = new StringBuffer(); sb.append(" -f -l . -Debug 3"). append(" -Lockfile ").append( getBasename( dagBasename, ".lock") ). append(" -Dag ").append( dagBasename ); //append(" -Rescue ").append( getBasename( dagBasename, ".rescue")). //specify condor log for condor version less than 7.1.2 if( mCondorVersion < CondorVersion.v_7_1_2 ){ sb.append(" -Condorlog ").append( getBasename( dagBasename, ".log")); } //allow for version mismatch as after 7.1.3 condor does tight //checking on dag.condor.sub file and the condor version used if( mCondorVersion >= CondorVersion.v_7_1_3 ){ sb.append( " -AllowVersionMismatch " ); } //for condor 7.1.0 sb.append( " -AutoRescue 1 -DoRescueFrom 0 "); //pass any dagman knobs that were specified in properties file // sb.append( this.mDAGManKnobs ); //put in the environment variables that are required job.envVariables.construct("_CONDOR_DAGMAN_LOG", directory + File.separator + dagBasename + ".dagman.out" ); job.envVariables.construct("_CONDOR_MAX_DAGMAN_LOG","0"); //set the arguments for the job job.setArguments(sb.toString()); //the environment need to be propogated for exitcode to be picked up job.condorVariables.construct("getenv","TRUE"); job.condorVariables.construct("remove_kill_sig","SIGUSR1"); //the log file for condor dagman for the dagman also needs to be created //it is different from the log file that is shared by jobs of //the partition. That is referred to by Condorlog // keep the log file common for all jobs and dagman albeit without // dag.dagman.log suffix // job.condorVariables.construct("log", getAbsolutePath( partition, dir,".dag.dagman.log")); // String dagName = mMegaDAG.dagInfo.nameOfADag; // String dagIndex= mMegaDAG.dagInfo.index; // job.condorVariables.construct("log", dir + mSeparator + // dagName + "_" + dagIndex + ".log"); //the job needs to be explicitly launched in //scheduler universe instead of local universe job.condorVariables.construct( Condor.UNIVERSE_KEY, Condor.SCHEDULER_UNIVERSE ); //add any notifications specified in the transformation //catalog for the job. JIRA PM-391 job.addNotifications( entry ); //incorporate profiles from the transformation catalog //and properties for the time being. Not from the site catalog. //the profile information from the transformation //catalog needs to be assimilated into the job //overriding the one from pool catalog. job.updateProfiles( entry ); //the profile information from the properties file //is assimilated overidding the one from transformation //catalog. job.updateProfiles(mProps); //we do not want the job to be launched via kickstart //Fix for Pegasus bug number 143 //http://bugzilla.globus.org/vds/show_bug.cgi?id=143 job.vdsNS.construct( Pegasus.GRIDSTART_KEY, GridStartFactory.GRIDSTART_SHORT_NAMES[GridStartFactory.NO_GRIDSTART_INDEX] ); return job; } /** * Returns a default TC entry to be used in case entry is not found in the * transformation catalog. * * @param site the site for which the default entry is required. * * * @return the default entry. */ private TransformationCatalogEntry defaultTCEntry( String site ){ //not implemented as we dont have handle to site catalog in this class return null; } /** * Returns a tranformation catalog entry object constructed from the environment * * An entry is constructed if either of the following environment variables * are defined * 1) CONDOR_HOME * 2) CONDOR_LOCATION * * CONDOR_HOME takes precedence over CONDOR_LOCATION * * * @return the constructed entry else null. */ private TransformationCatalogEntry constructTCEntryFromEnvironment( ){ //construct environment profiles Map<String,String> m = System.getenv(); ENV env = new ENV(); String key = "CONDOR_HOME"; if( m.containsKey( key ) ){ env.construct( key, m.get( key ) ); } key = "CONDOR_LOCATION"; if( m.containsKey( key ) ){ env.construct( key, m.get( key ) ); } return constructTCEntryFromEnvProfiles( env ); } /** * Returns a tranformation catalog entry object constructed from the environment * * An entry is constructed if either of the following environment variables * are defined * 1) CONDOR_HOME * 2) CONDOR_LOCATION * * CONDOR_HOME takes precedence over CONDOR_LOCATION * * @param env the environment profiles. * * * @return the entry constructed else null if environment variables not defined. */ private TransformationCatalogEntry constructTCEntryFromEnvProfiles( ENV env ) { TransformationCatalogEntry entry = null; //check if either CONDOR_HOME or CONDOR_LOCATION is defined String key = null; if( env.containsKey( "CONDOR_HOME") ){ key = "CONDOR_HOME"; } else if( env.containsKey( "CONDOR_LOCATION") ){ key = "CONDOR_LOCATION"; } if( key == null ){ //environment variables are not defined. return entry; } mLogger.log( "Constructing path to dagman on basis of env variable " + key, LogManager.DEBUG_MESSAGE_LEVEL ); entry = new TransformationCatalogEntry(); entry.setLogicalTransformation( CONDOR_DAGMAN_NAMESPACE, CONDOR_DAGMAN_LOGICAL_NAME, null ); entry.setType( TCType.INSTALLED ); entry.setResourceId( "local" ); //construct path to condor dagman StringBuffer path = new StringBuffer(); path.append( env.get( key ) ).append( File.separator ). append( "bin" ).append( File.separator). append( "condor_dagman" ); entry.setPhysicalTransformation( path.toString() ); return entry; } /** * A covenience method to construct the basename. * * @param prefix the first half of basename * @param suffix the latter half of basename * * @return basename */ protected String getBasename( String prefix, String suffix ){ StringBuffer sb = new StringBuffer(); sb.append( prefix ).append( suffix ); return sb.toString(); } /** * Initializes the file handler to the dag file and writes the header to it. * * @param dag the dag file to be written out to * @param workflow the workflow * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void initializeDagFileWriter( File dag, ADag workflow ) throws CodeGeneratorException{ try { //initialize the print stream to the file mDagWriter = new PrintWriter(new BufferedWriter(new FileWriter(dag))); printDagString(this.mSeparator); printDagString("# PEGASUS WMS GENERATED DAG FILE"); printDagString("# DAG " + workflow.getLabel() ); printDagString("# Index = " + workflow.getIndex() + ", Count = " + workflow.getCount() ); printDagString(this.mSeparator); } catch (Exception e) { throw new CodeGeneratorException( "While writing to DAG FILE " + dag, e); } } /** * Write out the DAGMan knobs for each category the user mentions in * the properties. * * @param properties the pegasus properties * * @return the String */ protected String getCategoryDAGManKnobs( PegasusProperties properties ){ //get all dagman properties Properties dagman = properties.matchingSubset( DAGMAN_PROPERTIES_PREFIX, false ); StringBuffer result = new StringBuffer(); String newLine = System.getProperty( "line.separator", "\r\n" ); //iterate through all the properties for( Iterator it = dagman.keySet().iterator(); it.hasNext(); ){ String name = ( String ) it.next();//like bigjob.maxjobs //System.out.println( name ); //figure out whether it is a category property or not //really a short cut way of doing it //if( (dotIndex = name.indexOf( "." )) != -1 && dotIndex != name.length() - 1 ){ if( Dagman.categoryRelatedKey( name.toUpperCase() ) ){ //we have a category and a key int dotIndex = name.indexOf( "." ); String category = name.substring( 0, dotIndex );//like bigjob String knob = name.substring( dotIndex + 1 );//like maxjobs String value = dagman.getProperty( name );//the value of the property in the properties //System.out.println( category + " " + knob + " " + value); result.append( knob.toUpperCase( ) ).append( " " ).append( category ). append( " " ).append( value ).append( newLine ); } } return result.toString(); } /** * Writes out the DOT file in the submit directory. * * @param filename basename of dot file to be written . * @param dag the <code>ADag</code> object. * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void writeDOTFile( String filename, ADag dag ) throws CodeGeneratorException{ // initialize file handler filename = mSubmitFileDir + File.separator + filename; try { Writer stream = new PrintWriter( new BufferedWriter ( new FileWriter( filename ) ) ); dag.toDOT( stream, null ); stream.close(); } catch (Exception e) { throw new CodeGeneratorException( "While writing to DOT FILE " + filename, e); } } /** * Writes out the metadata file, containing the metadata associated with the * jobs in the submit directory in JSON * * @param filename basename of medatadata file to be written . * @param dag the <code>ADag</code> object. * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void writeMetadataFile( String filename, ADag dag ) throws CodeGeneratorException{ // initialize file handler filename = mSubmitFileDir + File.separator + filename; Writer stream = null; try { stream = new PrintWriter( new BufferedWriter ( new FileWriter( filename ) ) ); GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setPrettyPrinting(); builder.registerTypeAdapter( GraphNode.class, new GraphNodeGSONAdapter()).create(); builder.registerTypeAdapter( Profiles.class, new ProfilesGSONAdapter()).create(); Gson gson = builder.create(); String json = gson.toJson( dag ); stream.write( json ); } catch (Exception e) { throw new CodeGeneratorException( "While writing to metadata FILE " + filename, e); } finally{ if( stream != null ){ try{ stream.close(); }catch(Exception e ){ } } } } /** * Writes out the job map file in the submit directory. * * @param filename basename of dot file to be written . * @param dag the <code>ADag</code> object. * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void writeJobMapFile( String filename, ADag dag ) throws CodeGeneratorException{ // initialize file handler filename = mSubmitFileDir + File.separator + filename; try { Writer stream = new PrintWriter( new BufferedWriter ( new FileWriter( filename ) ) ); NetloggerJobMapper njm = new NetloggerJobMapper( mLogger ); njm.writeOutMappings( stream, dag ); stream.close(); } catch (Exception e) { throw new CodeGeneratorException( "While writing to DOT FILE " + filename, e); } } /** * It writes the relations making up the DAG in the dag file and and closes * the file handle to it. * * @param dag the executable workflow * * @throws CodeGeneratorException */ protected void writeDagFileTail( ADag dag ) throws CodeGeneratorException{ try { for( Iterator<GraphNode> it = dag.jobIterator(); it.hasNext() ; ){ GraphNode gn = (GraphNode) it.next(); //get a list of parents of the node for( GraphNode child : gn.getChildren() ){ StringBuffer edge = new StringBuffer(); edge.append( "PARENT " ).append( " " ).append( gn.getID() ).append( " " ). append( "CHILD " ).append( child.getID() ); printDagString( edge.toString() ); } } printDagString(this.mSeparator); printDagString("# End of DAG"); printDagString(this.mSeparator); // close the print stream to the file mDagWriter.close(); } catch (Exception e) { throw new CodeGeneratorException( "Error Writing to Dag file " + e.getMessage(), e ); } } /** * Writes out the condor submit file for the dag created * * @param dag * @param dagFile */ protected void writeOutDAGManSubmitFile(ADag dag, File dagFile ) throws CodeGeneratorException{ PegasusSubmitDAG psd = new PegasusSubmitDAG(); psd.intialize(mBag); psd.generateCode(dag, dagFile); } /** * Writes a string to the dag file. When calling this function the * file handle to file is already initialized. * * @param str The String to be printed to the dag file. * * @throws CodeGeneratorException */ protected void printDagString(String str) throws CodeGeneratorException{ try { mDagWriter.println(str); } catch (Exception e) { throw new CodeGeneratorException( "Writing to Dag file " + e.getMessage(), e ); } } /** * Returns the name of Condor log file in a tmp directory that is created * if generation of symlink for condor logs is turned on. * * @return the name of the log file. */ protected String getCondorLogInTmpDirectory(){ return this.mTempLogFile; } /** * Returns the path to the condor log file in the submit directory. * It can be a symlink. * * @param dag the concrete workflow. * * @return the path to condor log file in the submit directory. */ protected String getCondorLogInSubmitDirectory( ){ return this.getCondorLogInSubmitDirectory( this.mConcreteWorkflow ); } /** * Returns the path to the condor log file in the submit directory. * It can be a symlink. * * @param dag the concrete workflow. * * @return the path to condor log file in the submit directory. */ protected String getCondorLogInSubmitDirectory( ADag dag ){ StringBuffer sb = new StringBuffer(); sb.append(this.mSubmitFileDir) .append(File.separator); String bprefix = mPOptions.getBasenamePrefix(); if( bprefix != null){ //the prefix is not null using it sb.append(bprefix); } else{ //generate the prefix from the name of the dag sb.append(dag.getLabel() ).append("-"). append(dag.getIndex() ); } //append the suffix sb.append(".log"); return sb.toString(); } /** * Returns a Map containing additional braindump entries that are specific * to a Code Generator. * * @param workflow the executable workflow * * @return Map containing entries for dag and condor_log */ public Map<String, String> getAdditionalBraindumpEntries( ADag workflow ) { Map entries = new HashMap(); entries.put( Braindump.GENERATOR_TYPE_KEY, "dag" ); entries.put( "dag", this.getDAGFilename( workflow, ".dag") ); entries.put( "condor_log", new File(this.getCondorLogInSubmitDirectory( workflow )).getName() ); entries.put( "notify", this.getDAGFilename( workflow, MonitordNotify.NOTIFICATIONS_FILE_SUFFIX ) ); return entries; } /** * This method generates a symlink to the actual log file written in the * local temp directory. The symlink is placed in the dag directory. * * @param logFile the full path to the log file. * @param symlink the full path to the symlink. * * @return boolean indicating if creation of symlink was successful or not */ protected boolean generateLogFileSymlink(String logFile, String symlink) { try{ Runtime rt = Runtime.getRuntime(); String command = "ln -s " +logFile + " " + symlink; mLogger.log("Creating symlink to the log file in the local temp directory\n" + command ,LogManager.DEBUG_MESSAGE_LEVEL); Process p = rt.exec(command,null); // set up to read subprogram output InputStream is = p.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr); // set up to read subprogram error InputStream er = p.getErrorStream(); InputStreamReader err = new InputStreamReader(er); BufferedReader ebr = new BufferedReader(err); // read output from subprogram // and display it String s,se=null; while ( ((s = br.readLine()) != null) || ((se = ebr.readLine()) != null ) ) { if(s!=null){ mLogger.log(s,LogManager.DEBUG_MESSAGE_LEVEL); } else { mLogger.log(se,LogManager.ERROR_MESSAGE_LEVEL ); } } br.close(); return true; } catch(Exception ex){ mLogger.log("Unable to create symlink to the log file" , ex, LogManager.ERROR_MESSAGE_LEVEL); return false; } } /** * Returns the basename of the file, that contains the output of the * dagman while running the dag generated for the workflow. * The basename of the .out file is dependant on whether the * basename prefix has been specified at runtime or not by command line * options. * * @param dag the DAG containing the concrete workflow * * @return the name of the dagfile. */ protected String getDAGMANOutFilename( ADag dag ){ //constructing the name of the dagfile StringBuffer sb = new StringBuffer(); String bprefix = mPOptions.getBasenamePrefix(); if( bprefix != null){ //the prefix is not null using it sb.append(bprefix); } else{ //generate the prefix from the name of the dag sb.append( dag.getLabel() ).append("-"). append( dag.getIndex() ); } //append the suffix sb.append(".dag.dagman.out"); return sb.toString(); } /** * A callout method that dictates what needs to be done in case the concrete * plan that is generated is empty. * It just logs a message saying the plan is empty. * * @param filename Filename of the dag to be written of type String. * @param dag the concrete dag that is empty. * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void concreteDagEmpty(String filename, ADag dag) throws CodeGeneratorException{ StringBuffer sb = new StringBuffer(); sb.append( "The concrete plan generated contains no nodes. "). append( "\nIt seems that the output files are already at the output pool" ); mLogger.log( sb.toString(), LogManager.INFO_MESSAGE_LEVEL ); } /** * It updates/adds the condor variables that are got through the Dax with * the values specified in the properties file, pool config file or adds some * variables internally. In case of clashes of Condor variables from * various sources the following order is followed,property file, pool config * file and then dax. * * @param job The Job object containing the information about the job. * * * @throws CodeGeneratorException */ protected void handleCondorVarForJob(Job job) throws CodeGeneratorException{ Condor cvar = job.condorVariables; String key = null; String value = null; //put in the classad expression for the values //construct the periodic_release and periodic_remove //values only if their final computed values are > 0 this.populatePeriodicReleaseAndRemoveInJob( job ); // have to change this later maybe key = "notification"; value = (String) cvar.removeKey(key); if (value == null) { cvar.construct(key, "NEVER"); } else { cvar.construct(key, value); //check if transfer_executable was set to //true by the user at runtime } key = "transfer_executable"; if (cvar.containsKey(key)) { //we do not put in the default value } else { // we assume pre-staged executables through the GVDS cvar.construct(key, "false"); } key = "copy_to_spool"; if (cvar.containsKey(key)) { //we do not put in the default value } else // no sense copying files to spool for globus jobs // and is mandatory for the archstart stuff to work // for local jobs cvar.construct(key, "false"); //construct the log file for the submit job key = "log"; if(!cvar.containsKey(key)){ //we put in the default value //cvar.construct("log",dagname + "_" + dagindex + ".log"); cvar.construct("log",this.getCondorLogInSubmitDirectory( ) ); } //also add the information as for the submit event trigger //for mei retry mechanism cvar.construct("submit_event_user_notes","pool:" + job.executionPool); //correctly quote the arguments according to //Condor Quoting Rules. // String args = (String) job.condorVariables.get("arguments"); String args = job.getArguments(); //put the arguments as appropriate condor profile if( args != null && args.length() > 0){ //PM-1037 consider both the profile value and default value //from properties to see if we need to quote arguments for the job boolean quote = mProps.useCondorQuotingForArguments(); //default from properties if not specified is true String profileKey = Pegasus.CONDOR_QUOTE_ARGUMENTS_KEY; if( job.vdsNS.containsKey( profileKey ) ){ quote = quote && job.vdsNS.getBooleanValue( profileKey ); } if( quote && args != null){ try { mLogger.log("Unquoted arguments are " + args, LogManager.DEBUG_MESSAGE_LEVEL); //insert a comment for the old args //job.condorVariables.construct("#arguments",args); args = CondorQuoteParser.quote(args, true); job.condorVariables.construct( Condor.ARGUMENTS_KEY, args); mLogger.log("Quoted arguments are " + args, LogManager.DEBUG_MESSAGE_LEVEL); } catch (CondorQuoteParserException e) { throw new RuntimeException("CondorQuoting Problem " + e.getMessage()); } } else{ //add without quoting job.condorVariables.construct( Condor.ARGUMENTS_KEY, args); } } //set the remote executable as condor executable job.condorVariables.construct( Condor.EXECUTABLE_KEY, job.getRemoteExecutable() ); return; } /** * Populates the periodic release and remove values in the job. * If an integer value is specified it is used to construct the default * expression, else the value specified in the profiles is used as is. * * The default expression for periodic_release and periodic_remove is * <pre> * periodic_release = False * periodic_remove = (JobStatus == 5) && ((CurrentTime - EnteredCurrentStatus) > 14400) * </pre> * where releasevalue is value of condor profile periodic_release * and removevalue is value of condor profile periodic_remove * * @param job the job object. */ public void populatePeriodicReleaseAndRemoveInJob( Job job ){ //get the periodic release values always a default //value is got if not specified. String releaseval = (String) job.condorVariables.get( Condor.PERIODIC_RELEASE_KEY ); if( releaseval == null ){ //construct default value job.condorVariables.construct( Condor.PERIODIC_RELEASE_KEY, CondorGenerator.DEFAULT_PERIODIC_RELEASE_VALUE ); } else{ //check if an integer value is specified PM-462 if ( isInteger( releaseval ) ){ mLogger.log( "Removing integer value " + releaseval + " for periodic_release for job " + job.getID(), LogManager.DEBUG_MESSAGE_LEVEL ); job.condorVariables.construct( Condor.PERIODIC_RELEASE_KEY, CondorGenerator.DEFAULT_PERIODIC_RELEASE_VALUE ); } } String removeval = (String) job.condorVariables.get( Condor.PERIODIC_REMOVE_KEY ); if( removeval == null ){ //construct default value job.condorVariables.construct( Condor.PERIODIC_REMOVE_KEY, CondorGenerator.DEFAULT_PERIODIC_REMOVE_VALUE ); } else{ //check if an integer value is specified PM-462 if ( isInteger( removeval ) ){ mLogger.log( "Removing integer value " + removeval + " for periodic_remove for job " + job.getID(), LogManager.DEBUG_MESSAGE_LEVEL ); job.condorVariables.construct( Condor.PERIODIC_REMOVE_KEY, CondorGenerator.DEFAULT_PERIODIC_REMOVE_VALUE ); } } } /** * Returns a boolean indicating whether the value represented is an * integer or not. * * @param value the String passed * * @return true if an int else false */ protected boolean isInteger ( String value ){ boolean result = true; try{ Integer.parseInt(value); } catch( Exception e ){ result = false; } return result; } /** * It changes the paths to the executable depending on whether we want to * transfer the executable or not. If the transfer_executable is set to true, * then the executable needs to be shipped from the submit host meaning the * local pool. This function changes the path of the executable to the one on * the local pool, so that it can be shipped. * * @param job the <code>Job</code> containing the job description. * * @throws CodeGeneratorException */ /* protected void handleTransferOfExecutable(Job sinfo) throws CodeGeneratorException{ Condor cvar = sinfo.condorVariables; if (!cvar.getBooleanValue("transfer_executable")) { //the executable paths are correct and //point to the executable on the remote pool return; } SiteCatalogEntry site = mSiteStore.lookup( sinfo.getSiteHandle() ); String gridStartPath = site.getKickstartPath(); if (gridStartPath == null) { //not using grid start //we need to stage in the executable from //the local pool. Not yet implemented mLogger.log("At present only the transfer of gridstart is supported", LogManager.ERROR_MESSAGE_LEVEL); return; } else { site = mSiteStore.lookup( "local" ); gridStartPath = site.getKickstartPath(); if (gridStartPath == null) { mLogger.log( "Gridstart needs to be shipped from the submit host to pool" + sinfo.executionPool + ".\nNo entry for it in pool local", LogManager.ERROR_MESSAGE_LEVEL); throw new CodeGeneratorException( "GridStart needs to be shipped from submit host to site " + sinfo.getSiteHandle() ); } else { //the jobs path to executable is updated //by the path on the submit host cvar.construct("executable", gridStartPath); //the arguments to gridstart need to be //appended with the true remote directory String args = (String) cvar.removeKey("arguments"); args = " -w " + mSiteStore.getInternalWorkDirectory( sinfo ) + " " + args; cvar.construct("arguments", args); //we have to remove the remote_initial dir for it. //as this is required for the LCG sites //Actually this should be done thru a LCG flag cvar.removeKey("remote_initialdir"); } } } */ /** * Applies a submit file style to the job, according to the fact whether * the job has to be submitted directly to condor or to a remote jobmanager * via CondorG and GRAM. * If no style is associated with the job, then for the job running on * local site, condor style is applied. For a job running on non local sites, * globus style is applied if none is associated with the job. * * @param job the job on which the style needs to be applied. * @param writer the PrintWriter stream to the submit file for the job. * * @throws CodeGeneratorException in case of any error occuring code generation. */ protected void applyStyle( Job job, PrintWriter writer ) throws CodeGeneratorException{ //load the appropriate style for the job CondorStyle cs = mStyleFactory.loadInstance( job ); String style = (String)job.vdsNS.get( Pegasus.STYLE_KEY ); boolean isGlobus = style.equals( Pegasus.GLOBUS_STYLE ) ? true : false; //handle GLOBUS RSL if required, and stdio appropriately String rslString = job.globusRSL.toString(); rslString += gridstart( writer, job, isGlobus ); if( isGlobus ){ //only for CondorG style does RSL make sense //instead of writing directly //incorporate as condor profile //job.condorVariables.construct( "globusrsl", rslString ); job.condorVariables.construct( "globusrsl", job.globusRSL.toString() ); } //apply the appropriate style on the job. if( job instanceof AggregatedJob ){ cs.apply( (AggregatedJob)job ); } else{ cs.apply( job ); } } /** * Adds common environment variables to the job * * @param dag * @param job The Job object containing the information about the job. */ protected void handleEnvVarForJob( ADag dag, Job job ) { //PM-867 add CONDOR_JOBID job.envVariables.construct( CondorGenerator.CONDOR_JOB_ID_ENV_KEY, CondorGenerator.DEFAULT_CONDOR_JOB_ID_ENV_VALUE ); //PM-875 job.envVariables.construct( ENV.PEGASUS_WF_ID_ENV_KEY, dag.getWorkflowUUID()); job.envVariables.construct( ENV.PEGASUS_WF_LABEL_ENV_KEY, dag.getLabel()); job.envVariables.construct( ENV.PEGASUS_JOB_ID_ENV_KEY, job.getID() ); job.envVariables.construct( ENV.PEGASUS_SITE_ID_ENV_KEY, job.getSiteHandle() ); } /** * It updates/adds the the Globus RSL parameters got through the dax that are * in Job object. In addition inserts the additional rsl attributes * that can be specified in the properties file or the pool config files in * the profiles tags. In case of clashes of RSL attributes from various * sources the following order is followed,property file, pool config file * and then dax. * * @param job The Job object containing the information about the job. */ protected void handleGlobusRSLForJob(Job sinfo) { Globus rsl = sinfo.globusRSL; String key = null; String value = null; //Getting all the rsl parameters specified //in dax /* if (job.globusRSL != null) { rsl.putAll(job.globusRSL); // 19-05 jsv: Need to change to {remote_}initialdir commands // allow TR to spec its own directory } */ // check job type, unless already specified // Note, we may need to adjust this again later if (!rsl.containsKey("jobtype")) { rsl.construct("jobtype", "single"); } //sanitize jobtype on basis of jobmanager //Karan Sept 12,2005 //This is to overcome specifically Duncan's problem //while running condor universe standard jobs. //For that the jobtype=condor needs to be set for the compute //job. This is set in the site catalog, but ends up //breaking transfer jobs that are run on jobmanager-fork String jmURL = sinfo.globusScheduler; if(jmURL != null && jmURL.endsWith("fork")){ rsl.construct("jobtype","single"); } } /** * Computes the priority for a job based on job type and depth in the workflow * * @param job the job whose priority needs to be computed * @param depth the depth in the workflow * * @return */ protected int getJobPriority(Job job, int depth) { int priority = 0; int type = job.getJobType(); switch ( type ){ case Job.CREATE_DIR_JOB: priority = CondorGenerator.DEFAULT_CREATE_DIR_PRIORITY_KEY; break; case Job.CHMOD_JOB: priority = CondorGenerator.DEFAULT_CHMOD_PRIORITY_KEY; break; case Job.CLEANUP_JOB: priority = CondorGenerator.DEFAULT_CLEANUP_PRIORITY_KEY; break; case Job.STAGE_IN_JOB: priority = CondorGenerator.DEFAULT_STAGE_IN_PRIORITY_KEY; break; case Job.INTER_POOL_JOB: priority = CondorGenerator.DEFAULT_INTER_SITE_PRIORITY_KEY; break; case Job.STAGE_OUT_JOB: priority = CondorGenerator.DEFAULT_STAGE_OUT_PRIORITY_KEY; break; case Job.REPLICA_REG_JOB: priority = CondorGenerator.DEFAULT_REPLICA_REG_PRIORITY_KEY; default: //compute on the basis of the depth priority = depth * 10; break; } return priority; } /** * This function creates the stdio handling with and without gridstart. * Please note that gridstart will become the default by end 2003, and * no gridstart support will be phased out. * * @param writer is an open stream for the Condor submit file. * @param job is the job information structure. * @param isGlobusJob is <code>true</code>, if the job generated a * line <code>universe = globus</code>, and thus runs remotely. * Set to <code>false</code>, if the job runs on the submit * host in any way. * * @return A possibly empty string which contains things that * need to be added to the "globusrsl" clause. The return * value is only of interest for isGlobusJob==true calls. * * @throws CodeGeneratorException in case of any error occuring code generation. */ private String gridstart(PrintWriter writer, Job job, boolean isGlobusJob) throws CodeGeneratorException { //To get the gridstart/kickstart path on the remote //pool, querying with entry for vanilla universe. //In the new format the gridstart is associated with the //pool not pool, condor universe // SiteInfo site = mPoolHandle.getPoolEntry(job.executionPool, // Condor.VANILLA_UNIVERSE); SiteCatalogEntry site = mSiteStore.lookup( job.getSiteHandle() ); //JIRA PM-491 . Path to kickstart should not be passed //to the factory. // String gridStartPath = site.getKickstartPath(); StringBuffer rslString = new StringBuffer(); String jobName = job.jobName; String script = null; //PM-1088 move to relative paths in the .dag file job.dagmanVariables.checkKeyInNS(Dagman.JOB_KEY, job.getFileRelativePath(SUBMIT_FILE_SUFFIX)); //remove the prescript arguments key //should be already be set to the prescript key // //NO NEED TO REMOVE AS WE ARE HANDLING CORRECTLY IN DAGMAN NAMESPACE // //NOW. THERE THE ARGUMENTS AND KEY ARE COMBINED. Karan May 11,2006 // //job.dagmanVariables.removeKey(Dagman.PRE_SCRIPT_ARGUMENTS_KEY); // script = (String)job.dagmanVariables.removeKey(Dagman.PRE_SCRIPT_KEY); // if(script != null){ // //put in the new key with the prescript // job.dagmanVariables.checkKeyInNS(PRE_SCRIPT_KEY,script); // } //condor streaming is now for both grid and non grid universe jobs // we always put in the streaming keys. they default to false boolean stream = Boolean.parse( (String)job.condorVariables.removeKey( Condor.STREAM_STDERR_KEY ), false ); if ( stream ) { //we want it to be staged writer.println("stream_error = true"); } else { writer.println("stream_error = false"); } stream = Boolean.parse( (String)job.condorVariables.removeKey( Condor.STREAM_STDOUT_KEY ), false ); if ( stream ) { //we want it to be staged writer.println("stream_output = true" ); } else{ //we want it to be staged writer.println("stream_output = false" ); } GridStart gridStart = mGridStartFactory.loadGridStart( job, null ); //enable the job boolean enable = false; if( job instanceof AggregatedJob ){ enable = gridStart.enable( (AggregatedJob) job, isGlobusJob ); } else{ enable = gridStart.enable( job,isGlobusJob ); } if( !enable ){ String msg = "Job " + jobName + " cannot be enabled by " + gridStart.shortDescribe() + " to run at " + job.getSiteHandle(); mLogger.log( msg, LogManager.FATAL_MESSAGE_LEVEL ); throw new CodeGeneratorException( msg ); } //apply the appropriate POSTScript POSTScript ps = mGridStartFactory.loadPOSTScript( job, gridStart ); boolean constructed = ps.construct( job, Dagman.POST_SCRIPT_KEY ); //write out all the dagman profile variables associated //with the job to the .dag file. // printDagString(job.dagmanVariables.toString(jobName)); return rslString.toString(); } /** * Returns the concurrency limit for a job * * @param job * * @return */ protected String getConcurrencyLimit(Job job) throws CodeGeneratorException { String limit = CondorGenerator.jobTypeToCondorConcurrencyLimits().get( job.getJobType()) ; if( limit == null ){ throw new CodeGeneratorException( "Unable to determine Condor concurrency limit for job " + job.getID() + " with type " + job.getJobType() ); } return limit; } } class GraphNodeGSONAdapter extends TypeAdapter<GraphNode> { @Override public void write(JsonWriter writer, GraphNode node) throws IOException { writer.beginObject(); Object content = node.getContent(); if( content instanceof Job ){ Job job = (Job)content; Metadata m = (Metadata) job.getMetadata(); if( !job.getMetadata().isEmpty() ){ for( Iterator it = m.getProfileKeyIterator(); it.hasNext(); ){ String key = (String) it.next(); writer.name( key ); writer.value((String) m.get(key)); } } //for input and output files prefix with lfn name for( PegasusFile pf : job.getInputFiles() ){ String prefix = pf.getLFN() + "@"; for( Iterator it = pf.getAllMetadata().getProfileKeyIterator(); it.hasNext(); ){ String key = (String) it.next(); writer.name( prefix + key ); writer.value((String) pf.getMetadata(key)); } } for( PegasusFile pf : job.getOutputFiles() ){ String prefix = pf.getLFN() + "."; for( Iterator it = pf.getAllMetadata().getProfileKeyIterator(); it.hasNext(); ){ String key = (String) it.next(); writer.name( prefix + key ); writer.value((String) pf.getMetadata(key)); } } } writer.endObject(); } @Override public GraphNode read(JsonReader reader) throws IOException { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } } class ProfilesGSONAdapter extends TypeAdapter<Profiles> { @Override public void write(JsonWriter writer, Profiles node) throws IOException { writer.beginObject(); Metadata m = (Metadata)node.get(Profiles.NAMESPACES.metadata); if( !m.isEmpty() ){ for( Iterator it = m.getProfileKeyIterator(); it.hasNext(); ){ String key = (String) it.next(); writer.name( key ); writer.value((String) m.get(key)); } } writer.endObject(); } @Override public Profiles read(JsonReader reader) throws IOException { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } }