/**
* 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.gridstart;
import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.logging.LogManagerFactory;
import edu.isi.pegasus.planner.classes.Job;
import edu.isi.pegasus.planner.code.POSTScript;
import java.io.File;
import edu.isi.pegasus.planner.common.PegasusProperties;
import edu.isi.pegasus.planner.namespace.Dagman;
import edu.isi.pegasus.planner.namespace.Pegasus;
import edu.isi.pegasus.planner.namespace.aggregator.UniqueMerge;
/**
* The exitcode wrapper, that can parse kickstart output's and put them in the
* database also.
*
* @author Karan Vahi vahi@isi.edu
* @version $Revision$
*/
public class PegasusExitCode implements POSTScript {
/**
* The arguments for pegasus-exitcode when you only want the log files to be rotated.
*/
public static final String POSTSCRIPT_ARGUMENTS_FOR_ONLY_ROTATING_LOG_FILE = "-r $RETURN";
/**
* The SHORTNAME for this implementation.
*/
public static final String SHORT_NAME = "pegasus-exitcode";
/**
* The delimiter used for delimited error and success message internally
*/
public static final String ERR_SUCCESS_MSG_DELIMITER = UniqueMerge.DEFAULT_DELIMITER;
/**
* The LogManager object which is used to log all the messages.
*/
protected LogManager mLogger;
/**
* The object holding all the properties pertaining to Pegasus.
*/
protected PegasusProperties mProps;
/**
* The path to the exitcode client that parses the exit status of
* the kickstart. The client is run as a postscript. It also
* includes the option to the command since at present it is same for all.
* It is $PEGASUS_HOME/bin/pegasus-exitcode
*/
protected String mExitParserPath;
/**
* The path to the log file that exitcode should log to
*/
protected String mExitCodeLogPath;
/**
* The properties that need to be passed to the postscript invocation
* on the command line in the java format.
*/
protected String mPostScriptProperties;
/**
* The submit directory where the submit files are being generated for
* the workflow.
*/
protected String mSubmitDir;
/**
* The default constructor.
*/
public PegasusExitCode(){
}
/**
* Initialize the POSTScript implementation.
*
* @param properties the <code>PegasusProperties</code> object containing all
* the properties required by Pegasus.
* @param path the path to the POSTScript on the submit host.
* @param submitDir the submit directory where the submit file for the job
* has to be generated.
* @param globalLog a global log file to use for logging
*/
public void initialize( PegasusProperties properties,
String path,
String submitDir,
String globalLog ){
mProps = properties;
mSubmitDir = submitDir;
mLogger = LogManagerFactory.loadSingletonInstance( properties );
//construct the exitcode paths and arguments
mExitParserPath = (path == null ) ? getDefaultExitCodePath() : path;
mPostScriptProperties = getPostScriptProperties( properties );
mExitCodeLogPath = globalLog;
}
/**
* Constructs the postscript that has to be invoked on the submit host
* after the job has executed on the remote end. The postscript usually
* works on the xml output generated by kickstart. The postscript invoked
* is exitcode that is shipped with VDS, and can usually be found at
* $PEGASUS_HOME/bin/exitcode.
* <p>
* The postscript is constructed and populated as a profile
* in the DAGMAN namespace.
*
*
* @param job the <code>Job</code> object containing the job description
* of the job that has to be enabled on the grid.
* @param key the key for the profile that has to be inserted.
*
* @return boolean true if postscript was generated,else false.
*/
public boolean construct( Job job, String key ) {
String postscript = mExitParserPath;
//PM-1088 set the relative path in the .dag
//in the condor submit file output has to be fully qualified path
//because of initialdir behavior
String output = (String)job.condorVariables.get("output");
String relative = "." + output.substring( output.indexOf( mSubmitDir ) + mSubmitDir.length());
job.dagmanVariables.construct( Dagman.OUTPUT_KEY, relative);
StringBuffer defaultOptions = new StringBuffer();
//put in the postscript properties if any
defaultOptions.append( this.mPostScriptProperties );
//check for existence of Pegasus profile key for exitcode.failuremsg and exitcode.successmsg
String failure = (String)job.vdsNS.get( Pegasus.EXITCODE_FAILURE_MESSAGE );
PegasusExitCodeEncode encoder = new PegasusExitCodeEncode();
if( failure != null ){
String[] failures = failure.split( ERR_SUCCESS_MSG_DELIMITER );
for( String value : failures ){
defaultOptions.append( " -f " ).append( encoder.encode(value ) );
}
}
String success = (String)job.vdsNS.get( Pegasus.EXITCODE_SUCCESS_MESSAGE );
if( success != null ){
String[] successes = success.split( ERR_SUCCESS_MSG_DELIMITER );
for( String value : successes ){
defaultOptions.append( " -s " ).append( encoder.encode( value ) );
}
}
//PM-928 set it to write to global log file per workflow
defaultOptions.append( " -l " ).append( this.mExitCodeLogPath );
//put the extra options into the exitcode arguments
//in the correct order.
Object args = job.dagmanVariables.get( Dagman.POST_SCRIPT_ARGUMENTS_KEY );
StringBuffer arguments = (args == null ) ?
//only have extra options
defaultOptions :
//have extra options in addition to existing args
new StringBuffer().append( defaultOptions )
.append( " " ).append( args );
job.dagmanVariables.construct( Dagman.POST_SCRIPT_ARGUMENTS_KEY, arguments.toString() );
//put in the postscript
mLogger.log("Postscript constructed is " + postscript,
LogManager.DEBUG_MESSAGE_LEVEL);
job.dagmanVariables.checkKeyInNS( key, postscript );
return true;
}
/**
* Returns the properties that need to be passed to the the postscript
* invocation in the java format. It is of the form
* "-Dprop1=value1 -Dprop2=value2 .."
*
* @param properties the properties object
*
* @return the empty string as pegasus-exitcode does not parse properties currently
*/
protected String getPostScriptProperties( PegasusProperties properties ){
//Returns an empty string, as python version of exitcode cannot parse properties
// file.
return "";
/*
StringBuffer sb = new StringBuffer();
appendProperty( sb,
"pegasus.user.properties",
properties.getPropertiesInSubmitDirectory( ));
return sb.toString();
*/
}
/**
* Appends a property to the StringBuffer, in the java command line format.
*
* @param sb the StringBuffer to append the property to.
* @param key the property.
* @param value the property value.
*/
protected void appendProperty( StringBuffer sb, String key, String value ){
sb.append( " ").append("-D").append( key ).append( "=" ).append( value );
}
/**
* Returns a short textual description of the implementing class.
*
* @return short textual description.
*/
public String shortDescribe(){
return PegasusExitCode.SHORT_NAME;
}
/**
* Returns the path to exitcode that is to be used on the kickstart
* output.
*
* @return the path to the exitcode script to be invoked.
*/
public String getDefaultExitCodePath(){
StringBuffer sb = new StringBuffer();
sb.append( mProps.getBinDir() );
sb.append( File.separator ).append( "pegasus-exitcode" );
return sb.toString();
}
}