Package PyFoam :: Package Infrastructure :: Module FoamServer
[hide private]
[frames] | no frames]

Source Code for Module PyFoam.Infrastructure.FoamServer

  1  #  ICE Revision: $Id$ 
  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   
39 -def findFreePort():
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
50 -class FoamAnswerer(object):
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
68 - def _insertLine(self,line):
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
78 - def isFoamServer(self):
79 """This is a Foam-Server (True by default)""" 80 return True
81
82 - def isLiving(self):
83 """The calculation still generates output and therefor seems to be living""" 84 return self.elapsedTime()<self._maxOutputTime
85
86 - def _kill(self):
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
95 - def stop(self):
96 """Stops the run gracefully (after writing the last time-step to disk)""" 97 self._master.stopGracefully() 98 return True
99
100 - def write(self):
101 """Makes the program write the next time-step to disk and the continue""" 102 self._master.writeResults() 103 return True
104
105 - def argv(self):
106 """Argument vector with which the runner was called""" 107 if self._master: 108 return self._master.origArgv 109 else: 110 return []
111
112 - def usedArgv(self):
113 """Argument vector with which the runner started the run""" 114 if self._master: 115 return self._master.argv 116 else: 117 return []
118
119 - def isParallel(self):
120 """Is it a parallel run?""" 121 if self._master: 122 return self._master.lam!=None 123 else: 124 return False
125
126 - def procNr(self):
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
136 - def nrWarnings(self):
137 """Number of warnings the executable emitted""" 138 if self._master: 139 return self._master.warnings 140 else: 141 return 0
142
143 - def commandLine(self):
144 """The command line""" 145 if self._master: 146 return " ".join(self._master.origArgv) 147 else: 148 return ""
149
150 - def actualCommandLine(self):
151 """The actual command line used""" 152 if self._master: 153 return self._master.cmd 154 else: 155 return ""
156
157 - def scriptName(self):
158 """Name of the Python-Script that runs the show""" 159 return sys.argv[0]
160
161 - def runnerData(self):
162 """@return: the data the runner collected so far""" 163 return self._master.data
164
165 - def lastLogLineSeen(self):
166 """@return: the time at which the last log-line was seen""" 167 return self._master.lastLogLineSeen
168
169 - def lastTimeStepSeen(self):
170 """@return: the time at which the last log-line was seen""" 171 return self._master.lastTimeStepSeen
172
173 - def lastLine(self):
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
182 - def tail(self):
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
193 - def elapsedTime(self):
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
201 - def getEnviron(self,name):
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
209 - def mpi(self):
210 """@return: name of the MPI-implementation""" 211 return foamMPI()
212
213 - def foamVersion(self):
214 """Version number of the Foam-Version""" 215 return self.getEnviron("WM_PROJECT_VERSION")
216
217 - def pyFoamVersion(self):
218 """@return: Version number of the PyFoam""" 219 return versionString()
220
221 - def uname(self):
222 """@return: the complete uname-information""" 223 return uname()
224
225 - def ip(self):
226 """@return: the ip of this machine""" 227 try: 228 address = socket.gethostbyname(socket.gethostname()) 229 # This gives 127.0.0.1 if specified so in the /etc/hosts ... 230 except: 231 address = '' 232 if not address or address.startswith('127.'): 233 # ...the hard way. 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 # Got no internet connection 240 address="127.0.0.1" 241 return address
242
243 - def hostname(self):
244 """@return: The name of the computer""" 245 return uname()[1]
246
247 - def configuration(self):
248 """@return: all the configured parameters""" 249 return config().dump()
250
251 - def cwd(self):
252 """@return: the current working directory""" 253 return path.abspath(path.curdir)
254
255 - def pid(self):
256 """@return: the PID of the script""" 257 return getpid()
258
259 - def loadAvg(self):
260 """@return: a tuple with the average loads of the last 1, 5 and 15 minutes""" 261 return getloadavg()
262
263 - def user(self):
264 """@return: the user that runs this script""" 265 return userName()
266
267 - def id(self):
268 """@return: an ID for this run: IP:port:startTimestamp """ 269 return "%s:%d:%f" % (self.ip(),self._foamserver._port,self.startTimestamp())
270
271 - def startTimestamp(self):
272 """@return: the unix-timestamp of the process start""" 273 return self._master.startTimestamp
274
275 - def time(self):
276 """@return: the current time in the simulation""" 277 if self._master.nowTime: 278 return self._master.nowTime 279 else: 280 return 0
281
282 - def createTime(self):
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
289 - def _readParameter(self,name):
290 """Reads a parametr from the controlDict 291 @param name: the parameter 292 @return: The value""" 293 control=ParameterFile(self._master.getSolutionDirectory().controlDict()) 294 return control.readParameter(name)
295
296 - def startTime(self):
297 """@return: parameter startTime from the controlDict""" 298 return float(self._readParameter("startTime"))
299
300 - def endTime(self):
301 """@return: parameter endTime from the controlDict""" 302 return float(self._readParameter("endTime"))
303
304 - def deltaT(self):
305 """@return: parameter startTime from the controlDict""" 306 return float(self._readParameter("deltaT"))
307
308 - def pathToSolution(self):
309 """@return: the path to the solution directory""" 310 return self._master.getSolutionDirectory().name
311
312 - def writtenTimesteps(self):
313 """@return: list of the timesteps on disc""" 314 return self._master.getSolutionDirectory().getTimes()
315
316 - def solutionFiles(self,time):
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
321 - def listFiles(self,directory):
322 """@param directory: Sub-directory of the case 323 @return: List of the filenames (not directories) in that case""" 324 return self._master.getSolutionDirectory().listFiles(directory)
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
357 - def getPlots(self):
358 """Get all the information about the plots""" 359 return allPlots().prepareForTransfer()
360
361 - def getPlotData(self):
362 """Get all the data for the plots""" 363 return allLines().prepareForTransfer()
364
365 - def controlDictUnmodified(self):
366 """Checks whether there is a pending change to the controlDict""" 367 return self._master.controlDict == None
368
369 - def getRemark(self):
370 """Get the user-defined remark for this job""" 371 if self._master.remark: 372 return self._master.remark 373 else: 374 return ""
375
376 - def setRemark(self,remark):
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
383 - def jobId(self):
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
390 -class FoamServer(Thread):
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] # compatible with 2.x and 3.x 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
448 - def run(self):
449 foamLogger().info("Running server at port %d" % self._port) 450 if self._port<0: 451 return 452 # wait befor registering to avoid timeouts 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 # This seems to be necessary since python 2.6 463 pass 464 465 # self._server.serve_forever() # the old way 466 self._server.server_close() 467 468 foamLogger().warning("Stopped serving on port %d" % self._port)
469
470 - def info(self):
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
475 - def kill(self):
476 """Interrupts the FOAM-process (and kills the server)""" 477 self._answerer._kill() 478 return self.killServer()
479
480 - def killServer(self):
481 """Kills the server process""" 482 tmp=self._running 483 self._running=False 484 return tmp
485
486 - def register(self):
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] # compatible with 2.x and 3.x 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 # print "Error during registering (no socket module?)" 511 pass
512
513 - def deregister(self):
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] # compatible with 2.x and 3.x 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
531 - def _insertLine(self,line):
532 """Inserts a new line, not to be called via XMLRPC""" 533 self._answerer._insertLine(line)
534 535 # Should work with Python3 and Python2 536