condor_transform_ads language

Transform rules files consist of lines containing key=value pairs or transform commands such as SET, RENAME, etc. Transform commands execute as they are read and can make use of values set up until that point using the $(key) macro substitution commands that HTCondor configuration files and condor_submit files use.

Most constructs that work in these files will also work in rules files such as if/else. Macro substitution will fetch attributes of the ClassAd to be transformed when $(MY.attr) is used.

The transform commands are:

SET <attr> <expr> Set <attr> to <expr>
EVALSET <attr> <expr> Evaluate <expr> and then set <attr> to the result
DEFAULT <attr> <expr> Set <attr> to <expr> if <attr> is undefined or missing
COPY <attr> <newattr> Copy the value of <attr> to <newattr>
COPY /<regex>/ <newattrs> Copy the values of attributes whose names match <regex> to <newattrs>
RENAME <attr> <newattr> Rename <attr> to <newattr>
RENAME /<regex>/ <newattrs> Rename attributes matching <regex> to <newattrs>
DELETE <attr> <newattr> Delete <attr>
DELETE /<regex>/ Delete attributes matching <regex>
EVALMACRO <key> <expr> Evaluate <expr> and then insert it as a transform macro value

In the above commands <attr> must be a valid attribute name and <expr> a valid ClassAd expression or literal. The various $() macros will be expanded in <expr>, <newattr> or <newattrs> before they are parsed as ClassAd expressions or attribute names.

When a COPY, RENAME, or DELETE with <regex> is used, regex capture groups are substituted in <newattrs> after $() expansion. \0 will expand to the entire match, \1 to the first capture, etc.

Optionally, a transform rule set can end with an iteration command

TRANSFORM [<N>] [<vars>] [in <list> | from <file> | matching <pattern>]

A TRANSFORM command must be the last command in the rules file. It takes the same options as the QUEUE statement from a HTCONDOR submit file. There is an implicit TRANSFORM 1 at the end of a rules file that has no explicit TRANSFORM command.

OSGs default route expressed in the TJ's proposed new transform language.

lines starting with the keywords set, delete, copy, rename, name are commands to the transform engine and are executed as they are read. lines that are of the form key = value set temporary variables that can be referenced in transform commmands using the various $() and $function() macro expansions that are common to config and submit files.

new transform language
NAME OSG CE Default route

MaxIdleJobs = 2000
MaxJobs = 10000

# by default, accept all jobs
Requirements = True

# these triggers control IF statements later in the transform
tmp.ExpireJob = False
tmp.RemoveIfIdle = False


# modify routed job attributes
#
DELETE CondorCE
SET    RoutedJob True

# remove routed job if the client disappears for 48 hours or it is idle for 6
#
IF $(tmp.RemoveIfIdle)
  SET    PeriodicRemove (LastClientContact - time() > 48*60*60) || (JobStatus == 1 && (time() - QDate) > 6*60)
ELSE
  DELETE PeriodicRemove
ENDIF

# insert HOME and OSG_* into environment
#
tmp.osg_env = OSG_GRID='/etc/osg/wn-client/' OSG_SQUID_LOCATION='fermicloud133.fnal.gov:3128' OSG_SITE_READ='None' OSG_APP='/share/osg/app' OSG_GLEXEC_LOCATION='None' OSG_DATA='UNAVAILABLE'
tmp.osg_env = $(tmp.osg_env) OSG_HOSTNAME='fermicloud136.fnal.gov' OSG_STORAGE_ELEMENT='False' OSG_SITE_NAME='herp' GLOBUS_LOCATION='/usr' OSG_WN_TMP='None' OSG_DEFAULT_SE='None' OSG_SITE_WRITE='None'
SET osg_environment "$(tmp.osg_env)"

tmp.user_home_expr = userHome(Owner, "/")
tmp.user_home = HOME=$EVAL(tmp.user_home_expr)

COPY Environment orig_environment
SET  Environment "$(tmp.user_home) $(MY.orig_environment) $(MY.osg_environment)"

# pick up GlobusRSL settings, we will use those later in the transform
# NOTE: is it a bug to leave this attribute behind?
#
# set InputRSL = ifThenElse(GlobusRSL is null, [], eval_rsl(GlobusRSL));
# or possibly this
IF DEFINED MY.GlobusRSL
  SET InputRSL eval_rsl(GlobusRSL)
ELSE
  SET InputRSL []
ENDIF

# Set new requirements
IF $(tmp.ExpireJob)
  SET Requirements (LastClientContact - time()) < 30*60
ELSE
  SET Requirements True
ENDIF

# pass attributes (maxMemory,xcount,jobtype,queue)
# via gWMS Factory described within ClassAd if undefined via RSL
# Note default memory request of 2GB
#
IF DEFINED MY.InputRSL.MaxMemory
  SET RequestMemory $(MY.InputRSL.MaxMemory)
ELIF $(MY.MaxMemory)
  SET RequestMemory MaxMemory
ELSE
  SET RequestMemory $(MY.default_maxMemory:2000)
ENDIF

IF DEFINED MY.InputRSL.Queue
  SET remote_queue "$(MY.InputRSL.Queue)"
ELIF DEFINED MY.Queue
  SET remote_queue Queue
ELSE
  SET remote_queue "$(MY.default_queue)"
ENDIF

# Figure out the number of cores. HTCondor uses RequestCpus
# blahp uses SMPGranularity and NodeNumber.  Default is 1 core.
#
IF DEFINED MY.InputRSL.xcount
  tmp.cpus = $(MY.InputRSL.xcount)
ELIF $(MY.xcount)
  tmp.cpus = $(MY.xcount)
ELSE
  tmp.cpus = $(MY.default_xcount:1)
ENDIF
SET RequestCpus $(tmp.cpus)
SET SMPGranularity $(tmp.cpus)
SET NodeNumber $(tmp.cpus)

# If remote_cerequirements is a string, BLAH will parse it as an expression before examining it
#
SET remote_cerequirements "CONDOR_CE == 1"

# add a walltime to the remote_cerequirements expression if one is given.
tmp.MaxWalltime_expr = 60 * (InputRSL.MaxWalltime ?: MaxWalltime ?: default_MaxWalltime ?: 0)
IF $INT(tmp.MaxWalltime_expr)
  SET remote_cerequirements "Walltime == $INT(tmp.MaxWalltime_expr) && CondorCE == 1"
ENDIF

For reference here is the same route expressed in the current new classad syntax

current job router language
[ MaxIdleJobs = 2000;
 MaxJobs = 10000;
 /* by default, accept all jobs */
 Requirements = True;

 /* now modify routed job attributes */

 /* remove routed job if the client disappears for 48 hours or it is idle for 6 */
 /*set_PeriodicRemove = (LastClientContact - time() > 48*60*60) || (JobStatus == 1 && (time() - QDate) > 6*60); */
 delete_PeriodicRemove = true;
 delete_CondorCE = true;
 set_RoutedJob = true;
 copy_environment = "orig_environment";
 set_osg_environment = "OSG_GRID='/etc/osg/wn-client/' OSG_SQUID_LOCATION='fermicloud133.fnal.gov:3128' OSG_SITE_READ='None' OSG_APP='/share/osg/app' OSG_GLEXEC_LOCATION='None' OSG_DATA='UNAVAILABLE' OSG_HOSTNAME='fermicloud136.fnal.gov' OSG_STORAGE_ELEMENT='False' OSG_SITE_NAME='herp' GLOBUS_LOCATION='/usr' OSG_WN_TMP='None' OSG_DEFAULT_SE='None' OSG_SITE_WRITE='None'";
 eval_set_environment = debug(strcat("HOME=",
                              userHome(Owner, "/"),
                              " ",
                              ifThenElse(orig_environment is undefined,
                                         osg_environment,
                                         strcat(osg_environment, " ", orig_environment) )));

 /* Set new requirements */
 /* set_requirements = LastClientContact - time() < 30*60;*/
 set_requirements = True;

 set_InputRSL = ifThenElse(GlobusRSL is null, [], eval_rsl(GlobusRSL));

 /* Note default memory request of 2GB */
 /* Note yet another nested condition allow pass attributes (maxMemory,xcount,jobtype,queue) via gWMS Factory described within ClassAd if undefined via RSL */
 eval_set_RequestMemory = ifThenElse(InputRSL.maxMemory isnt null,
                                     InputRSL.maxMemory,
                                     ifThenElse(maxMemory isnt null,
                                                maxMemory,
                                                ifThenElse(default_maxMemory isnt null,
                                                           default_maxMemory, 2000)));
 eval_set_remote_queue = ifThenElse(InputRSL.queue isnt null,
                                    InputRSL.queue,
                                    ifThenElse(queue isnt null,
                                               queue,
                                               ifThenElse(default_queue isnt null, default_queue, "")));

 /* HTCondor uses RequestCpus;
 blahp uses SMPGranularity and NodeNumber.  Default is 1 core. */
 eval_set_RequestCpus = ifThenElse(InputRSL.xcount isnt null,
                                   InputRSL.xcount,
                                   ifThenElse(xcount isnt null,
                                              xcount,
                                              ifThenElse(default_xcount isnt null, default_xcount, 1)));
 eval_set_remote_SMPGranularity = ifThenElse(InputRSL.xcount isnt null,
                                             InputRSL.xcount,
                                             ifThenElse(xcount isnt null,
                                                        xcount,
                                                        ifThenElse(default_xcount isnt null, default_xcount, 1)));
 eval_set_remote_NodeNumber = ifThenElse(InputRSL.xcount isnt null,
                                         InputRSL.xcount,
                                         ifThenElse(xcount isnt null,
                                                    xcount,
                                                    ifThenElse(default_xcount isnt null, default_xcount, 1)));

 /* If remote_cerequirements is a string, BLAH will parse it as an expression before examining it */
 eval_set_remote_cerequirements = ifThenElse(InputRSL.maxWalTlime isnt null,
                                             strcat("Walltime == ",string(60*InputRSL.maxWallTime)," && CondorCE == 1"),
                                             ifThenElse(maxWallTime isnt null,
                                                        strcat("Walltime == ",string(60*maxWallTime)," && CondorCE == 1"),
                                                        ifThenElse(default_maxWallTime isnt null,
                                                                   strcat("Walltime == ", string(60*default_maxWallTime), " && CondorCE == 1"),
                                                                   "CondorCE == 1")));
]