PageRenderTime 42ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/pp-1.6.0/ppserver.py

#
Python | 366 lines | 307 code | 14 blank | 45 comment | 26 complexity | 7d963a2ccb4e46307345a0efdb534bea MD5 | raw file
Possible License(s): BSD-3-Clause
  1. #!/usr/bin/env python
  2. # Parallel Python Software: http://www.parallelpython.com
  3. # Copyright (c) 2005-2010, Vitalii Vanovschi
  4. # All rights reserved.
  5. # Redistribution and use in source and binary forms, with or without
  6. # modification, are permitted provided that the following conditions are met:
  7. # * Redistributions of source code must retain the above copyright notice,
  8. # this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. # * Neither the name of the author nor the names of its contributors
  13. # may be used to endorse or promote products derived from this software
  14. # without specific prior written permission.
  15. #
  16. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  26. # THE POSSIBILITY OF SUCH DAMAGE.
  27. """
  28. Parallel Python Software, Network Server
  29. http://www.parallelpython.com - updates, documentation, examples and support
  30. forums
  31. """
  32. import logging
  33. import getopt
  34. import sys
  35. import socket
  36. import threading
  37. import random
  38. import string
  39. import time
  40. import os
  41. import pp
  42. import ppauto
  43. import ppcommon
  44. import pptransport
  45. copyright = "Copyright (c) 2005-2010 Vitalii Vanovschi. All rights reserved"
  46. version = "1.6.0"
  47. LISTEN_SOCKET_TIMEOUT = 20
  48. # compatibility with Python 2.6
  49. try:
  50. import hashlib
  51. sha_new = hashlib.sha1
  52. except ImportError:
  53. import sha
  54. sha_new = sha.new
  55. class _NetworkServer(pp.Server):
  56. """Network Server Class
  57. """
  58. def __init__(self, ncpus="autodetect", interface="0.0.0.0",
  59. broadcast="255.255.255.255", port=None, secret=None,
  60. timeout=None, restart=False, proto=2):
  61. pp.Server.__init__(self, ncpus, secret=secret, restart=restart,
  62. proto=proto)
  63. self.host = interface
  64. self.bcast = broadcast
  65. if port is not None:
  66. self.port = port
  67. else:
  68. self.port = self.default_port
  69. self.timeout = timeout
  70. self.ncon = 0
  71. self.last_con_time = time.time()
  72. self.ncon_lock = threading.Lock()
  73. self.logger.debug("Strarting network server interface=%s port=%i"
  74. % (self.host, self.port))
  75. if self.timeout is not None:
  76. self.logger.debug("ppserver will exit in %i seconds if no "\
  77. "connections with clients exist" % (self.timeout))
  78. ppcommon.start_thread("timeout_check", self.check_timeout)
  79. def ncon_add(self, val):
  80. """Keeps track of the number of connections and time of the last one"""
  81. self.ncon_lock.acquire()
  82. self.ncon += val
  83. self.last_con_time = time.time()
  84. self.ncon_lock.release()
  85. def check_timeout(self):
  86. """Checks if timeout happened and shutdowns server if it did"""
  87. while True:
  88. if self.ncon == 0:
  89. idle_time = time.time() - self.last_con_time
  90. if idle_time < self.timeout:
  91. time.sleep(self.timeout - idle_time)
  92. else:
  93. self.logger.debug("exiting ppserver due to timeout (no client"\
  94. " connections in last %i sec)", self.timeout)
  95. os._exit(0)
  96. else:
  97. time.sleep(self.timeout)
  98. def listen(self):
  99. """Initiates listenting to incoming connections"""
  100. try:
  101. self.ssocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  102. # following allows ppserver to restart faster on the same port
  103. self.ssocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  104. self.ssocket.settimeout(LISTEN_SOCKET_TIMEOUT)
  105. self.ssocket.bind((self.host, self.port))
  106. self.ssocket.listen(5)
  107. except socket.error:
  108. self.logger.error("Cannot create socket with port " + str(self.port)
  109. + " (port is already in use)")
  110. try:
  111. while 1:
  112. csocket = None
  113. #accept connections from outside
  114. try:
  115. (csocket, address) = self.ssocket.accept()
  116. except socket.timeout:
  117. pass
  118. if self._exiting:
  119. return
  120. #now do something with the clientsocket
  121. #in this case, we'll pretend this is a threaded server
  122. if csocket:
  123. ppcommon.start_thread("client_socket", self.crun, (csocket, ))
  124. except:
  125. if pp.SHOW_EXPECTED_EXCEPTIONS:
  126. self.logger.debug("Exception in listen method (possibly expected)", exc_info=True)
  127. self.logger.debug("Closing server socket")
  128. self.ssocket.close()
  129. def crun(self, csocket):
  130. """Authenticates client and handles its jobs"""
  131. mysocket = pptransport.CSocketTransport(csocket)
  132. #send PP version
  133. mysocket.send(version)
  134. #generate a random string
  135. srandom = "".join([random.choice(string.ascii_letters)
  136. for i in xrange(16)])
  137. mysocket.send(srandom)
  138. answer = sha_new(srandom+self.secret).hexdigest()
  139. clientanswer = mysocket.receive()
  140. if answer != clientanswer:
  141. self.logger.warning("Authentication failed, client host=%s, port=%i"
  142. % csocket.getpeername())
  143. mysocket.send("FAILED")
  144. csocket.close()
  145. return
  146. else:
  147. mysocket.send("OK")
  148. ctype = mysocket.receive()
  149. self.logger.debug("Control message received: " + ctype)
  150. self.ncon_add(1)
  151. try:
  152. if ctype == "STAT":
  153. #reset time at each new connection
  154. self.get_stats()["local"].time = 0.0
  155. mysocket.send(str(self.get_ncpus()))
  156. while 1:
  157. mysocket.receive()
  158. mysocket.send(str(self.get_stats()["local"].time))
  159. elif ctype=="EXEC":
  160. while 1:
  161. sfunc = mysocket.creceive()
  162. sargs = mysocket.receive()
  163. fun = self.insert(sfunc, sargs)
  164. sresult = fun(True)
  165. mysocket.send(sresult)
  166. except:
  167. if self._exiting:
  168. return
  169. if pp.SHOW_EXPECTED_EXCEPTIONS:
  170. self.logger.debug("Exception in crun method (possibly expected)", exc_info=True)
  171. self.logger.debug("Closing client socket")
  172. csocket.close()
  173. self.ncon_add(-1)
  174. def broadcast(self):
  175. """Initiaates auto-discovery mechanism"""
  176. discover = ppauto.Discover(self)
  177. ppcommon.start_thread("server_broadcast", discover.run,
  178. ((self.host, self.port), (self.bcast, self.port)))
  179. def parse_config(file_loc):
  180. """
  181. Parses a config file in a very forgiving way.
  182. """
  183. # If we don't have configobj installed then let the user know and exit
  184. try:
  185. from configobj import ConfigObj
  186. except ImportError, ie:
  187. print >> sys.stderr, ("ERROR: You must have config obj installed to use"
  188. "configuration files. You can still use command line switches.")
  189. sys.exit(1)
  190. if not os.access(file_loc, os.F_OK):
  191. print >> sys.stderr, "ERROR: Can not access %s." % arg
  192. sys.exit(1)
  193. # Load the configuration file
  194. config = ConfigObj(file_loc)
  195. # try each config item and use the result if it exists. If it doesn't
  196. # then simply pass and move along
  197. try:
  198. args['secret'] = config['general'].get('secret')
  199. except:
  200. pass
  201. try:
  202. autodiscovery = config['network'].as_bool('autodiscovery')
  203. except:
  204. pass
  205. try:
  206. args['interface'] = config['network'].get('interface',
  207. default="0.0.0.0")
  208. except:
  209. pass
  210. try:
  211. args['broadcast'] = config['network'].get('broadcast')
  212. except:
  213. pass
  214. try:
  215. args['port'] = config['network'].as_int('port')
  216. except:
  217. pass
  218. try:
  219. args['loglevel'] = config['general'].as_bool('debug')
  220. except:
  221. pass
  222. try:
  223. args['ncpus'] = config['general'].as_int('workers')
  224. except:
  225. pass
  226. try:
  227. args['proto'] = config['general'].as_int('proto')
  228. except:
  229. pass
  230. try:
  231. args['restart'] = config['general'].as_bool('restart')
  232. except:
  233. pass
  234. try:
  235. args['timeout'] = config['network'].as_int('timeout')
  236. except:
  237. pass
  238. # Return a tuple of the args dict and autodiscovery variable
  239. return args, autodiscovery
  240. def print_usage():
  241. """Prints help"""
  242. print "Parallel Python Network Server (pp-" + version + ")"
  243. print "Usage: ppserver.py [-hdar] [-f format] [-n proto]"\
  244. " [-c config_path] [-i interface] [-b broadcast]"\
  245. " [-p port] [-w nworkers] [-s secret] [-t seconds]"
  246. print
  247. print "Options: "
  248. print "-h : this help message"
  249. print "-d : set log level to debug"
  250. print "-f format : log format"
  251. print "-a : enable auto-discovery service"
  252. print "-r : restart worker process after each"\
  253. " task completion"
  254. print "-n proto : protocol number for pickle module"
  255. print "-c path : path to config file"
  256. print "-i interface : interface to listen"
  257. print "-b broadcast : broadcast address for auto-discovery service"
  258. print "-p port : port to listen"
  259. print "-w nworkers : number of workers to start"
  260. print "-s secret : secret for authentication"
  261. print "-t seconds : timeout to exit if no connections with "\
  262. "clients exist"
  263. print
  264. print "Due to the security concerns always use a non-trivial secret key."
  265. print "Secret key set by -s switch will override secret key assigned by"
  266. print "pp_secret variable in .pythonrc.py"
  267. print
  268. print "Please visit http://www.parallelpython.com for extended up-to-date"
  269. print "documentation, examples and support forums"
  270. def create_network_server(argv):
  271. try:
  272. opts, args = getopt.getopt(argv, "hdarn:c:b:i:p:w:s:t:f:", ["help"])
  273. except getopt.GetoptError:
  274. print_usage()
  275. raise
  276. args = {}
  277. autodiscovery = False
  278. log_level = logging.WARNING
  279. log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
  280. for opt, arg in opts:
  281. if opt in ("-h", "--help"):
  282. print_usage()
  283. sys.exit()
  284. elif opt == "-c":
  285. args, autodiscovery = parse_config(arg)
  286. elif opt == "-d":
  287. log_level = logging.DEBUG
  288. pp.SHOW_EXPECTED_EXCEPTIONS = True
  289. elif opt == "-f":
  290. log_format = arg
  291. elif opt == "-i":
  292. args["interface"] = arg
  293. elif opt == "-s":
  294. args["secret"] = arg
  295. elif opt == "-p":
  296. args["port"] = int(arg)
  297. elif opt == "-w":
  298. args["ncpus"] = int(arg)
  299. elif opt == "-a":
  300. autodiscovery = True
  301. elif opt == "-r":
  302. args["restart"] = True
  303. elif opt == "-b":
  304. args["broadcast"] = arg
  305. elif opt == "-n":
  306. args["proto"] = int(arg)
  307. elif opt == "-t":
  308. args["timeout"] = int(arg)
  309. log_handler = logging.StreamHandler()
  310. log_handler.setFormatter(logging.Formatter(log_format))
  311. logging.getLogger("pp").setLevel(log_level)
  312. logging.getLogger("pp").addHandler(log_handler)
  313. server = _NetworkServer(**args)
  314. if autodiscovery:
  315. server.broadcast()
  316. return server
  317. if __name__ == "__main__":
  318. server = create_network_server(sys.argv[1:])
  319. server.listen()
  320. #have to destroy it here explicitly otherwise an exception
  321. #comes out in Python 2.4
  322. del server
  323. # Parallel Python Software: http://www.parallelpython.com