/**
* 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.namespace;
import edu.isi.pegasus.planner.catalog.classes.Profiles;
import edu.isi.pegasus.planner.classes.Profile;
import edu.isi.pegasus.planner.common.PegasusProperties;
import edu.isi.pegasus.planner.namespace.aggregator.Aggregator;
import edu.isi.pegasus.planner.namespace.aggregator.MIN;
import edu.isi.pegasus.planner.namespace.aggregator.MAX;
import edu.isi.pegasus.planner.namespace.aggregator.Sum;
import edu.isi.pegasus.planner.namespace.aggregator.Update;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.TreeMap;
/**
* This helper class helps in handling the globus rsl key value pairs that
* come through profile information for namespace Globus.
* The information can either come in through transformation catalog, site catalog
* or through profile tags in DAX.
*
* @author Karan Vahi
* @version $Revision$
*/
public class Globus extends Namespace {
/**
* The name of the namespace that this class implements.
*/
public static final String NAMESPACE_NAME = Profile.GLOBUS;
private static Map<String,String> mRSLToPegasus;
/**
* Maps Globus RSL keys to corresponding Pegasus Profile Keys
*
* @return
*/
public static Map<String,String> rslToPegasusProfiles(){
if( mRSLToPegasus == null ){
mRSLToPegasus = new HashMap();
mRSLToPegasus.put( Globus.MAX_MEMORY_KEY, Pegasus.MEMORY_KEY );
mRSLToPegasus.put( Globus.MAX_WALLTIME_KEY, Pegasus.RUNTIME_KEY );
mRSLToPegasus.put( Globus.COUNT_KEY, Pegasus.CORES_KEY );
mRSLToPegasus.put( Globus.HOST_COUNT_KEY, Pegasus.NODES_KEY );
mRSLToPegasus.put( Globus.XCOUNT_KEY, Pegasus.PPN_KEY );
mRSLToPegasus.put( Globus.QUEUE_KEY, Pegasus.QUEUE_KEY );
mRSLToPegasus.put( Globus.PROJECT_KEY, Pegasus.PROJECT_KEY );
}
return mRSLToPegasus;
}
private static Map<String,String> mRSLToENV;
/**
* Maps Globus RSL keys to corresponding Env Profile Keys
*
* @return
*/
public static Map<String,String> rslToEnvProfiles(){
if( mRSLToENV == null ){
mRSLToENV = new HashMap();
mRSLToENV.put( Globus.MAX_MEMORY_KEY, "PEGASUS_MEMORY");
mRSLToENV.put( Globus.MAX_WALLTIME_KEY, "PEGASUS_RUNTIME" );
mRSLToENV.put( Globus.COUNT_KEY, "PEGASUS_CORES" );
mRSLToENV.put( Globus.HOST_COUNT_KEY, "PEGASUS_NODES") ;
mRSLToENV.put( Globus.XCOUNT_KEY, "PEGASUS_PPN");
mRSLToENV.put( Globus.QUEUE_KEY, "PEGASUS_QUEUE" );
mRSLToENV.put( Globus.PROJECT_KEY, "PEGASUS_PROJECT" );
}
return mRSLToENV;
}
/**
* Key indicating the number of cores to be used
*/
public static final String COUNT_KEY = "count";
/**
* Key indicating the number of processors per node to be used
*/
public static final String XCOUNT_KEY = "xcount";
/**
* Key indicating the number of hosts to be used
*/
public static final String HOST_COUNT_KEY = "hostcount";
/**
* Key indicating max walltime for a job.
*/
public static final String MAX_WALLTIME_KEY = "maxwalltime";
/**
* Key indicating the maximum memory used.
*/
public static final String MAX_MEMORY_KEY = "maxmemory";
/**
* Key indicating the queue to be used.
*/
public static final String QUEUE_KEY = "queue";
/**
* The project for the job to be associated with.
*/
public static final String PROJECT_KEY = "project";
/**
* The table that maps the various globus profile keys to their aggregator
* functions.
*
*
*/
public static Map mAggregatorTable;
/**
* The default aggregator to be used for profile aggregation, if none specified
* in the aggregator table;
*/
public static Aggregator mDefaultAggregator = new Update();
/**
* Initializer block that populates the Aggregator table just once.
*/
static{
mAggregatorTable = new HashMap( 5 );
Aggregator max = new MAX();
Aggregator sum = new Sum();
//all the times need to be added
mAggregatorTable.put( "maxtime", sum );
mAggregatorTable.put( "maxcputime", sum );
mAggregatorTable.put( "maxwalltime", sum );
//for the memory rsl params we take max
mAggregatorTable.put( "maxmemory", max );
mAggregatorTable.put( "minmemory", max );
}
/**
* The name of the implementing namespace. It should be one of the valid
* namespaces always.
*
* @see Namespace#isNamespaceValid(String)
*/
protected String mNamespace;
/**
* The default constructor.
*/
public Globus(){
mProfileMap = new TreeMap();
mNamespace = NAMESPACE_NAME;
}
/**
* The overloaded constructor
*
* @param map a possibly empty map.
*/
public Globus(Map map){
mProfileMap = new TreeMap(map);
mNamespace = NAMESPACE_NAME;
}
/**
* Returns the name of the namespace associated with the profile
* implementations.
*
* @return the namespace name.
* @see #NAMESPACE_NAME
*/
public String namespaceName(){
return mNamespace;
}
/**
* Constructs a new element of the format (key=value). All the keys
* are converted to lower case before storing.
*
* @param key is the left-hand-side
* @param value is the right hand side
*/
public void construct(String key, String value) {
mProfileMap.put(key.toLowerCase(), value);
}
/**
* Additional method to handle the globus namespace with
* convenience mappings. Currently supported keys are:
*
* <pre>
* arguments - not supported, clashes with Condor
* count - OK
* directory - not supported, clashes with Pegasus
* dryRun - OK, beware the consequences!
* environment - not supported, use env namespace
* executable - not supported, clashes with Condor
* gramMyjob - OK
* hostCount - OK
* jobType - OK to handle MPI jobs
* maxCpuTime - OK
* maxMemory - OK
* maxTime - OK
* maxWallTime - OK
* minMemory - OK
* totalMemory - OK
* project - OK
* queue - OK
* stdin - not supported, clashes with Pegasus
* stdout - not supported, clashes with Pegasus
* stderr - not supported, clashes with Pegasus
*
* rls - OK: Chimera's generic extension (AOB)
* </pre>
*
* @param key is the key within the globus namespace, must be lowercase!
* @param value is the value for the given key.
*
* @return MALFORMED_KEY
* VALID_KEY
* UNKNOWN_KEY
* NOT_PERMITTED_KEY
*/
public int checkKey(String key, String value) {
// sanity checks first
int res = 0;
if (key == null || key.length() < 2 ) {
res = MALFORMED_KEY ;
return res;
}
if( value == null || value.length() < 1 ){
res = EMPTY_KEY;
return res;
}
//before checking convert the key to lower case
key = key.toLowerCase();
switch (key.charAt(0)) {
case 'a':
if( ( key.compareTo( "arch" ) == 0 ) ){
res = VALID_KEY;
}
else if ( (key.compareTo("arguments") == 0) ){
res = NOT_PERMITTED_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'c':
if (key.compareTo( COUNT_KEY ) == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'd':
if (key.compareTo("directory") == 0) {
res = NOT_PERMITTED_KEY;
}
else if (key.compareTo("dryrun") == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'e':
if (key.compareTo("environment") == 0 ||
key.compareTo("executable") == 0) {
res = NOT_PERMITTED_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'g':
if (key.compareTo("grammyjob") == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'h':
if (key.compareTo( HOST_COUNT_KEY ) == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'j':
if (key.compareTo("jobtype") == 0) {
// FIXME: Gaurang?
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'm':
if (key.compareTo("maxcputime") == 0 ||
key.compareTo("maxmemory") == 0 ||
key.compareTo("maxtime") == 0 ||
key.compareTo(Globus.MAX_WALLTIME_KEY) == 0 ||
key.compareTo("minmemory") == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'p':
if (key.compareTo( PROJECT_KEY ) == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'q':
if (key.compareTo( Globus.QUEUE_KEY ) == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'r':
if (key.compareTo("rsl") == 0) {
// our own extension mechanism, no warnings here
// Note: The value IS the RSL!!!
new String(value);
}
else {
res = UNKNOWN_KEY;
}
break;
case 's':
if (key.compareTo("stdin") == 0 ||
key.compareTo("stdout") == 0 ||
key.compareTo("stderr") == 0) {
res = NOT_PERMITTED_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 't':
if (key.compareTo("totalmemory") == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
case 'x':
if (key.compareTo( Globus.XCOUNT_KEY ) == 0) {
res = VALID_KEY;
}
else {
res = UNKNOWN_KEY;
}
break;
default:
res = UNKNOWN_KEY;
}
return res;
}
/**
* Merge the profiles in the namespace in a controlled manner.
* In case of intersection, the new profile value overrides, the existing
* profile value.
*
* @param profiles the <code>Namespace</code> object containing the profiles.
*/
public void merge( Namespace profiles ){
//check if we are merging profiles of same type
if (!(profiles instanceof Globus )){
//throw an error
throw new IllegalArgumentException( "Profiles mismatch while merging" );
}
String key;
Aggregator agg;
for ( Iterator it = profiles.getProfileKeyIterator(); it.hasNext(); ){
key = (String)it.next();
agg = this.aggregator( key );
//load the appropriate aggregator to merge the profiles
this.construct( key,
agg.compute( (String)get( key ), (String)profiles.get( key ), "0" ) );
}
}
/**
* It puts in the namespace specific information specified in the properties
* file into the namespace. The name of the pool is also passed, as many of
* the properties specified in the properties file are on a per pool basis.
* An empty implementation for the timebeing. It is handled in the submit
* writer.
*
* @param properties the <code>PegasusProperties</code> object containing
* all the properties that the user specified at various
* places (like .chimerarc, properties file, command line).
* @param pool the pool name where the job is scheduled to run.
*/
public void checkKeyInNS(PegasusProperties properties, String pool){
//retrieve the relevant profiles from properties
//and merge them into the existing.
this.assimilate( properties ,Profiles.NAMESPACES.globus ) ;
}
/**
* Converts the contents of the map into the string that can be put in the
* Condor file for printing.
*
* @return the textual description.
*/
public String toCondor(){
return convert(mProfileMap);
}
/**
* Returns a copy of the current namespace object
*
* @return the Cloned object
*/
public Object clone(){
return new Globus(this.mProfileMap);
}
/**
* Returns the aggregator to be used for the profile key while merging.
* If no aggregator is found, the then default Aggregator (Update) is used.
*
* @param key the key for which the aggregator is found.
*
* @return the aggregator for the profile key.
*/
protected Aggregator aggregator( String key ){
Object aggregator = this.mAggregatorTable.get( key );
return ( aggregator == null )? mDefaultAggregator : (Aggregator)aggregator;
}
/**
* Converts a map with RSL kv-pairs into an RSL string.
*
* @param rsl is the RSL map to convert
* @return the new string to use in globusrsl of Condor.
*/
private String convert(java.util.Map rsl) {
StringBuffer result = new StringBuffer();
for (Iterator i = rsl.keySet().iterator(); i.hasNext(); ) {
String key = (String) i.next();
String value = (String) rsl.get(key);
if (value != null && value.length() > 0) {
if (key.compareTo("rsl") == 0) {
result.append(value);
}
else {
result.append('(').append(key).append('=').append(value).
append(')');
}
}
}
return result.toString();
}
}