/** * 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.client; import edu.isi.pegasus.common.logging.LogManager; import edu.isi.pegasus.common.logging.LoggingKeys; import edu.isi.pegasus.planner.common.PegasusDBAdmin; import edu.isi.pegasus.common.util.DefaultStreamGobblerCallback; import edu.isi.pegasus.common.util.FactoryException; import edu.isi.pegasus.common.util.StreamGobbler; import edu.isi.pegasus.common.util.Version; import edu.isi.pegasus.planner.catalog.SiteCatalog; import edu.isi.pegasus.planner.catalog.site.SiteCatalogException; import edu.isi.pegasus.planner.catalog.site.SiteFactory; import edu.isi.pegasus.planner.catalog.site.classes.GridGateway; 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.TransformationFactory; import edu.isi.pegasus.planner.classes.ADag; import edu.isi.pegasus.planner.classes.Job; import edu.isi.pegasus.planner.classes.NameValue; import edu.isi.pegasus.planner.classes.PegasusBag; import edu.isi.pegasus.planner.classes.PlannerMetrics; import edu.isi.pegasus.planner.classes.PlannerOptions; import edu.isi.pegasus.planner.code.CodeGenerator; import edu.isi.pegasus.planner.code.CodeGeneratorFactory; import edu.isi.pegasus.planner.code.GridStartFactory; import edu.isi.pegasus.planner.code.generator.Braindump; import edu.isi.pegasus.planner.common.PegasusConfiguration; import edu.isi.pegasus.planner.common.PegasusProperties; import edu.isi.pegasus.planner.common.RunDirectoryFilenameFilter; import edu.isi.pegasus.planner.common.Shiwa; import edu.isi.pegasus.planner.namespace.Dagman; import edu.isi.pegasus.planner.namespace.Pegasus; import edu.isi.pegasus.planner.parser.DAXParserFactory; import edu.isi.pegasus.planner.parser.Parser; import edu.isi.pegasus.planner.parser.dax.Callback; import edu.isi.pegasus.planner.parser.dax.DAXParser; import edu.isi.pegasus.planner.refiner.MainEngine; import gnu.getopt.Getopt; import gnu.getopt.LongOpt; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; import java.lang.management.MemoryUsage; import java.nio.channels.FileChannel; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; /** * This is the main program for the Pegasus. It parses the options specified * by the user and calls out to the appropriate components to parse the abstract * plan, concretize it and then write the submit files. * * @author Gaurang Mehta * @author Karan Vahi * @version $Revision$ */ public class CPlanner extends Executable{ /** * The default megadag mode that is used for generation of megadags in * deferred planning. */ public static final String DEFAULT_MEGADAG_MODE = "dag"; /** * The basename of the directory that contains the submit files for the * cleanup DAG that for the concrete dag generated for the workflow. */ public static final String CLEANUP_DIR = "cleanup"; /** * The prefix for the NoOP jobs that are created. */ public static final String NOOP_PREFIX = "noop_"; /** * The name of the property key that determines whether pegasus-run * should monitord or not. */ public static final String PEGASUS_MONITORD_LAUNCH_PROPERTY_KEY = "pegasus.monitord" ; /** * The regex used to match against a java property that is set using * -Dpropertyname=value in the argument string */ public static final String JAVA_COMMAND_LINE_PROPERTY_REGEX = "(env|condor|globus|dagman|pegasus)\\..*=.*" ; /** * The final successful message that is to be logged. */ private static final String EMPTY_FINAL_WORKFLOW_MESSAGE = "\n\n\n" + "The executable workflow generated contains only a single NOOP job.\n" + "It seems that the output files are already at the output site. \n"+ "To regenerate the output data from scratch specify --force option.\n" + "\n\n\n"; /** * The message to be logged in case of empty executable workflow. */ private static final String SUCCESS_MESSAGE = "\n\n\n" + "I have concretized your abstract workflow. The workflow has been entered \n" + "into the workflow database with a state of \"planned\". The next step is \n" + "to start or execute your workflow. The invocation required is" + "\n\n\n"; /** * The object containing all the options passed to the Concrete Planner. */ private PlannerOptions mPOptions; /** * The object containing the bag of pegasus objects */ private PegasusBag mBag; /** * The PlannerMetrics object storing the metrics about this planning instance. */ private PlannerMetrics mPMetrics; /** * The number formatter to format the run submit dir entries. */ private NumberFormat mNumFormatter; /** * The user name of the user running Pegasus. */ private String mUser; /** * A boolean indicating whether metrics should be sent to metrics server or not */ private boolean mSendMetrics; /** * Default constructor. */ public CPlanner(){ this( null ); } /** * The overload constructor. * * @param logger the logger object to use. can be null. */ public CPlanner( LogManager logger ){ super( logger ); } public void initialize(String [] opts , char confChar){ super.initialize(opts , confChar); mLogMsg = ""; mVersion = Version.instance().toString(); mNumFormatter = new DecimalFormat( "0000" ); this.mPOptions = new PlannerOptions(); mPOptions.setSubmitDirectory( ".", null ); mPOptions.setExecutionSites(new java.util.HashSet()); mPOptions.setOutputSite(""); mUser = mProps.getProperty( "user.name" ) ; if ( mUser == null ){ mUser = "user"; } mPMetrics = new PlannerMetrics(); mPMetrics.setUser( mUser ); mSendMetrics = true; mBag = new PegasusBag(); } /** * The main program for the CPlanner. * * * @param args the main arguments passed to the planner. */ public static void main(String[] args) { CPlanner cPlanner = new CPlanner(); int result = 0; Date startDate = new Date(); Date endDate = null; double starttime = startDate.getTime(); double duration = -1; Exception plannerException = null; try{ cPlanner.initialize(args , '6'); cPlanner.mPMetrics.setStartTime( startDate ); cPlanner.executeCommand(); } catch ( FactoryException fe){ plannerException = fe; cPlanner.log( fe.convertException() , LogManager.FATAL_MESSAGE_LEVEL); result = 2; } catch( OutOfMemoryError error ){ cPlanner.log( "Out of Memory Error " + error.getMessage(), LogManager.FATAL_MESSAGE_LEVEL ); error.printStackTrace(); //lets print out some GC stats cPlanner.logMemoryUsage(); result = 4; } catch ( RuntimeException rte ) { plannerException = rte; //catch all runtime exceptions including our own that //are thrown that may have chained causes cPlanner.log( convertException(rte, cPlanner.mLogger.getLevel() ), LogManager.FATAL_MESSAGE_LEVEL ); result = 1; } catch ( Exception e ) { plannerException = e; //unaccounted for exceptions cPlanner.log( convertException(e, cPlanner.mLogger.getLevel() ), LogManager.FATAL_MESSAGE_LEVEL ); result = 3; } finally { endDate = new Date(); } try{ cPlanner.mPMetrics.setEndTime( endDate ); double endtime = endDate.getTime(); duration = (endtime - starttime)/1000; cPlanner.mPMetrics.setDuration( duration ); cPlanner.mPMetrics.setExitcode( result ); if( plannerException != null ){ //we want the stack trace to a String Writer. StringWriter sw = new StringWriter(); plannerException.printStackTrace( new PrintWriter( sw ) ); cPlanner.mPMetrics.setMetricsTypeToError(); cPlanner.mPMetrics.setErrorMessage( sw.toString() ); } //lets write out the metrics if( cPlanner.mSendMetrics ){ edu.isi.pegasus.planner.code.generator.Metrics metrics = new edu.isi.pegasus.planner.code.generator.Metrics(); metrics.initialize( cPlanner.mBag ); metrics.logMetrics( cPlanner.mPMetrics ); } else{ //log cPlanner.log( "No metrics logged or sent to the metrics server", LogManager.DEBUG_MESSAGE_LEVEL ); } } catch( Exception e ){ System.out.println( "ERROR while logging metrics " + e.getMessage() ); } // 2012-03-06 (jsv): Copy dax file to submit directory. It's // MUCH SIMPLER to use the parsed CLI options at this point than // drill open the shell wrapper without messing up everything. if ( result == 0 ) { try { File src_file = new File( cPlanner.mPOptions.getDAX() ); File dst_file = new File( cPlanner.mPOptions.getSubmitDirectory(), src_file.getName() ); if ( ! dst_file.exists() ) dst_file.createNewFile(); FileChannel fc_src = null; FileChannel fc_dst = null; try { fc_src = new FileInputStream( src_file ).getChannel(); fc_dst = new FileOutputStream( dst_file ).getChannel(); fc_dst.transferFrom( fc_src, 0, fc_src.size() ); } finally { if ( fc_src != null ) fc_src.close(); if ( fc_dst != null ) fc_dst.close(); } } catch ( IOException ieo ) { // ignore -- copy is best effort for now } catch ( NullPointerException npe ) { // also ignore } } // warn about non zero exit code if ( result != 0 ) { cPlanner.log("Exiting with non-zero exit-code " + result, LogManager.DEBUG_MESSAGE_LEVEL ); } else{ //log the time taken to execute cPlanner.log("Time taken to execute is " + duration + " seconds", LogManager.CONSOLE_MESSAGE_LEVEL ); } cPlanner.mLogger.logEventCompletion(); System.exit(result); } /** * Loads all the properties that are needed by this class. */ public void loadProperties(){ } /** * Executes the command on the basis of the options specified. * * @param args the command line options. */ public void executeCommand( ) { executeCommand( parseCommandLineArguments( getCommandLineOptions() ) ); } /** * Executes the command on the basis of the options specified. * * @param options the command line options. * * * @return the Collection of <code>File</code> objects for the files written * out. */ public Collection<File> executeCommand( PlannerOptions options ) { String message = ""; mPOptions = options; mBag.add( PegasusBag.PEGASUS_PROPERTIES, mProps ); mBag.add( PegasusBag.PLANNER_OPTIONS, mPOptions ); mBag.add( PegasusBag.PEGASUS_LOGMANAGER, mLogger ); Collection result = null; //print help if asked for if( mPOptions.getHelp() ) { //PM-816 disable metrics logging this.mSendMetrics = false; printLongVersion(); return result; } //set the logging level only if -v was specified if(mPOptions.getLoggingLevel() >= 0){ mLogger.setLevel(mPOptions.getLoggingLevel()); } else{ //set log level to FATAL only mLogger.setLevel( LogManager.FATAL_MESSAGE_LEVEL ); } String shiwaBundle = mPOptions.getShiwaBundle(); if( shiwaBundle != null ){ Shiwa shiwa = new Shiwa( mLogger ); shiwa.readBundle( shiwaBundle, mProps, mPOptions ); } PegasusConfiguration configurator = new PegasusConfiguration( mLogger ); configurator.loadConfigurationPropertiesAndOptions( mProps , mPOptions ); mLogger.log( "Planner invoked with following arguments " + mPOptions.getOriginalArgString(), LogManager.INFO_MESSAGE_LEVEL ); //do sanity check on dax file String dax = mPOptions.getDAX(); String pdax = mPOptions.getPDAX(); String baseDir = mPOptions.getBaseSubmitDirectory(); if( dax == null ){ mLogger.log( "\nNeed to specify a dax file ( using --dax ) to plan a workflow", LogManager.CONSOLE_MESSAGE_LEVEL); this.printShortVersion(); return result; } //a sanity check for an old unsupported option if( pdax != null ){ //do the deferreed planning by parsing //the partition graph in the pdax file. throw new UnsupportedOperationException( "The --pdax option is no longer supported " ); } //check if sites set by user. If user has not specified any sites then //load all sites from site catalog. Collection eSites = mPOptions.getExecutionSites(); Set<String> toLoad = new HashSet<String>(); mLogger.log( "All sites will be loaded from the site catalog", LogManager.DEBUG_MESSAGE_LEVEL ); toLoad.add( "*" ); if( eSites.isEmpty() ) { mLogger.log("No sites given by user. Will use sites from the site catalog", LogManager.DEBUG_MESSAGE_LEVEL); eSites.add( "*" ); } //load the site catalog and transformation catalog accordingly SiteStore s = loadSiteStore( toLoad ); s.setForPlannerUse( mProps, mPOptions); if( eSites.contains( "*" ) ){ //set execution sites to all sites that are loaded into site store //only if a user passed * option on command line or did not specify eSites.remove( "*" ); eSites.addAll( s.list() ); //PM-1018 remove the local site from list of execution sites eSites.remove( "local" ); } mLogger.log( "Execution sites are " + eSites, LogManager.DEBUG_MESSAGE_LEVEL ); //update the local/output site entry if required configurator.updateSiteStoreAndOptions( s, mPOptions ); mBag.add( PegasusBag.SITE_STORE, s ); mBag.add( PegasusBag.TRANSFORMATION_CATALOG, TransformationFactory.loadInstance( mBag ) ); //populate planner metrics mPMetrics.setVOGroup( mPOptions.getVOGroup() ); mPMetrics.setBaseSubmitDirectory( mPOptions.getSubmitDirectory() ); mPMetrics.setDAX( mPOptions.getDAX() ); String dataConfiguration = mProps.getProperty( PegasusConfiguration.PEGASUS_CONFIGURATION_PROPERTY_KEY ) ; dataConfiguration = ( dataConfiguration == null ) ? PegasusConfiguration.DEFAULT_DATA_CONFIGURATION_VALUE: dataConfiguration; mPMetrics.setDataConfiguration( dataConfiguration ); mPMetrics.setPlannerOptions( mPOptions.getOriginalArgString() ); mPMetrics.setApplicationMetrics( mProps ); //try to get hold of the vds properties //set in the jvm that user specifed at command line mPOptions.setVDSProperties(mProps.getMatchingProperties("pegasus.",false)); List allVDSProps = mProps.getMatchingProperties("pegasus.",false); mLogger.log("Pegasus Properties set by the user",LogManager.CONFIG_MESSAGE_LEVEL ); for(java.util.Iterator it = allVDSProps.iterator(); it.hasNext();){ NameValue nv = (NameValue)it.next(); mLogger.log(nv.toString(),LogManager.CONFIG_MESSAGE_LEVEL); } //load the parser and parse the dax ADag orgDag = this.parseDAX( dax ); mLogger.log( "Parsed DAX with following metrics " + orgDag.getWorkflowMetrics().toJson(), LogManager.DEBUG_MESSAGE_LEVEL); //generate the flow ids for the classads information orgDag.generateFlowName(); orgDag.setFlowTimestamp( mPOptions.getDateTime( mProps.useExtendedTimeStamp() )); orgDag.setDAXMTime( new File(dax) ); orgDag.generateFlowID(); orgDag.setReleaseVersion(); //set out the root workflow id orgDag.setRootWorkflowUUID( determineRootWorkflowUUID( orgDag, this.mPOptions, this.mProps ) ); //set some initial workflow metrics mPMetrics.setRootWorkflowUUID( orgDag.getRootWorkflowUUID() ); mPMetrics.setWorkflowUUID( orgDag.getWorkflowUUID() ); mPMetrics.setWorkflowMetrics( orgDag.getWorkflowMetrics() ); //write out a the relevant properties to submit directory int state = 0; //the submit directory relative to the base specified . etermine on basis of --relative-submit-dir and --relative-dir String relativeSubmitDir = mPOptions.getRelativeSubmitDirectory(); String relativeExecDir = mPOptions.getRelativeDirectory(); String defaultRelativeDir = null; try{ //create our own relative dir defaultRelativeDir = determineRelativeSubmitDirectory( orgDag, baseDir, mUser, mPOptions.getVOGroup(), mProps.useTimestampForDirectoryStructure()); if( relativeSubmitDir == null ){ //PM-1113 relative submit directory is the default relative dir relativeSubmitDir = defaultRelativeDir; } if( relativeExecDir == null ){ //PM-1113 relative submit directory is the default relative dir relativeExecDir = defaultRelativeDir; } mPOptions.setSubmitDirectory( baseDir, relativeSubmitDir ); if ( options.partOfDeferredRun() ) { //PM-667 log what directory the planner is launched in //what the base submit directory is String launchDir = System.getProperty("user.dir") ; mLogger.log( "The directory in which the planner was launched " + launchDir, LogManager.CONFIG_MESSAGE_LEVEL ); if ( !mPOptions.getForceReplan() ) { //if --force-replan is not set handle //rescue dags boolean rescue = handleRescueDAG( orgDag, mPOptions ); if (rescue) { result = new LinkedList(); result.add( new File( mPOptions.getSubmitDirectory(), this.getDAGFilename( orgDag, mPOptions ) ) ); //for rescue dag workflows we don't want any metrics //to be sent to prevent double counting. mSendMetrics = false; mLogger.log( "No metrics will be sent for rescue dag submission", LogManager.DEBUG_MESSAGE_LEVEL ); return result; } } //replanning case. rescues already accounted for earlier. //the relativeSubmitDir is to be a symlink to relativeSubmitDir.XXX relativeSubmitDir = doBackupAndCreateSymbolicLinkForSubmitDirectory( baseDir, relativeSubmitDir ); //update the submit directory again. mPOptions.setSubmitDirectory( baseDir, relativeSubmitDir ); mLogger.log( "Setting relative submit dir to " + relativeSubmitDir, LogManager.DEBUG_MESSAGE_LEVEL ); } else { //create the relative submit directory if required sanityCheck( new File( baseDir, relativeSubmitDir ) ); } state++; mProps.writeOutProperties( mPOptions.getSubmitDirectory() ); mPMetrics.setRelativeSubmitDirectory( mPOptions.getRelativeSubmitDirectory() ); //also log in the planner metrics where the properties are mPMetrics.setProperties( mProps.getPropertiesInSubmitDirectory() ); } catch (IOException ioe) { String error = (state == 0) ? "Unable to write to directory" : "Unable to write out properties to directory"; throw new RuntimeException( error + mPOptions.getSubmitDirectory(), ioe ); } //we have enough information to pin the metrics file in the submit directory mPMetrics.setMetricsFileLocationInSubmitDirectory( new File( mPOptions.getSubmitDirectory() , edu.isi.pegasus.planner.code.generator.Abstract.getDAGFilename( mPOptions, orgDag.getLabel(), orgDag.getIndex(), edu.isi.pegasus.planner.code.generator.Metrics.METRICS_FILE_SUFFIX ) )); mLogger.log( "Metrics file will be written out to " + mPMetrics.getMetricsFileLocationInSubmitDirectory(), LogManager.CONFIG_MESSAGE_LEVEL ); //PM-1113 check if a relativeExec dir needs to be updated because of --random-dir option if ( mPOptions.generateRandomDirectory() && mPOptions.getRandomDir() == null ) { //user has specified the random dir name but wants //to go with default name which is the flow id //for the workflow unless a basename is specified. relativeExecDir = getRandomDirectory(orgDag); } else if ( mPOptions.getRandomDir() != null ) { //keep the name that the user passed relativeExecDir = mPOptions.getRandomDir(); } mLogger.log( "The base submit directory for the workflow " + baseDir, LogManager.CONFIG_MESSAGE_LEVEL ); mLogger.log( "The relative submit directory for the workflow " + relativeSubmitDir, LogManager.CONFIG_MESSAGE_LEVEL ); mLogger.log( "The relative execution directory for the workflow " + relativeExecDir, LogManager.CONFIG_MESSAGE_LEVEL ); mPOptions.setRandomDir( relativeExecDir ); //before starting the refinement process load //the stampede event generator and generate events for the dax generateStampedeEventsForAbstractWorkflow( orgDag, mBag ); //populate the singleton instance for user options //UserOptions opts = UserOptions.getInstance(mPOptions); MainEngine cwmain = new MainEngine( orgDag, mBag ); ADag finalDag = cwmain.runPlanner(); //store the workflow metrics from the final dag into //the planner metrics mPMetrics.setWorkflowMetrics( finalDag.getWorkflowMetrics() ); //we only need the script writer for daglite megadag generator mode CodeGenerator codeGenerator = null; codeGenerator = CodeGeneratorFactory.loadInstance( cwmain.getPegasusBag() ); //before generating the codes for the workflow check //for emtpy workflows boolean emptyWorkflow = false; if (finalDag.isEmpty()) { mLogger.log("Adding a noop job to the empty workflow ", LogManager.DEBUG_MESSAGE_LEVEL); finalDag.add(this.createNoOPJob(getNOOPJobName(finalDag))); emptyWorkflow = true; } message = "Generating codes for the executable workflow"; log(message, LogManager.INFO_MESSAGE_LEVEL); try { mLogger.logEventStart( LoggingKeys.EVENTS_PEGASUS_CODE_GENERATION, LoggingKeys.DAX_ID, finalDag.getAbstractWorkflowName() ); result = codeGenerator.generateCode( finalDag ); } catch (Exception e) { throw new RuntimeException( "Unable to generate code", e ); } finally { //close the connection to planner cache mBag.getHandleToPlannerCache().close(); mLogger.logEventCompletion(); } //PM-1003 update metrics with whether pmc was used or not. mPMetrics.setUsesPMC( Braindump.plannerUsedPMC(mBag)); checkMasterDatabaseForVersionCompatibility(); if ( mPOptions.submitToScheduler() ) {//submit the jobs StringBuffer invocation = new StringBuffer(); //construct the path to the bin directory invocation.append( mProps.getBinDir() ).append( File.separator ). append( getPegasusRunInvocation() ); boolean submit = submitWorkflow( invocation.toString() ); if (!submit) { throw new RuntimeException( "Unable to submit the workflow using pegasus-run" ); } } else { //log the success message this.logSuccessfulCompletion( emptyWorkflow ); } //log some memory usage //PM-747 if( mProps.logMemoryUsage() ){ this.logMemoryUsage(); } return result; } /** * Returns the name of the noop job. * * @param dag the workflow * * @return the name */ public String getNOOPJobName( ADag dag ){ StringBuffer sb = new StringBuffer(); sb.append( CPlanner.NOOP_PREFIX ).append( dag.getLabel() ). append( "_" ).append( dag.getIndex() ); return sb.toString(); } /** * It creates a NoOP job that runs on the submit host. * * @param name the name to be assigned to the noop job * * @return the noop job. */ protected Job createNoOPJob( String name ) { Job newJob = new Job(); //jobname has the dagname and index to indicate different //jobs for deferred planning newJob.setName( name ); newJob.setTransformation( "pegasus", "noop", "1.0" ); newJob.setDerivation( "pegasus", "noop", "1.0" ); // newJob.setUniverse( "vanilla" ); 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.setSiteHandle( "local" ); newJob.setJobType( 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; } /** * 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. */ protected void construct(Job job, String key, String value){ job.condorVariables.checkKeyInNS(key,value); } /** * Parses the command line arguments using GetOpt and returns a * <code>PlannerOptions</code> contains all the options passed by the * user at the command line. * * @param args the arguments passed by the user at command line. * * @return the options. */ public PlannerOptions parseCommandLineArguments( String[] args ){ return parseCommandLineArguments( args, true ); } /** * Parses the command line arguments using GetOpt and returns a * <code>PlannerOptions</code> contains all the options passed by the * user at the command line. * * @param args the arguments passed by the user at command line. * @param sanitizePath whether to sanitize path during construction of options * * * @return the options. */ public PlannerOptions parseCommandLineArguments( String[] args, boolean sanitizePath ){ LongOpt[] longOptions = generateValidOptions(); //store the args with which planner was invoked PlannerOptions options = new PlannerOptions(); options.setSanitizePath( sanitizePath ); options.setOriginalArgString( args ); //we default to inplace cleanup unless overriden on command line // options.setCleanup(PlannerOptions.CLEANUP_OPTIONS.inplace ); Getopt g = new Getopt("pegasus-plan",args, "vqhfSnzpVr::aD:d:s:o:O:y:P:c:C:b:g:2:j:3:F:X:4:5:6:78:9:B:1:", longOptions,false); g.setOpterr(false); int option = 0; //construct the property matcher regex Pattern propertyPattern = Pattern.compile( CPlanner.JAVA_COMMAND_LINE_PROPERTY_REGEX ); while( (option = g.getopt()) != -1){ //System.out.println("Option tag " + (char)option); switch (option) { case 'z'://deferred options.setPartOfDeferredRun( true ); break; case 'b'://optional basename options.setBasenamePrefix(g.getOptarg()); break; case 'B'://bundle options.setShiwaBundle( g.getOptarg() ); break; case 'c'://cache options.setCacheFiles( g.getOptarg() ); break; case 'C'://cluster options.setClusteringTechnique( g.getOptarg() ); break; case '1'://cleanup options.setCleanup( g.getOptarg() ); break; case '6':// conf //PM-667 we need to track conf file option options.setConfFile( g.getOptarg() ); break; case 'd'://dax options.setDAX(g.getOptarg()); break; case 'D': // -Dpegasus.blah= String optarg = g.getOptarg(); //if( optarg.matches( "pegasus\\..*=.*" ) ){ if( propertyPattern.matcher( optarg ).matches() ){ options.setProperty( optarg ); } else{ //JIRA PM-390 dont accept -D for --dir //log warning StringBuffer sb = new StringBuffer(); sb.append( "Submit Directory can only be set by specifying the --dir option now. " ). append( "Setting -D to " ).append( optarg ).append(" does not work" ); mLogger.log( sb.toString(), LogManager.WARNING_MESSAGE_LEVEL ); } break; case '8'://dir option options.setSubmitDirectory( g.getOptarg(), null ); break; case '2'://relative-dir options.setRelativeDirectory( g.getOptarg() ); break; case '3'://rescue options.setNumberOfRescueTries( g.getOptarg() ); break; case '4'://relative-submit-dir options.setRelativeSubmitDirectory( g.getOptarg() ); break; case 'f'://force options.setForce(true); break; case '7'://force replan options.setForceReplan( true ); break; case 'F'://forward options.addToForwardOptions( g.getOptarg() ); break; case 'g': //group options.setVOGroup( g.getOptarg() ); break; case 'h'://help options.setHelp(true); break; case '5'://inherited-rc-files options.setInheritedRCFiles( g.getOptarg() ); break; case 'I'://input-dir options.setInputDirectories( g.getOptarg() ); break; case 'j'://job-prefix options.setJobnamePrefix( g.getOptarg() ); break; case 'm'://megadag option options.setMegaDAGMode(g.getOptarg()); break; case 'n'://nocleanup option mLogger.log( "--nocleanup option is deprecated. Use --cleanup none " , LogManager.WARNING_MESSAGE_LEVEL ); options.setCleanup( PlannerOptions.CLEANUP_OPTIONS.none ); break; case 'y'://output option options.setOutputSite( g.getOptarg() ); //warn mLogger.log( "--output option is deprecated. Replaced by --output-site " , LogManager.WARNING_MESSAGE_LEVEL ); break; case 'o'://output-site options.setOutputSite(g.getOptarg()); break; case 'O'://output-dir options.setOutputDirectory( g.getOptarg() ); break; /* no longer supported case 'p'://partition and plan options.setPartitioningType( "Whole" ); break; case 'P'://pdax file options.setPDAX(g.getOptarg()); break; */ case 'q'://quiet options.decrementLogging(); break; case 'r'://randomdir options.setRandomDir(g.getOptarg()); break; case 'S'://submit option options.setSubmitToScheduler( true ); break; case 's'://sites options.setExecutionSites( g.getOptarg() ); break; case '9'://staging-sites options.addToStagingSitesMappings( g.getOptarg() ); break; case 'v'://verbose options.incrementLogging(); break; case 'V'://version mLogger.log(getGVDSVersion(),LogManager.CONSOLE_MESSAGE_LEVEL ); System.exit(0); case 'X'://jvm options options.addToNonStandardJavaOptions( g.getOptarg() ); break; default: //same as help printShortVersion(); throw new RuntimeException("Incorrect option or option usage " + (char)g.getOptopt()); } } //try and detect if there are any unparsed components of the //argument string such as inadvertent white space in values int nonOptionArgumentIndex = g.getOptind(); if( nonOptionArgumentIndex < args.length ){ //this works as planner does not take any positional arguments StringBuilder error = new StringBuilder(); error.append( "Unparsed component ").append( args[nonOptionArgumentIndex] ). append( " of the command line argument string: "). append( " " ).append( options.getOriginalArgString()); throw new RuntimeException( error.toString() ); } return options; } /** * Submits the workflow for execution using pegasus-run, a wrapper around * pegasus-submit-dag. * * @param invocation the pegasus run command * * @return boolean indicating whether could successfully submit the workflow or not. */ public boolean submitWorkflow( String invocation ){ boolean result = false; try{ //set the callback and run the pegasus-run command Runtime r = Runtime.getRuntime(); mLogger.log( "Executing " + invocation, LogManager.DEBUG_MESSAGE_LEVEL ); Process p = r.exec( invocation ); //spawn off the gobblers with the already initialized default callback StreamGobbler ips = new StreamGobbler( p.getInputStream(), new DefaultStreamGobblerCallback( LogManager.CONSOLE_MESSAGE_LEVEL )); StreamGobbler eps = new StreamGobbler( p.getErrorStream(), new DefaultStreamGobblerCallback( LogManager.ERROR_MESSAGE_LEVEL)); ips.start(); eps.start(); //wait for the threads to finish off ips.join(); eps.join(); //get the status int status = p.waitFor(); mLogger.log( "Submission of workflow exited with status " + status, LogManager.DEBUG_MESSAGE_LEVEL ); result = (status == 0) ?true : false; } catch(IOException ioe){ mLogger.log("IOException while executing pegasus-run ", ioe, LogManager.ERROR_MESSAGE_LEVEL); } catch( InterruptedException ie){ //ignore } return result; } /** * Sets the basename of the random directory that is created on the remote * sites per workflow. The name is generated by default from teh flow ID, * unless a basename prefix is specifed at runtime in the planner options. * * @param dag the DAG containing the abstract workflow. * * @return the basename of the random directory. */ protected String getRandomDirectory(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); sb.append("-"); //append timestamp to generate some uniqueness sb.append(dag.getFlowTimestamp()); } else{ //use the flow ID that contains the timestamp and the name both. sb.append(dag.getFlowID() ); } return sb.toString(); } /** * Tt generates the LongOpt which contain the valid options that the command * will accept. * * @return array of <code>LongOpt</code> objects , corresponding to the valid * options */ public LongOpt[] generateValidOptions(){ LongOpt[] longopts = new LongOpt[37]; longopts[0] = new LongOpt( "dir", LongOpt.REQUIRED_ARGUMENT, null, '8' ); longopts[1] = new LongOpt( "dax", LongOpt.REQUIRED_ARGUMENT, null, 'd' ); longopts[2] = new LongOpt( "sites", LongOpt.REQUIRED_ARGUMENT, null, 's' ); longopts[3] = new LongOpt( "output", LongOpt.REQUIRED_ARGUMENT, null, 'y' ); longopts[4] = new LongOpt( "verbose", LongOpt.NO_ARGUMENT, null, 'v' ); longopts[5] = new LongOpt( "help", LongOpt.NO_ARGUMENT, null, 'h' ); longopts[6] = new LongOpt( "force", LongOpt.NO_ARGUMENT, null, 'f' ); longopts[7] = new LongOpt( "submit", LongOpt.NO_ARGUMENT, null, 'S' ); longopts[8] = new LongOpt( "version", LongOpt.NO_ARGUMENT, null, 'V' ); longopts[9] = new LongOpt( "randomdir", LongOpt.OPTIONAL_ARGUMENT, null, 'r' ); longopts[10] = new LongOpt( "authenticate", LongOpt.NO_ARGUMENT, null, 'a' ); longopts[11] = new LongOpt( "conf", LongOpt.REQUIRED_ARGUMENT, null, '6' ); //deferred planning options longopts[12] = new LongOpt( "pdax", LongOpt.REQUIRED_ARGUMENT, null, 'P' ); longopts[13] = new LongOpt( "cache", LongOpt.REQUIRED_ARGUMENT, null, 'c' ); longopts[14] = new LongOpt( "megadag", LongOpt.REQUIRED_ARGUMENT, null, 'm' ); //collapsing for mpi longopts[15] = new LongOpt( "cluster", LongOpt.REQUIRED_ARGUMENT, null, 'C' ); //more deferred planning stuff longopts[16] = new LongOpt( "basename", LongOpt.REQUIRED_ARGUMENT, null, 'b' ); longopts[17] = new LongOpt( "monitor", LongOpt.NO_ARGUMENT, null , 1 ); longopts[18] = new LongOpt( "nocleanup", LongOpt.NO_ARGUMENT, null, 'n' ); longopts[19] = new LongOpt( "group", LongOpt.REQUIRED_ARGUMENT, null, 'g' ); longopts[20] = new LongOpt( "deferred", LongOpt.NO_ARGUMENT, null, 'z'); longopts[21] = new LongOpt( "relative-dir", LongOpt.REQUIRED_ARGUMENT, null, '2' ); longopts[22] = new LongOpt( "pap", LongOpt.NO_ARGUMENT, null, 'p' ); longopts[23] = new LongOpt( "job-prefix", LongOpt.REQUIRED_ARGUMENT, null, 'j' ); longopts[24] = new LongOpt( "rescue", LongOpt.REQUIRED_ARGUMENT, null, '3'); longopts[25] = new LongOpt( "forward", LongOpt.REQUIRED_ARGUMENT, null, 'F'); longopts[26] = new LongOpt( "X", LongOpt.REQUIRED_ARGUMENT, null, 'X' ); longopts[27] = new LongOpt( "relative-submit-dir", LongOpt.REQUIRED_ARGUMENT, null, '4' ); longopts[28] = new LongOpt( "quiet", LongOpt.NO_ARGUMENT, null, 'q' ); longopts[29] = new LongOpt( "inherited-rc-files", LongOpt.REQUIRED_ARGUMENT, null, '5' ); longopts[30] = new LongOpt( "force-replan" , LongOpt.NO_ARGUMENT, null, '7' ); longopts[31] = new LongOpt( "staging-site", LongOpt.REQUIRED_ARGUMENT, null, '9' ); longopts[32] = new LongOpt( "shiwa-bundle", LongOpt.REQUIRED_ARGUMENT, null, 'B' ); longopts[33] = new LongOpt( "input-dir" , LongOpt.REQUIRED_ARGUMENT, null, 'I' ); longopts[34] = new LongOpt( "output-dir" , LongOpt.REQUIRED_ARGUMENT, null, 'O' ); longopts[35] = new LongOpt( "output-site" , LongOpt.REQUIRED_ARGUMENT, null, 'o' ); longopts[36] = new LongOpt( "cleanup", LongOpt.REQUIRED_ARGUMENT, null, '1' ); return longopts; } /** * Prints out a short description of what the command does. */ public void printShortVersion(){ String text = "\n $Id$ " + "\n " + getGVDSVersion() + "\n Usage : pegasus-plan [-Dprop [..]] -d <dax file> " + " [-s site[,site[..]]] [--staging-site s1=ss1[,s2=ss2[..]][-b prefix] [-c f1[,f2[..]]] [--conf <path to property file>] "+ "\n [-f] [--force-replan] [-b basename] [-C t1[,t2[..]] [--dir <base dir for o/p files>] [-j <job-prefix>] " + "\n [--relative-dir <relative directory to base directory> ] [--relative-submit-dir <relative submit directory to base directory>]" + "\n [--inherited-rc-files f1[,f2[..]]] [--cleanup <cleanup strategy to use>] " + "\n [-g <vogroup>] [-I <input dir>] [-O <output dir>] [-o <output site>] [-r[dir name]] [-F option[=value] ] " + //"[--rescue <number of rescues before replanning>]" "\n [-S] [-n] [-v] [-q] [-V] [-X[non standard jvm option] [-h]"; System.out.println(text); } /** * Prints the long description, displaying in detail what the various options * to the command stand for. */ public void printLongVersion(){ StringBuffer text = new StringBuffer( ); text.append( "\n $Id$ " ). append( "\n " + getGVDSVersion() ). append( "\n pegasus-plan - The main class which is used to run Pegasus. " ). append( "\n Usage: pegasus-plan [-Dprop [..]] --dax|--pdax <file> [--sites <execution sites>] " ). append( "\n [--staging-site s1=ss1[,s2=ss2[..]] [--basename prefix] [--cache f1[,f2[..]] [--cluster t1[,t2[..]] [--conf <path to property file>]" ). append( "\n [--inherited-rc-files f1[,f2[..]]] [--cleanup <cleanup strategy to use>] " ). append( "\n [--dir <dir for o/p files>] [--force] [--force-replan] [--forward option=[value] ] [--group vogroup] " ). append( "\n [--input-dir dir1[,dir2[..]]] [--output-dir <output dir>] [--output output site] [--randomdir=[dir name]] [--verbose] [--version][--help] " ). append( "\n" ). append( "\n Mandatory Options " ). append( "\n -d fn " ). append( "\n --dax the path to the dax file containing the abstract workflow " ). append( "\n Other Options " ). append( "\n -b |--basename the basename prefix while constructing the per workflow files like .dag etc." ). append( "\n -B |--bundle the shiwa bundle to be used. ( prototypical option ) " ). append( "\n -c |--cache comma separated list of replica cache files." ). append( "\n --inherited-rc-files comma separated list of replica files. Locations mentioned in these have a lower priority than the locations in the DAX file" ). append( "\n --cleanup the cleanup strategy to use. Can be none|inplace|leaf|constraint. Defaults to inplace. "). append( "\n -C |--cluster comma separated list of clustering techniques to be applied to the workflow to " ). append( "\n to cluster jobs in to larger jobs, to avoid scheduling overheads." ). append( "\n --conf the path to the properties file to use for planning. Defaults to pegasus.properties file in the current working directory " ). append( "\n --dir the directory where to generate the executable workflow." ). append( "\n --relative-dir the relative directory to the base directory where to generate the concrete workflow." ). append( "\n --relative-submit-dir the relative submit directory where to generate the concrete workflow. Overrids --relative-dir ." ). append( "\n -f |--force skip reduction of the workflow, resulting in build style dag." ). append( "\n --force-replan force replanning for sub workflows in case of failure. " ). append( "\n -F |--forward any options that need to be passed ahead to pegasus-run in format option[=value] " ). append( "\n where value can be optional. e.g -F nogrid will result in --nogrid . The option " ). append( "\n can be repeated multiple times." ). append( "\n -g |--group the VO Group to which the user belongs " ). append( "\n -j |--job-prefix the prefix to be applied while construction job submit filenames " ). append( "\n -I |--input-dir comma separated list of optional input directories where the input files reside on submit host" ). append( "\n -O |--output-dir an optional output directory where the output files should be transferred to on submit host. " ). append( "\n the directory specified is asscociated with the local-storage directory for the output site." ). append( "\n -o |--output-site the output site where the data products during workflow execution are transferred to." ). append( "\n --output deprecated option . Replaced by --output-site option" ). append( "\n -s |--sites comma separated list of executions sites on which to map the workflow." ). append( "\n --staging-site comma separated list of key=value pairs , where the key is the execution site and value is the staging site for that execution site." ). append( "\n -r |--randomdir create random directories on remote execution sites in which jobs are executed" ). // "\n --rescue the number of times rescue dag should be submitted for sub workflows before triggering re-planning" + append( "\n can optionally specify the basename of the remote directories" ). append( "\n -n |--nocleanup deprecated option. use --cleanup none instead" ). append( "\n -S |--submit submit the executable workflow generated" ). append( "\n --staging-site comma separated list of key=value pairs, where key is the execution site and value is the staging site" ). append( "\n -v |--verbose increases the verbosity of messages about what is going on" ). append( "\n -q |--quiet decreases the verbosity of messages about what is going on" ). append( "\n -V |--version displays the version of the Pegasus Workflow Management System" ). append( "\n -X[non standard java option] pass to jvm a non standard option . e.g. -Xmx1024m -Xms512m" ). append( "\n -h |--help generates this help." ). append( "\n The following exitcodes are produced" ). append( "\n 0 planner was able to generate an executable workflow" ). append( "\n 1 an error occured. In most cases, the error message logged should give a" ). append( "\n clear indication as to where things went wrong." ). append( "\n 2 an error occured while loading a specific module implementation at runtime" ). append( "\n 3 an unaccounted java exception occured at runtime" ). append( "\n 4 encountered an out of memory exception. Most probably ran out of heap memory." ). append( "\n " ); System.out.println(text); //mLogger.log(text,LogManager.INFO_MESSAGE_LEVEL); } /** * Determines the workflow uuid for a workflow * * @param dag the workflow * @param options the options passed to the planner * @param properties the properties passed to the planner * * @return uuid for the root workflow instance */ private String determineRootWorkflowUUID(ADag dag, PlannerOptions options, PegasusProperties properties ) { //figure out the root workflow uuid to put for pegasus-state //JIRA PM-396 String uuid = null; if( options.partOfDeferredRun() ){ //in recursive workflow we are not on the root , but some level //we have to retrive from properties uuid = properties.getRootWorkflowUUID(); } else{ //the root workflow uuid is the uuid of the workflow //being planned right now. We are on the root level of the recursive //workflows uuid = dag.getWorkflowUUID(); } if ( uuid == null ){ //something amiss throw new RuntimeException( "Unable to determine Root Workflow UUID" ); } return uuid; } /** * Creates the submit directory for the workflow. This is not thread safe. * * @param dag the workflow being worked upon. * @param dir the base directory specified by the user. * @param user the username of the user. * @param vogroup the vogroup to which the user belongs to. * @param timestampBased boolean indicating whether to have a timestamp based dir or not * * @return the directory name created relative to the base directory passed * as input. * * @throws IOException in case of unable to create submit directory. */ protected String determineRelativeSubmitDirectory( ADag dag, String dir, String user, String vogroup, boolean timestampBased ) throws IOException { return determineRelativeSubmitDirectory( dag.getLabel(), dir, user, vogroup, timestampBased ); } /** * Creates the submit directory for the workflow. This is not thread safe. * * @param label the label of the workflow * @param dir the base directory specified by the user. * @param user the username of the user. * @param vogroup the vogroup to which the user belongs to. * @param timestampBased boolean indicating whether to have a timestamp based dir or not * * @return the directory name created relative to the base directory passed * as input. * * @throws IOException in case of unable to create submit directory. */ protected String determineRelativeSubmitDirectory( String label, String dir, String user, String vogroup, boolean timestampBased ) throws IOException { File base = new File( dir ); StringBuffer result = new StringBuffer(); //do a sanity check on the base sanityCheck( base ); //add the user name if possible base = new File( base, user ); result.append( user ).append( File.separator ); //add the vogroup base = new File( base, vogroup ); sanityCheck( base ); result.append( vogroup ).append( File.separator ); //add the label of the DAX base = new File( base, label ); sanityCheck( base ); result.append( label ).append( File.separator ); //create the directory name StringBuffer leaf = new StringBuffer(); if( timestampBased ){ leaf.append( mPOptions.getDateTime( mProps.useExtendedTimeStamp() ) ); } else{ //get all the files in this directory String[] files = base.list( new RunDirectoryFilenameFilter() ); //find the maximum run directory int num, max = 1; for( int i = 0; i < files.length ; i++ ){ num = Integer.parseInt( files[i].substring( RunDirectoryFilenameFilter.SUBMIT_DIRECTORY_PREFIX.length() ) ); if ( num + 1 > max ){ max = num + 1; } } //create the directory name leaf.append( RunDirectoryFilenameFilter.SUBMIT_DIRECTORY_PREFIX ).append( mNumFormatter.format( max ) ); } result.append( leaf.toString() ); base = new File( base, leaf.toString() ); mLogger.log( "Directory to be created is " + base.getAbsolutePath(), LogManager.DEBUG_MESSAGE_LEVEL ); return result.toString(); } /** * Checks the destination location for existence, if it can * be created, if it is writable etc. * * @param dir is the new base directory to optionally create. * * @throws IOException in case of error while writing out files. */ protected static void sanityCheck( File dir ) throws IOException{ if ( dir.exists() ) { // location exists if ( dir.isDirectory() ) { // ok, isa directory if ( dir.canWrite() ) { // can write, all is well return; } else { // all is there, but I cannot write to dir throw new IOException( "Cannot write to existing directory " + dir.getPath() ); } } else { // exists but not a directory throw new IOException( "Destination " + dir.getPath() + " already " + "exists, but is not a directory." ); } } else { // does not exist, try to make it if ( ! dir.mkdirs() ) { //try to get around JVM bug. JIRA PM-91 if( dir.getPath().endsWith( "." ) ){ //just try to create the parent directory if( !dir.getParentFile().mkdirs() ){ throw new IOException( "Unable to create directory " + dir.getPath() ); } return; } throw new IOException( "Unable to create directory " + dir.getPath() ); } } } /** * Returns the basename of the dag file * * @param dag the dag that was parsed. * @param options the planner options * * @return boolean true means submit the rescue * false do the planning operation */ protected String getDAGFilename( ADag dag, PlannerOptions options ){ //determine the name of the .dag file that will be written out. //constructing the name of the dagfile StringBuffer sb = new StringBuffer(); String bprefix = options.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" ); return sb.toString(); } /** * Checks for rescue dags, and determines whether to plan or not. * * * @param dag the dag that was parsed. * @param options the planner options * * @return boolean true means submit the rescue * false do the planning operation */ protected boolean handleRescueDAG( ADag dag, PlannerOptions options ) { return this.handleRescueDAG( getDAGFilename( dag, options ), options.getSubmitDirectory(), options.getNumberOfRescueTries() ); } /** * Checks for rescue dags, and determines whether to submit a rescue dag * or not. * * * @param dag the dag file for the dax * @param dir the submit directory. * @param numOfRescues the number of rescues to handle. * * * @return true means submit the rescue * false do the planning operation */ protected boolean handleRescueDAG( String dag, String dir, int numOfRescues ) { boolean result = false; //sanity check if( numOfRescues < 1 ){ return result; } //check for existence of dag file //if it does not exists means we need to plan File dagFile = new File( dir, dag ); mLogger.log( "Determining existence of dag file " + dagFile.getAbsolutePath(), LogManager.DEBUG_MESSAGE_LEVEL ); if ( !dagFile.exists() ){ return result; } /* //if it is default max value , then return true always if( numOfRescues == PlannerOptions.DEFAULT_NUMBER_OF_RESCUE_TRIES ){ return true; } */ int largestRescue = 0; String largestRescueFile = null; //check for existence of latest rescue file. NumberFormat nf = new DecimalFormat( "000" ); for( int i = 1; i <= numOfRescues + 1; i++ ){ String rescue = dag + ".rescue" + nf.format( i ); File rescueFile = new File( dir, rescue ); mLogger.log( "Determining existence of rescue file " + rescueFile.getAbsolutePath(), LogManager.DEBUG_MESSAGE_LEVEL ); if( rescueFile.exists() ){ largestRescue = i; largestRescueFile = rescue; } else{ break; } } if( largestRescue == 0 ){ //no rescue dag. but the dag still exists mLogger.log( "No planning attempted. Existing DAG will be submitted " + dagFile, LogManager.CONSOLE_MESSAGE_LEVEL ); return true; } if( largestRescue == numOfRescues + 1 ){ //we need to start planning now mLogger.log( "Reached user specified limit of rescue retries " + numOfRescues + " .Replanning will be triggered ", LogManager.CONFIG_MESSAGE_LEVEL ); return false; } if( largestRescueFile != null ){ //a rescue file was detected . lets log that mLogger.log( "Rescue DAG will be submitted. Largest Rescue File detected was " + largestRescueFile, LogManager.CONSOLE_MESSAGE_LEVEL ); } return true; } /** * This method generates a symlink between two files * * @param source the file that has to be symlinked * @param destination the destination of the symlink * @param directory the directory in which to execute the command * @param logErrorToDebug whether to log messeage to debug or not * * @return boolean indicating if creation of symlink was successful or not * */ protected boolean createSymbolicLink( String source, String destination , File directory, boolean logErrorToDebug ) { try{ Runtime rt = Runtime.getRuntime(); String command = "ln -sf " + source + " " + destination; mLogger.log( "Creating symlink between " + source + " " + destination, LogManager.DEBUG_MESSAGE_LEVEL); Process p = ( directory == null )? rt.exec( command, null )://dont specify the directory to execute in rt.exec( command, null, directory ); // 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 { if( logErrorToDebug ){ mLogger.log( se, LogManager.DEBUG_MESSAGE_LEVEL ); } else{ mLogger.log(se,LogManager.ERROR_MESSAGE_LEVEL ); } } } br.close(); return true; } catch(Exception ex){ if( logErrorToDebug ){ mLogger.log("Unable to create symlink to the log file" , ex, LogManager.DEBUG_MESSAGE_LEVEL ); }else{ mLogger.log("Unable to create symlink to the log file" , ex, LogManager.ERROR_MESSAGE_LEVEL); } return false; } } /** * Generates events for the abstract workflow. * * @param workflow the parsed dax * @param bag the initialized object bag */ private void generateStampedeEventsForAbstractWorkflow(ADag workflow, PegasusBag bag ) { CodeGenerator codeGenerator = CodeGeneratorFactory.loadInstance( bag, CodeGeneratorFactory.STAMPEDE_EVENT_GENERATOR_CLASS ); mLogger.logEventStart( LoggingKeys.EVENTS_PEGASUS_STAMPEDE_GENERATION, LoggingKeys.DAX_ID, workflow.getAbstractWorkflowName() ); // String message = "Generating Stampede Events for Abstract Workflow"; // log( message, LogManager.INFO_MESSAGE_LEVEL ); try{ Collection result = codeGenerator.generateCode( workflow ); for( Iterator it = result.iterator(); it.hasNext() ;){ mLogger.log("Written out stampede events for the abstract workflow to " + it.next(), LogManager.DEBUG_MESSAGE_LEVEL); } } catch ( Exception e ){ throw new RuntimeException( "Unable to generate stampede events for abstract workflow", e ); } mLogger.logEventCompletion(); // mLogger.log( message + " -DONE", LogManager.INFO_MESSAGE_LEVEL ); } /** * Loads the sites from the site catalog into the site store * * @param sites * @return SiteStore object containing the information about the sites. */ private SiteStore loadSiteStore( Set<String> sites ) { SiteStore result = new SiteStore(); SiteCatalog catalog = null; /* load the catalog using the factory */ catalog = SiteFactory.loadInstance( mProps ); //PM-1047 we want to save the catalogs all around. result.setFileSource( catalog.getFileSource() ); Set<String> toLoad = new HashSet<String>( sites ); /* we want to load the staging sites mentioned for the execution sites */ for( String eSite: sites ){ String ss = this.mPOptions.getStagingSite( eSite ); if( eSite != null ){ toLoad.add( ss ); } } /* always load local site */ toLoad.add( "local" ); /* load the sites in site catalog */ try{ catalog.load( new LinkedList( toLoad) ); //load into SiteStore from the catalog. if( toLoad.contains( "*" ) ){ //we need to load all sites into the site store toLoad.addAll( catalog.list() ); } for( Iterator<String> it = toLoad.iterator(); it.hasNext(); ){ SiteCatalogEntry s = catalog.lookup( it.next() ); if( s != null ){ result.addEntry( s ); } } /* query for the sites, and print them out */ mLogger.log( "Sites loaded are " + result.list( ) , LogManager.DEBUG_MESSAGE_LEVEL ); } catch ( SiteCatalogException e ){ throw new RuntimeException( "Unable to load from site catalog " , e ); } finally{ /* close the connection */ try{ catalog.close(); }catch( Exception e ){} } return result; } /** * Logs the successful completion message. * * @param emptyWorkflow indicates whether the workflow created was empty or not. */ private void logSuccessfulCompletion( boolean emptyWorkflow ){ StringBuffer message = new StringBuffer(); message.append( emptyWorkflow ? CPlanner.EMPTY_FINAL_WORKFLOW_MESSAGE : CPlanner.SUCCESS_MESSAGE ). append( "" ).append( getPegasusRunInvocation( ) ). append( "\n\n" ); mLogger.log( message.toString(), LogManager.CONSOLE_MESSAGE_LEVEL ); } /** * Returns the pegasus-run command on the workflow planned. * * * @return the pegasus-run command */ private String getPegasusRunInvocation( ){ StringBuffer result = new StringBuffer(); result.append( "pegasus-run "); //check if we need to add any other options to pegasus-run for( Iterator<NameValue> it = mPOptions.getForwardOptions().iterator(); it.hasNext() ; ){ NameValue nv = it.next(); result.append( " --" ).append( nv.getKey() ); if( nv.getValue() != null ){ result.append( " " ).append( nv.getValue() ); } } result.append( " " ).append( mPOptions.getSubmitDirectory() ); return result.toString(); } protected String doBackupAndCreateSymbolicLinkForSubmitDirectory( String baseDir, String relativeSubmitDir) throws IOException { //find the maximum run directory //get the parent of the current relativeSubmitDir File f = new File( relativeSubmitDir ); String relativeParentSubmitDir = f.getParent(); File parent = ( relativeParentSubmitDir == null ) ? new File( baseDir ): new File( baseDir, relativeParentSubmitDir ); String basename = f.getName(); int num, max = 0; String prefix = basename + "."; //check if parent exists. first time around the submit directory for //sub workflow may not exist if( parent.exists() ){ String[] files = parent.list( new SubmitDirectoryFilenameFilter( basename ) ); for( int i = 0; i < files.length ; i++ ){ num = Integer.parseInt( files[i].substring( prefix.length() ) ); if ( num + 1 > max ){ max = num + 1; } } } //create the directory name NumberFormat formatter = new DecimalFormat( "000" ); //prefix is just the basname of relativeSubmitDir.XXX prefix = prefix + formatter.format( max ) ; String relativeSubmitDirXXX = ( relativeParentSubmitDir == null ) ? new File( prefix ).getPath(): new File( relativeParentSubmitDir, prefix ).getPath(); //create the relativeSubmitDirXXX File fRelativeSubmitDirXXX = new File( baseDir, relativeSubmitDirXXX ); sanityCheck( fRelativeSubmitDirXXX ); //we have to create a symlink between relativeSubmitDir and relativeSubmitDir.xxx //and update relativeSubmitDir to be relativeSubmitDir.xxx File destination = new File( baseDir, relativeSubmitDir); if( destination.exists() ){ //delete existing file //no way in java to detect if a file is a symbolic link destination.delete(); } //we want symlinks to be created in parent directory //without absolute paths createSymbolicLink( fRelativeSubmitDirXXX.getName(), destination.getName(), parent, true ); return relativeSubmitDirXXX; } /** * Logs memory usage of the JVM */ private void logMemoryUsage() { try { String memoryUsage = new String(); List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans(); double totalUsed = 0; //in bytes double totalReserved = 0; //in bytes double divisor = 1024*1024;// display stats in MB for (MemoryPoolMXBean pool : pools) { MemoryUsage peak = pool.getPeakUsage(); totalUsed += peak.getUsed(); totalReserved += peak.getCommitted(); memoryUsage += String.format("Peak %s memory used : %.3f MB%n", pool.getName(),peak.getUsed()/divisor ); memoryUsage += String.format("Peak %s memory reserved: %.3f MB%n", pool.getName(), peak.getCommitted()/divisor); } // we print the result in the console mLogger.log( "JVM Memory Usage Breakdown \n" + memoryUsage.toString(), LogManager.INFO_MESSAGE_LEVEL); mLogger.log( String.format( "Total Peak memory used : %.3f MB", totalUsed/divisor), LogManager.INFO_MESSAGE_LEVEL); mLogger.log( String.format( "Total Peak memory reserved : %.3f MB", totalReserved/divisor), LogManager.INFO_MESSAGE_LEVEL); } catch (Throwable t) { //not fatal mLogger.log( "Error while logging peak memory usage " + t.getMessage(), LogManager.ERROR_MESSAGE_LEVEL ); } } /** * Parses the DAX and returns the associated ADag object * * @param dax path to the DAX file. * * @return */ private ADag parseDAX(String dax) { Parser p = (Parser)DAXParserFactory.loadDAXParser( mBag, "DAX2CDAG", dax ); Callback cb = ((DAXParser)p).getDAXCallback(); p.startParser( dax ); return (ADag)cb.getConstructedObject(); } /** * Calls out to the pegasus-db-admin tool to check for database compatibility. * * @param submitDirectory the submit directory created by the planner */ private void checkMasterDatabaseForVersionCompatibility() { PegasusDBAdmin dbCheck = new PegasusDBAdmin( mBag.getLogger() ); dbCheck.checkMasterDatabaseForVersionCompatibility( mProps.getPropertiesInSubmitDirectory() ); } } /** * A filename filter for identifying the submit directory * * @author Karan Vahi vahi@isi.edu */ class SubmitDirectoryFilenameFilter implements FilenameFilter { /** * Store the regular expressions necessary to parse kickstart output files */ private String mRegexExpression; /** * Stores compiled patterns at first use, quasi-Singleton. */ private Pattern mPattern = null; /** * Overloaded constructor. * * @param prefix prefix for the submit directory */ public SubmitDirectoryFilenameFilter( String prefix ){ mRegexExpression = "(" + prefix + ")([\\.][0-9][0-9][0-9])"; mPattern = Pattern.compile( mRegexExpression ); } /*** * Tests if a specified file should be included in a file list. * * @param dir the directory in which the file was found. * @param name - the name of the file. * * @return true if and only if the name should be included in the file list * false otherwise. * * */ public boolean accept( File dir, String name) { return mPattern.matcher( name ).matches(); } }