1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """utilities for interacting with processes"""
23
24 import errno
25 import os
26 import signal
27 import sys
28 import time
29
30 from flumotion.common import log
31 from flumotion.common.common import ensureDir
32 from flumotion.configure import configure
33
34 __version__ = "$Rev: 6690 $"
35
36
37 -def startup(processType, processName, daemonize=False, daemonizeTo='/'):
38 """
39 Prepare a process for starting, logging appropriate standarised messages.
40 First daemonizes the process, if daemonize is true.
41
42 @param processType: The process type, for example 'worker'. Used
43 as the first part of the log file and PID file names.
44 @type processType: str
45 @param processName: The service name of the process. Used to
46 disambiguate different instances of the same daemon.
47 Used as the second part of log file and PID file names.
48 @type processName: str
49 @param daemonize: whether to daemonize the current process.
50 @type daemonize: bool
51 @param daemonizeTo: The directory that the daemon should run in.
52 @type daemonizeTo: str
53 """
54 log.info(processType, "Starting %s '%s'", processType, processName)
55
56 if daemonize:
57 _daemonizeHelper(processType, daemonizeTo, processName)
58
59 log.info(processType, "Started %s '%s'", processType, processName)
60
61 def shutdownStarted():
62 log.info(processType, "Stopping %s '%s'", processType, processName)
63
64 def shutdownEnded():
65 log.info(processType, "Stopped %s '%s'", processType, processName)
66
67
68 from twisted.internet import reactor
69 reactor.addSystemEventTrigger('before', 'shutdown',
70 shutdownStarted)
71 reactor.addSystemEventTrigger('after', 'shutdown',
72 shutdownEnded)
73
74
75 -def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null',
76 directory='/'):
77 '''
78 This forks the current process into a daemon.
79 The stdin, stdout, and stderr arguments are file names that
80 will be opened and be used to replace the standard file descriptors
81 in sys.stdin, sys.stdout, and sys.stderr.
82 These arguments are optional and default to /dev/null.
83
84 The fork will switch to the given directory.
85
86 Used by external projects (ft).
87 '''
88
89 si = open(stdin, 'r')
90 os.dup2(si.fileno(), sys.stdin.fileno())
91 try:
92 log.outputToFiles(stdout, stderr)
93 except IOError, e:
94 if e.errno == errno.EACCES:
95 log.error('common', 'Permission denied writing to log file %s.',
96 e.filename)
97
98
99 try:
100 pid = os.fork()
101 if pid > 0:
102 sys.exit(0)
103 except OSError, e:
104 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror))
105 sys.exit(1)
106
107
108 try:
109 os.chdir(directory)
110 except OSError, e:
111 from flumotion.common import errors
112 raise errors.FatalError, "Failed to change directory to %s: %s" % (
113 directory, e.strerror)
114 os.umask(0)
115 os.setsid()
116
117
118 try:
119 pid = os.fork()
120 if pid > 0:
121 sys.exit(0)
122 except OSError, e:
123 sys.stderr.write("Failed to fork: (%d) %s\n" % (e.errno, e.strerror))
124 sys.exit(1)
125
126
127
128
129
130
132 """
133 Daemonize a process, writing log files and PID files to conventional
134 locations.
135
136 @param processType: The process type, for example 'worker'. Used
137 as the first part of the log file and PID file names.
138 @type processType: str
139 @param daemonizeTo: The directory that the daemon should run in.
140 @type daemonizeTo: str
141 @param processName: The service name of the process. Used to
142 disambiguate different instances of the same daemon.
143 Used as the second part of log file and PID file names.
144 @type processName: str
145 """
146
147 ensureDir(configure.logdir, "log dir")
148 ensureDir(configure.rundir, "run dir")
149 ensureDir(configure.cachedir, "cache dir")
150 ensureDir(configure.registrydir, "registry dir")
151
152 pid = getPid(processType, processName)
153 if pid:
154 raise SystemError(
155 "A %s service%s is already running with pid %d" % (
156 processType, processName and ' named %s' % processName or '',
157 pid))
158
159 log.debug(processType, "%s service named '%s' daemonizing",
160 processType, processName)
161
162 if processName:
163 logPath = os.path.join(configure.logdir,
164 '%s.%s.log' % (processType, processName))
165 else:
166 logPath = os.path.join(configure.logdir,
167 '%s.log' % (processType, ))
168 log.debug(processType, 'Further logging will be done to %s', logPath)
169
170 pidFile = _acquirePidFile(processType, processName)
171
172
173 daemonize(stdout=logPath, stderr=logPath, directory=daemonizeTo)
174
175 log.debug(processType, 'Started daemon')
176
177
178 path = writePidFile(processType, processName, file=pidFile)
179 log.debug(processType, 'written pid file %s', path)
180
181
182 from twisted.internet import reactor
183
184 def _deletePidFile():
185 log.debug(processType, 'deleting pid file')
186 deletePidFile(processType, processName)
187 reactor.addSystemEventTrigger('after', 'shutdown',
188 _deletePidFile)
189
190
192 """
193 Get the full path to the pid file for the given process type and name.
194 """
195 path = os.path.join(configure.rundir, '%s.pid' % type)
196 if name:
197 path = os.path.join(configure.rundir, '%s.%s.pid' % (type, name))
198 log.debug('common', 'getPidPath for type %s, name %r: %s' % (
199 type, name, path))
200 return path
201
202
223
224
226 """
227 Open a PID file for writing, using the given process type and
228 process name for the filename. The returned file can be then passed
229 to writePidFile after forking.
230
231 @rtype: str
232 @returns: file object, open for writing
233 """
234 ensureDir(configure.rundir, "rundir")
235 path = _getPidPath(type, name)
236 return open(path, 'w')
237
238
240 """
241 Delete the pid file in the run directory, using the given process type
242 and process name for the filename.
243
244 @rtype: str
245 @returns: full path to the pid file that was written
246 """
247 path = _getPidPath(type, name)
248 os.unlink(path)
249 return path
250
251
253 """
254 Get the pid from the pid file in the run directory, using the given
255 process type and process name for the filename.
256
257 @returns: pid of the process, or None if not running or file not found.
258 """
259
260 pidPath = _getPidPath(type, name)
261 log.log('common', 'pidfile for %s %s is %s' % (type, name, pidPath))
262 if not os.path.exists(pidPath):
263 return
264
265 pidFile = open(pidPath, 'r')
266 pid = pidFile.readline()
267 pidFile.close()
268 if not pid or int(pid) == 0:
269 return
270
271 return int(pid)
272
273
275 """
276 Send the given process a signal.
277
278 @returns: whether or not the process with the given pid was running
279 """
280 try:
281 os.kill(pid, signum)
282 return True
283 except OSError, e:
284
285 if e.errno == errno.EPERM:
286
287 return True
288 if e.errno == errno.ESRCH:
289
290 return False
291 raise
292
293
295 """
296 Send the given process a TERM signal.
297
298 @returns: whether or not the process with the given pid was running
299 """
300 return signalPid(pid, signal.SIGTERM)
301
302
304 """
305 Send the given process a KILL signal.
306
307 @returns: whether or not the process with the given pid was running
308 """
309 return signalPid(pid, signal.SIGKILL)
310
311
313 """
314 Check if the given pid is currently running.
315
316 @returns: whether or not a process with that pid is active.
317 """
318 return signalPid(pid, 0)
319
320
322 """
323 Wait for the given process type and name to have started and created
324 a pid file.
325
326 Return the pid.
327 """
328
329 pid = getPid(type, name)
330
331 while not pid:
332 time.sleep(0.1)
333 pid = getPid(type, name)
334
335 return pid
336
337
339 """
340 Wait until we get killed by a TERM signal (from someone else).
341 """
342
343 class Waiter:
344
345 def __init__(self):
346 self.sleeping = True
347 import signal
348 self.oldhandler = signal.signal(signal.SIGTERM,
349 self._SIGTERMHandler)
350
351 def _SIGTERMHandler(self, number, frame):
352 self.sleeping = False
353
354 def sleep(self):
355 while self.sleeping:
356 time.sleep(0.1)
357
358 waiter = Waiter()
359 waiter.sleep()
360