sliderule-cli 5.6.3__tar.gz → 5.7.0__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.
- {sliderule_cli-5.6.3/src/sliderule_cli.egg-info → sliderule_cli-5.7.0}/PKG-INFO +1 -1
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0/src/sliderule_cli.egg-info}/PKG-INFO +1 -1
- sliderule_cli-5.7.0/src/sliderule_cli.egg-info/scm_version.json +8 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_provisioner/main.py +14 -6
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_runner/main.py +141 -78
- sliderule_cli-5.6.3/src/sliderule_cli.egg-info/scm_version.json +0 -8
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/.gitignore +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/LICENSE +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/README.md +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/pyproject.toml +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/setup.cfg +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/SOURCES.txt +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/dependency_links.txt +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/entry_points.txt +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/requires.txt +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/scm_file_list.json +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cli.egg-info/top_level.txt +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cluster/__init__.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_cluster/main.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_provisioner/__init__.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_recorder/__init__.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_recorder/environment.yml +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_recorder/main.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_runner/__init__.py +0 -0
- {sliderule_cli-5.6.3 → sliderule_cli-5.7.0}/src/sliderule_runner/database.py +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
import json
|
|
2
3
|
import argparse
|
|
3
4
|
from datetime import datetime
|
|
@@ -72,10 +73,16 @@ class Tool:
|
|
|
72
73
|
# S3 Access
|
|
73
74
|
def s3access(self):
|
|
74
75
|
credentials = self.session.provisioner.s3access()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
fmt = {
|
|
77
|
+
"bash": "export {}='{}'",
|
|
78
|
+
"powershell": "$env:{}='{}'",
|
|
79
|
+
"cmd": 'set "{}={}"',
|
|
80
|
+
}[self.args.shell]
|
|
81
|
+
for name, key in [("AWS_ACCESS_KEY_ID", "access_key_id"),
|
|
82
|
+
("AWS_SECRET_ACCESS_KEY", "secret_access_key"),
|
|
83
|
+
("AWS_SESSION_TOKEN", "session_token"),
|
|
84
|
+
("AWS_CREDENTIAL_EXPIRATION", "expiration")]:
|
|
85
|
+
print(fmt.format(name, credentials[key]))
|
|
79
86
|
|
|
80
87
|
# Authenticate
|
|
81
88
|
def authenticate(self):
|
|
@@ -127,7 +134,7 @@ def main():
|
|
|
127
134
|
|
|
128
135
|
# report
|
|
129
136
|
report = subparsers.add_parser("report", parents=[common], help="report metadata of active clusters")
|
|
130
|
-
report.add_argument('--kind', type=str, default="
|
|
137
|
+
report.add_argument('--kind', type=str, default="clusters", help="'clusters' for cluster report, 'test' for test report")
|
|
131
138
|
report.set_defaults(func=Tool.report)
|
|
132
139
|
|
|
133
140
|
# test
|
|
@@ -141,6 +148,7 @@ def main():
|
|
|
141
148
|
|
|
142
149
|
# s3access
|
|
143
150
|
s3access = subparsers.add_parser("s3access", parents=[common], help="sets AWS credentials by outputting shell commands to 'eval' into current environment")
|
|
151
|
+
s3access.add_argument('--shell', type=str, default="bash", choices=["bash", "powershell", "cmd"])
|
|
144
152
|
s3access.set_defaults(func=Tool.s3access)
|
|
145
153
|
|
|
146
154
|
# authenticate
|
|
@@ -160,7 +168,7 @@ def main():
|
|
|
160
168
|
print(f'{json.dumps(result, indent=2)}')
|
|
161
169
|
except Exception as e:
|
|
162
170
|
if args.verbose: raise
|
|
163
|
-
print(f"Unhandled error: {e}")
|
|
171
|
+
print(f"Unhandled error: {e}", file=sys.stderr)
|
|
164
172
|
|
|
165
173
|
# running via direct invocation
|
|
166
174
|
if __name__ == "__main__": main()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import importlib
|
|
2
|
+
import sys
|
|
2
3
|
import json
|
|
3
4
|
import boto3
|
|
4
5
|
import random
|
|
@@ -6,6 +7,7 @@ import string
|
|
|
6
7
|
import argparse
|
|
7
8
|
import traceback
|
|
8
9
|
from sliderule import sliderule
|
|
10
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
11
|
from .database import Database, JobState, QueuePriority, JobStatus
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
@@ -28,21 +30,31 @@ class Tool:
|
|
|
28
30
|
# open database
|
|
29
31
|
self.database = Database(args.database)
|
|
30
32
|
# create sliderule session
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
if args.net:
|
|
34
|
+
self.session = sliderule.create_session(domain=args.domain, verbose=args.verbose, rethrow=True)
|
|
35
|
+
self.session.authenticate() # gives privileges to access SlideRule Runner
|
|
33
36
|
# aws clients
|
|
34
37
|
self.s3 = boto3.client("s3", region_name="us-west-2")
|
|
35
38
|
|
|
39
|
+
# Split out bucket and key from url
|
|
40
|
+
def __parse_url(self, url):
|
|
41
|
+
path = url.split("s3://")[-1]
|
|
42
|
+
bucket = path.split("/")[0]
|
|
43
|
+
key = '/'.join(path.split("/")[1:])
|
|
44
|
+
return bucket, key
|
|
45
|
+
|
|
36
46
|
# Load Remote File from S3
|
|
37
|
-
def __load_remote_file(self, bucket, key):
|
|
47
|
+
def __load_remote_file(self, bucket, key, parse_json=True):
|
|
38
48
|
obj = self.s3.get_object(Bucket=bucket, Key=key)
|
|
39
49
|
contents = obj["Body"].read().decode("utf-8")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
if parse_json:
|
|
51
|
+
try:
|
|
52
|
+
return json.loads(contents)
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
return contents
|
|
44
56
|
|
|
45
|
-
# Get
|
|
57
|
+
# Get Result for a Job Run
|
|
46
58
|
# output of job must include the following fields for this to work:
|
|
47
59
|
# {
|
|
48
60
|
# "status": <boolean status of run>,
|
|
@@ -50,44 +62,51 @@ class Tool:
|
|
|
50
62
|
# "stop": <stop time in seconds of run>,
|
|
51
63
|
# "outputs": [<output1>, <output2>, ... <outputN>]
|
|
52
64
|
# }
|
|
53
|
-
def
|
|
54
|
-
|
|
55
|
-
bucket =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
args_bucket = receipt["args"].split("s3://")[-1].split("/")[0]
|
|
59
|
-
args_file = "/".join(receipt["args"].split("s3://")[-1].split("/")[1:])
|
|
60
|
-
args_content = self.__load_remote_file(args_bucket, args_file)
|
|
61
|
-
if isinstance(args_content, list):
|
|
62
|
-
args_list = args_content
|
|
63
|
-
elif isinstance(args_content, dict) or isinstance(args_content, str):
|
|
64
|
-
args_list = [args_content]
|
|
65
|
-
for i in tqdm(range(len(args_list)), total=len(args_list), desc=f"{run_url}", unit="granule"):
|
|
66
|
-
result_file = f"{prefix}/result{len(args_list) > 1 and str(i) or ''}.json"
|
|
65
|
+
def __get_result(self, results, i):
|
|
66
|
+
result = results[i]
|
|
67
|
+
bucket, result_file = self.__parse_url(result["file"])
|
|
68
|
+
try:
|
|
69
|
+
rsps = self.__load_remote_file(bucket, result_file)
|
|
67
70
|
try:
|
|
68
|
-
result
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
71
|
+
result |= {
|
|
72
|
+
"status": rsps["status"] and JobStatus.SUCCESS or JobStatus.FAILURE,
|
|
73
|
+
"duration": rsps["status"] and (rsps["stop"] - rsps["start"]) or 0.0,
|
|
74
|
+
"outputs": rsps["outputs"]
|
|
72
75
|
}
|
|
73
|
-
rsps = self.__load_remote_file(bucket, result_file)
|
|
74
|
-
try:
|
|
75
|
-
result |= {
|
|
76
|
-
"status": rsps["status"] and JobStatus.SUCCESS or JobStatus.FAILURE,
|
|
77
|
-
"duration": rsps["status"] and (rsps["stop"] - rsps["start"]) or 0.0,
|
|
78
|
-
"outputs": rsps["outputs"]
|
|
79
|
-
}
|
|
80
|
-
except Exception as e:
|
|
81
|
-
result |= {
|
|
82
|
-
"status": JobStatus.UNSUPPORTED,
|
|
83
|
-
"result": rsps
|
|
84
|
-
}
|
|
85
76
|
except Exception as e:
|
|
86
77
|
result |= {
|
|
87
|
-
"status": JobStatus.
|
|
78
|
+
"status": JobStatus.UNSUPPORTED,
|
|
79
|
+
"result": rsps,
|
|
88
80
|
"error": f"{e}"
|
|
89
81
|
}
|
|
90
|
-
|
|
82
|
+
except Exception as e:
|
|
83
|
+
result |= {
|
|
84
|
+
"status": JobStatus.ERROR,
|
|
85
|
+
"error": f"{e}"
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Get all results for a submission
|
|
89
|
+
def __get_results(self, run_url, job_size):
|
|
90
|
+
bucket, prefix = self.__parse_url(run_url)
|
|
91
|
+
receipt = self.__load_remote_file(bucket, f"{prefix}/receipt.json") # {"name": ..., "username": ... "args": <path to arg file>, "environment": ...}
|
|
92
|
+
args_bucket, args_file = self.__parse_url(receipt["args"])
|
|
93
|
+
args_content = self.__load_remote_file(args_bucket, args_file, job_size > 1)
|
|
94
|
+
if isinstance(args_content, list):
|
|
95
|
+
args_list = args_content
|
|
96
|
+
else:
|
|
97
|
+
args_list = [args_content]
|
|
98
|
+
if len(args_list) != job_size:
|
|
99
|
+
print(f"Incorrect number of arguments {len(args_list)}, expected {job_size}")
|
|
100
|
+
return None
|
|
101
|
+
results = [{
|
|
102
|
+
"file": f"s3://{bucket}/{prefix}/result{len(args_list) > 1 and str(i) or ''}.json",
|
|
103
|
+
"environment": receipt["environment"],
|
|
104
|
+
"arg": args_list[i]
|
|
105
|
+
} for i in range(len(args_list))]
|
|
106
|
+
with ThreadPoolExecutor(max_workers=self.args.concurrency) as executor:
|
|
107
|
+
futures = [executor.submit(self.__get_result, results, i) for i in range(len(results))]
|
|
108
|
+
for future in tqdm(as_completed(futures), total=len(results), desc=f"{run_url}", unit="job"):
|
|
109
|
+
future.result()
|
|
91
110
|
return results
|
|
92
111
|
|
|
93
112
|
# Archive Database
|
|
@@ -95,6 +114,15 @@ class Tool:
|
|
|
95
114
|
self.database.write(self.args.archive)
|
|
96
115
|
self.database.remove()
|
|
97
116
|
|
|
117
|
+
# Prune Database
|
|
118
|
+
def prune_database(self):
|
|
119
|
+
name = self.args.name
|
|
120
|
+
if name in self.database.submissions:
|
|
121
|
+
print(f"Pruning submission {name} from database")
|
|
122
|
+
del self.database.submissions[name]
|
|
123
|
+
else:
|
|
124
|
+
print(f"Unable to find submission {name} in database")
|
|
125
|
+
|
|
98
126
|
# Submit Job
|
|
99
127
|
def submit_job(self):
|
|
100
128
|
# pull out arguments
|
|
@@ -107,6 +135,7 @@ class Tool:
|
|
|
107
135
|
memory = self.args.memory
|
|
108
136
|
image = self.args.image
|
|
109
137
|
queue = self.args.queue
|
|
138
|
+
secrets_file = self.args.secrets
|
|
110
139
|
# read script
|
|
111
140
|
with open(script_file, "r") as file:
|
|
112
141
|
script = file.read()
|
|
@@ -115,17 +144,27 @@ class Tool:
|
|
|
115
144
|
arguments = [str(arguments_input)]
|
|
116
145
|
else:
|
|
117
146
|
with open(arguments_input, "r") as file:
|
|
118
|
-
arguments = [line.strip() for line in file.readlines()]
|
|
119
|
-
|
|
147
|
+
arguments = [line.strip() for line in file.readlines() if line.strip()]
|
|
148
|
+
# read secrets
|
|
149
|
+
if secrets_file:
|
|
150
|
+
with open(secrets_file, "r") as file:
|
|
151
|
+
secret_values = json.load(file)
|
|
152
|
+
else:
|
|
153
|
+
secret_values = None
|
|
154
|
+
# process job in batches
|
|
120
155
|
for i in range(0, len(arguments), batch_size):
|
|
121
156
|
# build and check name
|
|
122
157
|
job_name = f"{name}_{i}"
|
|
123
158
|
if job_name in self.database.submissions:
|
|
124
|
-
|
|
125
|
-
|
|
159
|
+
if self.args.dups_ok:
|
|
160
|
+
unique = ''.join(random.choices(string.ascii_lowercase, k=3))
|
|
161
|
+
job_name = f"{name}_{unique}_{i}"
|
|
162
|
+
else:
|
|
163
|
+
print(f"Skipping duplicate submission: {job_name}")
|
|
164
|
+
continue
|
|
126
165
|
# submit & save job
|
|
127
166
|
args_list = arguments[i:i+batch_size]
|
|
128
|
-
rsps = self.session.runner.submit(name=job_name, script=script, args=args_list, optional_args={"vcpus":vcpus, "memory":memory, "image":image, "queue":queue})
|
|
167
|
+
rsps = self.session.runner.submit(name=job_name, script=script, args=args_list, optional_args={"vcpus":vcpus, "memory":memory, "image":image, "queue":queue}, secret_values=secret_values)
|
|
129
168
|
self.database.submissions[job_name] = rsps | {"complete": False, "job_size": len(args_list)}
|
|
130
169
|
print(f"Submitted job {job_name} using script {script_file} with {len(args_list)} entries: {rsps}")
|
|
131
170
|
|
|
@@ -160,27 +199,32 @@ class Tool:
|
|
|
160
199
|
queue = self.args.queue
|
|
161
200
|
for name,job in self.database.submissions.items():
|
|
162
201
|
complete = job["complete"]
|
|
163
|
-
|
|
202
|
+
sys.stdout.write(f"Statusing {name} ... ")
|
|
203
|
+
sys.stdout.flush()
|
|
164
204
|
if not complete:
|
|
165
|
-
if job["job_size"] > 1: # array processing
|
|
205
|
+
if job["job_size"] > 1: # array processing
|
|
166
206
|
queue_status = self.session.runner.queue(job_id=job["job_id"], queue=queue, verbose=True)
|
|
167
207
|
child_jobs = queue_status["jobs"]
|
|
168
208
|
self.database.submissions[name]["jobs"] = {}
|
|
169
209
|
for child_job in child_jobs:
|
|
170
210
|
self.database.submissions[name]["jobs"][int(child_job["index"])] = child_job
|
|
171
|
-
else: # single job
|
|
172
|
-
queue_status = self.session.runner.queue(
|
|
211
|
+
else: # single job
|
|
212
|
+
queue_status = self.session.runner.queue(job_id=job["job_id"], queue=queue, verbose=False)
|
|
173
213
|
# report
|
|
174
214
|
report = queue_status["report"]
|
|
175
215
|
self.database.submissions[name]["status"] = report
|
|
176
216
|
jobs_in_progress = sum([report[s] for s in [JobState.SUBMITTED, JobState.PENDING, JobState.RUNNABLE, JobState.STARTING, JobState.RUNNING]])
|
|
177
217
|
jobs_complete = sum([report[s] for s in [JobState.SUCCEEDED, JobState.FAILED]])
|
|
178
218
|
if jobs_in_progress == 0 and jobs_complete > 0:
|
|
179
|
-
print(
|
|
180
|
-
self.database.submissions[name]["
|
|
181
|
-
|
|
219
|
+
print("complete")
|
|
220
|
+
results = self.__get_results(job["run_url"], self.database.submissions[name]["job_size"])
|
|
221
|
+
if results:
|
|
222
|
+
self.database.submissions[name]["results"] = results
|
|
223
|
+
self.database.submissions[name]["complete"] = True
|
|
182
224
|
else:
|
|
183
|
-
print(f"
|
|
225
|
+
print(f"still pending")
|
|
226
|
+
else:
|
|
227
|
+
print("") # flush with new line
|
|
184
228
|
if self.args.verbose:
|
|
185
229
|
print(",".join([f"{c:>30}" for c in ["NAME"]] + [f"{c:>10}" for c in ["INDEX", "STATUS"]]))
|
|
186
230
|
for name,job in self.database.submissions.items():
|
|
@@ -193,21 +237,20 @@ class Tool:
|
|
|
193
237
|
|
|
194
238
|
# Generate Report
|
|
195
239
|
def generate_report(self):
|
|
196
|
-
print(",".join([f"{c:>30}" for c in ["name"]] + [f"{c:>12}" for c in
|
|
240
|
+
print(",".join([f"{c:>30}" for c in ["name"]] + [f"{c:>12}" for c in list(JobStatus)] + [f"{c:>12}" for c in ["duration"]]))
|
|
197
241
|
for name,submission in self.database.submissions.items():
|
|
198
242
|
if self.args.name and self.args.name != name: continue
|
|
199
243
|
stats = {status.value: 0 for status in JobStatus}
|
|
200
|
-
duration = {"avg": 0.0, "total": 0.0}
|
|
201
|
-
processed = 0
|
|
244
|
+
duration = {"avg": 0.0, "total": 0.0, "cnt": 0}
|
|
202
245
|
if submission["complete"]:
|
|
203
246
|
for result in submission["results"]:
|
|
204
247
|
stats[result["status"]] += 1
|
|
205
|
-
if "duration" in result:
|
|
248
|
+
if ("duration" in result) and (result["status"] == JobStatus.SUCCESS):
|
|
206
249
|
duration["total"] += result["duration"]
|
|
207
|
-
|
|
208
|
-
if
|
|
209
|
-
duration["avg"] = duration["total"] /
|
|
210
|
-
print(",".join([f"{c:>30}" for c in [name]] + [f"{c:>12}" for c in [
|
|
250
|
+
duration["cnt"] += 1
|
|
251
|
+
if duration["cnt"] > 0:
|
|
252
|
+
duration["avg"] = duration["total"] / duration["cnt"]
|
|
253
|
+
print(",".join([f"{c:>30}" for c in [name]] + [f"{c:>12}" for c in [stats[status] for status in list(JobStatus)]] + [f"{c:>12.3}" for c in [duration["avg"]]]))
|
|
211
254
|
|
|
212
255
|
# Cancel Job
|
|
213
256
|
def cancel_job(self):
|
|
@@ -231,7 +274,7 @@ class Tool:
|
|
|
231
274
|
|
|
232
275
|
# Finish
|
|
233
276
|
def finish(self):
|
|
234
|
-
if not self.args.
|
|
277
|
+
if not self.args.ro:
|
|
235
278
|
# save database
|
|
236
279
|
self.database.write()
|
|
237
280
|
|
|
@@ -241,6 +284,14 @@ class Tool:
|
|
|
241
284
|
|
|
242
285
|
def main():
|
|
243
286
|
|
|
287
|
+
# ----------------------------------------------------------------------------
|
|
288
|
+
# The sliderule-runner has a set of commands along with common options. Each
|
|
289
|
+
# command has three attributes:
|
|
290
|
+
# func - the function to call when that subcommand is issued
|
|
291
|
+
# ro - "read-only", i.e. is the database written to or only read from
|
|
292
|
+
# net - "network", i.e. does a sliderule session need to be created
|
|
293
|
+
# ----------------------------------------------------------------------------
|
|
294
|
+
|
|
244
295
|
# command line arguments
|
|
245
296
|
parser = argparse.ArgumentParser(prog="sliderule-runner", description="""SlideRule Runner""")
|
|
246
297
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
@@ -251,51 +302,58 @@ def main():
|
|
|
251
302
|
common.add_argument('--database', type=Path, default=Path.home() / ".cache" / "sliderule" / "runner_database.json")
|
|
252
303
|
common.add_argument('--queue', type=QueuePriority, default=QueuePriority.DEFAULT, choices=list(QueuePriority))
|
|
253
304
|
common.add_argument('--verbose', action='store_true', default=False)
|
|
254
|
-
common.add_argument('--dryrun', action='store_true', default=False)
|
|
255
305
|
|
|
256
306
|
# archive
|
|
257
307
|
archive = subparsers.add_parser("archive", parents=[common], help="save and clear the database")
|
|
258
308
|
archive.add_argument('archive', metavar="<full path to archive file>")
|
|
259
|
-
archive.set_defaults(func=Tool.archive_database)
|
|
309
|
+
archive.set_defaults(func=Tool.archive_database, ro=True, net=False)
|
|
310
|
+
|
|
311
|
+
# prune
|
|
312
|
+
prune = subparsers.add_parser("prune", parents=[common], help="remove a submission from the database")
|
|
313
|
+
prune.add_argument('name', type=str, metavar="<name>")
|
|
314
|
+
prune.set_defaults(func=Tool.prune_database, ro=False, net=False)
|
|
260
315
|
|
|
261
316
|
# submit
|
|
262
317
|
submit = subparsers.add_parser("submit", parents=[common], help="submit a job")
|
|
263
|
-
submit.add_argument('name', metavar="<name>")
|
|
264
|
-
submit.add_argument('script', metavar="<script
|
|
265
|
-
submit.add_argument('arguments', metavar="<arguments
|
|
318
|
+
submit.add_argument('name', type=str, metavar="<name>")
|
|
319
|
+
submit.add_argument('script', type=str, metavar="<script file>")
|
|
320
|
+
submit.add_argument('arguments', type=str, metavar="<arguments file>")
|
|
266
321
|
submit.add_argument('--arg_as_str', action='store_true', default=False) # changes arguments to be just a string instead of a file
|
|
322
|
+
submit.add_argument('--dups_ok', action='store_true', default=False) # allow a duplicate submission (will provide random unique qualitfier to name)
|
|
267
323
|
submit.add_argument('--vcpus', type=int, default=4)
|
|
268
324
|
submit.add_argument('--memory', type=int, default=16000)
|
|
269
325
|
submit.add_argument('--batch_size', type=int, default=10000)
|
|
270
|
-
submit.add_argument('--image', type=str, default="
|
|
271
|
-
submit.
|
|
326
|
+
submit.add_argument('--image', type=str, default="pysr:latest")
|
|
327
|
+
submit.add_argument('--secrets', type=Path, default=None) # contents of file must be json
|
|
328
|
+
submit.set_defaults(func=Tool.submit_job, ro=False, net=True)
|
|
272
329
|
|
|
273
330
|
# scrape
|
|
274
331
|
scrape = subparsers.add_parser("scrape", parents=[common], help="generate list of arguments from jobs with provided job status")
|
|
275
332
|
scrape.add_argument('--status', type=JobStatus, nargs='+', default=[JobStatus.FAILURE, JobStatus.UNSUPPORTED, JobStatus.ERROR])
|
|
276
333
|
scrape.add_argument('--name', type=str, default=None) # name of submission
|
|
277
334
|
scrape.add_argument('--output', type=str, default=None)
|
|
278
|
-
scrape.set_defaults(func=Tool.scrape_submissions)
|
|
335
|
+
scrape.set_defaults(func=Tool.scrape_submissions, ro=True, net=False)
|
|
279
336
|
|
|
280
337
|
# status
|
|
281
338
|
status = subparsers.add_parser("status", parents=[common], help="display status of submitted jobs")
|
|
282
|
-
status.
|
|
339
|
+
status.add_argument('--concurrency',type=int, default=20)
|
|
340
|
+
status.set_defaults(func=Tool.get_status, ro=False, net=True)
|
|
283
341
|
|
|
284
342
|
# report
|
|
285
343
|
report = subparsers.add_parser("report", parents=[common], help="generate report of completed jobs")
|
|
286
344
|
report.add_argument('--name', type=str, default=None) # name of submission
|
|
287
|
-
report.set_defaults(func=Tool.generate_report)
|
|
345
|
+
report.set_defaults(func=Tool.generate_report, ro=True, net=False)
|
|
288
346
|
|
|
289
347
|
# cancel
|
|
290
348
|
cancel = subparsers.add_parser("cancel", parents=[common], help="cancel a submitted job")
|
|
291
349
|
cancel.add_argument('--name', type=str, required=True) # name of submission
|
|
292
|
-
cancel.set_defaults(func=Tool.cancel_job)
|
|
350
|
+
cancel.set_defaults(func=Tool.cancel_job, ro=True, net=True)
|
|
293
351
|
|
|
294
352
|
# logs
|
|
295
353
|
logs = subparsers.add_parser("logs", parents=[common], help="get log messages for a child job")
|
|
296
|
-
logs.add_argument('--name',
|
|
297
|
-
logs.add_argument('--index',
|
|
298
|
-
logs.set_defaults(func=Tool.get_logs)
|
|
354
|
+
logs.add_argument('--name', type=str, required=True) # name of submission
|
|
355
|
+
logs.add_argument('--index', type=int, default=None) # job index
|
|
356
|
+
logs.set_defaults(func=Tool.get_logs, ro=True, net=True)
|
|
299
357
|
|
|
300
358
|
# parse command line
|
|
301
359
|
args = parser.parse_args()
|
|
@@ -304,13 +362,18 @@ def main():
|
|
|
304
362
|
tool = Tool(args)
|
|
305
363
|
|
|
306
364
|
# route command
|
|
365
|
+
exit_status = 0
|
|
307
366
|
try:
|
|
308
367
|
args.func(tool)
|
|
309
|
-
tool.finish() # only execute if tool function completed successfully
|
|
310
368
|
except Exception as e:
|
|
311
|
-
if args.verbose: raise
|
|
312
369
|
print(f"Unhandled error: {e}")
|
|
313
370
|
traceback.print_exc()
|
|
371
|
+
exit_status = 1
|
|
372
|
+
finally:
|
|
373
|
+
tool.finish()
|
|
374
|
+
|
|
375
|
+
# return back to caller
|
|
376
|
+
sys.exit(exit_status)
|
|
314
377
|
|
|
315
378
|
# running via direct invocation
|
|
316
379
|
if __name__ == "__main__": main()
|
|
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
|