-
Notifications
You must be signed in to change notification settings - Fork 176
Extended JobManifest including execution logic
Current DIRAC Workflow implementation is over complicated, and at the same time it misses a fundamental piece: logic control (what to do if Module X execution produces an error, or if I want to do loops, or if statements).
Start from the current JobManifest implementation as DIRAC CFG we need to allow a complete description of the workload execution Executable and Arguments are not simple Options like now, but rather full sections. Once we agree on such description a tool that would take care of controlling its execution should allow to substitute the current "dirac-jobexec + JobDescription.xml" solution. The new mechanism should handle in the same way all user jobs.
The extended JobManifest will move from:
{ Executable = /bin/ls Arguments = -ltr *.txt }
To:
{ Executable { ... } Arguments { ... } }
Let's start with Arguments. In the simpler case it is just an option with an string value (just like now) but also other alternatives must be added: numerical values, coma separated list of strings or numerical values, and finally a CFG section that translate into a kwargs dict. So we should be able to handle:
Arguments = Hello
Arguments = Hello, World
Arguments = 1
Arguments = 0.5, 0.5, 3
Arguments { Arguments = Any of the above }
Arguments { Site = MySite LogLevel = DEBUG Iterations = 10 }
Have a look at https://github.com/acasajus/DIRAC/tree/dev-split, in https://github.com/DIRACGrid/DIRAC/pull/1625, that allows to use variables in CFGs and thus in JobManifest:
https://github.com/acasajus/DIRAC/blob/dev-split/Core/Utilities/CFG.py#L1019
@gCFGSynchro def expand( self ): """ Expand all options into themselves a = something-$b b = hello will end up with a = something-hello b = hello """
The logic below will also be required:
{ A { ... } B = $A }
producing:
{ A { ... } B { ... } }
B will be a deep copy of A (as a section), this needs to be implemented.
For the Executable, in the simpler case we have to support the current situation, it is an option with a string value. But in the general case it will be a full CFG section describing the job execution logic:
Executable = /bin/ls
should become:
Executable { ... }
What needs to be described in that new section is:
- A list of named modules that are going to be used. Few standard ones should be available like "Exit", "ShellExec", "GetData", etc
- An execution logic
- An error handing logic
So in first approach it will look like:
Executable { Modules { ... } Execution { ... } }
Some Options could be defined to manipulate arguments, merging, concatenating, etc. And in a trivial case Modules section can be skipped.
Now to each of these section (this is still quite preliminary ideas, but I think are not too far from a working solution).
Modules: it is a container of sections each one describing an named module. On execution each module will be instantiated, it will have to inherit from a proper "Module" class to be defined and include an execute method that returns S_OK/S_ERROR with some limitations (only string, numerical types and lists, sequences or dicts using them are allowed) and a "getCFG" method that returns the default entry to be added when creating a new job description.
Modules { # This are named description of each module to be used in Execution # For simple cases the Module can be named after the python Module # and search for in a default location: # [XXX]DIRAC.Job.Modules.[ModuleName] # in this case Path can be skipped # the Default Arguments for the Module is the Input CFG defined in Execution section # Arguments can be a string, a list of strings or a full section (dictionary) # the default Output is the Result dictionary returned by the execute method # All Options with updated values after the execution of the module should be made available # ShellExec { # This path could be the default one to be searched for in all available extensions Path = Job.Modules.ShellExec # When called each module will get an Input CFG with named arguments # This is the default if nothing else is set Arguments = $Input # This is the default if nothing else is set # Result = CFG version of the result returned after the execution # After execution the return dictionary is passed as Result and the JobManifest reevaluated Status = $Result/OK # The result assumes that apart from OK and Value/Message this module also includes # ExitCode, StdOut, StdErr (name of StdOut/StdErr Files) # in the returned result dict ExitCode = $Result/ExitCode StdOut = $Result/StdOut StdErr = $Result/StdErr } }
Execution: it is a container for subsection describing the execution logic of the job (remember that CFG keeps track of the order). Something like:
Execution {
# If we are not going to add anything we can directly use Input # For old stile JDLs, they will translate into something like # Arguments # { # Executable = some executable_path # Arguments = string, number, list of strings, list of numbers or CFG (dict) # } # And will make use of a "default" ShellExec module, does not need to be declared # LogicFlow # { # Arguments = $Input # Module = ShellExec, Arguments, Status # } # OnError # { # # Exit is just another available module like ShellExec # Module = Exit, 1 # } # # Otherwise, use something like # For a linear execution with loop and conditional statements LogicFlow {
# This is a sequential list of describing how to execute Modules The number is irrelevant, but # names must start with keyword Module, # plus some options to allow manipulation of Input Arguments # each Module can be a list or a list of lists (a list of Modules to be executed, that can be empty) # The list should include at least one item (the name of the Module in the Modules section) # The second item is passed as argument to the execute method of the Module # they can be a string, a list (args) or a dictionary (kwargs) # The third and any further item are outputs from the execution of the module, they may come from # the Result Dict returned by the actual execution, or be determined in some other way. Module1 = ShellExec, $Input, Status # After execution of Module1, $Module1/Result will always, as well as # be defined and in this case also $Module1/Status, Arguments = $Module1/Arguments/Executable, '-l', $Module1/Result/StdOut Module2 = ShellExec, $Arguments # or even things like this at a later stage (This look very much like # what we need for workflows of Jobs) # Iterations of the same Module 1 definition Module3 = [ $Module1 for i in [1, 2, 3, 4, 5] ] # Conditional execution Module4 = [ $Module1 for i in [1,] if $Module3/Status ]} OnError {
# If a given module from the LogicFlow is defined here, and Module['Result']['OK'] == False # the defined Error "Module is executed" Module1 = Exit, 1 Module3 = Exit, 3 Module4 = Exit, 0}
}