1
2 """A XMLRPC-Server that answeres about the current state of a Foam-Run"""
3
4 from PyFoam.ThirdParty.six import PY3
5
6 from PyFoam.Infrastructure.ServerBase import ServerBase
7
8 if PY3:
9 from xmlrpc.client import ServerProxy
10 else:
11 from xmlrpclib import ServerProxy
12
13 from time import sleep
14 from random import random
15 import select
16
17 from PyFoam import configuration as config
18 from PyFoam import versionString
19 from PyFoam.Basics.RingBuffer import RingBuffer
20 from PyFoam.Infrastructure.NetworkHelpers import freeServerPort
21 from PyFoam.Infrastructure.Logging import foamLogger
22 from PyFoam.FoamInformation import foamMPI
23 from PyFoam.RunDictionary.ParameterFile import ParameterFile
24 from PyFoam.Error import warning
25 from PyFoam.Basics.GeneralPlotTimelines import allPlots
26 from PyFoam.Basics.TimeLineCollection import allLines
27
28 from PyFoam.Infrastructure.Hardcoded import userName
29
30 from threading import Lock,Thread,Timer
31 from time import time
32 from os import environ,path,getpid,getloadavg
33 from platform import uname
34 import socket
35
36 import sys,string
37 from traceback import extract_tb
38
40 """Finds a free server port on this machine and returns it
41
42 Valid server ports are in the range 18000 upward (the function tries to
43 find the lowest possible port number
44
45 ATTENTION: this part may introduce race conditions"""
46
47 return freeServerPort(config().getint("Network","startServerPort"),
48 length=config().getint("Network","nrServerPorts"))
49
51 """The class that handles the actual requests (only needed to hide the
52 Thread-methods from the world
53 """
54 - def __init__(self,run=None,master=None,lines=100,foamserver=None):
55 """
56 @param run: The thread that controls the run
57 @param master: The Runner-Object that controls everything
58 @param lines: the number of lines the server should remember
59 """
60 self._run=run
61 self._master=master
62 self._foamserver=foamserver
63 self._lines=RingBuffer(nr=lines)
64 self._lastTime=time()
65 self._linesLock=Lock()
66 self._maxOutputTime=config().getfloat("IsAlive","maxTimeStart")
67
69 """Inserts a new line, not to be called via XMLRPC"""
70 self._linesLock.acquire()
71 self._lines.insert(line)
72 tmp=time()
73 if (tmp-self._lastTime)>self._maxOutputTime:
74 self._maxOutputTime=tmp-self._lastTime
75 self._lastTime=tmp
76 self._linesLock.release()
77
79 """This is a Foam-Server (True by default)"""
80 return True
81
83 """The calculation still generates output and therefor seems to be living"""
84 return self.elapsedTime()<self._maxOutputTime
85
87 """Interrupts the FOAM-process"""
88 if self._run:
89 foamLogger().warning("Killed by request")
90 self._run.interrupt()
91 return True
92 else:
93 return False
94
96 """Stops the run gracefully (after writing the last time-step to disk)"""
97 self._master.stopGracefully()
98 return True
99
101 """Makes the program write the next time-step to disk and the continue"""
102 self._master.writeResults()
103 return True
104
106 """Argument vector with which the runner was called"""
107 if self._master:
108 return self._master.origArgv
109 else:
110 return []
111
113 """Argument vector with which the runner started the run"""
114 if self._master:
115 return self._master.argv
116 else:
117 return []
118
120 """Is it a parallel run?"""
121 if self._master:
122 return self._master.lam!=None
123 else:
124 return False
125
127 """How many processors are used?"""
128 if self._master:
129 if self._master.lam!=None:
130 return self._master.lam.cpuNr()
131 else:
132 return 1
133 else:
134 return 0
135
137 """Number of warnings the executable emitted"""
138 if self._master:
139 return self._master.warnings
140 else:
141 return 0
142
144 """The command line"""
145 if self._master:
146 return " ".join(self._master.origArgv)
147 else:
148 return ""
149
151 """The actual command line used"""
152 if self._master:
153 return self._master.cmd
154 else:
155 return ""
156
158 """Name of the Python-Script that runs the show"""
159 return sys.argv[0]
160
162 """@return: the data the runner collected so far"""
163 return self._master.data
164
166 """@return: the time at which the last log-line was seen"""
167 return self._master.lastLogLineSeen
168
170 """@return: the time at which the last log-line was seen"""
171 return self._master.lastTimeStepSeen
172
174 """@return: the last line that was output by the running FOAM-process"""
175 self._linesLock.acquire()
176 result=self._lines.last()
177 self._linesLock.release()
178 if not result:
179 return ""
180 return result
181
183 """@return: the current last lines as a string"""
184 self._linesLock.acquire()
185 tmp=self._lines.dump()
186 self._linesLock.release()
187 result=""
188 for l in tmp:
189 result+=l
190
191 return result
192
194 """@return: time in seconds since the last line was output"""
195 self._linesLock.acquire()
196 result=time()-self._lastTime
197 self._linesLock.release()
198
199 return result
200
202 """@param name: name of an environment variable
203 @return: value of the variable, empty string if non-existing"""
204 result=""
205 if name in environ:
206 result=environ[name]
207 return result
208
210 """@return: name of the MPI-implementation"""
211 return foamMPI()
212
214 """Version number of the Foam-Version"""
215 return self.getEnviron("WM_PROJECT_VERSION")
216
218 """@return: Version number of the PyFoam"""
219 return versionString()
220
222 """@return: the complete uname-information"""
223 return uname()
224
226 """@return: the ip of this machine"""
227 try:
228 address = socket.gethostbyname(socket.gethostname())
229
230 except:
231 address = ''
232 if not address or address.startswith('127.'):
233
234 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
235 try:
236 s.connect(('4.2.2.1', 0))
237 address = s.getsockname()[0]
238 except:
239
240 address="127.0.0.1"
241 return address
242
244 """@return: The name of the computer"""
245 return uname()[1]
246
248 """@return: all the configured parameters"""
249 return config().dump()
250
252 """@return: the current working directory"""
253 return path.abspath(path.curdir)
254
256 """@return: the PID of the script"""
257 return getpid()
258
260 """@return: a tuple with the average loads of the last 1, 5 and 15 minutes"""
261 return getloadavg()
262
264 """@return: the user that runs this script"""
265 return userName()
266
268 """@return: an ID for this run: IP:port:startTimestamp """
269 return "%s:%d:%f" % (self.ip(),self._foamserver._port,self.startTimestamp())
270
272 """@return: the unix-timestamp of the process start"""
273 return self._master.startTimestamp
274
276 """@return: the current time in the simulation"""
277 if self._master.nowTime:
278 return self._master.nowTime
279 else:
280 return 0
281
283 """@return: the time in the simulation for which the mesh was created"""
284 if self._master.nowTime:
285 return self._master.createTime
286 else:
287 return 0
288
295
297 """@return: parameter startTime from the controlDict"""
298 return float(self._readParameter("startTime"))
299
301 """@return: parameter endTime from the controlDict"""
302 return float(self._readParameter("endTime"))
303
305 """@return: parameter startTime from the controlDict"""
306 return float(self._readParameter("deltaT"))
307
311
315
317 """@param time: name of the timestep
318 @return: list of the solution files at that timestep"""
319 return self._master.getSolutionDirectory()[time].getFiles()
320
325
326 - def getDictionaryText(self,directory,name):
327 """@param directory: Sub-directory of the case
328 @param name: name of the dictionary file
329 @return: the contents of the file as a big string"""
330 return self._master.getSolutionDirectory().getDictionaryText(directory,name)
331
332 - def getDictionaryContents(self,directory,name):
333 """@param directory: Sub-directory of the case
334 @param name: name of the dictionary file
335 @return: the contents of the file as a python data-structure"""
336 return self._master.getSolutionDirectory().getDictionaryContents(directory,name)
337
338 - def writeDictionaryText(self,directory,name,text):
339 """Writes the contents of a dictionary
340 @param directory: Sub-directory of the case
341 @param name: name of the dictionary file
342 @param text: String with the dictionary contents"""
343
344 self._master.getSolutionDirectory().writeDictionaryText(directory,name,text)
345
346 return True
347
348 - def writeDictionaryContents(self,directory,name,contents):
349 """Writes the contents of a dictionary
350 @param directory: Sub-directory of the case
351 @param name: name of the dictionary file
352 @param contents: Python-dictionary with the dictionary contents"""
353
354 self._master.getSolutionDirectory().writeDictionaryContents(directory,name,contents)
355 return True
356
360
364
366 """Checks whether there is a pending change to the controlDict"""
367 return self._master.controlDict == None
368
370 """Get the user-defined remark for this job"""
371 if self._master.remark:
372 return self._master.remark
373 else:
374 return ""
375
377 """Overwrite the user-defined remark
378 @return: True if the remark was set previously"""
379 oldRemark=self._master.remark
380 self._master.remark=remark
381 return oldRemark!=None
382
384 """Return the job-ID of the queuing-system. Empty if unset"""
385 if self._master.jobId:
386 return self._master.jobId
387 else:
388 return ""
389
391 """This is the class that serves the requests about the FOAM-Run"""
392 - def __init__(self,run=None,master=None,lines=100):
393 """
394 @param run: The thread that controls the run
395 @param master: The Runner-Object that controls everything
396 @param lines: the number of lines the server should remember
397 """
398 Thread.__init__(self)
399
400 self.isRegistered=False
401
402 tries=0
403
404 maxTries=config().getint("Network","socketRetries")
405
406 ok=False
407
408 while not ok and tries<maxTries:
409 ok=True
410 tries+=1
411
412 self._port=findFreePort()
413
414 self._running=False
415
416 if self._port<0:
417 foamLogger().warning("Could not get a free port. Server not started")
418 return
419
420 try:
421 foamLogger().info("Serving on port %d" % self._port)
422 self._server=ServerBase(('',self._port),logRequests=False)
423 self._server.register_introspection_functions()
424 self._answerer=FoamAnswerer(run=run,master=master,lines=lines,foamserver=self)
425 self._server.register_instance(self._answerer)
426 self._server.register_function(self.killServer)
427 self._server.register_function(self.kill)
428 if run:
429 self._server.register_function(run.cpuTime)
430 self._server.register_function(run.cpuUserTime)
431 self._server.register_function(run.cpuSystemTime)
432 self._server.register_function(run.wallTime)
433 self._server.register_function(run.usedMemory)
434 except socket.error:
435 reason = sys.exc_info()[1]
436 ok=False
437 warning("Could not start on port",self._port,"althoug it was promised. Try:",tries,"of",maxTries)
438 foamLogger().warning("Could not get port %d - SocketError: %s. Try %d of %d" % (self._port,str(reason),tries,maxTries))
439 sleep(2+20*random())
440
441 if not ok:
442 foamLogger().warning("Exceeded maximum number of tries for getting a port: %d" % maxTries)
443 warning("Did not get a port after %d tries" % tries)
444 else:
445 if tries>1:
446 warning("Got a port after %d tries" % tries)
447
449 foamLogger().info("Running server at port %d" % self._port)
450 if self._port<0:
451 return
452
453 reg=Timer(5.,self.register)
454 reg.start()
455
456 self._running=True
457
458 try:
459 while self._running:
460 self._server.handle_request()
461 except select.error:
462
463 pass
464
465
466 self._server.server_close()
467
468 foamLogger().warning("Stopped serving on port %d" % self._port)
469
471 """Returns the IP, the PID and the port of the server (as one tuple)"""
472
473 return self._answerer.ip(),self._answerer.pid(),self._port
474
476 """Interrupts the FOAM-process (and kills the server)"""
477 self._answerer._kill()
478 return self.killServer()
479
481 """Kills the server process"""
482 tmp=self._running
483 self._running=False
484 return tmp
485
487 """Tries to register with the Meta-Server"""
488
489 foamLogger().info("Trying to register as IP:%s PID:%d Port:%d"
490 % (self._answerer.ip(),
491 self._answerer.pid(),self._port))
492 try:
493 try:
494 meta=ServerProxy(
495 "http://%s:%d" % (config().get(
496 "Metaserver","ip"),config().getint("Metaserver","port")))
497 response=meta.registerServer(self._answerer.ip(),
498 self._answerer.pid(),self._port)
499 self.isRegistered=True
500 foamLogger().info("Registered with server. Response "
501 + str(response))
502 except socket.error:
503 reason = sys.exc_info()[1]
504 foamLogger().warning("Can't connect to meta-server - SocketError: "+str(reason))
505 except:
506 foamLogger().error("Can't connect to meta-server - Unknown Error: "+str(sys.exc_info()[0]))
507 foamLogger().error(str(sys.exc_info()[1]))
508 foamLogger().error("Traceback: "+str(extract_tb(sys.exc_info()[2])))
509 except:
510
511 pass
512
514 """Tries to deregister with the Meta-Server"""
515
516 if self.isRegistered:
517 try:
518 meta=ServerProxy("http://%s:%d" % (config().get("Metaserver","ip"),config().getint("Metaserver","port")))
519 meta.deregisterServer(self._answerer.ip(),self._answerer.pid(),self._port)
520 except socket.error:
521 reason = sys.exc_info()[1]
522 foamLogger().warning("Can't connect to meta-server - SocketError: "+str(reason))
523 except:
524 foamLogger().error("Can't connect to meta-server - Unknown Error: "+str(sys.exc_info()[0]))
525 foamLogger().error(str(sys.exc_info()[1]))
526 foamLogger().error("Traceback: "+str(extract_tb(sys.exc_info()[2])))
527 else:
528 foamLogger().warning("Not deregistering, because it seems we were not registered in the first place ")
529 self._server.server_close()
530
532 """Inserts a new line, not to be called via XMLRPC"""
533 self._answerer._insertLine(line)
534
535
536