From 7e10a0be242f519d4741407a0bdd74d93559e1a3 Mon Sep 17 00:00:00 2001 From: Ryunosuke O'Neil Date: Thu, 2 Apr 2026 13:44:38 +0200 Subject: [PATCH 1/4] feat: replace git clone with importlib.resources in CWL job wrapper Load the job wrapper template from the installed dirac-cwl package via importlib.resources instead of cloning the repo and installing pixi on every job. The wrapper fetches CWL and params from the diracX API at runtime using the job ID. --- .../Utilities/Utils.py | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py index 1cad93ece59..4ee6f2ff555 100644 --- a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py +++ b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py @@ -2,8 +2,6 @@ import os from pathlib import Path -from glob import glob -import subprocess import sys import json @@ -129,39 +127,40 @@ def createJobWrapper( def __createCWLJobWrapper(jobID, wrapperPath, log, rootLocation): - # Get the new JobWrapper + """Create a CWL job wrapper that fetches the workflow from the diracX API. + + The CWL definition and input parameters are fetched at runtime from diracX + using the job_id. The job wrapper template is loaded from dirac-cwl via + importlib.resources. + """ if not rootLocation: rootLocation = wrapperPath - protoPath = Path(wrapperPath) / f"proto{jobID}" - protoPath.unlink(missing_ok=True) - log.info("Cloning JobWrapper from repository https://github.com/DIRACGrid/dirac-cwl.git into", protoPath) + + # Locate the job wrapper template from the installed dirac-cwl package try: - subprocess.run(["git", "clone", "https://github.com/DIRACGrid/dirac-cwl.git", str(protoPath)], check=True) - except subprocess.CalledProcessError: - return S_ERROR("Failed to clone the JobWrapper repository") - wrapperFound = glob(os.path.join(str(protoPath), "**", "job_wrapper_template.py"), recursive=True) - if len(wrapperFound) < 1 or not Path(wrapperFound[0]).is_file(): - return S_ERROR("Could not find the JobWrapper in the cloned repository") - jobWrapperFile = wrapperFound[0] - directJobWrapperFile = str(Path(rootLocation) / Path(wrapperFound[0]).relative_to(wrapperPath)) - - jobWrapperJsonFile = Path(wrapperPath) / f"InputSandbox{jobID}" / "job.json" - directJobWrapperJsonFile = Path(rootLocation) / f"InputSandbox{jobID}" / "job.json" - # Create the executable file + import importlib.resources + + wrapper_ref = importlib.resources.files("dirac_cwl.job") / "job_wrapper_template.py" + jobWrapperFile = os.path.join(wrapperPath, f"CWLWrapper_{jobID}.py") + with importlib.resources.as_file(wrapper_ref) as template_path: + with open(template_path) as src, open(jobWrapperFile, "w") as dst: + dst.write(src.read()) + except (ImportError, FileNotFoundError) as e: + return S_ERROR(f"Could not load dirac-cwl job wrapper template: {e}") + + # Write job config — the wrapper fetches workflow_id and params from the diracX API + jobWrapperJsonFile = os.path.join(wrapperPath, f"cwl_job_{jobID}.json") + with open(jobWrapperJsonFile, "w") as f: + json.dump({"JobID": jobID}, f) + + directJobWrapperFile = str(Path(rootLocation) / Path(jobWrapperFile).relative_to(wrapperPath)) + directJobWrapperJsonFile = str(Path(rootLocation) / Path(jobWrapperJsonFile).relative_to(wrapperPath)) + + # Create the executable — fetches CWL + params from diracX API at runtime jobExeFile = os.path.join(wrapperPath, f"Job{jobID}") - protoPath = str(Path(rootLocation) / Path(protoPath).relative_to(wrapperPath)) - pixiPath = str(Path(rootLocation) / ".pixi") jobFileContents = f"""#!/bin/bash -# Install pixi -export PIXI_NO_PATH_UPDATE=1 -export PIXI_HOME={pixiPath} -curl -fsSL https://pixi.sh/install.sh | bash -export PATH="{pixiPath}/bin:$PATH" -pixi install --manifest-path {protoPath} -# Get json -dirac-wms-job-get-input {jobID} -D {rootLocation} -# Run JobWrapper -pixi run --manifest-path {protoPath} python {directJobWrapperFile} {directJobWrapperJsonFile} {jobID} +# Fetch CWL workflow and params from diracX API, then run via dirac-cwl +python {directJobWrapperFile} {directJobWrapperJsonFile} {jobID} """ return S_OK((jobWrapperFile, jobWrapperJsonFile, jobExeFile, jobFileContents)) From 8e39564b89b408fe5d5118690182c0130f45428d Mon Sep 17 00:00:00 2001 From: Ryunosuke O'Neil Date: Thu, 2 Apr 2026 13:59:58 +0200 Subject: [PATCH 2/4] feat: replace git clone with importlib.resources in CWL job wrapper Load the job wrapper template from diracx-logic via importlib.resources instead of cloning the repo and installing pixi per job. Add diracx-logic as a dependency. The wrapper fetches CWL and params from the diracX API at runtime using the job ID. --- setup.cfg | 1 + .../Utilities/Utils.py | 19 +++++++------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6c9e1c1b2bd..1528681022e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,6 +34,7 @@ install_requires = diracx-client >=v0.0.1 diracx-core >=v0.0.1 diracx-cli >=v0.0.1 + diracx-logic >=v0.0.1 db12 fabric fts3 diff --git a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py index 4ee6f2ff555..4a1c375e3b9 100644 --- a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py +++ b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py @@ -130,37 +130,32 @@ def __createCWLJobWrapper(jobID, wrapperPath, log, rootLocation): """Create a CWL job wrapper that fetches the workflow from the diracX API. The CWL definition and input parameters are fetched at runtime from diracX - using the job_id. The job wrapper template is loaded from dirac-cwl via - importlib.resources. + using the job ID. The wrapper script is extracted from diracx-logic via + importlib.resources and written to disk for execution. """ + import importlib.resources + if not rootLocation: rootLocation = wrapperPath - # Locate the job wrapper template from the installed dirac-cwl package try: - import importlib.resources - - wrapper_ref = importlib.resources.files("dirac_cwl.job") / "job_wrapper_template.py" + wrapper_ref = importlib.resources.files("diracx.logic.jobs") / "job_wrapper_template.py" jobWrapperFile = os.path.join(wrapperPath, f"CWLWrapper_{jobID}.py") with importlib.resources.as_file(wrapper_ref) as template_path: with open(template_path) as src, open(jobWrapperFile, "w") as dst: dst.write(src.read()) except (ImportError, FileNotFoundError) as e: - return S_ERROR(f"Could not load dirac-cwl job wrapper template: {e}") + return S_ERROR(f"Could not load CWL job wrapper template: {e}") - # Write job config — the wrapper fetches workflow_id and params from the diracX API jobWrapperJsonFile = os.path.join(wrapperPath, f"cwl_job_{jobID}.json") with open(jobWrapperJsonFile, "w") as f: json.dump({"JobID": jobID}, f) directJobWrapperFile = str(Path(rootLocation) / Path(jobWrapperFile).relative_to(wrapperPath)) - directJobWrapperJsonFile = str(Path(rootLocation) / Path(jobWrapperJsonFile).relative_to(wrapperPath)) - # Create the executable — fetches CWL + params from diracX API at runtime jobExeFile = os.path.join(wrapperPath, f"Job{jobID}") jobFileContents = f"""#!/bin/bash -# Fetch CWL workflow and params from diracX API, then run via dirac-cwl -python {directJobWrapperFile} {directJobWrapperJsonFile} {jobID} +python {directJobWrapperFile} {jobID} """ return S_OK((jobWrapperFile, jobWrapperJsonFile, jobExeFile, jobFileContents)) From c2544261f422fd6882ab7c5cb0ae760841893cb9 Mon Sep 17 00:00:00 2001 From: Ryunosuke O'Neil Date: Thu, 2 Apr 2026 16:11:08 +0200 Subject: [PATCH 3/4] refactor: load CWL job wrapper from diracx-cli instead of diracx-logic Move wrapper reference to diracx.cli.internal.job_wrapper and drop diracx-logic from dependencies since it is not needed on worker nodes. --- setup.cfg | 1 - src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 1528681022e..6c9e1c1b2bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,7 +34,6 @@ install_requires = diracx-client >=v0.0.1 diracx-core >=v0.0.1 diracx-cli >=v0.0.1 - diracx-logic >=v0.0.1 db12 fabric fts3 diff --git a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py index 4a1c375e3b9..fd56571c274 100644 --- a/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py +++ b/src/DIRAC/WorkloadManagementSystem/Utilities/Utils.py @@ -139,7 +139,7 @@ def __createCWLJobWrapper(jobID, wrapperPath, log, rootLocation): rootLocation = wrapperPath try: - wrapper_ref = importlib.resources.files("diracx.logic.jobs") / "job_wrapper_template.py" + wrapper_ref = importlib.resources.files("diracx.cli.internal") / "job_wrapper.py" jobWrapperFile = os.path.join(wrapperPath, f"CWLWrapper_{jobID}.py") with importlib.resources.as_file(wrapper_ref) as template_path: with open(template_path) as src, open(jobWrapperFile, "w") as dst: From 1a6dfa6a19bf56898db1446d8e7dac2c4ebb230a Mon Sep 17 00:00:00 2001 From: Ryunosuke O'Neil Date: Thu, 9 Apr 2026 16:51:38 +0200 Subject: [PATCH 4/4] fix: pin diracx deps to feat branch for pilot install The pilot pip-installs DIRAC from the Modules git URL, which resolves dependencies from setup.cfg. Pin all diracx packages to the feat/cwl-job-submission branch so the worker gets the unreleased code (including job_wrapper.py in diracx-cli). --- setup.cfg | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6c9e1c1b2bd..ec9509018ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,9 +31,11 @@ install_requires = cwltool diraccfg DIRACCommon - diracx-client >=v0.0.1 - diracx-core >=v0.0.1 - diracx-cli >=v0.0.1 + diracx-api @ git+https://github.com/ryuwd/diracx.git@feat/cwl-job-submission#subdirectory=diracx-api + diracx-cli @ git+https://github.com/ryuwd/diracx.git@feat/cwl-job-submission#subdirectory=diracx-cli + diracx-client @ git+https://github.com/ryuwd/diracx.git@feat/cwl-job-submission#subdirectory=diracx-client + diracx-core @ git+https://github.com/ryuwd/diracx.git@feat/cwl-job-submission#subdirectory=diracx-core + diracx-logic @ git+https://github.com/ryuwd/diracx.git@feat/cwl-job-submission#subdirectory=diracx-logic db12 fabric fts3