/**
* 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.refiner.createdir;
import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.util.Separator;
import edu.isi.pegasus.planner.catalog.site.classes.FileServer;
import edu.isi.pegasus.planner.catalog.site.classes.GridGateway;
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.code.GridStartFactory;
import edu.isi.pegasus.planner.namespace.Dagman;
import edu.isi.pegasus.planner.namespace.Pegasus;
import edu.isi.pegasus.planner.partitioner.graph.GraphNode;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;
/**
* This class inserts the nodes for creating the random directories on the remote
* execution pools. This is done when the resources have already been selected
* to execute the jobs in the Dag. It adds a make directory node at the top level
* of the graph, and all these concat to a single dummy job before branching
* out to the root nodes of the original/ concrete dag so far. So we end up
* introducing a classic X shape at the top of the graph. Hence the name
* HourGlass.
*
* @author Karan Vahi
* @author Gaurang Mehta
*
* @version $Revision$
*/
public class HourGlass extends AbstractStrategy{
/**
* The name concatenating dummy job that ensures that Condor does not start
* staging in before the directories are created.
*/
public static final String DUMMY_CONCAT_JOB = "pegasus_concat";
/**
* The prefix assigned to the concatenating dummy job that ensures that Condor does not start
* staging in before the directories are created.
*/
public static final String DUMMY_CONCAT_JOB_PREFIX = "pegasus_concat_";
/**
* The transformation namespace for the create dir jobs.
*/
public static final String TRANSFORMATION_NAMESPACE = "pegasus";
/**
* The logical name of the transformation that creates directories on the
* remote execution pools.
*/
public static final String TRANSFORMATION_NAME = "dirmanager";
/**
* The version number for the derivations for create dir jobs.
*/
public static final String TRANSFORMATION_VERSION = null;
/**
* The complete TC name for dirmanager.
*/
public static final String COMPLETE_TRANSFORMATION_NAME = Separator.combine(
TRANSFORMATION_NAMESPACE,
TRANSFORMATION_NAME,
TRANSFORMATION_VERSION );
/**
* The derivation namespace for the create dir jobs.
*/
public static final String DERIVATION_NAMESPACE = "pegasus";
/**
* The logical name of the transformation that creates directories on the
* remote execution pools.
*/
public static final String DERIVATION_NAME = "dirmanager";
/**
* The version number for the derivations for create dir jobs.
*/
public static final String DERIVATION_VERSION = "1.0";
/**
* Intializes the class.
*
* @param bag bag of initialization objects
* @param impl the implementation instance that creates create dir job
*/
public void initialize( PegasusBag bag, Implementation impl ){
super.initialize( bag, impl );
}
/**
* It modifies the concrete dag passed in the constructor and adds the create
* random directory nodes to it at the root level. These directory nodes have
* a common child that acts as a concatenating job and ensures that Condor
* does not start staging in the data before the directories have been added.
* The root nodes in the unmodified dag are now chidren of this concatenating
* dummy job.
* @param dag the workflow to which the nodes have to be added.
*
* @return the added workflow
*/
public ADag addCreateDirectoryNodes( ADag dag ){
Set set = this.getCreateDirSites( dag );
//remove the entry for the local pool
//set.remove("local");
String pool = null;
String jobName = null;
Job newJob = null;
Job concatJob = null;
//add the concat job
if (!set.isEmpty()) {
concatJob = makeDummyConcatJob( dag );
introduceRootDependencies( dag, concatJob);
}
//for each execution pool add
//a create directory node.
for (Iterator it = set.iterator();it.hasNext();){
pool = (String) it.next();
jobName = getCreateDirJobName( dag, pool);
newJob = mImpl.makeCreateDirJob( pool,
jobName,
mSiteStore.getExternalWorkDirectoryURL( pool , FileServer.OPERATION.put ) );
dag.add(newJob);
//add the relation to the concat job
String msg = "Adding relation " + jobName + " -> " + concatJob.jobName;
mLogger.log( msg, LogManager.DEBUG_MESSAGE_LEVEL );
dag.addNewRelation( jobName, concatJob.jobName );
}
return dag;
}
/**
* It traverses through the root jobs of the dag and introduces a new super
* root node to it.
*
* @param dag the DAG
* @param newRoot the concat job that is the new root of the graph.
*/
private void introduceRootDependencies( ADag dag, Job newRoot) {
List<GraphNode> rootNodes = dag.getRoots();
//PM-747 add new root before we add any edges
dag.add(newRoot);
GraphNode newRootNode = dag.getNode( newRoot.getID() );
for( GraphNode existingRoot: rootNodes ){
mLogger.log( "Adding relation " + newRootNode.getID() + " -> " + existingRoot.getID(),LogManager.DEBUG_MESSAGE_LEVEL);
dag.addEdge( newRootNode, existingRoot );
}
}
/**
* It creates a dummy concat job that is run at the local submit host.
* This job should run always provided the directories were created
* successfully.
*
* @param dag the workflow
*
* @return the dummy concat job.
*/
public Job makeDummyConcatJob( ADag dag ) {
Job newJob = new Job();
List entries = null;
String execPath = null;
//jobname has the dagname and index to indicate different
//jobs for deferred planning
newJob.jobName = getConcatJobname( dag );
newJob.setTransformation( HourGlass.TRANSFORMATION_NAMESPACE,
HourGlass.TRANSFORMATION_NAME,
HourGlass.TRANSFORMATION_VERSION );
newJob.setDerivation( HourGlass.DERIVATION_NAMESPACE,
HourGlass.DERIVATION_NAME,
HourGlass.DERIVATION_VERSION );
// newJob.condorUniverse = Engine.REGISTRATION_UNIVERSE;
newJob.setUniverse( GridGateway.JOB_TYPE.auxillary.toString());
//the noop job does not get run by condor
//even if it does, giving it the maximum
//possible chance
newJob.executable = "/bin/true";
//construct noop keys
newJob.executionPool = "local";
newJob.jobClass = Job.CREATE_DIR_JOB;
newJob.dagmanVariables.construct( Dagman.NOOP_KEY, "true" );
construct(newJob,"noop_job","true");
construct(newJob,"noop_job_exit_code","0");
//we do not want the job to be launched
//by kickstart, as the job is not run actually
newJob.vdsNS.checkKeyInNS( Pegasus.GRIDSTART_KEY,
GridStartFactory.GRIDSTART_SHORT_NAMES[GridStartFactory.NO_GRIDSTART_INDEX] );
return newJob;
}
/**
* Returns the name of the concat job
*
* @return name
*/
protected String getConcatJobname( ADag dag ){
StringBuffer sb = new StringBuffer();
sb.append( HourGlass.DUMMY_CONCAT_JOB_PREFIX );
//append the job prefix if specified in options at runtime
if ( mJobPrefix != null ) { sb.append( mJobPrefix ) ;}
sb.append( dag.getLabel() ).append( "_" ).
append( dag.getIndex() )/*.append( "_" )*/;
//append the job prefix if specified in options at runtime
//if ( mJobPrefix != null ) { sb.append( mJobPrefix ); }
//sb.append( this.DUMMY_CONCAT_JOB );
return sb.toString();
}
/**
* Constructs a condor variable in the condor profile namespace
* associated with the job. Overrides any preexisting key values.
*
* @param job contains the job description.
* @param key the key of the profile.
* @param value the associated value.
*/
private void construct(Job job, String key, String value){
job.condorVariables.checkKeyInNS(key,value);
}
}