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) |
