Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/galaxy/util/pastescript/serve.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author | guerler |
---|---|
date | Fri, 31 Jul 2020 00:32:28 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
0:d30785e31577 | 1:56ad4e20f292 |
---|---|
1 # Most of this code is: | |
2 | |
3 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) | |
4 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php | |
5 | |
6 # The server command includes the additional header: | |
7 | |
8 # For discussion of daemonizing: | |
9 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 | |
10 # Code taken also from QP: | |
11 # http://www.mems-exchange.org/software/qp/ | |
12 # From lib/site.py | |
13 | |
14 # Galaxy originally used PasteScript and PasteDeploy for application | |
15 # loading, to maintain compatibility we've internalized some of that | |
16 # code here, stripping out uneeded functionality. | |
17 | |
18 # All top level imports from each package moved here and organized | |
19 from __future__ import absolute_import | |
20 from __future__ import print_function | |
21 | |
22 import atexit | |
23 import errno | |
24 import grp | |
25 import logging | |
26 import optparse | |
27 import os | |
28 import pwd | |
29 import re | |
30 import resource | |
31 import signal | |
32 import socket | |
33 import subprocess | |
34 import sys | |
35 import textwrap | |
36 import threading | |
37 import time | |
38 from logging.config import fileConfig | |
39 | |
40 from six.moves import configparser | |
41 | |
42 from .loadwsgi import loadapp, loadserver | |
43 | |
44 | |
45 difflib = None | |
46 | |
47 # ---- from paste.script.bool_optparse -------------------------------- | |
48 | |
49 """ | |
50 A subclass of ``optparse.OptionParser`` that allows boolean long | |
51 options (like ``--verbose``) to also take arguments (like | |
52 ``--verbose=true``). Arguments *must* use ``=``. | |
53 """ | |
54 | |
55 try: | |
56 _ = optparse._ | |
57 except AttributeError: | |
58 from gettext import gettext as _ | |
59 | |
60 | |
61 class BoolOptionParser(optparse.OptionParser): | |
62 | |
63 def _process_long_opt(self, rargs, values): | |
64 arg = rargs.pop(0) | |
65 | |
66 # Value explicitly attached to arg? Pretend it's the next | |
67 # argument. | |
68 if "=" in arg: | |
69 (opt, next_arg) = arg.split("=", 1) | |
70 rargs.insert(0, next_arg) | |
71 had_explicit_value = True | |
72 else: | |
73 opt = arg | |
74 had_explicit_value = False | |
75 | |
76 opt = self._match_long_opt(opt) | |
77 option = self._long_opt[opt] | |
78 if option.takes_value(): | |
79 nargs = option.nargs | |
80 if len(rargs) < nargs: | |
81 if nargs == 1: | |
82 self.error(_("%s option requires an argument") % opt) | |
83 else: | |
84 self.error(_("%s option requires %d arguments") | |
85 % (opt, nargs)) | |
86 elif nargs == 1: | |
87 value = rargs.pop(0) | |
88 else: | |
89 value = tuple(rargs[0:nargs]) | |
90 del rargs[0:nargs] | |
91 | |
92 elif had_explicit_value: | |
93 value = rargs[0].lower().strip() | |
94 del rargs[0:1] | |
95 if value in ('true', 'yes', 'on', '1', 'y', 't'): | |
96 value = None | |
97 elif value in ('false', 'no', 'off', '0', 'n', 'f'): | |
98 # Don't process | |
99 return | |
100 else: | |
101 self.error(_('%s option takes a boolean value only (true/false)') % opt) | |
102 | |
103 else: | |
104 value = None | |
105 | |
106 option.process(opt, value, values, self) | |
107 | |
108 # ---- from paste.script.command -------------------------------------- | |
109 | |
110 # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) | |
111 # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php | |
112 | |
113 | |
114 class BadCommand(Exception): | |
115 | |
116 def __init__(self, message, exit_code=2): | |
117 self.message = message | |
118 self.exit_code = exit_code | |
119 Exception.__init__(self, message) | |
120 | |
121 def _get_message(self): | |
122 """Getter for 'message'; needed only to override deprecation | |
123 in BaseException.""" | |
124 return self.__message | |
125 | |
126 def _set_message(self, value): | |
127 """Setter for 'message'; needed only to override deprecation | |
128 in BaseException.""" | |
129 self.__message = value | |
130 | |
131 # BaseException.message has been deprecated since Python 2.6. | |
132 # To prevent DeprecationWarning from popping up over this | |
133 # pre-existing attribute, use a new property that takes lookup | |
134 # precedence. | |
135 message = property(_get_message, _set_message) | |
136 | |
137 | |
138 class NoDefault(object): | |
139 pass | |
140 | |
141 | |
142 # run and invoke methods moved below ServeCommand | |
143 class Command(object): | |
144 | |
145 def __init__(self, name): | |
146 self.command_name = name | |
147 | |
148 max_args = None | |
149 max_args_error = 'You must provide no more than %(max_args)s arguments' | |
150 min_args = None | |
151 min_args_error = 'You must provide at least %(min_args)s arguments' | |
152 required_args = None | |
153 # If this command takes a configuration file, set this to 1 or -1 | |
154 # Then if invoked through #! the config file will be put into the positional | |
155 # arguments -- at the beginning with 1, at the end with -1 | |
156 takes_config_file = None | |
157 | |
158 # Grouped in help messages by this: | |
159 group_name = '' | |
160 | |
161 required_args = () | |
162 description = None | |
163 usage = '' | |
164 hidden = False | |
165 # This is the default verbosity level; --quiet subtracts, | |
166 # --verbose adds: | |
167 default_verbosity = 0 | |
168 # This is the default interactive state: | |
169 default_interactive = 0 | |
170 return_code = 0 | |
171 | |
172 BadCommand = BadCommand | |
173 | |
174 # Must define: | |
175 # parser | |
176 # summary | |
177 # command() | |
178 | |
179 def run(self, args): | |
180 self.parse_args(args) | |
181 | |
182 # Setup defaults: | |
183 for name, default in [('verbose', 0), | |
184 ('quiet', 0), | |
185 ('interactive', False), | |
186 ('overwrite', False)]: | |
187 if not hasattr(self.options, name): | |
188 setattr(self.options, name, default) | |
189 if getattr(self.options, 'simulate', False): | |
190 self.options.verbose = max(self.options.verbose, 1) | |
191 self.interactive = self.default_interactive | |
192 if getattr(self.options, 'interactive', False): | |
193 self.interactive += self.options.interactive | |
194 if getattr(self.options, 'no_interactive', False): | |
195 self.interactive = False | |
196 self.verbose = self.default_verbosity | |
197 self.verbose += self.options.verbose | |
198 self.verbose -= self.options.quiet | |
199 self.simulate = getattr(self.options, 'simulate', False) | |
200 | |
201 # For #! situations: | |
202 if os.environ.get('PASTE_CONFIG_FILE') and self.takes_config_file is not None: | |
203 take = self.takes_config_file | |
204 filename = os.environ.get('PASTE_CONFIG_FILE') | |
205 if take == 1: | |
206 self.args.insert(0, filename) | |
207 elif take == -1: | |
208 self.args.append(filename) | |
209 else: | |
210 assert 0, ( | |
211 "Value takes_config_file must be None, 1, or -1 (not %r)" | |
212 % take) | |
213 | |
214 if os.environ.get('PASTE_DEFAULT_QUIET'): | |
215 self.verbose = 0 | |
216 | |
217 # Validate: | |
218 if self.min_args is not None and len(self.args) < self.min_args: | |
219 raise BadCommand( | |
220 self.min_args_error % {'min_args': self.min_args, | |
221 'actual_args': len(self.args)}) | |
222 if self.max_args is not None and len(self.args) > self.max_args: | |
223 raise BadCommand( | |
224 self.max_args_error % {'max_args': self.max_args, | |
225 'actual_args': len(self.args)}) | |
226 for var_name, option_name in self.required_args: | |
227 if not getattr(self.options, var_name, None): | |
228 raise BadCommand( | |
229 'You must provide the option %s' % option_name) | |
230 result = self.command() | |
231 if result is None: | |
232 return self.return_code | |
233 else: | |
234 return result | |
235 | |
236 def parse_args(self, args): | |
237 if self.usage: | |
238 usage = ' ' + self.usage | |
239 else: | |
240 usage = '' | |
241 self.parser.usage = "%%prog [options]%s\n%s" % ( | |
242 usage, self.summary) | |
243 self.parser.prog = self._prog_name() | |
244 if self.description: | |
245 desc = self.description | |
246 desc = textwrap.dedent(desc) | |
247 self.parser.description = desc | |
248 self.options, self.args = self.parser.parse_args(args) | |
249 | |
250 def _prog_name(self): | |
251 return '%s %s' % (os.path.basename(sys.argv[0]), self.command_name) | |
252 | |
253 ######################################## | |
254 # Utility methods | |
255 ######################################## | |
256 | |
257 def pad(self, s, length, dir='left'): | |
258 if len(s) >= length: | |
259 return s | |
260 if dir == 'left': | |
261 return s + ' ' * (length - len(s)) | |
262 else: | |
263 return ' ' * (length - len(s)) + s | |
264 | |
265 def standard_parser(cls, verbose=True, | |
266 interactive=False, | |
267 no_interactive=False, | |
268 simulate=False, | |
269 quiet=False, | |
270 overwrite=False): | |
271 """ | |
272 Create a standard ``OptionParser`` instance. | |
273 | |
274 Typically used like:: | |
275 | |
276 class MyCommand(Command): | |
277 parser = Command.standard_parser() | |
278 | |
279 Subclasses may redefine ``standard_parser``, so use the | |
280 nearest superclass's class method. | |
281 """ | |
282 parser = BoolOptionParser() | |
283 if verbose: | |
284 parser.add_option('-v', '--verbose', | |
285 action='count', | |
286 dest='verbose', | |
287 default=0) | |
288 if quiet: | |
289 parser.add_option('-q', '--quiet', | |
290 action='count', | |
291 dest='quiet', | |
292 default=0) | |
293 if no_interactive: | |
294 parser.add_option('--no-interactive', | |
295 action="count", | |
296 dest="no_interactive", | |
297 default=0) | |
298 if interactive: | |
299 parser.add_option('-i', '--interactive', | |
300 action='count', | |
301 dest='interactive', | |
302 default=0) | |
303 if simulate: | |
304 parser.add_option('-n', '--simulate', | |
305 action='store_true', | |
306 dest='simulate', | |
307 default=False) | |
308 if overwrite: | |
309 parser.add_option('-f', '--overwrite', | |
310 dest="overwrite", | |
311 action="store_true", | |
312 help="Overwrite files (warnings will be emitted for non-matching files otherwise)") | |
313 return parser | |
314 | |
315 standard_parser = classmethod(standard_parser) | |
316 | |
317 def quote_first_command_arg(self, arg): | |
318 """ | |
319 There's a bug in Windows when running an executable that's | |
320 located inside a path with a space in it. This method handles | |
321 that case, or on non-Windows systems or an executable with no | |
322 spaces, it just leaves well enough alone. | |
323 """ | |
324 if sys.platform != 'win32' or ' ' not in arg: | |
325 # Problem does not apply: | |
326 return arg | |
327 try: | |
328 import win32api | |
329 except ImportError: | |
330 raise ValueError( | |
331 "The executable %r contains a space, and in order to " | |
332 "handle this issue you must have the win32api module " | |
333 "installed" % arg) | |
334 arg = win32api.GetShortPathName(arg) | |
335 return arg | |
336 | |
337 def parse_vars(self, args): | |
338 """ | |
339 Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': | |
340 'b', 'c': 'd'}`` | |
341 """ | |
342 result = {} | |
343 for arg in args: | |
344 if '=' not in arg: | |
345 raise BadCommand( | |
346 'Variable assignment %r invalid (no "=")' | |
347 % arg) | |
348 name, value = arg.split('=', 1) | |
349 result[name] = value | |
350 return result | |
351 | |
352 def logging_file_config(self, config_file): | |
353 """ | |
354 Setup logging via the logging module's fileConfig function with the | |
355 specified ``config_file``, if applicable. | |
356 | |
357 ConfigParser defaults are specified for the special ``__file__`` | |
358 and ``here`` variables, similar to PasteDeploy config loading. | |
359 """ | |
360 parser = configparser.ConfigParser() | |
361 parser.read([config_file]) | |
362 if parser.has_section('loggers'): | |
363 config_file = os.path.abspath(config_file) | |
364 fileConfig(config_file, dict(__file__=config_file, | |
365 here=os.path.dirname(config_file))) | |
366 | |
367 | |
368 class NotFoundCommand(Command): | |
369 | |
370 def run(self, args): | |
371 print('Command %r not known (you may need to run setup.py egg_info)' | |
372 % self.command_name) | |
373 commands = list() | |
374 commands.sort() | |
375 if not commands: | |
376 print('No commands registered.') | |
377 print('Have you installed Paste Script?') | |
378 print('(try running python setup.py develop)') | |
379 return 2 | |
380 print('Known commands:') | |
381 longest = max([len(n) for n, c in commands]) | |
382 for name, command in commands: | |
383 print(' %s %s' % (self.pad(name, length=longest), | |
384 command.load().summary)) | |
385 return 2 | |
386 | |
387 | |
388 # ---- From paste.script.serve ---------------------------------------- | |
389 | |
390 MAXFD = 1024 | |
391 | |
392 jython = sys.platform.startswith('java') | |
393 | |
394 | |
395 class DaemonizeException(Exception): | |
396 pass | |
397 | |
398 | |
399 class ServeCommand(Command): | |
400 | |
401 min_args = 0 | |
402 usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]' | |
403 takes_config_file = 1 | |
404 summary = "Serve the described application" | |
405 description = """\ | |
406 This command serves a web application that uses a paste.deploy | |
407 configuration file for the server and application. | |
408 | |
409 If start/stop/restart is given, then --daemon is implied, and it will | |
410 start (normal operation), stop (--stop-daemon), or do both. | |
411 | |
412 You can also include variable assignments like 'http_port=8080' | |
413 and then use %(http_port)s in your config files. | |
414 """ | |
415 | |
416 # used by subclasses that configure apps and servers differently | |
417 requires_config_file = True | |
418 | |
419 parser = Command.standard_parser(quiet=True) | |
420 parser.add_option('-n', '--app-name', | |
421 dest='app_name', | |
422 metavar='NAME', | |
423 help="Load the named application (default main)") | |
424 parser.add_option('-s', '--server', | |
425 dest='server', | |
426 metavar='SERVER_TYPE', | |
427 help="Use the named server.") | |
428 parser.add_option('--server-name', | |
429 dest='server_name', | |
430 metavar='SECTION_NAME', | |
431 help="Use the named server as defined in the configuration file (default: main)") | |
432 if hasattr(os, 'fork'): | |
433 parser.add_option('--daemon', | |
434 dest="daemon", | |
435 action="store_true", | |
436 help="Run in daemon (background) mode") | |
437 parser.add_option('--pid-file', | |
438 dest='pid_file', | |
439 metavar='FILENAME', | |
440 help="Save PID to file (default to paster.pid if running in daemon mode)") | |
441 parser.add_option('--log-file', | |
442 dest='log_file', | |
443 metavar='LOG_FILE', | |
444 help="Save output to the given log file (redirects stdout)") | |
445 parser.add_option('--reload', | |
446 dest='reload', | |
447 action='store_true', | |
448 help="Use auto-restart file monitor") | |
449 parser.add_option('--reload-interval', | |
450 dest='reload_interval', | |
451 default=1, | |
452 help="Seconds between checking files (low number can cause significant CPU usage)") | |
453 parser.add_option('--monitor-restart', | |
454 dest='monitor_restart', | |
455 action='store_true', | |
456 help="Auto-restart server if it dies") | |
457 parser.add_option('--status', | |
458 action='store_true', | |
459 dest='show_status', | |
460 help="Show the status of the (presumably daemonized) server") | |
461 | |
462 if hasattr(os, 'setuid'): | |
463 # I don't think these are available on Windows | |
464 parser.add_option('--user', | |
465 dest='set_user', | |
466 metavar="USERNAME", | |
467 help="Set the user (usually only possible when run as root)") | |
468 parser.add_option('--group', | |
469 dest='set_group', | |
470 metavar="GROUP", | |
471 help="Set the group (usually only possible when run as root)") | |
472 | |
473 parser.add_option('--stop-daemon', | |
474 dest='stop_daemon', | |
475 action='store_true', | |
476 help='Stop a daemonized server (given a PID file, or default paster.pid file)') | |
477 | |
478 if jython: | |
479 parser.add_option('--disable-jython-reloader', | |
480 action='store_true', | |
481 dest='disable_jython_reloader', | |
482 help="Disable the Jython reloader") | |
483 | |
484 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) | |
485 | |
486 default_verbosity = 1 | |
487 | |
488 _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' | |
489 _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' | |
490 | |
491 possible_subcommands = ('start', 'stop', 'restart', 'status') | |
492 | |
493 def command(self): | |
494 if self.options.stop_daemon: | |
495 return self.stop_daemon() | |
496 | |
497 if not hasattr(self.options, 'set_user'): | |
498 # Windows case: | |
499 self.options.set_user = self.options.set_group = None | |
500 # @@: Is this the right stage to set the user at? | |
501 self.change_user_group( | |
502 self.options.set_user, self.options.set_group) | |
503 | |
504 if self.requires_config_file: | |
505 if not self.args: | |
506 raise BadCommand('You must give a config file') | |
507 app_spec = self.args[0] | |
508 if len(self.args) > 1 and self.args[1] in self.possible_subcommands: | |
509 cmd = self.args[1] | |
510 restvars = self.args[2:] | |
511 else: | |
512 cmd = None | |
513 restvars = self.args[1:] | |
514 else: | |
515 app_spec = "" | |
516 if self.args and self.args[0] in self.possible_subcommands: | |
517 cmd = self.args[0] | |
518 restvars = self.args[1:] | |
519 else: | |
520 cmd = None | |
521 restvars = self.args[:] | |
522 | |
523 if (getattr(self.options, 'daemon', False) and | |
524 getattr(self.options, 'reload', False)): | |
525 raise BadCommand('The --daemon and --reload options may not be used together') | |
526 | |
527 jython_monitor = False | |
528 if self.options.reload: | |
529 if jython and not self.options.disable_jython_reloader: | |
530 # JythonMonitor raises the special SystemRestart | |
531 # exception that'll cause the Jython interpreter to | |
532 # reload in the existing Java process (avoiding | |
533 # subprocess startup time) | |
534 try: | |
535 from paste.reloader import JythonMonitor | |
536 except ImportError: | |
537 pass | |
538 else: | |
539 jython_monitor = JythonMonitor(poll_interval=int( | |
540 self.options.reload_interval)) | |
541 if self.requires_config_file: | |
542 jython_monitor.watch_file(self.args[0]) | |
543 | |
544 if not jython_monitor: | |
545 if os.environ.get(self._reloader_environ_key): | |
546 from paste import reloader | |
547 if self.verbose > 1: | |
548 print('Running reloading file monitor') | |
549 reloader.install(int(self.options.reload_interval)) | |
550 if self.requires_config_file: | |
551 reloader.watch_file(self.args[0]) | |
552 else: | |
553 return self.restart_with_reloader() | |
554 | |
555 if cmd not in (None, 'start', 'stop', 'restart', 'status'): | |
556 raise BadCommand( | |
557 'Error: must give start|stop|restart (not %s)' % cmd) | |
558 | |
559 if cmd == 'status' or self.options.show_status: | |
560 return self.show_status() | |
561 | |
562 if cmd == 'restart' or cmd == 'stop': | |
563 result = self.stop_daemon() | |
564 if result: | |
565 print("Could not stop daemon") | |
566 # It's ok to continue trying to restart if stop_daemon returns | |
567 # a 1, otherwise shortcut and return. | |
568 if cmd == 'restart' and result != 1: | |
569 return result | |
570 if cmd == 'stop': | |
571 return result | |
572 self.options.daemon = True | |
573 | |
574 if cmd == 'start': | |
575 self.options.daemon = True | |
576 | |
577 app_name = self.options.app_name | |
578 vars = self.parse_vars(restvars) | |
579 if not self._scheme_re.search(app_spec): | |
580 app_spec = 'config:' + app_spec | |
581 server_name = self.options.server_name | |
582 if self.options.server: | |
583 server_spec = 'egg:PasteScript' | |
584 assert server_name is None | |
585 server_name = self.options.server | |
586 else: | |
587 server_spec = app_spec | |
588 base = os.getcwd() | |
589 | |
590 if getattr(self.options, 'daemon', False): | |
591 if not self.options.pid_file: | |
592 self.options.pid_file = 'paster.pid' | |
593 if not self.options.log_file: | |
594 self.options.log_file = 'paster.log' | |
595 | |
596 # Ensure the log file is writeable | |
597 if self.options.log_file: | |
598 try: | |
599 writeable_log_file = open(self.options.log_file, 'a') | |
600 except IOError as ioe: | |
601 msg = 'Error: Unable to write to log file: %s' % ioe | |
602 raise BadCommand(msg) | |
603 writeable_log_file.close() | |
604 | |
605 # Ensure the pid file is writeable | |
606 if self.options.pid_file: | |
607 try: | |
608 writeable_pid_file = open(self.options.pid_file, 'a') | |
609 except IOError as ioe: | |
610 msg = 'Error: Unable to write to pid file: %s' % ioe | |
611 raise BadCommand(msg) | |
612 writeable_pid_file.close() | |
613 | |
614 if getattr(self.options, 'daemon', False): | |
615 try: | |
616 self.daemonize() | |
617 except DaemonizeException as ex: | |
618 if self.verbose > 0: | |
619 print(str(ex)) | |
620 return | |
621 | |
622 if (self.options.monitor_restart and not | |
623 os.environ.get(self._monitor_environ_key)): | |
624 return self.restart_with_monitor() | |
625 | |
626 if self.options.pid_file: | |
627 self.record_pid(self.options.pid_file) | |
628 | |
629 if self.options.log_file: | |
630 stdout_log = LazyWriter(self.options.log_file, 'a') | |
631 sys.stdout = stdout_log | |
632 sys.stderr = stdout_log | |
633 logging.basicConfig(stream=stdout_log) | |
634 | |
635 log_fn = app_spec | |
636 if log_fn.startswith('config:'): | |
637 log_fn = app_spec[len('config:'):] | |
638 elif log_fn.startswith('egg:'): | |
639 log_fn = None | |
640 if log_fn: | |
641 log_fn = os.path.join(base, log_fn) | |
642 self.logging_file_config(log_fn) | |
643 | |
644 server = loadserver(server_spec, name=server_name, relative_to=base, global_conf=vars) | |
645 | |
646 app = loadapp(app_spec, name=app_name, relative_to=base, global_conf=vars) | |
647 | |
648 if self.verbose > 0: | |
649 if hasattr(os, 'getpid'): | |
650 msg = 'Starting server in PID %i.' % os.getpid() | |
651 else: | |
652 msg = 'Starting server.' | |
653 print(msg) | |
654 | |
655 def serve(): | |
656 try: | |
657 server(app) | |
658 except (SystemExit, KeyboardInterrupt) as e: | |
659 if self.verbose > 1: | |
660 raise | |
661 if str(e): | |
662 msg = ' ' + str(e) | |
663 else: | |
664 msg = '' | |
665 print('Exiting%s (-v to see traceback)' % msg) | |
666 except AttributeError as e: | |
667 # Capturing bad error response from paste | |
668 if str(e) == "'WSGIThreadPoolServer' object has no attribute 'thread_pool'": | |
669 raise socket.error(98, 'Address already in use') | |
670 else: | |
671 raise AttributeError(e) | |
672 | |
673 if jython_monitor: | |
674 # JythonMonitor has to be ran from the main thread | |
675 threading.Thread(target=serve).start() | |
676 print('Starting Jython file monitor') | |
677 jython_monitor.periodic_reload() | |
678 else: | |
679 serve() | |
680 | |
681 def daemonize(self): | |
682 pid = live_pidfile(self.options.pid_file) | |
683 if pid: | |
684 raise DaemonizeException( | |
685 "Daemon is already running (PID: %s from PID file %s)" | |
686 % (pid, self.options.pid_file)) | |
687 | |
688 if self.verbose > 0: | |
689 print('Entering daemon mode') | |
690 pid = os.fork() | |
691 if pid: | |
692 # The forked process also has a handle on resources, so we | |
693 # *don't* want proper termination of the process, we just | |
694 # want to exit quick (which os._exit() does) | |
695 os._exit(0) | |
696 # Make this the session leader | |
697 os.setsid() | |
698 # Fork again for good measure! | |
699 pid = os.fork() | |
700 if pid: | |
701 os._exit(0) | |
702 | |
703 # @@: Should we set the umask and cwd now? | |
704 | |
705 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] | |
706 if maxfd == resource.RLIM_INFINITY: | |
707 maxfd = MAXFD | |
708 # Iterate through and close all file descriptors. | |
709 for fd in range(0, maxfd): | |
710 try: | |
711 os.close(fd) | |
712 except OSError: # ERROR, fd wasn't open to begin with (ignored) | |
713 pass | |
714 | |
715 if hasattr(os, "devnull"): | |
716 REDIRECT_TO = os.devnull | |
717 else: | |
718 REDIRECT_TO = "/dev/null" | |
719 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) | |
720 # Duplicate standard input to standard output and standard error. | |
721 os.dup2(0, 1) # standard output (1) | |
722 os.dup2(0, 2) # standard error (2) | |
723 | |
724 def record_pid(self, pid_file): | |
725 pid = os.getpid() | |
726 if self.verbose > 1: | |
727 print('Writing PID %s to %s' % (pid, pid_file)) | |
728 f = open(pid_file, 'w') | |
729 f.write(str(pid)) | |
730 f.close() | |
731 atexit.register(_remove_pid_file, pid, pid_file, self.verbose) | |
732 | |
733 def stop_daemon(self): | |
734 pid_file = self.options.pid_file or 'paster.pid' | |
735 if not os.path.exists(pid_file): | |
736 print('No PID file exists in %s' % pid_file) | |
737 return 1 | |
738 pid = read_pidfile(pid_file) | |
739 if not pid: | |
740 print("Not a valid PID file in %s" % pid_file) | |
741 return 1 | |
742 pid = live_pidfile(pid_file) | |
743 if not pid: | |
744 print("PID in %s is not valid (deleting)" % pid_file) | |
745 try: | |
746 os.unlink(pid_file) | |
747 except (OSError, IOError) as e: | |
748 print("Could not delete: %s" % e) | |
749 return 2 | |
750 return 1 | |
751 for _i in range(10): | |
752 if not live_pidfile(pid_file): | |
753 break | |
754 os.kill(pid, signal.SIGTERM) | |
755 time.sleep(1) | |
756 else: | |
757 print("failed to kill web process %s" % pid) | |
758 return 3 | |
759 if os.path.exists(pid_file): | |
760 os.unlink(pid_file) | |
761 return 0 | |
762 | |
763 def show_status(self): | |
764 pid_file = self.options.pid_file or 'paster.pid' | |
765 if not os.path.exists(pid_file): | |
766 print('No PID file %s' % pid_file) | |
767 return 1 | |
768 pid = read_pidfile(pid_file) | |
769 if not pid: | |
770 print('No PID in file %s' % pid_file) | |
771 return 1 | |
772 pid = live_pidfile(pid_file) | |
773 if not pid: | |
774 print('PID %s in %s is not running' % (pid, pid_file)) | |
775 return 1 | |
776 print('Server running in PID %s' % pid) | |
777 return 0 | |
778 | |
779 def restart_with_reloader(self): | |
780 self.restart_with_monitor(reloader=True) | |
781 | |
782 def restart_with_monitor(self, reloader=False): | |
783 if self.verbose > 0: | |
784 if reloader: | |
785 print('Starting subprocess with file monitor') | |
786 else: | |
787 print('Starting subprocess with monitor parent') | |
788 while 1: | |
789 args = [self.quote_first_command_arg(sys.executable)] + sys.argv | |
790 new_environ = os.environ.copy() | |
791 if reloader: | |
792 new_environ[self._reloader_environ_key] = 'true' | |
793 else: | |
794 new_environ[self._monitor_environ_key] = 'true' | |
795 proc = None | |
796 try: | |
797 try: | |
798 _turn_sigterm_into_systemexit() | |
799 proc = subprocess.Popen(args, env=new_environ) | |
800 exit_code = proc.wait() | |
801 proc = None | |
802 except KeyboardInterrupt: | |
803 print('^C caught in monitor process') | |
804 if self.verbose > 1: | |
805 raise | |
806 return 1 | |
807 finally: | |
808 if proc is not None and hasattr(os, 'kill'): | |
809 try: | |
810 os.kill(proc.pid, signal.SIGTERM) | |
811 except (OSError, IOError): | |
812 pass | |
813 | |
814 if reloader: | |
815 # Reloader always exits with code 3; but if we are | |
816 # a monitor, any exit code will restart | |
817 if exit_code != 3: | |
818 return exit_code | |
819 if self.verbose > 0: | |
820 print('-' * 20, 'Restarting', '-' * 20) | |
821 | |
822 def change_user_group(self, user, group): | |
823 if not user and not group: | |
824 return | |
825 uid = gid = None | |
826 if group: | |
827 try: | |
828 gid = int(group) | |
829 group = grp.getgrgid(gid).gr_name | |
830 except ValueError: | |
831 try: | |
832 entry = grp.getgrnam(group) | |
833 except KeyError: | |
834 raise BadCommand( | |
835 "Bad group: %r; no such group exists" % group) | |
836 gid = entry.gr_gid | |
837 try: | |
838 uid = int(user) | |
839 user = pwd.getpwuid(uid).pw_name | |
840 except ValueError: | |
841 try: | |
842 entry = pwd.getpwnam(user) | |
843 except KeyError: | |
844 raise BadCommand( | |
845 "Bad username: %r; no such user exists" % user) | |
846 if not gid: | |
847 gid = entry.pw_gid | |
848 uid = entry.pw_uid | |
849 if self.verbose > 0: | |
850 print('Changing user to %s:%s (%s:%s)' % ( | |
851 user, group or '(unknown)', uid, gid)) | |
852 if hasattr(os, 'initgroups'): | |
853 os.initgroups(user, gid) | |
854 else: | |
855 os.setgroups([e.gr_gid for e in grp.getgrall() | |
856 if user in e.gr_mem] + [gid]) | |
857 if gid: | |
858 os.setgid(gid) | |
859 if uid: | |
860 os.setuid(uid) | |
861 | |
862 | |
863 class LazyWriter(object): | |
864 | |
865 """ | |
866 File-like object that opens a file lazily when it is first written | |
867 to. | |
868 """ | |
869 | |
870 def __init__(self, filename, mode='w'): | |
871 self.filename = filename | |
872 self.fileobj = None | |
873 self.lock = threading.Lock() | |
874 self.mode = mode | |
875 | |
876 def open(self): | |
877 if self.fileobj is None: | |
878 self.lock.acquire() | |
879 try: | |
880 if self.fileobj is None: | |
881 self.fileobj = open(self.filename, self.mode) | |
882 finally: | |
883 self.lock.release() | |
884 return self.fileobj | |
885 | |
886 def write(self, text): | |
887 fileobj = self.open() | |
888 fileobj.write(text) | |
889 fileobj.flush() | |
890 | |
891 def writelines(self, text): | |
892 fileobj = self.open() | |
893 fileobj.writelines(text) | |
894 fileobj.flush() | |
895 | |
896 def flush(self): | |
897 self.open().flush() | |
898 | |
899 | |
900 def live_pidfile(pidfile): | |
901 """(pidfile:str) -> int | None | |
902 Returns an int found in the named file, if there is one, | |
903 and if there is a running process with that process id. | |
904 Return None if no such process exists. | |
905 """ | |
906 pid = read_pidfile(pidfile) | |
907 if pid: | |
908 try: | |
909 os.kill(int(pid), 0) | |
910 return pid | |
911 except OSError as e: | |
912 if e.errno == errno.EPERM: | |
913 return pid | |
914 return None | |
915 | |
916 | |
917 def read_pidfile(filename): | |
918 if os.path.exists(filename): | |
919 try: | |
920 f = open(filename) | |
921 content = f.read() | |
922 f.close() | |
923 return int(content.strip()) | |
924 except (ValueError, IOError): | |
925 return None | |
926 else: | |
927 return None | |
928 | |
929 | |
930 def _remove_pid_file(written_pid, filename, verbosity): | |
931 current_pid = os.getpid() | |
932 if written_pid != current_pid: | |
933 # A forked process must be exiting, not the process that | |
934 # wrote the PID file | |
935 return | |
936 if not os.path.exists(filename): | |
937 return | |
938 f = open(filename) | |
939 content = f.read().strip() | |
940 f.close() | |
941 try: | |
942 pid_in_file = int(content) | |
943 except ValueError: | |
944 pass | |
945 else: | |
946 if pid_in_file != current_pid: | |
947 print("PID file %s contains %s, not expected PID %s" % ( | |
948 filename, pid_in_file, current_pid)) | |
949 return | |
950 if verbosity > 0: | |
951 print("Removing PID file %s" % filename) | |
952 try: | |
953 os.unlink(filename) | |
954 return | |
955 except OSError as e: | |
956 # Record, but don't give traceback | |
957 print("Cannot remove PID file: %s" % e) | |
958 # well, at least lets not leave the invalid PID around... | |
959 try: | |
960 f = open(filename, 'w') | |
961 f.write('') | |
962 f.close() | |
963 except OSError as e: | |
964 print('Stale PID left in file: %s (%e)' % (filename, e)) | |
965 else: | |
966 print('Stale PID removed') | |
967 | |
968 | |
969 def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2): | |
970 """ | |
971 This makes sure any open ports are closed. | |
972 | |
973 Does this by connecting to them until they give connection | |
974 refused. Servers should call like:: | |
975 | |
976 import paste.script | |
977 ensure_port_cleanup([80, 443]) | |
978 """ | |
979 atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries, | |
980 sleeptime=sleeptime) | |
981 | |
982 | |
983 def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2): | |
984 # Wait for the server to bind to the port. | |
985 for bound_address in bound_addresses: | |
986 for _i in range(maxtries): | |
987 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
988 try: | |
989 sock.connect(bound_address) | |
990 except socket.error as e: | |
991 if e.errno != errno.ECONNREFUSED: | |
992 raise | |
993 break | |
994 else: | |
995 time.sleep(sleeptime) | |
996 else: | |
997 raise SystemExit('Timeout waiting for port.') | |
998 sock.close() | |
999 | |
1000 | |
1001 def _turn_sigterm_into_systemexit(): | |
1002 """ | |
1003 Attempts to turn a SIGTERM exception into a SystemExit exception. | |
1004 """ | |
1005 | |
1006 def handle_term(signo, frame): | |
1007 raise SystemExit | |
1008 signal.signal(signal.SIGTERM, handle_term) | |
1009 | |
1010 | |
1011 # ---- from paste.script.command -------------------------------------- | |
1012 python_version = sys.version.splitlines()[0].strip() | |
1013 | |
1014 parser = optparse.OptionParser(add_help_option=False, | |
1015 # version='%s from %s (python %s)' | |
1016 # % (dist, dist.location, python_version), | |
1017 usage='%prog [paster_options] COMMAND [command_options]') | |
1018 | |
1019 parser.add_option( | |
1020 '-h', '--help', | |
1021 action='store_true', | |
1022 dest='do_help', | |
1023 help="Show this help message") | |
1024 parser.disable_interspersed_args() | |
1025 | |
1026 # @@: Add an option to run this in another Python interpreter | |
1027 | |
1028 commands = { | |
1029 'serve': ServeCommand | |
1030 } | |
1031 | |
1032 | |
1033 def run(args=None): | |
1034 if (not args and len(sys.argv) >= 2 and os.environ.get('_') and | |
1035 sys.argv[0] != os.environ['_'] and os.environ['_'] == sys.argv[1]): | |
1036 # probably it's an exe execution | |
1037 args = ['exe', os.environ['_']] + sys.argv[2:] | |
1038 if args is None: | |
1039 args = sys.argv[1:] | |
1040 options, args = parser.parse_args(args) | |
1041 options.base_parser = parser | |
1042 if options.do_help: | |
1043 args = ['help'] + args | |
1044 if not args: | |
1045 print('Usage: %s COMMAND' % sys.argv[0]) | |
1046 args = ['help'] | |
1047 command_name = args[0] | |
1048 if command_name not in commands: | |
1049 command = NotFoundCommand | |
1050 else: | |
1051 command = commands[command_name] | |
1052 invoke(command, command_name, options, args[1:]) | |
1053 | |
1054 | |
1055 def invoke(command, command_name, options, args): | |
1056 try: | |
1057 runner = command(command_name) | |
1058 exit_code = runner.run(args) | |
1059 except BadCommand as e: | |
1060 print(e) | |
1061 exit_code = e.exit_code | |
1062 sys.exit(exit_code) |