commit | author | age
|
8ab985
|
1 |
import argparse |
SP |
2 |
import paramiko |
|
3 |
from . import Remote |
|
4 |
from . import trisurf |
|
5 |
import socket |
|
6 |
import os,sys |
|
7 |
import tabulate |
|
8 |
import subprocess,re |
|
9 |
import psutil |
|
10 |
#import http.server |
|
11 |
#import socketserver |
|
12 |
if sys.version_info>=(3,0): |
|
13 |
from urllib.parse import urlparse |
|
14 |
from . import WebTrisurf |
|
15 |
else: |
|
16 |
from urlparse import urlparse |
|
17 |
from vtk import * |
8b7022
|
18 |
|
8ab985
|
19 |
#import io |
SP |
20 |
|
af3d0e
|
21 |
from IPython import embed |
8ab985
|
22 |
|
f0131d
|
23 |
import __main__ as main |
SP |
24 |
|
8ab985
|
25 |
#Color definitions for terminal |
SP |
26 |
class bcolors: |
|
27 |
HEADER = '\033[95m' |
|
28 |
OKBLUE = '\033[94m' |
|
29 |
OKGREEN = '\033[92m' |
|
30 |
WARNING = '\033[93m' |
|
31 |
FAIL = '\033[91m' |
|
32 |
ENDC = '\033[0m' |
|
33 |
BOLD = '\033[1m' |
|
34 |
UNDERLINE = '\033[4m' |
|
35 |
|
|
36 |
#parses Command Line Arguments and returns the list of parsed values |
|
37 |
def ParseCLIArguments(arguments): |
|
38 |
parser = argparse.ArgumentParser(description='Manages (start, stop, status) multiple simulation processes of trisurf according to the configuration file.') |
|
39 |
parser.add_argument('proc_no', metavar='PROC_NO', nargs='*', |
|
40 |
help='process number at host. If hostname is not specified, localhost is assumed. If no processes are specified all processes on all hosts are assumed.') |
|
41 |
action_group=parser.add_mutually_exclusive_group(required=True) |
|
42 |
action_group.add_argument('-c','--comment',nargs=1, help='append comment to current comment') |
86c973
|
43 |
action_group.add_argument('--analysis', nargs='+', help='runs analysis function defined in configuration file') |
8ab985
|
44 |
action_group.add_argument('--delete-comment', help='delete comment',action='store_true') |
SP |
45 |
action_group.add_argument('-k','--kill','--stop','--suspend', help='stop/kill the process', action='store_true') |
|
46 |
action_group.add_argument('-r','--run','--start','--continue', help='start/continue process', action='store_true') |
|
47 |
action_group.add_argument('-s','--status',help='print status of the processes',action='store_true') |
|
48 |
action_group.add_argument('-v','--version', help='print version information and exit', action='store_true') |
|
49 |
action_group.add_argument('--web-server', type=int,metavar="PORT", nargs=1, help='EXPERIMENTAL: starts web server and never exist.') |
|
50 |
action_group.add_argument('--jump-to-ipython', help='loads the variables and jumps to IPython shell', action="store_true") |
|
51 |
action_group.add_argument('-p','--preview',help='preview last VTU shape',action='store_true') |
|
52 |
parser.add_argument('--force', help='if dangerous operation (killing all the processes) is requested, this flag is required to execute the operation. Otherwise, the request will be ignored.', action="store_true") |
1c8250
|
53 |
parser.add_argument('-H', '--host', nargs='+', help='specifies which host is itended for the operation. Defauts to localhost for all operations except --status and --version, where all configured hosts are assumed.') |
8ab985
|
54 |
parser.add_argument('--html', help='Generate HTML output', action="store_true") |
SP |
55 |
parser.add_argument('-n', nargs='+', metavar='PROC_NO', type=int, help='OBSOLETE. Specifies process numbers.') |
|
56 |
parser.add_argument('-R','--raw',help='print status and the rest of the information in raw format', action="store_true") |
|
57 |
parser.add_argument('-x','--local-only',help='do not attempt to contact remote hosts. Run all operations only on local machine',action='store_true') |
6f912f
|
58 |
parser.add_argument('--originating-host',nargs=1,help='specify which host started the remote connections. Useful mainly fo internal functionaly of tsmgr and analyses.') |
c1504d
|
59 |
parser.add_argument('--analysis-options', nargs='+', help='options passed down to analyses') |
8ab985
|
60 |
args = parser.parse_args(arguments) |
SP |
61 |
return args |
|
62 |
|
|
63 |
|
|
64 |
#gets version of trisurf currently running |
|
65 |
def getTrisurfVersion(): |
|
66 |
p = subprocess.Popen('trisurf --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
|
67 |
lines=p.stdout.readlines() |
|
68 |
version=re.findall(r'[0-9a-f]{7}(?:-dirty)?', lines[0].decode('ascii')) |
|
69 |
p.wait() |
|
70 |
if(len(version)): |
|
71 |
return version[0] |
|
72 |
else: |
|
73 |
return "unknown version" |
|
74 |
|
|
75 |
|
|
76 |
|
|
77 |
def copyConfigAndConnect(hosts): |
|
78 |
print("Connecting to remote hosts and copying config files, tapes and snapshots") |
d7e21a
|
79 |
#create a list of files to be copied across all the remote hosts |
SP |
80 |
file_list=[] |
|
81 |
for h in hosts: |
|
82 |
for r in h['runs']: |
|
83 |
if(r.isFromSnapshot): |
a5f021
|
84 |
file_list.append(r.snapshotFile) |
d7e21a
|
85 |
else: |
SP |
86 |
file_list.append(r.tapeFilename) |
|
87 |
file_list.append(main.__file__) |
8ab985
|
88 |
for host in hosts: |
SP |
89 |
if(host['name'] !=socket.gethostname()): #if I am not the computer named in host name |
|
90 |
try: |
|
91 |
username=host['username'] |
|
92 |
except: |
|
93 |
username=os.getusername() #default username is current user user's name |
|
94 |
try: |
|
95 |
port=host['port'] |
|
96 |
except: |
|
97 |
port=22 #default ssh port |
af3d0e
|
98 |
rm=Remote.Connection(hostname=host['address'],username=username, port=port) |
SP |
99 |
rm.connect() |
f0131d
|
100 |
# print ("Sendind file:"+main.__file__) |
d7e21a
|
101 |
if('remotebasepath' in host): |
SP |
102 |
remote_dir=host['remotebasepath'] |
|
103 |
else: |
|
104 |
remote_dir='trisurf_simulations' |
|
105 |
rm.send_multiple_files_in_directory(file_list,remote_dir) |
|
106 |
# rm.send_file(main.__file__,'remote_control.py') |
|
107 |
# for run in host['runs']: |
|
108 |
# try: |
|
109 |
# rm.send_file(run.tapeFile,run.tapeFile) |
|
110 |
# except: |
|
111 |
# pass |
|
112 |
# try: |
|
113 |
# rm.send_file(run.snapshotFile,run.snapshotFile) |
|
114 |
# except: |
|
115 |
# pass |
8ab985
|
116 |
host['_conn']= rm |
SP |
117 |
# we are connected to all hosts... |
|
118 |
return hosts |
|
119 |
|
|
120 |
|
|
121 |
|
|
122 |
def getTargetRunIdxList(args): |
|
123 |
target_runs=list(map(int,args['proc_no'])) |
|
124 |
if len(target_runs)==0: |
|
125 |
#check if obsolete -n flags have numbers |
|
126 |
target_runs=args['n'] |
|
127 |
if target_runs==None: |
|
128 |
return None |
|
129 |
target_runs=list(set(target_runs)) |
|
130 |
return target_runs |
|
131 |
|
|
132 |
|
|
133 |
|
|
134 |
def status_processes(args,host): |
|
135 |
target_runs=getTargetRunIdxList(args) |
|
136 |
if target_runs==None: |
|
137 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
138 |
report=[] |
f0131d
|
139 |
# print("was here") |
8ab985
|
140 |
for i in target_runs: |
SP |
141 |
line=host['runs'][i-1].getStatistics() |
|
142 |
line.insert(0,i) |
|
143 |
report.append(line) |
|
144 |
if(args['raw']): |
|
145 |
print(report) |
|
146 |
else: |
|
147 |
if(args['html']): |
|
148 |
tablefmt='html' |
|
149 |
else: |
|
150 |
tablefmt='fancy_grid' |
|
151 |
print(tabulate.tabulate(report,headers=["Run no.", "Run start time", "ETA", "Status", "PID", "Path", "Comment"], tablefmt=tablefmt)) |
|
152 |
return |
|
153 |
|
|
154 |
def run_processes(args,host): |
|
155 |
target_runs=getTargetRunIdxList(args) |
|
156 |
if target_runs==None: |
|
157 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
158 |
for i in target_runs: |
|
159 |
host['runs'][i-1].start() |
|
160 |
return |
|
161 |
|
|
162 |
def kill_processes(args,host): |
|
163 |
target_runs=getTargetRunIdxList(args) |
|
164 |
if target_runs==None: |
|
165 |
if args['force']==True: |
|
166 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
167 |
else: |
|
168 |
print("Not stopping all processes on the host. Run with --force flag if you are really sure to stop all simulations") |
|
169 |
return |
|
170 |
for i in target_runs: |
|
171 |
host['runs'][i-1].stop() |
|
172 |
return |
|
173 |
|
|
174 |
def comment_processes(args,host): |
|
175 |
target_runs=getTargetRunIdxList(args) |
|
176 |
if target_runs==None: |
|
177 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
178 |
for i in target_runs: |
|
179 |
host['runs'][i-1].writeComment(args['comment'][0],'a') |
|
180 |
print("Comment added") |
|
181 |
return |
|
182 |
|
|
183 |
def delete_comments(args,host): |
|
184 |
target_runs=getTargetRunIdxList(args) |
|
185 |
if target_runs==None: |
|
186 |
if args['force']==True: |
|
187 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
188 |
else: |
|
189 |
print("Not deleting comments on all posts on the host. Run with --force flag if you are really sure to delete all comments") |
|
190 |
return |
|
191 |
for i in target_runs: |
|
192 |
host['runs'][i-1].writeComment("") |
|
193 |
print("Comment deleted") |
|
194 |
return |
|
195 |
|
|
196 |
|
|
197 |
def start_web_server(args,host): |
|
198 |
print('Server listening on port {}'.format(args['web_server'][0])) |
|
199 |
if sys.version_info>=(3,0): |
|
200 |
WebTrisurf.WebServer(port=args['web_server'][0]) |
|
201 |
else: |
|
202 |
print("Cannot start WebServer in python 2.7") |
|
203 |
exit(0) |
|
204 |
|
f0131d
|
205 |
|
c1504d
|
206 |
def analyze(args,host,analysis,hosts): |
SP |
207 |
if len(trisurf._analysis_list)==0: |
|
208 |
print ('Error: no decorated function with @analysis decorator!') |
86c973
|
209 |
exit(1) |
f0131d
|
210 |
target_runs=getTargetRunIdxList(args) |
SP |
211 |
if target_runs==None: |
|
212 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
213 |
for i in target_runs: |
|
214 |
|
|
215 |
for anal in analysis: |
c1504d
|
216 |
if(anal not in trisurf._analysis_list): |
SP |
217 |
print("Analysis '"+anal+"' is not known. Available analyses: "+", ".join(trisurf._analysis_list.keys())+".") |
f0131d
|
218 |
exit(0) |
SP |
219 |
for anal in analysis: |
c1504d
|
220 |
retval=trisurf._analysis_list[anal](host['runs'][i-1],host=host, args=args, hosts=hosts) |
8c3c29
|
221 |
#try: |
SP |
222 |
if(retval): |
|
223 |
exit(0) |
|
224 |
#except: |
|
225 |
# pass |
f0131d
|
226 |
|
1c8250
|
227 |
|
SP |
228 |
|
f0131d
|
229 |
|
SP |
230 |
def perform_action(args,host,**kwargs): |
8ab985
|
231 |
#find which flags have been used and act upon them. -r -s -k -v -c --delete-comment are mutually exclusive, so only one of them is active |
SP |
232 |
if args['run']: |
|
233 |
run_processes(args,host) |
|
234 |
elif args['kill']: |
|
235 |
kill_processes(args,host) |
|
236 |
elif args['status']: |
|
237 |
status_processes(args,host) |
|
238 |
elif args['comment']!= None: |
|
239 |
comment_processes(args,host) |
|
240 |
elif args['delete_comment']: |
|
241 |
delete_comments(args,host) |
|
242 |
elif args['web_server']!=None: |
|
243 |
start_web_server(args,host) |
|
244 |
elif args['preview']: |
|
245 |
preview_vtu(args,host) |
|
246 |
elif args['jump_to_ipython']: |
|
247 |
print('Jumping to shell...') |
|
248 |
embed() |
f0131d
|
249 |
exit(0) |
SP |
250 |
elif args['analysis']!= None: |
c1504d
|
251 |
analyze(args,host, args['analysis'],kwargs.get('hosts',None)) |
8ab985
|
252 |
exit(0) |
SP |
253 |
else: #version requested |
|
254 |
print(getTrisurfVersion()) |
|
255 |
return |
|
256 |
|
|
257 |
|
|
258 |
|
|
259 |
def preview_vtu(args,host): |
d00e25
|
260 |
from . import VTKRendering |
f49271
|
261 |
target_runs=getTargetRunIdxList(args) |
SP |
262 |
if target_runs==None: |
|
263 |
target_runs=list(range(1,len(host['runs'])+1)) |
8ab985
|
264 |
if host['name'] == socket.gethostname(): |
f49271
|
265 |
for i in target_runs: |
SP |
266 |
VTKRendering.Renderer(args,host,host['runs'][i-1]) |
|
267 |
else: |
|
268 |
print("VTK rendering currently works on localhost only!") |
|
269 |
|
8ab985
|
270 |
|
SP |
271 |
def getListOfHostConfigurationByHostname(hosts,host): |
|
272 |
rhost=[] |
|
273 |
for chost in hosts: |
|
274 |
if chost['name'] in host: |
|
275 |
rhost.append(chost) |
|
276 |
return rhost |
|
277 |
|
|
278 |
|
|
279 |
|
c1504d
|
280 |
def start(hosts,argv=sys.argv[1:]): |
8ab985
|
281 |
args=vars(ParseCLIArguments(argv)) |
SP |
282 |
#Backward compatibility... If running just on localmode, the host specification is unnecessary. Check if only Runs are specified |
|
283 |
try: |
|
284 |
test_host=hosts[0]['name'] |
|
285 |
except: |
f0131d
|
286 |
print("Old syntax detected.") |
SP |
287 |
hosts=({'name':socket.gethostname(),'address':'127.0.0.1', 'runs':hosts},) |
8ab985
|
288 |
|
SP |
289 |
#find the host at which the action is attended |
|
290 |
if args['host']==None: |
1c8250
|
291 |
#Only status and version commands are automatically executed on all the hosts. stopping or starting or other actions is not! |
8ab985
|
292 |
if(args['status']==False and args['version']==False): |
SP |
293 |
hosts=getListOfHostConfigurationByHostname(hosts,socket.gethostname()) |
|
294 |
else: |
|
295 |
hosts=getListOfHostConfigurationByHostname(hosts,args['host']) |
|
296 |
if len(hosts)==0: |
|
297 |
print ('Hostname "{}" does not exist in configuration file. Please check the spelling'.format(args['host'][0])) |
|
298 |
exit(1) |
|
299 |
if not args['local_only']: |
|
300 |
hosts=copyConfigAndConnect(hosts) |
|
301 |
#do local stuff: |
|
302 |
for host in hosts: |
|
303 |
if host['name'] == socket.gethostname(): |
|
304 |
if(args['html']): |
8c3c29
|
305 |
print("Host <font color='orange'>"+host['name']+"</font> reports:") |
8ab985
|
306 |
else: |
8c3c29
|
307 |
print("Host "+bcolors.WARNING+host['name']+bcolors.ENDC+" reports:") |
c1504d
|
308 |
perform_action(args,host, hosts=hosts) |
8ab985
|
309 |
elif not args['local_only']: |
d7e21a
|
310 |
if('remotebasepath' in host): |
SP |
311 |
remote_dir=host['remotebasepath'] |
|
312 |
else: |
|
313 |
remote_dir='trisurf_simulations' |
4e6b88
|
314 |
#output=host['_conn'].execute('cd '+remote_dir) |
SP |
315 |
#print(remote_dir) |
|
316 |
#print(main.__file__) |
|
317 |
#print('python3 '+main.__file__+' -x '+" ".join(argv)) |
af3d0e
|
318 |
output=host['_conn'].execute('cd '+remote_dir+ '; python3 '+main.__file__+' -x --originating-host ' +socket.gethostname()+" "+" ".join(argv)) |
SP |
319 |
for line in output: |
|
320 |
print(line.replace('\n','')) |
8ab985
|
321 |
|
SP |
322 |
|
|
323 |
if not args['local_only']: |
|
324 |
print("Closing connections to remote hosts") |
|
325 |
for host in hosts: |
|
326 |
if(host['name'] !=socket.gethostname()): |
af3d0e
|
327 |
host['_conn'].disconnect() |
8ab985
|
328 |
|
SP |
329 |
|
|
330 |
|