1
|
|
|
from __future__ import absolute_import |
2
|
|
|
|
3
|
|
|
import os |
4
|
|
|
import re |
5
|
|
|
import itertools |
6
|
|
|
import time as t |
7
|
|
|
from os.path import join as pjoin |
8
|
|
|
|
9
|
|
|
import smartdispatch |
10
|
|
|
from smartdispatch import utils |
11
|
|
|
from smartdispatch.filelock import open_with_lock |
12
|
|
|
from smartdispatch.argument_template import argument_templates |
13
|
|
|
|
14
|
|
|
UID_TAG = "{UID}" |
15
|
|
|
|
16
|
|
|
|
17
|
|
|
def generate_name_from_command(command, max_length_arg=None, max_length=None): |
18
|
|
|
''' Generates name from a given command. |
19
|
|
|
|
20
|
|
|
Generate a name by replacing spaces in command with dashes and |
21
|
|
|
by trimming lengthty (as defined by max_length_arg) arguments. |
22
|
|
|
|
23
|
|
|
Parameters |
24
|
|
|
---------- |
25
|
|
|
command : str |
26
|
|
|
command from which to generate the name |
27
|
|
|
max_length_arg : int |
28
|
|
|
arguments longer than this will be trimmed keeping last characters (Default: inf) |
29
|
|
|
max_length : int |
30
|
|
|
trim name if longer than this keeping last characters (Default: inf) |
31
|
|
|
|
32
|
|
|
Returns |
33
|
|
|
------- |
34
|
|
|
name : str |
35
|
|
|
slugified name |
36
|
|
|
''' |
37
|
|
|
if max_length_arg is not None: |
38
|
|
|
max_length_arg = min(-max_length_arg, max_length_arg) |
39
|
|
|
|
40
|
|
|
return generate_logfolder_name('_'.join([utils.slugify(argvalue)[max_length_arg:] for argvalue in command.split()]), max_length) |
41
|
|
|
|
42
|
|
|
|
43
|
|
|
def generate_logfolder_name(name, max_length=None): |
44
|
|
|
folder_name = t.strftime("%Y-%m-%d_%H-%M-%S_") |
45
|
|
|
folder_name += name |
46
|
|
|
return folder_name[:max_length] |
47
|
|
|
|
48
|
|
|
|
49
|
|
|
def get_commands_from_file(fileobj): |
50
|
|
|
''' Reads commands from `fileobj`. |
51
|
|
|
|
52
|
|
|
Parameters |
53
|
|
|
---------- |
54
|
|
|
fileobj : file |
55
|
|
|
opened file where to read commands from |
56
|
|
|
|
57
|
|
|
Returns |
58
|
|
|
------- |
59
|
|
|
commands : list of str |
60
|
|
|
commands read from the file |
61
|
|
|
''' |
62
|
|
|
return fileobj.read().strip().split('\n') |
63
|
|
|
|
64
|
|
|
|
65
|
|
|
def unfold_command(command): |
66
|
|
|
''' Unfolds a command into a list of unfolded commands. |
67
|
|
|
|
68
|
|
|
Unfolding is performed for every folded arguments (see *Arguments templates*) |
69
|
|
|
found in `command`. Then, resulting commands are generated using the product |
70
|
|
|
of every unfolded arguments. |
71
|
|
|
|
72
|
|
|
Parameters |
73
|
|
|
---------- |
74
|
|
|
command : list of str |
75
|
|
|
command to unfold |
76
|
|
|
|
77
|
|
|
Returns |
78
|
|
|
------- |
79
|
|
|
commands : list of str |
80
|
|
|
commands obtained after unfolding `command` |
81
|
|
|
|
82
|
|
|
Arguments template |
83
|
|
|
------------------ |
84
|
|
|
*list*: "[item1 item2 ... itemN]" |
85
|
|
|
*range*: "[start:end]" or "[start:end:step]" |
86
|
|
|
''' |
87
|
|
|
text = utils.encode_escaped_characters(command) |
88
|
|
|
|
89
|
|
|
# Build the master regex with all argument's regex |
90
|
|
|
regex = "(" + "|".join(["(?P<{0}>{1})".format(name, arg.regex) for name, arg in argument_templates.items()]) + ")" |
91
|
|
|
|
92
|
|
|
pos = 0 |
93
|
|
|
arguments = [] |
94
|
|
|
for match in re.finditer(regex, text): |
95
|
|
|
# Add already unfolded argument |
96
|
|
|
arguments.append([text[pos:match.start()]]) |
97
|
|
|
|
98
|
|
|
# Unfold argument |
99
|
|
|
argument_template_name, matched_text = next((k, v) for k, v in match.groupdict().items() if v is not None) |
100
|
|
|
arguments.append(argument_templates[argument_template_name].unfold(matched_text)) |
101
|
|
|
pos = match.end() |
102
|
|
|
|
103
|
|
|
arguments.append([text[pos:]]) # Add remaining unfolded arguments |
104
|
|
|
arguments = [map(utils.decode_escaped_characters, argvalues) for argvalues in arguments] |
105
|
|
|
return ["".join(argvalues) for argvalues in itertools.product(*arguments)] |
106
|
|
|
|
107
|
|
|
|
108
|
|
|
def replace_uid_tag(commands): |
109
|
|
|
return [command.replace("{UID}", utils.generate_uid_from_string(command)) for command in commands] |
110
|
|
|
|
111
|
|
|
|
112
|
|
|
def get_available_queues(cluster_name): |
113
|
|
|
""" Fetches all available queues on the current cluster """ |
114
|
|
|
if cluster_name is None: |
115
|
|
|
return {} |
116
|
|
|
|
117
|
|
|
smartdispatch_dir, _ = os.path.split(smartdispatch.__file__) |
118
|
|
|
config_dir = pjoin(smartdispatch_dir, 'config') |
119
|
|
|
|
120
|
|
|
config_filename = cluster_name + ".json" |
121
|
|
|
config_filepath = pjoin(config_dir, config_filename) |
122
|
|
|
|
123
|
|
|
if not os.path.isfile(config_filepath): |
124
|
|
|
return {} # Unknown cluster |
125
|
|
|
|
126
|
|
|
queues_infos = utils.load_dict_from_json_file(config_filepath) |
127
|
|
|
return queues_infos |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
def get_job_folders(path, jobname, create_if_needed=False): |
131
|
|
|
""" Get all folder paths for a specific job (creating them if needed). """ |
132
|
|
|
path_job = pjoin(path, jobname) |
133
|
|
|
path_job_logs = pjoin(path_job, 'logs') |
134
|
|
|
path_job_commands = pjoin(path_job, 'commands') |
135
|
|
|
|
136
|
|
|
if not os.path.isdir(path_job_commands): |
137
|
|
|
os.makedirs(path_job_commands) |
138
|
|
|
if not os.path.isdir(path_job_logs): |
139
|
|
|
os.makedirs(path_job_logs) |
140
|
|
|
if not os.path.isdir(pjoin(path_job_logs, "worker")): |
141
|
|
|
os.makedirs(pjoin(path_job_logs, "worker")) |
142
|
|
|
if not os.path.isdir(pjoin(path_job_logs, "job")): |
143
|
|
|
os.makedirs(pjoin(path_job_logs, "job")) |
144
|
|
|
|
145
|
|
|
return path_job, path_job_logs, path_job_commands |
146
|
|
|
|
147
|
|
|
|
148
|
|
|
def log_command_line(path_job, command_line): |
149
|
|
|
""" Logs a command line in a job folder. |
150
|
|
|
|
151
|
|
|
The command line is append to a file named 'command_line.log' that resides |
152
|
|
|
in the given job folder. The current date and time is also added along |
153
|
|
|
each command line logged. |
154
|
|
|
|
155
|
|
|
Notes |
156
|
|
|
----- |
157
|
|
|
Commands save in log file might differ from sys.argv since we want to make sure |
158
|
|
|
we can paste the command line as-is in the terminal. This means that the quotes |
159
|
|
|
symbole " and the square brackets will be escaped. |
160
|
|
|
""" |
161
|
|
|
with open_with_lock(pjoin(path_job, "command_line.log"), 'a') as command_line_log: |
162
|
|
|
command_line_log.write(t.strftime("## %Y-%m-%d %H:%M:%S ##\n")) |
163
|
|
|
command_line = command_line.replace('"', r'\"') # Make sure we can paste the command line as-is |
164
|
|
|
command_line = re.sub(r'(\[)([^\[\]]*\\ [^\[\]]*)(\])', r'"\1\2\3"', command_line) # Make sure we can paste the command line as-is |
165
|
|
|
command_line_log.write(command_line + "\n\n") |
166
|
|
|
|