0001
0002
0003
0004
0005
0006
0007
0008
0009import re
0010import os
0011import errno
0012import signal
0013import sys
0014import time
0015try:
0016 import subprocess
0017except ImportError:
0018 from paste.util import subprocess24 as subprocess
0019from command import Command, BadCommand
0020from paste.deploy import loadapp, loadserver
0021import threading
0022import atexit
0023import logging
0024import ConfigParser
0025
0026MAXFD = 1024
0027
0028class DaemonizeException(Exception):
0029 pass
0030
0031
0032class ServeCommand(Command):
0033
0034 min_args = 0
0035 usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
0036 takes_config_file = 1
0037 summary = "Serve the described application"
0038 description = """\
0039 This command serves a web application that uses a paste.deploy
0040 configuration file for the server and application.
0041
0042 If start/stop/restart is given, then --daemon is implied, and it will
0043 start (normal operation), stop (--stop-daemon), or do both.
0044
0045 You can also include variable assignments like 'http_port=8080'
0046 and then use %(http_port)s in your config files.
0047 """
0048
0049
0050 requires_config_file = True
0051
0052 parser = Command.standard_parser(quiet=True)
0053 parser.add_option('-n', '--app-name',
0054 dest='app_name',
0055 metavar='NAME',
0056 help="Load the named application (default main)")
0057 parser.add_option('-s', '--server',
0058 dest='server',
0059 metavar='SERVER_TYPE',
0060 help="Use the named server.")
0061 parser.add_option('--server-name',
0062 dest='server_name',
0063 metavar='SECTION_NAME',
0064 help="Use the named server as defined in the configuration file (default: main)")
0065 if hasattr(os, 'fork'):
0066 parser.add_option('--daemon',
0067 dest="daemon",
0068 action="store_true",
0069 help="Run in daemon (background) mode")
0070 parser.add_option('--pid-file',
0071 dest='pid_file',
0072 metavar='FILENAME',
0073 help="Save PID to file (default to paster.pid if running in daemon mode)")
0074 parser.add_option('--log-file',
0075 dest='log_file',
0076 metavar='LOG_FILE',
0077 help="Save output to the given log file (redirects stdout)")
0078 parser.add_option('--reload',
0079 dest='reload',
0080 action='store_true',
0081 help="Use auto-restart file monitor")
0082 parser.add_option('--reload-interval',
0083 dest='reload_interval',
0084 default=1,
0085 help="Seconds between checking files (low number can cause significant CPU usage)")
0086 parser.add_option('--monitor-restart',
0087 dest='monitor_restart',
0088 action='store_true',
0089 help="Auto-restart server if it dies")
0090 parser.add_option('--status',
0091 action='store_true',
0092 dest='show_status',
0093 help="Show the status of the (presumably daemonized) server")
0094
0095
0096 if hasattr(os, 'setuid'):
0097
0098 parser.add_option('--user',
0099 dest='set_user',
0100 metavar="USERNAME",
0101 help="Set the user (usually only possible when run as root)")
0102 parser.add_option('--group',
0103 dest='set_group',
0104 metavar="GROUP",
0105 help="Set the group (usually only possible when run as root)")
0106
0107 parser.add_option('--stop-daemon',
0108 dest='stop_daemon',
0109 action='store_true',
0110 help='Stop a daemonized server (given a PID file, or default paster.pid file)')
0111
0112
0113 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
0114
0115 default_verbosity = 1
0116
0117 _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
0118 _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
0119
0120 possible_subcommands = ('start', 'stop', 'restart', 'status')
0121 def command(self):
0122 if self.options.stop_daemon:
0123 return self.stop_daemon()
0124
0125 if not hasattr(self.options, 'set_user'):
0126
0127 self.options.set_user = self.options.set_group = None
0128
0129 self.change_user_group(
0130 self.options.set_user, self.options.set_group)
0131
0132 if self.requires_config_file:
0133 if not self.args:
0134 raise BadCommand('You must give a config file')
0135 app_spec = self.args[0]
0136 if (len(self.args) > 1
0137 and self.args[1] in self.possible_subcommands):
0138 cmd = self.args[1]
0139 restvars = self.args[2:]
0140 else:
0141 cmd = None
0142 restvars = self.args[1:]
0143 else:
0144 app_spec = ""
0145 if (self.args
0146 and self.args[0] in self.possible_subcommands):
0147 cmd = self.args[0]
0148 restvars = self.args[1:]
0149 else:
0150 cmd = None
0151 restvars = self.args[:]
0152
0153 if self.options.reload:
0154 if os.environ.get(self._reloader_environ_key):
0155 from paste import reloader
0156 if self.verbose > 1:
0157 print 'Running reloading file monitor'
0158 reloader.install(int(self.options.reload_interval))
0159 if self.requires_config_file:
0160 reloader.watch_file(self.args[0])
0161 else:
0162 return self.restart_with_reloader()
0163
0164 if cmd not in (None, 'start', 'stop', 'restart', 'status'):
0165 raise BadCommand(
0166 'Error: must give start|stop|restart (not %s)' % cmd)
0167
0168 if cmd == 'status' or self.options.show_status:
0169 return self.show_status()
0170
0171 if cmd == 'restart' or cmd == 'stop':
0172 result = self.stop_daemon()
0173 if result:
0174 if cmd == 'restart':
0175 print "Could not stop daemon; aborting"
0176 else:
0177 print "Could not stop daemon"
0178 return result
0179 if cmd == 'stop':
0180 return result
0181
0182 app_name = self.options.app_name
0183 vars = self.parse_vars(restvars)
0184 if not self._scheme_re.search(app_spec):
0185 app_spec = 'config:' + app_spec
0186 server_name = self.options.server_name
0187 if self.options.server:
0188 server_spec = 'egg:PasteScript'
0189 assert server_name is None
0190 server_name = self.options.server
0191 else:
0192 server_spec = app_spec
0193 base = os.getcwd()
0194
0195 if getattr(self.options, 'daemon', False):
0196 if not self.options.pid_file:
0197 self.options.pid_file = 'paster.pid'
0198 if not self.options.log_file:
0199 self.options.log_file = 'paster.log'
0200
0201
0202 if self.options.log_file:
0203 try:
0204 writeable_log_file = open(self.options.log_file, 'a')
0205 except IOError, ioe:
0206 msg = 'Error: Unable to write to log file: %s' % ioe
0207 raise BadCommand(msg)
0208 writeable_log_file.close()
0209
0210
0211 if self.options.pid_file:
0212 try:
0213 writeable_pid_file = open(self.options.pid_file, 'a')
0214 except IOError, ioe:
0215 msg = 'Error: Unable to write to pid file: %s' % ioe
0216 raise BadCommand(msg)
0217 writeable_pid_file.close()
0218
0219 if getattr(self.options, 'daemon', False):
0220 try:
0221 self.daemonize()
0222 except DaemonizeException, ex:
0223 if self.verbose > 0:
0224 print str(ex)
0225 return
0226
0227 if (self.options.monitor_restart
0228 and not os.environ.get(self._monitor_environ_key)):
0229 return self.restart_with_monitor()
0230
0231 if self.options.pid_file:
0232 self.record_pid(self.options.pid_file)
0233
0234 if self.options.log_file:
0235 stdout_log = LazyWriter(self.options.log_file, 'a')
0236 sys.stdout = stdout_log
0237 sys.stderr = stdout_log
0238 logging.basicConfig(stream=stdout_log)
0239
0240 log_fn = app_spec
0241 if log_fn.startswith('config:'):
0242 log_fn = app_spec[len('config:'):]
0243 elif log_fn.startswith('egg:'):
0244 log_fn = None
0245 if log_fn:
0246 log_fn = os.path.join(base, log_fn)
0247 self.logging_file_config(log_fn)
0248
0249 server = self.loadserver(server_spec, name=server_name,
0250 relative_to=base, global_conf=vars)
0251 app = self.loadapp(app_spec, name=app_name,
0252 relative_to=base, global_conf=vars)
0253
0254 if self.verbose > 0:
0255 print 'Starting server in PID %i.' % os.getpid()
0256 try:
0257 server(app)
0258 except (SystemExit, KeyboardInterrupt), e:
0259 if self.verbose > 1:
0260 raise
0261 if str(e):
0262 msg = ' '+str(e)
0263 else:
0264 msg = ''
0265 print 'Exiting%s (-v to see traceback)' % msg
0266
0267 def loadserver(self, server_spec, name, relative_to, **kw):
0268 return loadserver(
0269 server_spec, name=name,
0270 relative_to=relative_to, **kw)
0271
0272 def loadapp(self, app_spec, name, relative_to, **kw):
0273 return loadapp(
0274 app_spec, name=name, relative_to=relative_to,
0275 **kw)
0276
0277 def daemonize(self):
0278 pid = live_pidfile(self.options.pid_file)
0279 if pid:
0280 raise DaemonizeException(
0281 "Daemon is already running (PID: %s from PID file %s)"
0282 % (pid, self.options.pid_file))
0283
0284 if self.verbose > 0:
0285 print 'Entering daemon mode'
0286 pid = os.fork()
0287 if pid:
0288
0289
0290
0291 os._exit(0)
0292
0293 os.setsid()
0294
0295 pid = os.fork()
0296 if pid:
0297 os._exit(0)
0298
0299
0300
0301 import resource
0302 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
0303 if (maxfd == resource.RLIM_INFINITY):
0304 maxfd = MAXFD
0305
0306 for fd in range(0, maxfd):
0307 try:
0308 os.close(fd)
0309 except OSError:
0310 pass
0311
0312 if (hasattr(os, "devnull")):
0313 REDIRECT_TO = os.devnull
0314 else:
0315 REDIRECT_TO = "/dev/null"
0316 os.open(REDIRECT_TO, os.O_RDWR)
0317
0318 os.dup2(0, 1)
0319 os.dup2(0, 2)
0320
0321 def record_pid(self, pid_file):
0322 pid = os.getpid()
0323 if self.verbose > 1:
0324 print 'Writing PID %s to %s' % (pid, pid_file)
0325 f = open(pid_file, 'w')
0326 f.write(str(pid))
0327 f.close()
0328 atexit.register(_remove_pid_file, pid, pid_file, self.verbose)
0329
0330 def stop_daemon(self):
0331 pid_file = self.options.pid_file or 'paster.pid'
0332 if not os.path.exists(pid_file):
0333 print 'No PID file exists in %s' % pid_file
0334 return 1
0335 pid = read_pidfile(pid_file)
0336 if not pid:
0337 print "Not a valid PID file in %s" % pid_file
0338 return 1
0339 pid = live_pidfile(pid_file)
0340 if not pid:
0341 print "PID in %s is not valid (deleting)" % pid_file
0342 try:
0343 os.unlink(pid_file)
0344 except (OSError, IOError), e:
0345 print "Could not delete: %s" % e
0346 return 2
0347 return 1
0348 for j in range(10):
0349 if not live_pidfile(pid_file):
0350 break
0351 os.kill(pid, signal.SIGTERM)
0352 time.sleep(1)
0353 else:
0354 print "failed to kill web process %s" % pid
0355 return 3
0356 if os.path.exists(pid_file):
0357 os.unlink(pid_file)
0358 return 0
0359
0360 def show_status(self):
0361 pid_file = self.options.pid_file or 'paster.pid'
0362 if not os.path.exists(pid_file):
0363 print 'No PID file %s' % pid_file
0364 return 1
0365 pid = read_pidfile(pid_file)
0366 if not pid:
0367 print 'No PID in file %s' % pid_file
0368 return 1
0369 pid = live_pidfile(pid_file)
0370 if not pid:
0371 print 'PID %s in %s is not running' % (pid, pid_file)
0372 return 1
0373 print 'Server running in PID %s' % pid
0374 return 0
0375
0376 def restart_with_reloader(self):
0377 self.restart_with_monitor(reloader=True)
0378
0379 def restart_with_monitor(self, reloader=False):
0380 if self.verbose > 0:
0381 if reloader:
0382 print 'Starting subprocess with file monitor'
0383 else:
0384 print 'Starting subprocess with monitor parent'
0385 while 1:
0386 args = [self.quote_first_command_arg(sys.executable)] + sys.argv
0387 new_environ = os.environ.copy()
0388 if reloader:
0389 new_environ[self._reloader_environ_key] = 'true'
0390 else:
0391 new_environ[self._monitor_environ_key] = 'true'
0392 proc = None
0393 try:
0394 try:
0395 _turn_sigterm_into_systemexit()
0396 proc = subprocess.Popen(args, env=new_environ)
0397 exit_code = proc.wait()
0398 proc = None
0399 except KeyboardInterrupt:
0400 print '^C caught in monitor process'
0401 if self.verbose > 1:
0402 raise
0403 return 1
0404 finally:
0405 if (proc is not None
0406 and hasattr(os, 'kill')):
0407 import signal
0408 try:
0409 os.kill(proc.pid, signal.SIGTERM)
0410 except (OSError, IOError):
0411 pass
0412
0413 if reloader:
0414
0415
0416 if exit_code != 3:
0417 return exit_code
0418 if self.verbose > 0:
0419 print '-'*20, 'Restarting', '-'*20
0420
0421 def change_user_group(self, user, group):
0422 if not user and not group:
0423 return
0424 import pwd, grp
0425 uid = gid = None
0426 if group:
0427 try:
0428 gid = int(group)
0429 group = grp.getgrgid(gid).gr_name
0430 except ValueError:
0431 import grp
0432 try:
0433 entry = grp.getgrnam(group)
0434 except KeyError:
0435 raise BadCommand(
0436 "Bad group: %r; no such group exists" % group)
0437 gid = entry.gr_gid
0438 try:
0439 uid = int(user)
0440 user = pwd.getpwuid(uid).pw_name
0441 except ValueError:
0442 try:
0443 entry = pwd.getpwnam(user)
0444 except KeyError:
0445 raise BadCommand(
0446 "Bad username: %r; no such user exists" % user)
0447 if not gid:
0448 gid = entry.pw_gid
0449 uid = entry.pw_uid
0450 if self.