quapp-hpc 0.0.1.dev5__tar.gz → 0.0.1.dev7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quapp_hpc-0.0.1.dev5/quapp_hpc.egg-info → quapp_hpc-0.0.1.dev7}/PKG-INFO +1 -1
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/pyproject.toml +33 -33
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7/quapp_hpc.egg-info}/PKG-INFO +1 -1
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc.egg-info/SOURCES.txt +3 -1
- quapp_hpc-0.0.1.dev7/quapp_hpc.egg-info/top_level.txt +2 -0
- quapp_hpc-0.0.1.dev7/quapp_slurm_hpc/__init__.py +0 -0
- quapp_hpc-0.0.1.dev7/quapp_slurm_hpc/runner.py +142 -0
- quapp_hpc-0.0.1.dev5/quapp_hpc.egg-info/top_level.txt +0 -1
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/LICENSE +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/README.md +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/component/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/component/backend/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/component/backend/hpc_invocation.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/component/backend/slurm_job_fetching.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/factory/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/factory/hpc_device_factory.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/factory/hpc_handler_factory.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/factory/hpc_provider_factory.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/handler/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/handler/invocation_handler.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/handler/job_fetching_handler.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/model/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/model/device/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/model/device/slurm_device.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/model/provider/__init__.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/model/provider/slurm_provider.py +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc.egg-info/dependency_links.txt +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc.egg-info/requires.txt +0 -0
- {quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/setup.cfg +0 -0
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0.0", "wheel==0.45.1"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "quapp-hpc"
|
|
7
|
-
version = "0.0.1.
|
|
8
|
-
description = "Quapp HPC library — Slurm integration for Quapp Platform"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
authors = [{ name = "CITYNOW Co. Ltd.", email = "corp@citynow.vn" }]
|
|
11
|
-
license = { file = "LICENSE" }
|
|
12
|
-
classifiers = [
|
|
13
|
-
"License :: OSI Approved :: MIT License",
|
|
14
|
-
"Programming Language :: Python",
|
|
15
|
-
"Programming Language :: Python :: 3",
|
|
16
|
-
]
|
|
17
|
-
keywords = ["quapp", "quapp-hpc", "slurm", "hpc"]
|
|
18
|
-
dependencies = [
|
|
19
|
-
"quapp-common==0.0.12.dev12",
|
|
20
|
-
"requests>=2.31.0",
|
|
21
|
-
"boto3>=1.28.0",
|
|
22
|
-
]
|
|
23
|
-
requires-python = ">=3.10,<3.13"
|
|
24
|
-
|
|
25
|
-
[project.optional-dependencies]
|
|
26
|
-
dev = ["black", "bumpver", "isort", "pip-tools", "pytest"]
|
|
27
|
-
|
|
28
|
-
[tool.setuptools.packages.find]
|
|
29
|
-
include = ["quapp_hpc*"]
|
|
30
|
-
exclude = ["*.md", "*.yml", "*.yaml", "*.toml", "tests*", ".gitignore"]
|
|
31
|
-
|
|
32
|
-
[project.urls]
|
|
33
|
-
Homepage = "https://quapp.cloud/"
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel==0.45.1"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "quapp-hpc"
|
|
7
|
+
version = "0.0.1.dev7"
|
|
8
|
+
description = "Quapp HPC library — Slurm integration for Quapp Platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "CITYNOW Co. Ltd.", email = "corp@citynow.vn" }]
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
classifiers = [
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
]
|
|
17
|
+
keywords = ["quapp", "quapp-hpc", "slurm", "hpc"]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"quapp-common==0.0.12.dev12",
|
|
20
|
+
"requests>=2.31.0",
|
|
21
|
+
"boto3>=1.28.0",
|
|
22
|
+
]
|
|
23
|
+
requires-python = ">=3.10,<3.13"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["black", "bumpver", "isort", "pip-tools", "pytest"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.packages.find]
|
|
29
|
+
include = ["quapp_hpc*", "quapp_slurm_hpc*"]
|
|
30
|
+
exclude = ["*.md", "*.yml", "*.yaml", "*.toml", "tests*", ".gitignore"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://quapp.cloud/"
|
|
@@ -22,4 +22,6 @@ quapp_hpc/model/__init__.py
|
|
|
22
22
|
quapp_hpc/model/device/__init__.py
|
|
23
23
|
quapp_hpc/model/device/slurm_device.py
|
|
24
24
|
quapp_hpc/model/provider/__init__.py
|
|
25
|
-
quapp_hpc/model/provider/slurm_provider.py
|
|
25
|
+
quapp_hpc/model/provider/slurm_provider.py
|
|
26
|
+
quapp_slurm_hpc/__init__.py
|
|
27
|
+
quapp_slurm_hpc/runner.py
|
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""CLI runner — invoked by the sbatch script inside the Apptainer container.
|
|
2
|
+
|
|
3
|
+
The Java SlurmPlatformManager generates a sbatch script that runs:
|
|
4
|
+
|
|
5
|
+
apptainer exec docker://<image> python -m quapp_slurm_hpc.runner \
|
|
6
|
+
--input-file /data/jobs/<jobId>/input.json \
|
|
7
|
+
--output-file /data/jobs/<jobId>/output.json \
|
|
8
|
+
--handler-dir /home/app/function
|
|
9
|
+
|
|
10
|
+
This module:
|
|
11
|
+
1. Reads the invocation input from --input-file (JSON written by the sbatch script).
|
|
12
|
+
2. Loads handler.py from --handler-dir.
|
|
13
|
+
3. Calls handler.processing(invocation_input) → bash script string.
|
|
14
|
+
4. Executes the bash script, captures stdout / stderr / exit_code.
|
|
15
|
+
5. Calls handler.post_processing(raw_result) if it exists.
|
|
16
|
+
6. Writes the final result dict to --output-file.
|
|
17
|
+
7. Uploads output.json to S3 (key: {QUAPP_JOB_ID}/output.json) when SLURM_S3_BUCKET is set.
|
|
18
|
+
"""
|
|
19
|
+
import argparse
|
|
20
|
+
import importlib.util
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
import traceback
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from types import ModuleType
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_handler(handler_dir: str) -> ModuleType:
|
|
32
|
+
handler_path = Path(handler_dir) / "handler.py"
|
|
33
|
+
if not handler_path.exists():
|
|
34
|
+
raise FileNotFoundError(
|
|
35
|
+
f"handler.py not found in {handler_dir!r}. "
|
|
36
|
+
"Ensure the function image was built with the handler in /home/app/function."
|
|
37
|
+
)
|
|
38
|
+
if str(handler_dir) not in sys.path:
|
|
39
|
+
sys.path.insert(0, str(handler_dir))
|
|
40
|
+
spec = importlib.util.spec_from_file_location("handler", handler_path)
|
|
41
|
+
module = importlib.util.module_from_spec(spec)
|
|
42
|
+
spec.loader.exec_module(module)
|
|
43
|
+
return module
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _run_script(script: str) -> dict:
|
|
47
|
+
"""Write script to a temp file and execute it with bash, capturing output."""
|
|
48
|
+
with tempfile.NamedTemporaryFile(
|
|
49
|
+
mode="w", suffix=".sh", delete=False, encoding="utf-8"
|
|
50
|
+
) as f:
|
|
51
|
+
f.write(script)
|
|
52
|
+
tmp_path = f.name
|
|
53
|
+
try:
|
|
54
|
+
result = subprocess.run(
|
|
55
|
+
["bash", tmp_path],
|
|
56
|
+
capture_output=True,
|
|
57
|
+
text=True,
|
|
58
|
+
)
|
|
59
|
+
return {
|
|
60
|
+
"exit_code": result.returncode,
|
|
61
|
+
"stdout": result.stdout,
|
|
62
|
+
"stderr": result.stderr,
|
|
63
|
+
}
|
|
64
|
+
finally:
|
|
65
|
+
Path(tmp_path).unlink(missing_ok=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main() -> None:
|
|
69
|
+
parser = argparse.ArgumentParser(
|
|
70
|
+
description="Quapp HPC runner — executes handler.py inside an Apptainer container"
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument("--input-file", required=True, help="Path to input JSON file")
|
|
73
|
+
parser.add_argument("--output-file", required=True, help="Path to write output JSON")
|
|
74
|
+
parser.add_argument("--handler-dir", required=True, help="Directory containing handler.py")
|
|
75
|
+
args = parser.parse_args()
|
|
76
|
+
|
|
77
|
+
output: dict = {}
|
|
78
|
+
try:
|
|
79
|
+
with open(args.input_file, encoding="utf-8") as f:
|
|
80
|
+
invocation_input = json.load(f)
|
|
81
|
+
|
|
82
|
+
handler = _load_handler(args.handler_dir)
|
|
83
|
+
|
|
84
|
+
if not hasattr(handler, "processing"):
|
|
85
|
+
raise AttributeError(
|
|
86
|
+
"handler.py must define a processing(invocation_input: dict) -> str function"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
script = handler.processing(invocation_input)
|
|
90
|
+
if not isinstance(script, str):
|
|
91
|
+
raise TypeError(
|
|
92
|
+
f"handler.processing() must return a str (bash script), got {type(script).__name__}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
raw_result = _run_script(script)
|
|
96
|
+
|
|
97
|
+
if hasattr(handler, "post_processing"):
|
|
98
|
+
output = handler.post_processing(raw_result)
|
|
99
|
+
else:
|
|
100
|
+
output = raw_result
|
|
101
|
+
|
|
102
|
+
except Exception:
|
|
103
|
+
output = {
|
|
104
|
+
"exit_code": 1,
|
|
105
|
+
"stdout": "",
|
|
106
|
+
"stderr": traceback.format_exc(),
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
with open(args.output_file, "w", encoding="utf-8") as f:
|
|
110
|
+
json.dump(output, f)
|
|
111
|
+
|
|
112
|
+
_upload_to_s3(args.output_file)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _upload_to_s3(output_file: str) -> None:
|
|
116
|
+
"""Upload output.json to S3 so the Java backend can read the result.
|
|
117
|
+
|
|
118
|
+
Requires SLURM_S3_BUCKET and QUAPP_JOB_ID env vars (injected by sbatch script).
|
|
119
|
+
Skips silently when the bucket is not configured.
|
|
120
|
+
"""
|
|
121
|
+
s3_bucket = os.getenv("SLURM_S3_BUCKET", "")
|
|
122
|
+
job_uuid = os.getenv("QUAPP_JOB_ID", "")
|
|
123
|
+
if not s3_bucket or not job_uuid:
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
import boto3
|
|
128
|
+
s3 = boto3.client(
|
|
129
|
+
"s3",
|
|
130
|
+
region_name=os.getenv("SLURM_S3_REGION", "ap-southeast-1"),
|
|
131
|
+
aws_access_key_id=os.getenv("SLURM_S3_ACCESS_KEY"),
|
|
132
|
+
aws_secret_access_key=os.getenv("SLURM_S3_SECRET_KEY"),
|
|
133
|
+
)
|
|
134
|
+
s3_key = f"{job_uuid}/output.json"
|
|
135
|
+
s3.upload_file(output_file, s3_bucket, s3_key)
|
|
136
|
+
print(f"[runner] Uploaded output to s3://{s3_bucket}/{s3_key}", flush=True)
|
|
137
|
+
except Exception as exc:
|
|
138
|
+
print(f"[runner] WARNING: S3 upload failed: {exc}", file=sys.stderr, flush=True)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
quapp_hpc
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{quapp_hpc-0.0.1.dev5 → quapp_hpc-0.0.1.dev7}/quapp_hpc/component/backend/slurm_job_fetching.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|