Skip to content

Commit 1597697

Browse files
authored
Merge pull request DIRACGrid#8425 from Loxeris/dirac-cwl-proto-jobwrapper
feat: Add the possibility to use the dirac-cwl project's job wrapper
2 parents 86208f9 + 8fe01ff commit 1597697

5 files changed

Lines changed: 138 additions & 33 deletions

File tree

src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py

Lines changed: 79 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
""" Utilities for WMS
22
"""
33
import os
4+
from pathlib import Path
5+
from glob import glob
6+
import subprocess
47
import sys
58
import json
69

@@ -63,8 +66,6 @@ def createJobWrapper(
6366
if os.path.exists(jobWrapperFile):
6467
log.verbose("Removing existing Job Wrapper for", jobID)
6568
os.remove(jobWrapperFile)
66-
with open(os.path.join(diracRoot, defaultWrapperLocation)) as fd:
67-
wrapperTemplate = fd.read()
6869

6970
if "LogLevel" in jobParams:
7071
logLevel = jobParams["LogLevel"]
@@ -76,34 +77,43 @@ def createJobWrapper(
7677
pythonPath = os.path.realpath(sys.executable)
7778
log.debug("Real python path after resolving links is: ", pythonPath)
7879

79-
# Making real substitutions
80-
sitePython = os.getcwd()
81-
if rootLocation:
82-
sitePython = rootLocation
83-
wrapperTemplate = wrapperTemplate.replace("@SITEPYTHON@", sitePython)
84-
85-
jobWrapperJsonFile = jobWrapperFile + ".json"
86-
with open(jobWrapperJsonFile, "w", encoding="utf8") as jsonFile:
87-
json.dump(arguments, jsonFile, ensure_ascii=False)
88-
89-
with open(jobWrapperFile, "w") as wrapper:
90-
wrapper.write(wrapperTemplate)
91-
92-
if not rootLocation:
93-
rootLocation = wrapperPath
94-
95-
# The "real" location of the jobwrapper after it is started
96-
jobWrapperDirect = os.path.join(rootLocation, f"Wrapper_{jobID}")
97-
jobExeFile = os.path.join(wrapperPath, f"Job{jobID}")
98-
jobFileContents = """#!/bin/sh
99-
{} {} {} -o LogLevel={} -o /DIRAC/Security/UseServerCertificate=no {}
100-
""".format(
101-
pythonPath,
102-
jobWrapperDirect,
103-
extraOptions if extraOptions else "",
104-
logLevel,
105-
cfgPath if cfgPath else "",
106-
)
80+
if "Executable" in jobParams and jobParams["Executable"] == "dirac-cwl-exec":
81+
ret = __createCWLJobWrapper(jobID, wrapperPath, log, rootLocation)
82+
if not ret["OK"]:
83+
return ret
84+
jobWrapperFile, jobWrapperJsonFile, jobExeFile, jobFileContents = ret["Value"]
85+
else:
86+
with open(os.path.join(diracRoot, defaultWrapperLocation)) as fd:
87+
wrapperTemplate = fd.read()
88+
89+
# Making real substitutions
90+
sitePython = os.getcwd()
91+
if rootLocation:
92+
sitePython = rootLocation
93+
wrapperTemplate = wrapperTemplate.replace("@SITEPYTHON@", sitePython)
94+
95+
jobWrapperJsonFile = jobWrapperFile + ".json"
96+
with open(jobWrapperJsonFile, "w", encoding="utf8") as jsonFile:
97+
json.dump(arguments, jsonFile, ensure_ascii=False)
98+
99+
with open(jobWrapperFile, "w") as wrapper:
100+
wrapper.write(wrapperTemplate)
101+
102+
if not rootLocation:
103+
rootLocation = wrapperPath
104+
105+
# The "real" location of the jobwrapper after it is started
106+
jobWrapperDirect = os.path.join(rootLocation, f"Wrapper_{jobID}")
107+
jobExeFile = os.path.join(wrapperPath, f"Job{jobID}")
108+
jobFileContents = """#!/bin/sh
109+
{} {} {} -o LogLevel={} -o /DIRAC/Security/UseServerCertificate=no {}
110+
""".format(
111+
pythonPath,
112+
jobWrapperDirect,
113+
extraOptions if extraOptions else "",
114+
logLevel,
115+
cfgPath if cfgPath else "",
116+
)
107117

108118
with open(jobExeFile, "w") as jobFile:
109119
jobFile.write(jobFileContents)
@@ -113,11 +123,49 @@ def createJobWrapper(
113123
"JobWrapperConfigPath": jobWrapperJsonFile,
114124
"JobWrapperPath": jobWrapperFile,
115125
}
116-
if rootLocation != wrapperPath:
126+
if rootLocation and rootLocation != wrapperPath:
117127
generatedFiles["JobExecutableRelocatedPath"] = os.path.join(rootLocation, os.path.basename(jobExeFile))
118128
return S_OK(generatedFiles)
119129

120130

131+
def __createCWLJobWrapper(jobID, wrapperPath, log, rootLocation):
132+
# Get the new JobWrapper
133+
if not rootLocation:
134+
rootLocation = wrapperPath
135+
protoPath = Path(wrapperPath) / f"proto{jobID}"
136+
protoPath.unlink(missing_ok=True)
137+
log.info("Cloning JobWrapper from repository https://github.com/DIRACGrid/dirac-cwl.git into", protoPath)
138+
try:
139+
subprocess.run(["git", "clone", "https://github.com/DIRACGrid/dirac-cwl.git", str(protoPath)], check=True)
140+
except subprocess.CalledProcessError:
141+
return S_ERROR("Failed to clone the JobWrapper repository")
142+
wrapperFound = glob(os.path.join(str(protoPath), "**", "job_wrapper_template.py"), recursive=True)
143+
if len(wrapperFound) < 1 or not Path(wrapperFound[0]).is_file():
144+
return S_ERROR("Could not find the JobWrapper in the cloned repository")
145+
jobWrapperFile = wrapperFound[0]
146+
directJobWrapperFile = str(Path(rootLocation) / Path(wrapperFound[0]).relative_to(wrapperPath))
147+
148+
jobWrapperJsonFile = Path(wrapperPath) / f"InputSandbox{jobID}" / "job.json"
149+
directJobWrapperJsonFile = Path(rootLocation) / f"InputSandbox{jobID}" / "job.json"
150+
# Create the executable file
151+
jobExeFile = os.path.join(wrapperPath, f"Job{jobID}")
152+
protoPath = str(Path(rootLocation) / Path(protoPath).relative_to(wrapperPath))
153+
pixiPath = str(Path(rootLocation) / ".pixi")
154+
jobFileContents = f"""#!/bin/bash
155+
# Install pixi
156+
export PIXI_NO_PATH_UPDATE=1
157+
export PIXI_HOME={pixiPath}
158+
curl -fsSL https://pixi.sh/install.sh | bash
159+
export PATH="{pixiPath}/bin:$PATH"
160+
pixi install --manifest-path {protoPath}
161+
# Get json
162+
dirac-wms-job-get-input {jobID} -D {rootLocation}
163+
# Run JobWrapper
164+
pixi run --manifest-path {protoPath} python {directJobWrapperFile} {directJobWrapperJsonFile}
165+
"""
166+
return S_OK((jobWrapperFile, jobWrapperJsonFile, jobExeFile, jobFileContents))
167+
168+
121169
def rescheduleJobs(
122170
jobIDs: list[int],
123171
source: str = "",

src/DIRAC/tests/Utilities/testJobDefinitions.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,22 @@ def parametricJobInputData():
416416
return endOfAllJobs(J)
417417

418418

419+
def cwlTest():
420+
"""Testing the CWL executable"""
421+
422+
J = baseToAllJobs("CWL_Test")
423+
J.executable = "dirac-cwl-exec"
424+
try:
425+
J.setInputSandbox([find_all("job.json", rootPath, "DIRAC/tests/Workflow")[0]])
426+
except IndexError:
427+
try:
428+
J.setInputSandbox([find_all("job.json", ".", "DIRAC/tests/Workflow")[0]])
429+
except IndexError: # we are in Jenkins
430+
J.setInputSandbox([find_all("job.json", "/home/dirac", "DIRAC/tests/Workflow")[0]])
431+
432+
return endOfAllJobs(J)
433+
434+
419435
def jobWithOutput():
420436
"""Creates a job that uploads an output.
421437
The output SE is not set here, so it would use the default /Resources/StorageElementGroups/SE-USER

tests/Integration/WorkloadManagementSystem/exe-script.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
#!/usr/bin/env python
22
"""Script to run Executable application"""
3-
import sys
4-
import subprocess
3+
54
import shlex
5+
import subprocess
6+
import sys
67

78
# Main
89
if __name__ == "__main__":

tests/System/unitTestUserJobs.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ def test_submit(self):
146146
self.assertTrue(res["OK"])
147147
jobsSubmittedList.append(res["Value"])
148148

149+
res = cwlTest()
150+
self.assertTrue(res["OK"])
151+
jobsSubmittedList.append(res["Value"])
152+
149153
print(f"submitted {len(jobsSubmittedList)} jobs: {','.join(str(js) for js in jobsSubmittedList)}")
150154

151155

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"task": {
3+
"id": ".",
4+
"class": "CommandLineTool",
5+
"inputs": [],
6+
"outputs": [
7+
{
8+
"id": "output1",
9+
"type": "File",
10+
"outputBinding": {
11+
"glob": "test-output"
12+
}
13+
}
14+
],
15+
"requirements": [],
16+
"hints": [
17+
{
18+
"class": "dirac:ExecutionHooks",
19+
"hook_plugin": "QueryBasedPlugin"
20+
}
21+
],
22+
"cwlVersion": "v1.2",
23+
"baseCommand": [
24+
"echo",
25+
"test file content"
26+
],
27+
"stdout": "test-output",
28+
"$namespaces": {
29+
"dirac": "schemas/dirac-metadata.json#/$defs/"
30+
},
31+
"$schemas": [
32+
"schemas/dirac-metadata.json"
33+
]
34+
},
35+
"input": null
36+
}

0 commit comments

Comments
 (0)