flux-batch 0.0.1__tar.gz → 0.0.11__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.
- {flux_batch-0.0.1/flux_batch.egg-info → flux_batch-0.0.11}/PKG-INFO +13 -2
- {flux_batch-0.0.1 → flux_batch-0.0.11}/README.md +12 -1
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/__init__.py +2 -1
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/jobspec.py +22 -0
- flux_batch-0.0.11/flux_batch/service/__init__.py +81 -0
- flux_batch-0.0.11/flux_batch/service/scribe/__init__.py +2 -0
- flux_batch-0.0.11/flux_batch/service/scribe/template.py +53 -0
- flux_batch-0.0.11/flux_batch/submit.py +86 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/version.py +1 -1
- {flux_batch-0.0.1 → flux_batch-0.0.11/flux_batch.egg-info}/PKG-INFO +13 -2
- flux_batch-0.0.1/flux_batch/service/__init__.py +0 -31
- flux_batch-0.0.1/flux_batch/service/scribe/__init__.py +0 -1
- flux_batch-0.0.1/flux_batch/service/scribe/template.py +0 -12
- flux_batch-0.0.1/flux_batch/submit.py +0 -53
- {flux_batch-0.0.1 → flux_batch-0.0.11}/LICENSE +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/MANIFEST.in +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/NOTICE +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/logger/__init__.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/logger/generate.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/logger/logger.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/models.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/script/__init__.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/script/save_logs.sh +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/service/scribe/__main__.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/service/scribe/database.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/service/scribe/models.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/utils/__init__.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/utils/fileio.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/utils/text.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch/utils/timer.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/SOURCES.txt +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/dependency_links.txt +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/entry_points.txt +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/not-zip-safe +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/requires.txt +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/flux_batch.egg-info/top_level.txt +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/pyproject.toml +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/setup.cfg +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/setup.py +0 -0
- {flux_batch-0.0.1 → flux_batch-0.0.11}/tests/test_flux_batch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flux-batch
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Python SDK for flux batch jobs and services
|
|
5
5
|
Home-page: https://github.com/converged-computing/flux-batch
|
|
6
6
|
Author: Vanessa Sochat
|
|
@@ -36,7 +36,7 @@ Requires-Dist: rich; extra == "scribe"
|
|
|
36
36
|
|
|
37
37
|
> Python SDK to generate Flux batch jobs and services
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+

|
|
40
40
|
|
|
41
41
|

|
|
42
42
|
|
|
@@ -70,10 +70,21 @@ pip install -e . --break-system-packages
|
|
|
70
70
|
|
|
71
71
|
We have a few simple examples:
|
|
72
72
|
|
|
73
|
+
#### Saving Logs
|
|
74
|
+
|
|
73
75
|
```bash
|
|
74
76
|
python3 ./examples/save_logs.py
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
#### Flux Scribe Module
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export FLUX_SCRIBE_DATABASE=sqlite:///flux-batch-job.db
|
|
83
|
+
python3 ./examples/flux_scribe_module.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### General Test
|
|
87
|
+
|
|
77
88
|
Or run the controlled example to see a batch job with prolog and epilog run and complete:
|
|
78
89
|
|
|
79
90
|
```bash
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Python SDK to generate Flux batch jobs and services
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
@@ -36,10 +36,21 @@ pip install -e . --break-system-packages
|
|
|
36
36
|
|
|
37
37
|
We have a few simple examples:
|
|
38
38
|
|
|
39
|
+
#### Saving Logs
|
|
40
|
+
|
|
39
41
|
```bash
|
|
40
42
|
python3 ./examples/save_logs.py
|
|
41
43
|
```
|
|
42
44
|
|
|
45
|
+
#### Flux Scribe Module
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
export FLUX_SCRIBE_DATABASE=sqlite:///flux-batch-job.db
|
|
49
|
+
python3 ./examples/flux_scribe_module.py
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### General Test
|
|
53
|
+
|
|
43
54
|
Or run the controlled example to see a batch job with prolog and epilog run and complete:
|
|
44
55
|
|
|
45
56
|
```bash
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from .jobspec import BatchJobspecV1
|
|
2
2
|
from .models import BatchAttributesV1, BatchJobV1
|
|
3
|
+
from .submit import preview as jobspec
|
|
3
4
|
from .submit import submit
|
|
4
5
|
|
|
5
|
-
__all__ = ["BatchJobV1", "BatchAttributesV1", "BatchJobspecV1", "submit"]
|
|
6
|
+
__all__ = ["BatchJobV1", "BatchAttributesV1", "BatchJobspecV1", "submit", "jobspec"]
|
|
6
7
|
|
|
7
8
|
from .version import __version__ # noqa
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import shlex
|
|
2
3
|
from typing import List
|
|
3
4
|
|
|
@@ -19,6 +20,7 @@ class BatchJobspecV1:
|
|
|
19
20
|
self.prologs: List[str] = []
|
|
20
21
|
self.epilogs: List[str] = []
|
|
21
22
|
self.services: List[str] = []
|
|
23
|
+
self.modules: List[str] = []
|
|
22
24
|
|
|
23
25
|
@classmethod
|
|
24
26
|
def from_command(cls, command: List[str], **kwargs):
|
|
@@ -52,6 +54,9 @@ class BatchJobspecV1:
|
|
|
52
54
|
def add_epilog(self, cmd: str):
|
|
53
55
|
self.epilogs.append(cmd)
|
|
54
56
|
|
|
57
|
+
def add_module(self, service_name: str):
|
|
58
|
+
self.modules.append(service_name)
|
|
59
|
+
|
|
55
60
|
def get_cli_flags(self) -> List[str]:
|
|
56
61
|
"""
|
|
57
62
|
Converts BatchAttributesV1 into a list of strings for subprocess.
|
|
@@ -108,8 +113,25 @@ class BatchJobspecV1:
|
|
|
108
113
|
for val in getattr(attr, field_name):
|
|
109
114
|
flags.extend([flag, str(val)])
|
|
110
115
|
|
|
116
|
+
# If we have modules, ensure they are added --conf <module>=true
|
|
117
|
+
if self.modules:
|
|
118
|
+
# Tell Flux to look in our user home for rc scripts
|
|
119
|
+
modprobe_path = os.path.expanduser("~/.flux-batch")
|
|
120
|
+
flags.extend(["--env", f"FLUX_MODPROBE_PATH_APPEND={modprobe_path}"])
|
|
121
|
+
|
|
122
|
+
# If modules are used, we need to pass the service names into the Flux config
|
|
123
|
+
# so the @task 'needs_config' filter allows them to run
|
|
124
|
+
for mod in self.modules:
|
|
125
|
+
flags.extend(["--conf", f"{mod}=true"])
|
|
126
|
+
|
|
111
127
|
return flags
|
|
112
128
|
|
|
129
|
+
def render(self) -> str:
|
|
130
|
+
"""
|
|
131
|
+
Generate the jobspec.
|
|
132
|
+
"""
|
|
133
|
+
return self.generate_wrapper_script()
|
|
134
|
+
|
|
113
135
|
def generate_wrapper_script(self) -> str:
|
|
114
136
|
"""
|
|
115
137
|
Generate the wrapper script.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import flux_batch.service.scribe as scribe
|
|
6
|
+
|
|
7
|
+
# Lookup of known services
|
|
8
|
+
services = {"flux-scribe": scribe.SERVICE_TEMPLATE}
|
|
9
|
+
modules = {
|
|
10
|
+
"flux-scribe": {
|
|
11
|
+
"startup": scribe.START_MODULE_TEMPLATE,
|
|
12
|
+
"shutdown": scribe.STOP_MODULE_TEMPLATE,
|
|
13
|
+
"module": scribe.MODULE_NAME,
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def write_modprobe_script(rc_path, script, args=None):
|
|
19
|
+
"""
|
|
20
|
+
Shared function to write service file.
|
|
21
|
+
"""
|
|
22
|
+
args = args or {}
|
|
23
|
+
if not os.path.exists(rc_path):
|
|
24
|
+
with open(rc_path, "w") as f:
|
|
25
|
+
f.write(script.format(**args))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ensure_modprobe_scripts(service_name: str):
|
|
29
|
+
"""
|
|
30
|
+
Ensures rc1.d (start) and rc3.d (stop) scripts exist for the service.
|
|
31
|
+
"""
|
|
32
|
+
if service_name not in modules:
|
|
33
|
+
print("Warning: module {service_name} is not known.")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
# We will add these to FLUX_MODPROBE_PATH_APPEND
|
|
37
|
+
base_dir = os.path.expanduser("~/.flux-batch")
|
|
38
|
+
for subdir in ["rc1.d", "rc3.d"]:
|
|
39
|
+
os.makedirs(os.path.join(base_dir, subdir), exist_ok=True)
|
|
40
|
+
|
|
41
|
+
service_func = service_name.replace("-", "_")
|
|
42
|
+
|
|
43
|
+
# Path for rc1.d (startup)
|
|
44
|
+
args = {
|
|
45
|
+
"service_name": service_name,
|
|
46
|
+
"service_func": service_func,
|
|
47
|
+
"python_bin": sys.executable,
|
|
48
|
+
"module_name": modules[service_name]["module"],
|
|
49
|
+
}
|
|
50
|
+
rc1_path = os.path.join(base_dir, "rc1.d", f"{service_name}.py")
|
|
51
|
+
script = modules[service_name]["startup"]
|
|
52
|
+
write_modprobe_script(rc1_path, script, args=args)
|
|
53
|
+
|
|
54
|
+
# Path for rc3.d (shutdown)
|
|
55
|
+
args = {"service_name": service_name, "service_func": service_func}
|
|
56
|
+
rc3_path = os.path.join(base_dir, "rc3.d", f"{service_name}.py")
|
|
57
|
+
script = modules[service_name]["shutdown"]
|
|
58
|
+
write_modprobe_script(rc3_path, script, args=args)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ensure_user_service(service_name: str):
|
|
62
|
+
"""
|
|
63
|
+
Checks for the existence of a systemd service file in the user's home.
|
|
64
|
+
If it doesn't exist, it creates it and reloads the daemon.
|
|
65
|
+
"""
|
|
66
|
+
user_systemd_dir = os.path.expanduser("~/.config/systemd/user")
|
|
67
|
+
os.makedirs(user_systemd_dir, exist_ok=True)
|
|
68
|
+
service_path = os.path.join(user_systemd_dir, f"{service_name}.service")
|
|
69
|
+
|
|
70
|
+
if not os.path.exists(service_path):
|
|
71
|
+
if service_name in services:
|
|
72
|
+
template = services[service_name]
|
|
73
|
+
print(f"[*] Provisioning {service_name} at {service_path}")
|
|
74
|
+
with open(service_path, "w") as f:
|
|
75
|
+
f.write(template.format(python_path=sys.executable))
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
print(f"[*] Service {service_name} is not known, assuming exists.")
|
|
79
|
+
|
|
80
|
+
# Reload the user-session manager to recognize the new unit
|
|
81
|
+
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Template for the Scribe Journal Consumer
|
|
2
|
+
SERVICE_TEMPLATE = """[Unit]
|
|
3
|
+
Description=Flux Scribe Journal Consumer
|
|
4
|
+
After=network.target
|
|
5
|
+
|
|
6
|
+
[Service]
|
|
7
|
+
ExecStart={python_path} -m flux_batch.service.scribe
|
|
8
|
+
Restart=on-failure
|
|
9
|
+
|
|
10
|
+
[Install]
|
|
11
|
+
WantedBy=default.target
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
START_MODULE_TEMPLATE = """
|
|
15
|
+
from flux.modprobe import task
|
|
16
|
+
import flux.subprocess as subprocess
|
|
17
|
+
|
|
18
|
+
@task(
|
|
19
|
+
"start-{service_name}",
|
|
20
|
+
ranks="0",
|
|
21
|
+
needs_config=["{service_name}"],
|
|
22
|
+
after=["resource", "job-list"],
|
|
23
|
+
)
|
|
24
|
+
def start_{service_func}(context):
|
|
25
|
+
# This triggers the systemd user service provisioned earlier
|
|
26
|
+
# context.bash("systemctl --user start {service_name}")
|
|
27
|
+
subprocess.rexec_bg(
|
|
28
|
+
context.handle,
|
|
29
|
+
["{python_bin}", "-m", "{module_name}"],
|
|
30
|
+
label="{service_name}",
|
|
31
|
+
nodeid=0
|
|
32
|
+
)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
STOP_MODULE_TEMPLATE = """
|
|
36
|
+
from flux.modprobe import task
|
|
37
|
+
import flux.subprocess as subprocess
|
|
38
|
+
|
|
39
|
+
@task(
|
|
40
|
+
"stop-{service_name}",
|
|
41
|
+
ranks="0",
|
|
42
|
+
needs_config=["{service_name}"],
|
|
43
|
+
before=["resource", "job-list"],
|
|
44
|
+
)
|
|
45
|
+
def stop_{service_func}(context):
|
|
46
|
+
# context.bash("systemctl --user stop {service_name}")
|
|
47
|
+
subprocess.kill(context.handle, signum=2, label="{service_name}").get()
|
|
48
|
+
try:
|
|
49
|
+
status = subprocess.wait(context.handle, label="{service_name}").get()["status"]
|
|
50
|
+
print(status)
|
|
51
|
+
except:
|
|
52
|
+
pass
|
|
53
|
+
"""
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import stat
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
import flux
|
|
8
|
+
import flux.job
|
|
9
|
+
|
|
10
|
+
import flux_batch.models as models
|
|
11
|
+
import flux_batch.service as services
|
|
12
|
+
import flux_batch.utils as utils
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def setup(spec):
|
|
16
|
+
"""
|
|
17
|
+
shared function to generate services / modules from a spec.
|
|
18
|
+
"""
|
|
19
|
+
# Provision services (like flux-scribe) if requested
|
|
20
|
+
for service in spec.services:
|
|
21
|
+
services.ensure_user_service(service)
|
|
22
|
+
for module in spec.modules:
|
|
23
|
+
services.ensure_modprobe_scripts(module)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def generate_jobspec(spec, script, wrapper_path):
|
|
27
|
+
"""
|
|
28
|
+
Shared function to write a script to a wrapper path and generate
|
|
29
|
+
a jobspec for it via flux batch --dry-run.
|
|
30
|
+
"""
|
|
31
|
+
utils.write_file(script, wrapper_path)
|
|
32
|
+
|
|
33
|
+
# Make the script executable so 'flux batch' can analyze it
|
|
34
|
+
os.chmod(wrapper_path, os.stat(wrapper_path).st_mode | stat.S_IEXEC)
|
|
35
|
+
|
|
36
|
+
# Generate the RFC 25 Jobspec JSON via the Flux CLI
|
|
37
|
+
# This handles all resource mapping (-N, -n, etc.)
|
|
38
|
+
cmd = ["flux", "batch"] + spec.get_cli_flags() + ["--dry-run", wrapper_path]
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
print(f"Error during flux batch dryrun: {e.stderr}")
|
|
44
|
+
raise
|
|
45
|
+
return result.stdout
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def preview(spec: models.BatchJobV1) -> int:
|
|
49
|
+
"""
|
|
50
|
+
Preview the jobspec.
|
|
51
|
+
"""
|
|
52
|
+
setup(spec)
|
|
53
|
+
|
|
54
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
55
|
+
|
|
56
|
+
# Write the wrapper script (handling prologs, services, and jobs)
|
|
57
|
+
wrapper_path = os.path.join(tmpdir, "wrapper.sh")
|
|
58
|
+
script = spec.generate_wrapper_script()
|
|
59
|
+
jobspec = generate_jobspec(spec, script, wrapper_path)
|
|
60
|
+
return json.loads(jobspec)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def submit(handle: flux.Flux, spec: models.BatchJobV1, dry_run=False) -> int:
|
|
64
|
+
"""
|
|
65
|
+
Orchestrates the submission process:
|
|
66
|
+
1. Provisions any required user-space services.
|
|
67
|
+
2. Generates the wrapper shell script.
|
|
68
|
+
3. Uses 'flux batch --dryrun' to compile the Jobspec JSON.
|
|
69
|
+
4. Submits the Jobspec to the Flux instance.
|
|
70
|
+
"""
|
|
71
|
+
setup(spec)
|
|
72
|
+
|
|
73
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
74
|
+
# Write the wrapper script (handling prologs, services, and jobs)
|
|
75
|
+
wrapper_path = os.path.join(tmpdir, "wrapper.sh")
|
|
76
|
+
|
|
77
|
+
# dry run here just displays it
|
|
78
|
+
script = spec.generate_wrapper_script()
|
|
79
|
+
if dry_run:
|
|
80
|
+
return script
|
|
81
|
+
|
|
82
|
+
jobspec = generate_jobspec(spec, script, wrapper_path)
|
|
83
|
+
|
|
84
|
+
# Submit the JSON string to the Flux instance
|
|
85
|
+
# The result.stdout contains the raw JSON Jobspec
|
|
86
|
+
return flux.job.submit(handle, jobspec)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: flux-batch
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: Python SDK for flux batch jobs and services
|
|
5
5
|
Home-page: https://github.com/converged-computing/flux-batch
|
|
6
6
|
Author: Vanessa Sochat
|
|
@@ -36,7 +36,7 @@ Requires-Dist: rich; extra == "scribe"
|
|
|
36
36
|
|
|
37
37
|
> Python SDK to generate Flux batch jobs and services
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+

|
|
40
40
|
|
|
41
41
|

|
|
42
42
|
|
|
@@ -70,10 +70,21 @@ pip install -e . --break-system-packages
|
|
|
70
70
|
|
|
71
71
|
We have a few simple examples:
|
|
72
72
|
|
|
73
|
+
#### Saving Logs
|
|
74
|
+
|
|
73
75
|
```bash
|
|
74
76
|
python3 ./examples/save_logs.py
|
|
75
77
|
```
|
|
76
78
|
|
|
79
|
+
#### Flux Scribe Module
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
export FLUX_SCRIBE_DATABASE=sqlite:///flux-batch-job.db
|
|
83
|
+
python3 ./examples/flux_scribe_module.py
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### General Test
|
|
87
|
+
|
|
77
88
|
Or run the controlled example to see a batch job with prolog and epilog run and complete:
|
|
78
89
|
|
|
79
90
|
```bash
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import subprocess
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
from .scribe import SERVICE_TEMPLATE as scribe_template
|
|
6
|
-
|
|
7
|
-
# Lookup of known services
|
|
8
|
-
services = {"flux-scribe": scribe_template}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def ensure_user_service(service_name: str):
|
|
12
|
-
"""
|
|
13
|
-
Checks for the existence of a systemd service file in the user's home.
|
|
14
|
-
If it doesn't exist, it creates it and reloads the daemon.
|
|
15
|
-
"""
|
|
16
|
-
user_systemd_dir = os.path.expanduser("~/.config/systemd/user")
|
|
17
|
-
os.makedirs(user_systemd_dir, exist_ok=True)
|
|
18
|
-
service_path = os.path.join(user_systemd_dir, f"{service_name}.service")
|
|
19
|
-
|
|
20
|
-
if not os.path.exists(service_path):
|
|
21
|
-
if service_name in services:
|
|
22
|
-
template = services[service_name]
|
|
23
|
-
print(f"[*] Provisioning {service_name} at {service_path}")
|
|
24
|
-
with open(service_path, "w") as f:
|
|
25
|
-
f.write(template.format(python_path=sys.executable))
|
|
26
|
-
|
|
27
|
-
else:
|
|
28
|
-
print(f"[*] Service {service_name} is not known, assuming exists.")
|
|
29
|
-
|
|
30
|
-
# Reload the user-session manager to recognize the new unit
|
|
31
|
-
subprocess.run(["systemctl", "--user", "daemon-reload"], check=True)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .template import SERVICE_TEMPLATE
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Template for the Scribe Journal Consumer
|
|
2
|
-
SERVICE_TEMPLATE = """[Unit]
|
|
3
|
-
Description=Flux Scribe Journal Consumer
|
|
4
|
-
After=network.target
|
|
5
|
-
|
|
6
|
-
[Service]
|
|
7
|
-
ExecStart={python_path} -m flux_batch.service.scribe
|
|
8
|
-
Restart=on-failure
|
|
9
|
-
|
|
10
|
-
[Install]
|
|
11
|
-
WantedBy=default.target
|
|
12
|
-
"""
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import stat
|
|
3
|
-
import subprocess
|
|
4
|
-
import tempfile
|
|
5
|
-
|
|
6
|
-
import flux
|
|
7
|
-
import flux.job
|
|
8
|
-
|
|
9
|
-
import flux_batch.models as models
|
|
10
|
-
import flux_batch.utils as utils
|
|
11
|
-
from flux_batch.service import ensure_user_service
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def submit(handle: flux.Flux, spec: models.BatchJobV1, dry_run=False) -> int:
|
|
15
|
-
"""
|
|
16
|
-
Orchestrates the submission process:
|
|
17
|
-
1. Provisions any required user-space services.
|
|
18
|
-
2. Generates the wrapper shell script.
|
|
19
|
-
3. Uses 'flux batch --dryrun' to compile the Jobspec JSON.
|
|
20
|
-
4. Submits the Jobspec to the Flux instance.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
# Provision services (like flux-scribe) if requested
|
|
24
|
-
for service in spec.services:
|
|
25
|
-
ensure_user_service(service)
|
|
26
|
-
|
|
27
|
-
with tempfile.TemporaryDirectory() as tmpdir:
|
|
28
|
-
# Write the wrapper script (handling prologs, services, and jobs)
|
|
29
|
-
wrapper_path = os.path.join(tmpdir, "wrapper.sh")
|
|
30
|
-
|
|
31
|
-
# dry run here just displays it
|
|
32
|
-
script = spec.generate_wrapper_script()
|
|
33
|
-
if dry_run:
|
|
34
|
-
return script
|
|
35
|
-
|
|
36
|
-
utils.write_file(script, wrapper_path)
|
|
37
|
-
|
|
38
|
-
# Make the script executable so 'flux batch' can analyze it
|
|
39
|
-
os.chmod(wrapper_path, os.stat(wrapper_path).st_mode | stat.S_IEXEC)
|
|
40
|
-
|
|
41
|
-
# Generate the RFC 25 Jobspec JSON via the Flux CLI
|
|
42
|
-
# This handles all resource mapping (-N, -n, etc.)
|
|
43
|
-
cmd = ["flux", "batch"] + spec.get_cli_flags() + ["--dry-run", wrapper_path]
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
47
|
-
except subprocess.CalledProcessError as e:
|
|
48
|
-
print(f"Error during flux batch dryrun: {e.stderr}")
|
|
49
|
-
raise
|
|
50
|
-
|
|
51
|
-
# Submit the JSON string to the Flux instance
|
|
52
|
-
# The result.stdout contains the raw JSON Jobspec
|
|
53
|
-
return flux.job.submit(handle, result.stdout)
|
|
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
|
|
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
|