2This modules contains objects that make it simple for the user to
3create python scripts that build Condor DAGs to run code on the LSC
6This file is part of the Grid LSC User Environment (GLUE)
8GLUE is free software: you can redistribute it and/or modify it under the
9terms of the GNU General Public License as published by the Free Software
10Foundation, either version 3 of the License, or (at your option) any later
13This program is distributed in the hope that it will be useful, but WITHOUT
14ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18You should have received a copy of the GNU General Public License along with
19this program. If not, see <http://www.gnu.org/licenses/>.
22from __future__
import print_function
23__author__ =
'Duncan Brown <duncan@gravity.phys.uwm.edu>'
24from gstlal
import __date__, __version__
26from collections
import OrderedDict
37from hashlib
import md5
41 from cjson
import decode
43 from json
import loads
as decode
46 "all functionality within this module exists to support deprecated pipeline "
47 "generation programs, and will be removed from gstlal in the future",
53 """Error thrown by Condor Jobs"""
58class CondorSubmitError(CondorError):
73 Generic condor job class. Provides methods to set the options in the
74 condor submit file for a particular executable
76 def __init__(self, universe, executable, queue):
78 @param universe: the condor universe to run the job in.
79 @param executable: the executable to run.
80 @param queue: number of jobs to queue.
107 Return the name of the executable for this job.
113 Set the name of the executable for this job.
119 Return the condor universe that the job will run in.
125 Set the condor universe for the job to run in.
126 @param universe: the condor universe to run the job in.
132 Return the grid type of the job.
138 Set the type of grid resource for the job.
139 @param grid_type: type of grid resource.
145 Return the grid server on which the job will run.
151 Set the grid server on which to run the job.
152 @param grid_server: grid server on which to run.
158 Return the grid scheduler.
164 Set the grid scheduler.
165 @param grid_scheduler: grid scheduler on which to run.
171 If executable installed is true, then no copying of the executable is
172 done. If it is false, pegasus stages the executable to the remote site.
173 Default is executable is installed (i.e. True).
174 @param installed: true or fale
180 return whether or not the executable is installed
186 Add a Condor command to the submit file (e.g. a class add or evironment).
187 @param cmd: Condor command directive.
188 @param value: value for command.
194 Return the dictionary of condor keywords to add to the job
200 Add filename as a necessary input file for this DAG node.
202 @param filename: input filename to add
209 Add filename as a output file for this DAG node.
211 @param filename: output filename to add
218 Add filename as a checkpoint file for this DAG job.
225 Return list of input files for this DAG node.
231 Return list of output files for this DAG node.
237 Return a list of checkpoint files for this DAG node
243 Add an argument to the executable. Arguments are appended after any
244 options and their order is guaranteed.
245 @param arg: argument to add.
251 Add a file argument to the executable. Arguments are appended after any
252 options and their order is guaranteed. Also adds the file name to the
253 list of required input data for this job.
254 @param filename: file to add as argument.
262 Return the list of arguments that are to be passed to the executable.
268 Add a command line option to the executable. The order that the arguments
269 will be appended to the command line is not guaranteed, but they will
270 always be added before any command line arguments. The name of the option
271 is prefixed with double hyphen and the program is expected to parse it
273 @param opt: command line option to add.
274 @param value: value to pass to the option (None for no argument).
280 Returns the value associated with the given command line option.
281 Returns None if the option does not exist in the options list.
282 @param opt: command line option
290 Add a command line option to the executable. The order that the arguments
291 will be appended to the command line is not guaranteed, but they will
292 always be added before any command line arguments. The name of the option
293 is prefixed with double hyphen and the program is expected to parse it
295 @param opt: command line option to add.
296 @param value: value to pass to the option (None for no argument).
304 Return the dictionary of opts for the job.
310 Add a command line option to the executable. The order that the arguments
311 will be appended to the command line is not guaranteed, but they will
312 always be added before any command line arguments. The name of the option
313 is prefixed with single hyphen and the program is expected to parse it
314 with getopt() or getopt_long() (if a single character option), or
315 getopt_long_only() (if multiple characters). Long and (single-character)
316 short options may be mixed if the executable permits this.
317 @param opt: command line option to add.
318 @param value: value to pass to the option (None for no argument).
324 Return the dictionary of short options for the job.
330 Parse command line options from a given section in an ini file and
331 pass to the executable.
332 @param cp: ConfigParser object pointing to the ini file.
333 @param section: section of the ini file to add to the options.
335 for opt
in cp.options(section):
336 arg = str(cp.get(section,opt)).strip()
341 Set the email address to send notification to.
342 @param value: email address or never for no notification.
348 Set the Condor log file.
349 @param path: path to log file.
355 Set the file from which Condor directs the stdin of the job.
356 @param path: path to stdin file.
362 Get the file from which Condor directs the stdin of the job.
368 Set the file to which Condor directs the stderr of the job.
369 @param path: path to stderr file.
375 Get the file to which Condor directs the stderr of the job.
381 Set the file to which Condor directs the stdout of the job.
382 @param path: path to stdout file.
388 Get the file to which Condor directs the stdout of the job.
394 Set the name of the file to write the Condor submit file to when
395 write_sub_file() is called.
396 @param path: path to submit file.
402 Get the name of the file which the Condor submit file will be
403 written to when write_sub_file() is called.
409 Write a submit file for this Condor job.
439 subfile.write(
'universe = ' + self.
__universe +
'\n' )
440 subfile.write(
'executable = ' + self.
__executable +
'\n' )
444 subfile.write(
'grid_resource = %s %s\n' % (self.
__grid_type,
447 subfile.write(
'grid_resource = %s %s %s\n' % (self.
__grid_type,
451 subfile.write(
'when_to_transfer_output = ON_EXIT\n')
452 subfile.write(
'transfer_output_files = $(macrooutput)\n')
453 subfile.write(
'transfer_input_files = $(macroinput)\n')
456 subfile.write(
'arguments = "' )
458 subfile.write(
' ' + c )
461 subfile.write(
' --' + c +
' ' + self.
__options[c] )
463 subfile.write(
' --' + c )
468 subfile.write(
' -' + c )
469 subfile.write(
' "\n' )
472 subfile.write( str(cmd) +
" = " + str(self.
__condor_cmds[cmd]) +
'\n' )
474 subfile.write(
'log = ' + self.
__log_file +
'\n' )
476 subfile.write(
'input = ' + self.
__in_file +
'\n' )
477 subfile.write(
'error = ' + self.
__err_file +
'\n' )
478 subfile.write(
'output = ' + self.
__out_file +
'\n' )
481 subfile.write(
'queue ' + str(self.
__queue) +
'\n' )
489 A Condor DAG job never notifies the user on completion and can have variable
490 options that are set for a particular node in the DAG. Inherits methods
495 universe = the condor universe to run the job in.
496 executable = the executable to run in the DAG.
498 super(CondorDAGJob,self).
__init__(universe, executable, 1)
499 CondorJob.set_notification(self,
'never')
509 Create a condor node from this job. This provides a basic interface to
510 the CondorDAGNode class. Most jobs in a workflow will subclass the
511 CondorDAGNode class and overwrite this to give more details when
512 initializing the node. However, this will work fine for jobs with very simp
519 Set the grid site to run on. If not specified,
520 will not give hint to Pegasus
528 Return the grid site for this node
534 Add a variable (or macro) option to the condor job. The option is added
535 to the submit file and a different argument to the option can be set for
536 each node in the DAG.
537 @param opt: name of option to add.
545 self.
add_opt(opt,
'$(macro' + macro +
')')
549 Add a condor command to the submit file that allows variable (macro)
550 arguments to be passes to the executable.
559 Add a command to the submit file to allow variable (macro) arguments
560 to be passed to the executable.
568 self.
__var_args.append(
"'$(macroargument%s)'" % str(arg_index))
570 self.
__var_args.append(
'$(macroargument%s)' % str(arg_index))
577 Condor DAGMan job class. Appropriate for setting up DAGs to run within a
582 dag = the name of the condor dag file to run
583 dir = the diretory in which the dag file is located
591 Create a condor node from this job. This provides a basic interface to
592 the CondorDAGManNode class. Most jobs in a workflow will subclass the
593 CondorDAGManNode class and overwrite this to give more details when
594 initializing the node. However, this will work fine for jobs with very simp
601 Set the directory where the dag will be run
602 @param dir: the name of the directory where the dag will be run
608 Get the directory where the dag will be run
614 Set the email address to send notification to.
615 @param value: email address or never for no notification.
621 Return the name of the dag as the submit file name for the
622 SUBDAG EXTERNAL command in the uber-dag
628 Do nothing as there is not need for a sub file with the
629 SUBDAG EXTERNAL command in the uber-dag
635 Return the name of any associated dag file
642 A CondorDAGNode represents a node in the DAG. It corresponds to a particular
643 condor job (and so a particular submit file). If the job has variable
644 (macro) options, they can be set here so each nodes executes with the
649 @param job: the CondorJob that this node corresponds to.
651 if not isinstance(job, CondorDAGJob)
and \
652 not isinstance(job,CondorDAGManJob):
654 "A DAG node must correspond to a Condor DAG job or Condor DAGMan job")
674 if isinstance(job,CondorDAGJob)
and job.get_universe()==
'standard':
680 t = str( int( time.time() * 1000 ) )
681 r = str( int( random.random() * 100000000000000000 ) )
682 a = str( self.__class__ )
683 self.
__name = md5((t + r + a).encode()).hexdigest()
691 Return the CondorJob that this node is associated with.
697 Sets the name of the pre script that is executed before the DAG node is
699 @param script: path to script
705 Adds an argument to the pre script that is executed before the DAG node is
712 Sets the name of the post script that is executed before the DAG node is
714 @param script: path to script
720 returns the name of the post script that is executed before the DAG node is
722 @param script: path to script
728 Adds an argument to the post script that is executed before the DAG node is
735 Returns and array of arguments to the post script that is executed before
742 Set the name for this node in the DAG.
748 Get the name for this node in the DAG.
754 Set the category for this node in the DAG.
760 Get the category for this node in the DAG.
766 Set the priority for this node in the DAG.
772 Get the priority for this node in the DAG.
778 Add filename as a necessary input file for this DAG node.
780 @param filename: input filename to add
784 if not isinstance(self.
job(), CondorDAGManJob):
785 if self.
job().get_universe() ==
'grid':
790 Add filename as a output file for this DAG node.
792 @param filename: output filename to add
796 if not isinstance(self.
job(), CondorDAGManJob):
797 if self.
job().get_universe() ==
'grid':
802 Add filename as a checkpoint file for this DAG node
803 @param filename: checkpoint filename to add
807 if not isinstance(self.
job(), CondorDAGManJob):
808 if self.
job().get_universe() ==
'grid':
813 Return list of input files for this DAG node and its job.
816 if isinstance(self.
job(), CondorDAGJob):
822 Return list of output files for this DAG node and its job.
825 if isinstance(self.
job(), CondorDAGJob):
831 Return a list of checkpoint files for this DAG node and its job.
834 if isinstance(self.
job(), CondorDAGJob):
836 return checkpoint_files
840 Set the name of the VDS group key when generating a DAX
841 @param group: name of group for thus nore
847 Returns the VDS group key for this node
853 Add a variable (macro) for this node. This can be different for
854 each node in the DAG, even if they use the same CondorJob. Within
855 the CondorJob, the value of the macro can be referenced as
856 '$(name)' -- for instance, to define a unique output or error file
858 @param name: macro name.
859 @param value: value of the macro for this node in the DAG
862 self.
__opts[macro] = value
866 Add a variable (macro) for storing the input/output files associated
868 @param io: macroinput or macrooutput
869 @param filename: filename of input/output file
873 self.
__opts[io] = filename
875 if filename
not in self.
__opts[io]:
876 self.
__opts[io] +=
',%s' % filename
880 Add a variable (macro) for storing the input files associated with
882 @param filename: filename of input file
888 Add a variable (macro) for storing the output files associated with
890 @param filename: filename of output file
894 def add_checkpoint_macro(self,filename):
899 Return the opts for this node. Note that this returns only
900 the options for this instance of the node and not those
901 associated with the underlying job template.
907 Add a variable (macro) condor command for this node. If the command
908 specified does not exist in the CondorJob, it is added so the submit file
910 PLEASE NOTE: AS with other add_var commands, the variable must be set for
911 all nodes that use the CondorJob instance.
912 @param command: command name
913 @param value: Value of the command for this node in the DAG/DAX.
916 self.
__macros[
'macro' + macro] = value
921 Add a variable (macro) option for this node. If the option
922 specified does not exist in the CondorJob, it is added so the submit
923 file will be correct when written.
924 @param opt: option name.
925 @param value: value of the option for this node in the DAG.
928 self.
__opts[
'macro' + macro] = value
933 Add a variable (macro) option for this node. If the option
934 specified does not exist in the CondorJob, it is added so the submit
935 file will be correct when written. The value of the option is also
936 added to the list of input files for the DAX.
937 @param opt: option name.
938 @param value: value of the option for this node in the DAG.
939 @param file_is_output_file: A boolean if the file will be an output file
940 instead of an input file. The default is to have it be an input.
948 Add a variable (or macro) argument to the condor job. The argument is
949 added to the submit file and a different value of the argument can be set
950 for each node in the DAG.
951 @param arg: name of option to add.
959 Add a variable (or macro) file name argument to the condor job. The
960 argument is added to the submit file and a different value of the
961 argument can be set for each node in the DAG. The file name is also
962 added to the list of input files for the DAX.
963 @param filename: name of option to add.
970 Return the arguments for this node. Note that this returns
971 only the arguments for this instance of the node and not those
972 associated with the underlying job template.
978 Set the number of times that this node in the DAG should retry.
979 @param retry: number of times to retry node.
985 Return the number of times that this node in the DAG should retry.
986 @param retry: number of times to retry node.
992 Write the DAG entry for this node's job to the DAG file descriptor.
993 @param fh: descriptor of open DAG file.
995 if isinstance(self.
job(),CondorDAGManJob):
998 [
'SUBDAG EXTERNAL', self.
__name, self.
__job.get_sub_file()]) )
999 if self.
job().get_dag_directory():
1000 fh.write(
' DIR ' + self.
job().get_dag_directory() )
1003 fh.write(
'JOB ' + self.
__name +
' ' + self.
__job.get_sub_file() )
1006 fh.write(
'RETRY ' + self.
__name +
' ' + str(self.
__retry) +
'\n' )
1010 Write the DAG entry for this node's category to the DAG file descriptor.
1011 @param fh: descriptor of open DAG file.
1017 Write the DAG entry for this node's priority to the DAG file descriptor.
1018 @param fh: descriptor of open DAG file.
1024 Write the variable (macro) options and arguments to the DAG file
1026 @param fh: descriptor of open DAG file.
1029 fh.write(
'VARS ' + self.
__name )
1031 fh.write(
' ' + str(k) +
'="' + str(self.
__macros[k]) +
'"' )
1032 for k
in self.
__opts.keys():
1033 fh.write(
' ' + str(k) +
'="' + str(self.
__opts[k]) +
'"' )
1036 fh.write(
' macroargument' + str(i) +
'="' + self.
__args[i] +
'"' )
1041 Write the parent/child relations for this job to the DAG file descriptor.
1042 @param fh: descriptor of open DAG file.
1045 fh.write(
'PARENT ' +
" ".join((str(p)
for p
in self.
__parents)) +
' CHILD ' + str(self) +
'\n' )
1049 Write the pre script for the job, if there is one
1050 @param fh: descriptor of open DAG file.
1053 fh.write(
'SCRIPT PRE ' + str(self) +
' ' + self.
__pre_script +
' ' +
1058 Write the post script for the job, if there is one
1059 @param fh: descriptor of open DAG file.
1062 fh.write(
'SCRIPT POST ' + str(self) +
' ' + self.
__post_script +
' ' +
1067 Write as a comment into the DAG file the list of input files
1070 @param fh: descriptor of open DAG file.
1073 fh.write(
"## Job %s requires input file %s\n" % (self.
__name, f))
1077 Write as a comment into the DAG file the list of output files
1080 @param fh: descriptor of open DAG file.
1083 fh.write(
"## Job %s generates output file %s\n" % (self.
__name, f))
1087 Set the Condor log file to be used by this CondorJob.
1088 @param log: path of Condor log file.
1094 Add a parent to this node. This node will not be executed until the
1095 parent node has run sucessfully.
1096 @param node: CondorDAGNode to add as a parent.
1098 if not isinstance(node, (CondorDAGNode,CondorDAGManNode) ):
1104 Return a list of tuples containg the command line arguments
1108 pat = re.compile(
r'\$\((.+)\)')
1109 argpat = re.compile(
r'\d+')
1120 arg_index = int(argpat.findall(a)[0])
1122 cmd_list.append((
"%s" % macros[arg_index],
""))
1126 cmd_list.append((
"%s" % a,
""))
1139 cmd_list.append((
"--%s" % k, str(value)))
1141 cmd_list.append((
"--%s" % k, str(val)))
1144 options = self.
job().get_short_opts()
1153 cmd_list.append((
"-%s" % k, str(value)))
1155 cmd_list.append((
"-%s" % k, str(val)))
1161 Return the full command line that will be used when this node
1167 for argument
in cmd_list:
1168 cmd +=
' '.join(argument) +
" "
1174 The finalize method of a node is called before the node is
1175 finally added to the DAG and can be overridden to do any last
1176 minute clean up (such as setting extra command line arguments)
1181class CondorDAGManNode(CondorDAGNode):
1183 Condor DAGMan node class. Appropriate for setting up DAGs to run within a
1184 DAG. Adds the user-tag functionality to condor_dagman processes running in
1185 the DAG. May also be used to extend dagman-node specific functionality.
1189 @job: a CondorDAGNodeJob
1191 super(CondorDAGManNode,self).
__init__(job)
1198 Set the user tag that is passed to the analysis code.
1199 @param user_tag: the user tag to identify the job
1205 Returns the usertag string
1211 Add a category to this DAG called categoryName with a maxjobs of maxJobsNum.
1212 @param node: Add (categoryName,maxJobsNum) tuple to CondorDAG.__maxjobs_categories.
1218 Return an array of tuples containing (categoryName,maxJobsNum)
1224 Set the type of job clustering pegasus can use to collapse jobs
1225 @param cluster: clustering type
1231 Returns the usertag string
1238 A CondorDAG is a Condor Directed Acyclic Graph that describes a collection
1239 of Condor jobs and the order in which to run them. All Condor jobs in the
1240 DAG must write their Codor logs to the same file.
1241 NOTE: The log file must not be on an NFS mounted system as the Condor jobs
1242 must be able to get an exclusive file lock on the log file.
1246 @param log: path to log file which must not be on an NFS mounted file system.
1259 Return a list containing all the nodes in the DAG
1265 Return a list containing all the jobs in the DAG
1271 Use integer node names for the DAG
1277 Set the name of the file into which the DAG is written.
1278 @param path: path to DAG file.
1284 Return the path to the DAG file.
1293 Add a CondorDAGNode to this DAG. The CondorJob that the node uses is
1294 also added to the list of Condor jobs in the DAG so that a list of the
1295 submit files needed by the DAG can be maintained. Each unique CondorJob
1296 will be added once to prevent duplicate submit files being written.
1297 @param node: CondorDAGNode to add to the CondorDAG.
1299 if not isinstance(node, CondorDAGNode):
1300 raise CondorDAGError(
"Nodes must be class CondorDAGNode or subclass")
1301 if not isinstance(node.job(), CondorDAGManJob):
1307 if node.job()
not in self.
__jobs:
1308 self.
__jobs.append(node.job())
1312 Add a category to this DAG called categoryName with a maxjobs of maxJobsNum.
1313 @param node: Add (categoryName,maxJobsNum) tuple to CondorDAG.__maxjobs_categories.
1319 Return an array of tuples containing (categoryName,maxJobsNum)
1325 Write the DAG entry for this category's maxjobs to the DAG file descriptor.
1326 @param fh: descriptor of open DAG file.
1327 @param category: tuple containing type of jobs to set a maxjobs limit for
1328 and the maximum number of jobs of that type to run at once.
1330 fh.write(
'MAXJOBS ' + str(category[0]) +
' ' + str(category[1]) +
'\n' )
1334 Write all the submit files used by the dag to disk. Each submit file is
1335 written to the file name set in the CondorJob.
1341 job.write_sub_file()
1345 Write all the nodes in the DAG to the DAG file.
1354 node.write_job(dagfile)
1355 node.write_vars(dagfile)
1356 if node.get_category():
1357 node.write_category(dagfile)
1358 if node.get_priority():
1359 node.write_priority(dagfile)
1360 node.write_pre_script(dagfile)
1361 node.write_post_script(dagfile)
1362 node.write_input_files(dagfile)
1363 node.write_output_files(dagfile)
1365 node.write_parents(dagfile)
1381 Write the workflow to a script (.sh instead of .dag).
1383 Assuming that parents were added to the DAG before their children,
1384 dependencies should be handled correctly.
1390 outfilename =
".".join(dfp.split(
".")[:-1]) +
".sh"
1391 outfile = open(outfilename,
"w")
1396 outfile.write(
"# Job %s\n" % node.get_name())
1398 if isinstance(node,CondorDAGManNode):
1399 outfile.write(
"condor_submit_dag %s\n\n" % (node.job().get_dag()))
1401 outfile.write(
"%s %s\n\n" % (node.job().get_executable(),
1402 node.get_cmd_line()))
1405 os.chmod(outfilename, os.stat(outfilename)[0] | stat.S_IEXEC)
set_grid_site(self, site)
add_var_condor_cmd(self, command)
add_var_arg(self, arg_index, quote=False)
__init__(self, universe, executable)
add_var_opt(self, opt, short=False)
set_dag_directory(self, dir)
set_notification(self, value)
__init__(self, dag, dir=None)
set_user_tag(self, usertag)
get_maxjobs_categories(self)
list __maxjobs_categories
set_cluster_jobs(self, cluster)
add_maxjobs_category(self, categoryName, maxJobsNum)
set_priority(self, priority)
set_post_script(self, script)
add_var_opt(self, opt, value, short=False)
add_output_macro(self, filename)
add_checkpoint_file(self, filename)
add_var_arg(self, arg, quote=False)
set_pre_script(self, script)
add_input_macro(self, filename)
add_pre_script_arg(self, arg)
get_checkpoint_files(self)
write_post_script(self, fh)
add_output_file(self, filename)
write_pre_script(self, fh)
add_io_macro(self, io, filename)
get_post_script_arg(self)
add_var_condor_cmd(self, command, value)
add_post_script_arg(self, arg)
add_file_arg(self, filename)
add_input_file(self, filename)
write_output_files(self, fh)
add_macro(self, name, value)
set_vds_group(self, group)
write_input_files(self, fh)
set_category(self, category)
add_checkpoint_macro(self, filename)
add_file_opt(self, opt, filename, file_is_output_file=False)
add_maxjobs_category(self, categoryName, maxJobsNum)
write_maxjobs(self, fh, category)
set_integer_node_names(self)
get_maxjobs_categories(self)
list __maxjobs_categories
add_ini_opts(self, cp, section)
add_output_file(self, filename)
set_notification(self, value)
get_executable_installed(self)
set_stdin_file(self, path)
__init__(self, universe, executable, queue)
set_grid_type(self, grid_type)
add_checkpoint_file(self, filename)
add_file_arg(self, filename)
set_universe(self, universe)
set_executable(self, executable)
add_short_opt(self, opt, value)
set_stderr_file(self, path)
set_grid_scheduler(self, grid_scheduler)
get_checkpoint_files(self)
set_stdout_file(self, path)
add_input_file(self, filename)
add_file_opt(self, opt, filename)
add_condor_cmd(self, cmd, value)
add_opt(self, opt, value)
set_grid_server(self, grid_server)
set_executable_installed(self, installed)
bool __executable_installed