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