root/galaxy-central/eggs/PasteScript-1.7.3-py2.6.egg/paste/script/serve.py

リビジョン 3, 21.7 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3# @@: This should be moved to paste.deploy
4# For discussion of daemonizing:
5#   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
6# Code taken also from QP:
7#   http://www.mems-exchange.org/software/qp/
8#   From lib/site.py
9import re
10import os
11import errno
12import sys
13import time
14try:
15    import subprocess
16except ImportError:
17    from paste.util import subprocess24 as subprocess
18from command import Command, BadCommand
19from paste.deploy import loadapp, loadserver
20import threading
21import atexit
22import logging
23import ConfigParser
24
25MAXFD = 1024
26
27jython = sys.platform.startswith('java')
28
29class DaemonizeException(Exception):
30    pass
31
32
33class ServeCommand(Command):
34
35    min_args = 0
36    usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]'
37    takes_config_file = 1
38    summary = "Serve the described application"
39    description = """\
40    This command serves a web application that uses a paste.deploy
41    configuration file for the server and application. 
42   
43    If start/stop/restart is given, then --daemon is implied, and it will
44    start (normal operation), stop (--stop-daemon), or do both.
45
46    You can also include variable assignments like 'http_port=8080'
47    and then use %(http_port)s in your config files.
48    """
49   
50    # used by subclasses that configure apps and servers differently
51    requires_config_file = True
52
53    parser = Command.standard_parser(quiet=True)
54    parser.add_option('-n', '--app-name',
55                      dest='app_name',
56                      metavar='NAME',
57                      help="Load the named application (default main)")
58    parser.add_option('-s', '--server',
59                      dest='server',
60                      metavar='SERVER_TYPE',
61                      help="Use the named server.")
62    parser.add_option('--server-name',
63                      dest='server_name',
64                      metavar='SECTION_NAME',
65                      help="Use the named server as defined in the configuration file (default: main)")
66    if hasattr(os, 'fork'):
67        parser.add_option('--daemon',
68                          dest="daemon",
69                          action="store_true",
70                          help="Run in daemon (background) mode")
71    parser.add_option('--pid-file',
72                      dest='pid_file',
73                      metavar='FILENAME',
74                      help="Save PID to file (default to paster.pid if running in daemon mode)")
75    parser.add_option('--log-file',
76                      dest='log_file',
77                      metavar='LOG_FILE',
78                      help="Save output to the given log file (redirects stdout)")
79    parser.add_option('--reload',
80                      dest='reload',
81                      action='store_true',
82                      help="Use auto-restart file monitor")
83    parser.add_option('--reload-interval',
84                      dest='reload_interval',
85                      default=1,
86                      help="Seconds between checking files (low number can cause significant CPU usage)")
87    parser.add_option('--monitor-restart',
88                      dest='monitor_restart',
89                      action='store_true',
90                      help="Auto-restart server if it dies")
91    parser.add_option('--status',
92                      action='store_true',
93                      dest='show_status',
94                      help="Show the status of the (presumably daemonized) server")
95
96
97    if hasattr(os, 'setuid'):
98        # I don't think these are available on Windows
99        parser.add_option('--user',
100                          dest='set_user',
101                          metavar="USERNAME",
102                          help="Set the user (usually only possible when run as root)")
103        parser.add_option('--group',
104                          dest='set_group',
105                          metavar="GROUP",
106                          help="Set the group (usually only possible when run as root)")
107
108    parser.add_option('--stop-daemon',
109                      dest='stop_daemon',
110                      action='store_true',
111                      help='Stop a daemonized server (given a PID file, or default paster.pid file)')
112
113    if jython:
114        parser.add_option('--disable-jython-reloader',
115                          action='store_true',
116                          dest='disable_jython_reloader',
117                          help="Disable the Jython reloader")
118
119
120    _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
121
122    default_verbosity = 1
123
124    _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN'
125    _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN'
126
127    possible_subcommands = ('start', 'stop', 'restart', 'status')
128    def command(self):
129        if self.options.stop_daemon:
130            return self.stop_daemon()
131
132        if not hasattr(self.options, 'set_user'):
133            # Windows case:
134            self.options.set_user = self.options.set_group = None
135        # @@: Is this the right stage to set the user at?
136        self.change_user_group(
137            self.options.set_user, self.options.set_group)
138
139        if self.requires_config_file:
140            if not self.args:
141                raise BadCommand('You must give a config file')
142            app_spec = self.args[0]
143            if (len(self.args) > 1
144                and self.args[1] in self.possible_subcommands):
145                cmd = self.args[1]
146                restvars = self.args[2:]
147            else:
148                cmd = None
149                restvars = self.args[1:]
150        else:
151            app_spec = ""
152            if (self.args
153                and self.args[0] in self.possible_subcommands):
154                cmd = self.args[0]
155                restvars = self.args[1:]
156            else:
157                cmd = None
158                restvars = self.args[:]
159
160        jython_monitor = False
161        if self.options.reload:
162            if jython and not self.options.disable_jython_reloader:
163                # JythonMonitor raises the special SystemRestart
164                # exception that'll cause the Jython interpreter to
165                # reload in the existing Java process (avoiding
166                # subprocess startup time)
167                try:
168                    from paste.reloader import JythonMonitor
169                except ImportError:
170                    pass
171                else:
172                    jython_monitor = JythonMonitor(poll_interval=int(
173                            self.options.reload_interval))
174                    if self.requires_config_file:
175                        jython_monitor.watch_file(self.args[0])
176
177            if not jython_monitor:
178                if os.environ.get(self._reloader_environ_key):
179                    from paste import reloader
180                    if self.verbose > 1:
181                        print 'Running reloading file monitor'
182                    reloader.install(int(self.options.reload_interval))
183                    if self.requires_config_file:
184                        reloader.watch_file(self.args[0])
185                else:
186                    return self.restart_with_reloader()
187
188        if cmd not in (None, 'start', 'stop', 'restart', 'status'):
189            raise BadCommand(
190                'Error: must give start|stop|restart (not %s)' % cmd)
191
192        if cmd == 'status' or self.options.show_status:
193            return self.show_status()
194
195        if cmd == 'restart' or cmd == 'stop':
196            result = self.stop_daemon()
197            if result:
198                if cmd == 'restart':
199                    print "Could not stop daemon; aborting"
200                else:
201                    print "Could not stop daemon"
202                return result
203            if cmd == 'stop':
204                return result
205
206        app_name = self.options.app_name
207        vars = self.parse_vars(restvars)
208        if not self._scheme_re.search(app_spec):
209            app_spec = 'config:' + app_spec
210        server_name = self.options.server_name
211        if self.options.server:
212            server_spec = 'egg:PasteScript'
213            assert server_name is None
214            server_name = self.options.server
215        else:
216            server_spec = app_spec
217        base = os.getcwd()
218
219        if getattr(self.options, 'daemon', False):
220            if not self.options.pid_file:
221                self.options.pid_file = 'paster.pid'
222            if not self.options.log_file:
223                self.options.log_file = 'paster.log'
224
225        # Ensure the log file is writeable
226        if self.options.log_file:
227            try:
228                writeable_log_file = open(self.options.log_file, 'a')
229            except IOError, ioe:
230                msg = 'Error: Unable to write to log file: %s' % ioe
231                raise BadCommand(msg)
232            writeable_log_file.close()
233
234        # Ensure the pid file is writeable
235        if self.options.pid_file:
236            try:
237                writeable_pid_file = open(self.options.pid_file, 'a')
238            except IOError, ioe:
239                msg = 'Error: Unable to write to pid file: %s' % ioe
240                raise BadCommand(msg)
241            writeable_pid_file.close()
242
243        if getattr(self.options, 'daemon', False):
244            try:
245                self.daemonize()
246            except DaemonizeException, ex:
247                if self.verbose > 0:
248                    print str(ex)
249                return
250
251        if (self.options.monitor_restart
252            and not os.environ.get(self._monitor_environ_key)):
253            return self.restart_with_monitor()
254
255        if self.options.pid_file:
256            self.record_pid(self.options.pid_file)
257
258        if self.options.log_file:
259            stdout_log = LazyWriter(self.options.log_file, 'a')
260            sys.stdout = stdout_log
261            sys.stderr = stdout_log
262            logging.basicConfig(stream=stdout_log)
263
264        log_fn = app_spec
265        if log_fn.startswith('config:'):
266            log_fn = app_spec[len('config:'):]
267        elif log_fn.startswith('egg:'):
268            log_fn = None
269        if log_fn:
270            log_fn = os.path.join(base, log_fn)
271            self.logging_file_config(log_fn)
272
273        server = self.loadserver(server_spec, name=server_name,
274                                 relative_to=base, global_conf=vars)
275        app = self.loadapp(app_spec, name=app_name,
276                           relative_to=base, global_conf=vars)
277
278        if self.verbose > 0:
279            if hasattr(os, 'getpid'):
280                msg = 'Starting server in PID %i.' % os.getpid()
281            else:
282                msg = 'Starting server.'
283            print msg
284
285        def serve():
286            try:
287                server(app)
288            except (SystemExit, KeyboardInterrupt), e:
289                if self.verbose > 1:
290                    raise
291                if str(e):
292                    msg = ' '+str(e)
293                else:
294                    msg = ''
295                print 'Exiting%s (-v to see traceback)' % msg
296
297        if jython_monitor:
298            # JythonMonitor has to be ran from the main thread
299            threading.Thread(target=serve).start()
300            print 'Starting Jython file monitor'
301            jython_monitor.periodic_reload()
302        else:
303            serve()
304   
305    def loadserver(self, server_spec, name, relative_to, **kw):
306            return loadserver(
307                server_spec, name=name,
308                relative_to=relative_to, **kw)
309   
310    def loadapp(self, app_spec, name, relative_to, **kw):
311            return loadapp(
312                app_spec, name=name, relative_to=relative_to,
313                **kw)
314
315    def daemonize(self):
316        pid = live_pidfile(self.options.pid_file)
317        if pid:
318            raise DaemonizeException(
319                "Daemon is already running (PID: %s from PID file %s)"
320                % (pid, self.options.pid_file))
321
322        if self.verbose > 0:
323            print 'Entering daemon mode'
324        pid = os.fork()
325        if pid:
326            # The forked process also has a handle on resources, so we
327            # *don't* want proper termination of the process, we just
328            # want to exit quick (which os._exit() does)
329            os._exit(0)
330        # Make this the session leader
331        os.setsid()
332        # Fork again for good measure!
333        pid = os.fork()
334        if pid:
335            os._exit(0)
336
337        # @@: Should we set the umask and cwd now?
338
339        import resource  # Resource usage information.
340        maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
341        if (maxfd == resource.RLIM_INFINITY):
342            maxfd = MAXFD
343        # Iterate through and close all file descriptors.
344        for fd in range(0, maxfd):
345            try:
346                os.close(fd)
347            except OSError:  # ERROR, fd wasn't open to begin with (ignored)
348                pass
349
350        if (hasattr(os, "devnull")):
351            REDIRECT_TO = os.devnull
352        else:
353            REDIRECT_TO = "/dev/null"
354        os.open(REDIRECT_TO, os.O_RDWR)  # standard input (0)
355        # Duplicate standard input to standard output and standard error.
356        os.dup2(0, 1)  # standard output (1)
357        os.dup2(0, 2)  # standard error (2)
358       
359    def record_pid(self, pid_file):
360        pid = os.getpid()
361        if self.verbose > 1:
362            print 'Writing PID %s to %s' % (pid, pid_file)
363        f = open(pid_file, 'w')
364        f.write(str(pid))
365        f.close()
366        atexit.register(_remove_pid_file, pid, pid_file, self.verbose)
367
368    def stop_daemon(self):
369        pid_file = self.options.pid_file or 'paster.pid'
370        if not os.path.exists(pid_file):
371            print 'No PID file exists in %s' % pid_file
372            return 1
373        pid = read_pidfile(pid_file)
374        if not pid:
375            print "Not a valid PID file in %s" % pid_file
376            return 1
377        pid = live_pidfile(pid_file)
378        if not pid:
379            print "PID in %s is not valid (deleting)" % pid_file
380            try:
381                os.unlink(pid_file)
382            except (OSError, IOError), e:
383                print "Could not delete: %s" % e
384                return 2
385            return 1
386        for j in range(10):
387            if not live_pidfile(pid_file):
388                break
389            import signal
390            os.kill(pid, signal.SIGTERM)
391            time.sleep(1)
392        else:
393            print "failed to kill web process %s" % pid
394            return 3
395        if os.path.exists(pid_file):
396            os.unlink(pid_file)
397        return 0
398
399    def show_status(self):
400        pid_file = self.options.pid_file or 'paster.pid'
401        if not os.path.exists(pid_file):
402            print 'No PID file %s' % pid_file
403            return 1
404        pid = read_pidfile(pid_file)
405        if not pid:
406            print 'No PID in file %s' % pid_file
407            return 1
408        pid = live_pidfile(pid_file)
409        if not pid:
410            print 'PID %s in %s is not running' % (pid, pid_file)
411            return 1
412        print 'Server running in PID %s' % pid
413        return 0
414
415    def restart_with_reloader(self):
416        self.restart_with_monitor(reloader=True)
417
418    def restart_with_monitor(self, reloader=False):
419        if self.verbose > 0:
420            if reloader:
421                print 'Starting subprocess with file monitor'
422            else:
423                print 'Starting subprocess with monitor parent'
424        while 1:
425            args = [self.quote_first_command_arg(sys.executable)] + sys.argv
426            new_environ = os.environ.copy()
427            if reloader:
428                new_environ[self._reloader_environ_key] = 'true'
429            else:
430                new_environ[self._monitor_environ_key] = 'true'
431            proc = None
432            try:
433                try:
434                    _turn_sigterm_into_systemexit()
435                    proc = subprocess.Popen(args, env=new_environ)
436                    exit_code = proc.wait()
437                    proc = None
438                except KeyboardInterrupt:
439                    print '^C caught in monitor process'
440                    if self.verbose > 1:
441                        raise
442                    return 1
443            finally:
444                if (proc is not None
445                    and hasattr(os, 'kill')):
446                    import signal
447                    try:
448                        os.kill(proc.pid, signal.SIGTERM)
449                    except (OSError, IOError):
450                        pass
451               
452            if reloader:
453                # Reloader always exits with code 3; but if we are
454                # a monitor, any exit code will restart
455                if exit_code != 3:
456                    return exit_code
457            if self.verbose > 0:
458                print '-'*20, 'Restarting', '-'*20
459
460    def change_user_group(self, user, group):
461        if not user and not group:
462            return
463        import pwd, grp
464        uid = gid = None
465        if group:
466            try:
467                gid = int(group)
468                group = grp.getgrgid(gid).gr_name
469            except ValueError:
470                import grp
471                try:
472                    entry = grp.getgrnam(group)
473                except KeyError:
474                    raise BadCommand(
475                        "Bad group: %r; no such group exists" % group)
476                gid = entry.gr_gid
477        try:
478            uid = int(user)
479            user = pwd.getpwuid(uid).pw_name
480        except ValueError:
481            try:
482                entry = pwd.getpwnam(user)
483            except KeyError:
484                raise BadCommand(
485                    "Bad username: %r; no such user exists" % user)
486            if not gid:
487                gid = entry.pw_gid
488            uid = entry.pw_uid
489        if self.verbose > 0:
490            print 'Changing user to %s:%s (%s:%s)' % (
491                user, group or '(unknown)', uid, gid)
492        if gid:
493            os.setgid(gid)
494        if uid:
495            os.setuid(uid)
496           
497class LazyWriter(object):
498
499    """
500    File-like object that opens a file lazily when it is first written
501    to.
502    """
503
504    def __init__(self, filename, mode='w'):
505        self.filename = filename
506        self.fileobj = None
507        self.lock = threading.Lock()
508        self.mode = mode
509       
510    def open(self):
511        if self.fileobj is None:
512            self.lock.acquire()
513            try:
514                if self.fileobj is None:
515                    self.fileobj = open(self.filename, self.mode)
516            finally:
517                self.lock.release()
518        return self.fileobj
519
520    def write(self, text):
521        fileobj = self.open()
522        fileobj.write(text)
523        fileobj.flush()
524
525    def writelines(self, text):
526        fileobj = self.open()
527        fileobj.writelines(text)
528        fileobj.flush()
529
530    def flush(self):
531        self.open().flush()
532
533def live_pidfile(pidfile):
534    """(pidfile:str) -> int | None
535    Returns an int found in the named file, if there is one,
536    and if there is a running process with that process id.
537    Return None if no such process exists.
538    """
539    pid = read_pidfile(pidfile)
540    if pid:
541        try:
542            os.kill(int(pid), 0)
543            return pid
544        except OSError, e:
545            if e.errno == errno.EPERM:
546                return pid
547    return None
548
549def read_pidfile(filename):
550    if os.path.exists(filename):
551        try:
552            f = open(filename)
553            content = f.read()
554            f.close()
555            return int(content.strip())
556        except (ValueError, IOError):
557            return None
558    else:
559        return None
560       
561def _remove_pid_file(written_pid, filename, verbosity):
562    current_pid = os.getpid()
563    if written_pid != current_pid:
564        # A forked process must be exiting, not the process that
565        # wrote the PID file
566        return
567    if not os.path.exists(filename):
568        return
569    f = open(filename)
570    content = f.read().strip()
571    f.close()
572    try:
573        pid_in_file = int(content)
574    except ValueError:
575        pass
576    else:
577        if pid_in_file != current_pid:
578            print "PID file %s contains %s, not expected PID %s" % (
579                filename, pid_in_file, current_pid)
580            return
581    if verbosity > 0:
582        print "Removing PID file %s" % filename
583    try:
584        os.unlink(filename)
585        return
586    except OSError, e:
587        # Record, but don't give traceback
588        print "Cannot remove PID file: %s" % e
589    # well, at least lets not leave the invalid PID around...
590    try:
591        f = open(filename, 'w')
592        f.write('')
593        f.close()
594    except OSError, e:
595        print 'Stale PID left in file: %s (%e)' % (filename, e)
596    else:
597        print 'Stale PID removed'
598       
599           
600def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2):
601    """
602    This makes sure any open ports are closed.
603
604    Does this by connecting to them until they give connection
605    refused.  Servers should call like::
606
607        import paste.script
608        ensure_port_cleanup([80, 443])
609    """
610    atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries,
611                    sleeptime=sleeptime)
612
613def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2):
614    # Wait for the server to bind to the port.
615    import socket
616    import errno
617    for bound_address in bound_addresses:
618        for attempt in range(maxtries):
619            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
620            try:
621                sock.connect(bound_address)
622            except socket.error, e:
623                if e.args[0] != errno.ECONNREFUSED:
624                    raise
625                break
626            else:
627                time.sleep(sleeptime)
628        else:
629            raise SystemExit('Timeout waiting for port.')
630        sock.close()
631
632def _turn_sigterm_into_systemexit():
633    """
634    Attempts to turn a SIGTERM exception into a SystemExit exception.
635    """
636    try:
637        import signal
638    except ImportError:
639        return
640    def handle_term(signo, frame):
641        raise SystemExit
642    signal.signal(signal.SIGTERM, handle_term)
643   
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。