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 * |
|
18 |
from . import VTKRendering |
|
19 |
#import io |
|
20 |
|
|
21 |
from IPython import embed |
|
22 |
|
|
23 |
#Color definitions for terminal |
|
24 |
class bcolors: |
|
25 |
HEADER = '\033[95m' |
|
26 |
OKBLUE = '\033[94m' |
|
27 |
OKGREEN = '\033[92m' |
|
28 |
WARNING = '\033[93m' |
|
29 |
FAIL = '\033[91m' |
|
30 |
ENDC = '\033[0m' |
|
31 |
BOLD = '\033[1m' |
|
32 |
UNDERLINE = '\033[4m' |
|
33 |
|
|
34 |
#parses Command Line Arguments and returns the list of parsed values |
|
35 |
def ParseCLIArguments(arguments): |
|
36 |
parser = argparse.ArgumentParser(description='Manages (start, stop, status) multiple simulation processes of trisurf according to the configuration file.') |
|
37 |
parser.add_argument('proc_no', metavar='PROC_NO', nargs='*', |
|
38 |
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.') |
|
39 |
action_group=parser.add_mutually_exclusive_group(required=True) |
|
40 |
action_group.add_argument('-c','--comment',nargs=1, help='append comment to current comment') |
|
41 |
action_group.add_argument('--delete-comment', help='delete comment',action='store_true') |
|
42 |
action_group.add_argument('-k','--kill','--stop','--suspend', help='stop/kill the process', action='store_true') |
|
43 |
action_group.add_argument('-r','--run','--start','--continue', help='start/continue process', action='store_true') |
|
44 |
action_group.add_argument('-s','--status',help='print status of the processes',action='store_true') |
|
45 |
action_group.add_argument('-v','--version', help='print version information and exit', action='store_true') |
|
46 |
action_group.add_argument('--web-server', type=int,metavar="PORT", nargs=1, help='EXPERIMENTAL: starts web server and never exist.') |
|
47 |
action_group.add_argument('--jump-to-ipython', help='loads the variables and jumps to IPython shell', action="store_true") |
|
48 |
action_group.add_argument('-p','--preview',help='preview last VTU shape',action='store_true') |
|
49 |
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") |
|
50 |
parser.add_argument('-H', '--host', nargs=1, 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.') |
|
51 |
parser.add_argument('--html', help='Generate HTML output', action="store_true") |
|
52 |
parser.add_argument('-n', nargs='+', metavar='PROC_NO', type=int, help='OBSOLETE. Specifies process numbers.') |
|
53 |
parser.add_argument('-R','--raw',help='print status and the rest of the information in raw format', action="store_true") |
|
54 |
parser.add_argument('-x','--local-only',help='do not attempt to contact remote hosts. Run all operations only on local machine',action='store_true') |
|
55 |
args = parser.parse_args(arguments) |
|
56 |
return args |
|
57 |
|
|
58 |
|
|
59 |
#gets version of trisurf currently running |
|
60 |
def getTrisurfVersion(): |
|
61 |
p = subprocess.Popen('trisurf --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
|
62 |
lines=p.stdout.readlines() |
|
63 |
version=re.findall(r'[0-9a-f]{7}(?:-dirty)?', lines[0].decode('ascii')) |
|
64 |
p.wait() |
|
65 |
if(len(version)): |
|
66 |
return version[0] |
|
67 |
else: |
|
68 |
return "unknown version" |
|
69 |
|
|
70 |
|
|
71 |
|
|
72 |
def copyConfigAndConnect(hosts): |
|
73 |
print("Connecting to remote hosts and copying config files, tapes and snapshots") |
|
74 |
for host in hosts: |
|
75 |
if(host['name'] !=socket.gethostname()): #if I am not the computer named in host name |
|
76 |
try: |
|
77 |
username=host['username'] |
|
78 |
except: |
|
79 |
username=os.getusername() #default username is current user user's name |
|
80 |
try: |
|
81 |
port=host['port'] |
|
82 |
except: |
|
83 |
port=22 #default ssh port |
|
84 |
rm=Remote.Connection(hostname=host['address'],username=username, port=port) |
|
85 |
rm.connect() |
|
86 |
rm.send_file(__file__,'remote_control.py') |
|
87 |
for run in host['runs']: |
|
88 |
try: |
|
89 |
rm.send_file(run.tapeFile,run.tapeFile) |
|
90 |
except: |
|
91 |
pass |
|
92 |
try: |
|
93 |
rm.send_file(run.snapshotFile,run.snapshotFile) |
|
94 |
except: |
|
95 |
pass |
|
96 |
host['_conn']= rm |
|
97 |
# we are connected to all hosts... |
|
98 |
return hosts |
|
99 |
|
|
100 |
|
|
101 |
|
|
102 |
def getTargetRunIdxList(args): |
|
103 |
target_runs=list(map(int,args['proc_no'])) |
|
104 |
if len(target_runs)==0: |
|
105 |
#check if obsolete -n flags have numbers |
|
106 |
target_runs=args['n'] |
|
107 |
if target_runs==None: |
|
108 |
return None |
|
109 |
target_runs=list(set(target_runs)) |
|
110 |
return target_runs |
|
111 |
|
|
112 |
|
|
113 |
|
|
114 |
def status_processes(args,host): |
|
115 |
target_runs=getTargetRunIdxList(args) |
|
116 |
if target_runs==None: |
|
117 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
118 |
report=[] |
|
119 |
print("was here") |
|
120 |
for i in target_runs: |
|
121 |
line=host['runs'][i-1].getStatistics() |
|
122 |
line.insert(0,i) |
|
123 |
report.append(line) |
|
124 |
if(args['raw']): |
|
125 |
print(report) |
|
126 |
else: |
|
127 |
if(args['html']): |
|
128 |
tablefmt='html' |
|
129 |
else: |
|
130 |
tablefmt='fancy_grid' |
|
131 |
print(tabulate.tabulate(report,headers=["Run no.", "Run start time", "ETA", "Status", "PID", "Path", "Comment"], tablefmt=tablefmt)) |
|
132 |
return |
|
133 |
|
|
134 |
def run_processes(args,host): |
|
135 |
target_runs=getTargetRunIdxList(args) |
|
136 |
if target_runs==None: |
|
137 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
138 |
for i in target_runs: |
|
139 |
host['runs'][i-1].start() |
|
140 |
return |
|
141 |
|
|
142 |
def kill_processes(args,host): |
|
143 |
target_runs=getTargetRunIdxList(args) |
|
144 |
if target_runs==None: |
|
145 |
if args['force']==True: |
|
146 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
147 |
else: |
|
148 |
print("Not stopping all processes on the host. Run with --force flag if you are really sure to stop all simulations") |
|
149 |
return |
|
150 |
for i in target_runs: |
|
151 |
host['runs'][i-1].stop() |
|
152 |
return |
|
153 |
|
|
154 |
def comment_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].writeComment(args['comment'][0],'a') |
|
160 |
print("Comment added") |
|
161 |
return |
|
162 |
|
|
163 |
def delete_comments(args,host): |
|
164 |
target_runs=getTargetRunIdxList(args) |
|
165 |
if target_runs==None: |
|
166 |
if args['force']==True: |
|
167 |
target_runs=list(range(1,len(host['runs'])+1)) |
|
168 |
else: |
|
169 |
print("Not deleting comments on all posts on the host. Run with --force flag if you are really sure to delete all comments") |
|
170 |
return |
|
171 |
for i in target_runs: |
|
172 |
host['runs'][i-1].writeComment("") |
|
173 |
print("Comment deleted") |
|
174 |
return |
|
175 |
|
|
176 |
|
|
177 |
def start_web_server(args,host): |
|
178 |
print('Server listening on port {}'.format(args['web_server'][0])) |
|
179 |
if sys.version_info>=(3,0): |
|
180 |
WebTrisurf.WebServer(port=args['web_server'][0]) |
|
181 |
else: |
|
182 |
print("Cannot start WebServer in python 2.7") |
|
183 |
exit(0) |
|
184 |
|
|
185 |
def perform_action(args,host): |
|
186 |
#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 |
|
187 |
if args['run']: |
|
188 |
run_processes(args,host) |
|
189 |
elif args['kill']: |
|
190 |
kill_processes(args,host) |
|
191 |
elif args['status']: |
|
192 |
status_processes(args,host) |
|
193 |
elif args['comment']!= None: |
|
194 |
comment_processes(args,host) |
|
195 |
elif args['delete_comment']: |
|
196 |
delete_comments(args,host) |
|
197 |
elif args['web_server']!=None: |
|
198 |
start_web_server(args,host) |
|
199 |
elif args['preview']: |
|
200 |
preview_vtu(args,host) |
|
201 |
elif args['jump_to_ipython']: |
|
202 |
print('Jumping to shell...') |
|
203 |
embed() |
|
204 |
exit(0) |
|
205 |
else: #version requested |
|
206 |
print(getTrisurfVersion()) |
|
207 |
return |
|
208 |
|
|
209 |
|
|
210 |
|
|
211 |
def preview_vtu(args,host): |
|
212 |
#only for localhost at the moment |
|
213 |
if sys.version_info>=(3,0): |
|
214 |
print("Preview works only with python 2.7") |
|
215 |
exit(1) |
|
216 |
if host['name'] == socket.gethostname(): |
|
217 |
VTKRendering.Renderer(args,host) |
|
218 |
|
|
219 |
def getListOfHostConfigurationByHostname(hosts,host): |
|
220 |
rhost=[] |
|
221 |
for chost in hosts: |
|
222 |
if chost['name'] in host: |
|
223 |
rhost.append(chost) |
|
224 |
return rhost |
|
225 |
|
|
226 |
|
|
227 |
|
|
228 |
def start(hosts,argv=sys.argv[1:]): |
|
229 |
args=vars(ParseCLIArguments(argv)) |
|
230 |
#print(vars(args)) |
|
231 |
|
|
232 |
#Backward compatibility... If running just on localmode, the host specification is unnecessary. Check if only Runs are specified |
|
233 |
try: |
|
234 |
test_host=hosts[0]['name'] |
|
235 |
except: |
|
236 |
print("Network mode disabled. Old syntax detected.") |
|
237 |
host={'name':socket.gethostname(),'address':'127.0.0.1', 'runs':hosts} |
|
238 |
perform_action(args,host) |
|
239 |
exit(0) |
|
240 |
|
|
241 |
|
|
242 |
#find the host at which the action is attended |
|
243 |
if args['host']==None: |
|
244 |
if(args['status']==False and args['version']==False): |
|
245 |
hosts=getListOfHostConfigurationByHostname(hosts,socket.gethostname()) |
|
246 |
else: |
|
247 |
hosts=getListOfHostConfigurationByHostname(hosts,args['host']) |
|
248 |
if len(hosts)==0: |
|
249 |
print ('Hostname "{}" does not exist in configuration file. Please check the spelling'.format(args['host'][0])) |
|
250 |
exit(1) |
|
251 |
|
|
252 |
if not args['local_only']: |
|
253 |
hosts=copyConfigAndConnect(hosts) |
|
254 |
|
|
255 |
#do local stuff: |
|
256 |
for host in hosts: |
|
257 |
if host['name'] == socket.gethostname(): |
|
258 |
if(args['html']): |
|
259 |
print("Host <font color='orange'>"+host['name']+"</font> reports the following:") |
|
260 |
else: |
|
261 |
print("Host "+bcolors.WARNING+host['name']+bcolors.ENDC+" reports the following:") |
|
262 |
perform_action(args,host) |
|
263 |
elif not args['local_only']: |
|
264 |
output=host['_conn'].execute('python3 ./remote_control.py -x '+" ".join(argv)) |
|
265 |
for line in output: |
|
266 |
print(line.replace('\n','')) |
|
267 |
|
|
268 |
|
|
269 |
if not args['local_only']: |
|
270 |
print("Closing connections to remote hosts") |
|
271 |
for host in hosts: |
|
272 |
if(host['name'] !=socket.gethostname()): |
|
273 |
host['_conn'].disconnect() |
|
274 |
|
|
275 |
|
|
276 |
|