~hudson-openstack/nova/trunk

« back to all changes in this revision

Viewing changes to vendor/python-daemon/daemon/runner.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
 
 
3
# daemon/runner.py
 
4
# Part of python-daemon, an implementation of PEP 3143.
 
5
#
 
6
# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
 
7
# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
 
8
# Copyright © 2003 Clark Evans
 
9
# Copyright © 2002 Noah Spurrier
 
10
# Copyright © 2001 Jürgen Hermann
 
11
#
 
12
# This is free software: you may copy, modify, and/or distribute this work
 
13
# under the terms of the Python Software Foundation License, version 2 or
 
14
# later as published by the Python Software Foundation.
 
15
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
 
16
 
 
17
""" Daemon runner library.
 
18
    """
 
19
 
 
20
import sys
 
21
import os
 
22
import signal
 
23
import errno
 
24
 
 
25
import pidlockfile
 
26
 
 
27
from daemon import DaemonContext
 
28
 
 
29
 
 
30
class DaemonRunnerError(Exception):
 
31
    """ Abstract base class for errors from DaemonRunner. """
 
32
 
 
33
class DaemonRunnerInvalidActionError(ValueError, DaemonRunnerError):
 
34
    """ Raised when specified action for DaemonRunner is invalid. """
 
35
 
 
36
class DaemonRunnerStartFailureError(RuntimeError, DaemonRunnerError):
 
37
    """ Raised when failure starting DaemonRunner. """
 
38
 
 
39
class DaemonRunnerStopFailureError(RuntimeError, DaemonRunnerError):
 
40
    """ Raised when failure stopping DaemonRunner. """
 
41
 
 
42
 
 
43
class DaemonRunner(object):
 
44
    """ Controller for a callable running in a separate background process.
 
45
 
 
46
        The first command-line argument is the action to take:
 
47
 
 
48
        * 'start': Become a daemon and call `app.run()`.
 
49
        * 'stop': Exit the daemon process specified in the PID file.
 
50
        * 'restart': Stop, then start.
 
51
 
 
52
        """
 
53
 
 
54
    start_message = "started with pid %(pid)d"
 
55
 
 
56
    def __init__(self, app):
 
57
        """ Set up the parameters of a new runner.
 
58
 
 
59
            The `app` argument must have the following attributes:
 
60
 
 
61
            * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem
 
62
              paths to open and replace the existing `sys.stdin`,
 
63
              `sys.stdout`, `sys.stderr`.
 
64
 
 
65
            * `pidfile_path`: Absolute filesystem path to a file that
 
66
              will be used as the PID file for the daemon. If
 
67
              ``None``, no PID file will be used.
 
68
 
 
69
            * `pidfile_timeout`: Used as the default acquisition
 
70
              timeout value supplied to the runner's PID lock file.
 
71
 
 
72
            * `run`: Callable that will be invoked when the daemon is
 
73
              started.
 
74
            
 
75
            """
 
76
        self.parse_args()
 
77
        self.app = app
 
78
        self.daemon_context = DaemonContext()
 
79
        self.daemon_context.stdin = open(app.stdin_path, 'r')
 
80
        self.daemon_context.stdout = open(app.stdout_path, 'w+')
 
81
        self.daemon_context.stderr = open(
 
82
            app.stderr_path, 'w+', buffering=0)
 
83
 
 
84
        self.pidfile = None
 
85
        if app.pidfile_path is not None:
 
86
            self.pidfile = make_pidlockfile(
 
87
                app.pidfile_path, app.pidfile_timeout)
 
88
        self.daemon_context.pidfile = self.pidfile
 
89
 
 
90
    def _usage_exit(self, argv):
 
91
        """ Emit a usage message, then exit.
 
92
            """
 
93
        progname = os.path.basename(argv[0])
 
94
        usage_exit_code = 2
 
95
        action_usage = "|".join(self.action_funcs.keys())
 
96
        message = "usage: %(progname)s %(action_usage)s" % vars()
 
97
        emit_message(message)
 
98
        sys.exit(usage_exit_code)
 
99
 
 
100
    def parse_args(self, argv=None):
 
101
        """ Parse command-line arguments.
 
102
            """
 
103
        if argv is None:
 
104
            argv = sys.argv
 
105
 
 
106
        min_args = 2
 
107
        if len(argv) < min_args:
 
108
            self._usage_exit(argv)
 
109
 
 
110
        self.action = argv[1]
 
111
        if self.action not in self.action_funcs:
 
112
            self._usage_exit(argv)
 
113
 
 
114
    def _start(self):
 
115
        """ Open the daemon context and run the application.
 
116
            """
 
117
        if is_pidfile_stale(self.pidfile):
 
118
            self.pidfile.break_lock()
 
119
 
 
120
        try:
 
121
            self.daemon_context.open()
 
122
        except pidlockfile.AlreadyLocked:
 
123
            pidfile_path = self.pidfile.path
 
124
            raise DaemonRunnerStartFailureError(
 
125
                "PID file %(pidfile_path)r already locked" % vars())
 
126
 
 
127
        pid = os.getpid()
 
128
        message = self.start_message % vars()
 
129
        emit_message(message)
 
130
 
 
131
        self.app.run()
 
132
 
 
133
    def _terminate_daemon_process(self):
 
134
        """ Terminate the daemon process specified in the current PID file.
 
135
            """
 
136
        pid = self.pidfile.read_pid()
 
137
        try:
 
138
            os.kill(pid, signal.SIGTERM)
 
139
        except OSError, exc:
 
140
            raise DaemonRunnerStopFailureError(
 
141
                "Failed to terminate %(pid)d: %(exc)s" % vars())
 
142
 
 
143
    def _stop(self):
 
144
        """ Exit the daemon process specified in the current PID file.
 
145
            """
 
146
        if not self.pidfile.is_locked():
 
147
            pidfile_path = self.pidfile.path
 
148
            raise DaemonRunnerStopFailureError(
 
149
                "PID file %(pidfile_path)r not locked" % vars())
 
150
 
 
151
        if is_pidfile_stale(self.pidfile):
 
152
            self.pidfile.break_lock()
 
153
        else:
 
154
            self._terminate_daemon_process()
 
155
 
 
156
    def _restart(self):
 
157
        """ Stop, then start.
 
158
            """
 
159
        self._stop()
 
160
        self._start()
 
161
 
 
162
    action_funcs = {
 
163
        'start': _start,
 
164
        'stop': _stop,
 
165
        'restart': _restart,
 
166
        }
 
167
 
 
168
    def _get_action_func(self):
 
169
        """ Return the function for the specified action.
 
170
 
 
171
            Raises ``DaemonRunnerInvalidActionError`` if the action is
 
172
            unknown.
 
173
 
 
174
            """
 
175
        try:
 
176
            func = self.action_funcs[self.action]
 
177
        except KeyError:
 
178
            raise DaemonRunnerInvalidActionError(
 
179
                "Unknown action: %(action)r" % vars(self))
 
180
        return func
 
181
 
 
182
    def do_action(self):
 
183
        """ Perform the requested action.
 
184
            """
 
185
        func = self._get_action_func()
 
186
        func(self)
 
187
 
 
188
 
 
189
def emit_message(message, stream=None):
 
190
    """ Emit a message to the specified stream (default `sys.stderr`). """
 
191
    if stream is None:
 
192
        stream = sys.stderr
 
193
    stream.write("%(message)s\n" % vars())
 
194
    stream.flush()
 
195
 
 
196
 
 
197
def make_pidlockfile(path, acquire_timeout):
 
198
    """ Make a PIDLockFile instance with the given filesystem path. """
 
199
    if not isinstance(path, basestring):
 
200
        error = ValueError("Not a filesystem path: %(path)r" % vars())
 
201
        raise error
 
202
    if not os.path.isabs(path):
 
203
        error = ValueError("Not an absolute path: %(path)r" % vars())
 
204
        raise error
 
205
    lockfile = pidlockfile.TimeoutPIDLockFile(path, acquire_timeout)
 
206
 
 
207
    return lockfile
 
208
 
 
209
 
 
210
def is_pidfile_stale(pidfile):
 
211
    """ Determine whether a PID file is stale.
 
212
 
 
213
        Return ``True`` (“stale”) if the contents of the PID file are
 
214
        valid but do not match the PID of a currently-running process;
 
215
        otherwise return ``False``.
 
216
 
 
217
        """
 
218
    result = False
 
219
 
 
220
    pidfile_pid = pidfile.read_pid()
 
221
    if pidfile_pid is not None:
 
222
        try:
 
223
            os.kill(pidfile_pid, signal.SIG_DFL)
 
224
        except OSError, exc:
 
225
            if exc.errno == errno.ESRCH:
 
226
                # The specified PID does not exist
 
227
                result = True
 
228
 
 
229
    return result