-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli_interface.py
More file actions
341 lines (281 loc) · 9.96 KB
/
Copy pathcli_interface.py
File metadata and controls
341 lines (281 loc) · 9.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"""
Rich interface for generating Slurmify job configurations
"""
import os
from rich.console import Console
from rich.prompt import Prompt, IntPrompt, Confirm
from rich.panel import Panel
from rich.table import Table
from rich.syntax import Syntax
# Import Slurmify classes
from utils.config_info import (
Jobs,
Job,
System,
Resources,
Environment,
Module,
Modules,
Logs,
)
console = Console()
def header():
"""Display a nice header."""
console.print(
Panel.fit(
"[bold blue]Slurmify Job Generator[/bold blue]",
border_style="blue",
padding=(1, 2),
)
)
console.print("Create a SLURM job configuration interactively")
console.print()
def get_basic_info():
"""Collect basic job information."""
console.print(
Panel("[bold green]Basic Job Information[/bold green]", border_style="green")
)
name = Prompt.ask("[yellow]Job name[/yellow]", default="SlurmJob")
account = Prompt.ask(
"[yellow]Account ID[/yellow] (format: p200000)", default="default"
)
return name, account
def get_resources():
"""Collect resource requirements."""
console.print(
Panel("[bold green]Resource Requirements[/bold green]", border_style="green")
)
# Create a table showing partition options
table = Table(title="Available Partitions")
table.add_column("Partition", style="cyan")
table.add_column("Description", style="green")
table.add_row("cpu", "Standard CPU partition")
table.add_row("gpu", "GPU-enabled partition")
table.add_row("fpga", "FPGA-enabled partition")
table.add_row("largemem", "Large memory nodes")
console.print(table)
partition = Prompt.ask(
"[yellow]Partition[/yellow]",
choices=["cpu", "gpu", "fpga", "largemem"],
default="cpu",
)
cores = IntPrompt.ask("[yellow]CPU cores per task[/yellow]", default=1)
# Only ask for GPU count if gpu partition selected
gpu = None
if partition == "gpu":
gpu = IntPrompt.ask("[yellow]Number of GPUs per task[/yellow]", default=1)
# Create a table showing QoS options
table = Table(title="Available QoS Options")
table.add_column("QoS", style="cyan")
table.add_column("Max Time", style="yellow")
table.add_column("Description", style="green")
table.add_row("default", "48:00:00", "Standard QoS for production jobs")
table.add_row("dev", "06:00:00", "Interactive development jobs")
table.add_row("test", "00:30:00", "Quick testing and debugging")
table.add_row("short", "06:00:00", "Short jobs for backfilling")
table.add_row("long", "144:00:00", "Long-running jobs")
console.print(table)
qos = Prompt.ask(
"[yellow]QoS mode[/yellow]",
choices=[
"default",
"dev",
"test",
"short",
"short-preempt",
"long",
"large",
"urgent",
],
default="default",
)
nodes = IntPrompt.ask("[yellow]Number of nodes[/yellow]", default=1)
tasks = IntPrompt.ask("[yellow]Number of tasks[/yellow]", default=1)
time_format = "HH:MM:SS"
time = Prompt.ask(
f"[yellow]Time limit[/yellow] (format: {time_format})", default="00:15:00"
)
return partition, cores, gpu, qos, nodes, tasks, time
def get_commands_and_env():
"""Get execution commands and environment setup."""
console.print(
Panel("[bold green]Commands & Environment[/bold green]", border_style="green")
)
commands = []
console.print("[yellow]Enter execution commands (empty line to finish):[/yellow]")
while True:
cmd = Prompt.ask("Command", default="")
if not cmd:
break
commands.append(cmd)
if not commands:
commands = ["echo 'No command specified'"]
# Environment variables
env_commands = []
if Confirm.ask("Do you want to set environment variables?"):
console.print(
"[yellow]Enter environment commands (empty line to finish):[/yellow]"
)
while True:
env_cmd = Prompt.ask("Environment command", default="")
if not env_cmd:
break
env_commands.append(env_cmd)
# Modules
modules = []
if Confirm.ask("Do you want to load modules?"):
console.print("[yellow]Enter module names (empty line to finish):[/yellow]")
console.print("Format: name/version (e.g., GCC/10.3.0)")
while True:
module = Prompt.ask("Module", default="")
if not module:
break
modules.append(module)
return commands, env_commands, modules
def get_logs():
"""Get log file configuration."""
console.print(
Panel("[bold green]Log Configuration[/bold green]", border_style="green")
)
use_logs = Confirm.ask("Do you want to specify log files?")
if not use_logs:
return None, None
console.print("Default placeholders: %j (job id), %x (job name)")
stdout = Prompt.ask(
"[yellow]Path for standard output[/yellow]", default="job-%j.out"
)
stderr = Prompt.ask(
"[yellow]Path for standard error[/yellow]", default="job-%j.err"
)
return stdout, stderr
def generate_config(job_params):
"""Generate a configuration file based on the collected parameters."""
jobs = Jobs()
job = jobs.generate_job_based_on_params(**job_params)
# Create python configuration code
config_code = f"""from utils.config_info import Resources, System, Job, Jobs, Environment, Module, Modules, Logs
# Create Jobs container
jobs = Jobs()
# Define resources
resources = Resources(
account="{job_params['account']}",
partitions="{job_params['partition']}",
cores={job_params['cores']},
gpu={job_params['gpu']},
mode="{job_params['mode']}",
nodes={job_params['nodes']},
time="{job_params['time']}",
ntasks={job_params['ntasks']}
)
# Create system configuration
system = System(resources=resources)
# Command to execute
exec_command = {repr(job_params['exec_command'])}
"""
if job_params.get("logs_default") or job_params.get("logs_error"):
config_code += f"""
# Configure logs
logs = Logs(
default="{job_params.get('logs_default', 'job-%j.out')}",
error="{job_params.get('logs_error', 'job-%j.err')}"
)
"""
else:
config_code += "\n# No log configuration specified\nlogs = None\n"
if job_params.get("environment_commands"):
config_code += f"""
# Configure environment
env = Environment(
name="Env_{job_params['name']}",
commands={repr(job_params.get('environment_commands'))}
)
environments = [env]
"""
else:
config_code += (
"\n# No environment configuration specified\nenvironments = None\n"
)
if job_params.get("module_names"):
config_code += f"""
# Configure modules
modules_list = [Module(name=module_name) for module_name in {repr(job_params.get('module_names'))}]
modules = Modules(list_of_modules=modules_list)
"""
else:
config_code += "\n# No modules specified\nmodules = None\n"
config_code += f"""
# Create the job
job = Job(
name="{job_params['name']}",
system=system,
exec_command=exec_command,
environments=environments,
logs=logs,
modules=modules
)
# Add job to the Jobs collection
jobs.add_job(job)
# This variable needs to be present for Slurmify to find the jobs
Jobs = jobs
"""
return config_code
def main():
"""Main function to run the interface."""
header()
# Collect all required information
name, account = get_basic_info()
partition, cores, gpu, qos, nodes, tasks, time = get_resources()
commands, env_commands, modules = get_commands_and_env()
stdout_log, stderr_log = get_logs()
# Prepare job parameters
job_params = {
"name": name,
"account": account,
"exec_command": commands,
"cores": cores,
"gpu": gpu,
"mode": qos,
"nodes": nodes,
"time": time,
"ntasks": tasks,
"partition": partition,
"environment_commands": env_commands if env_commands else None,
"module_names": modules if modules else None,
"logs_default": stdout_log,
"logs_error": stderr_log,
}
# Generate configuration
config_code = generate_config(job_params)
# Show the generated code
console.print(
Panel("[bold green]Generated Configuration[/bold green]", border_style="green")
)
syntax = Syntax(config_code, "python", theme="monokai", line_numbers=True)
console.print(syntax)
# Save to file
if Confirm.ask("Save this configuration to a file?"):
filename = Prompt.ask(
"Filename", default=f"{name.lower().replace(' ', '_')}_config.py"
)
with open(filename, "w") as f:
f.write(config_code)
console.print(f"[bold green]Configuration saved to {filename}[/bold green]")
# Offer to validate
if Confirm.ask("Validate this configuration?"):
console.print("[bold]Running validation...[/bold]")
import subprocess
result = subprocess.run(
["python", "main.py", "--file", filename],
capture_output=True,
text=True,
)
if (
result.returncode == 0
): # TODO: This is actualy not how to check if the config is valid
console.print("[bold green]Configuration is valid![/bold green]")
else:
console.print("[bold red]Configuration validation failed:[/bold red]")
console.print(result.stdout)
console.print(result.stderr)
if __name__ == "__main__":
main()