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
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 @param force: if errors due to the file not existing should be ignored
245 @type force: bool
246
247 @rtype: str
248 @returns: full path to the pid file that was written
249 """
250 path = _getPidPath(type, name)
251 try:
252 os.unlink(path)
253 except OSError, e:
254 if e.errno == errno.ENOENT and force:
255 pass
256 else:
257 raise
258 return path
259
260
262 """
263 Get the pid from the pid file in the run directory, using the given
264 process type and process name for the filename.
265
266 @returns: pid of the process, or None if not running or file not found.
267 """
268
269 pidPath = _getPidPath(type, name)
270 log.log('common', 'pidfile for %s %s is %s' % (type, name, pidPath))
271 if not os.path.exists(pidPath):
272 return
273
274 pidFile = open(pidPath, 'r')
275 pid = pidFile.readline()
276 pidFile.close()
277 if not pid or int(pid) == 0:
278 return
279
280 return int(pid)
281
282
284 """
285 Send the given process a signal.
286
287 @returns: whether or not the process with the given pid was running
288 """
289 try:
290 os.kill(pid, signum)
291 return True
292 except OSError, e:
293
294 if e.errno == errno.EPERM:
295
296 return True
297 if e.errno == errno.ESRCH:
298
299 return False
300 raise
301
302
304 """
305 Send the given process a TERM signal.
306
307 @returns: whether or not the process with the given pid was running
308 """
309 return signalPid(pid, signal.SIGTERM)
310
311
313 """
314 Send the given process a KILL signal.
315
316 @returns: whether or not the process with the given pid was running
317 """
318 return signalPid(pid, signal.SIGKILL)
319
320
322 """
323 Check if the given pid is currently running.
324
325 @returns: whether or not a process with that pid is active.
326 """
327 return signalPid(pid, 0)
328
329
331 """
332 Wait for the given process type and name to have started and created
333 a pid file.
334
335 Return the pid.
336 """
337
338 pid = getPid(type, name)
339
340 while not pid:
341 time.sleep(0.1)
342 pid = getPid(type, name)
343
344 return pid
345
346
348 """
349 Wait until we get killed by a TERM signal (from someone else).
350 """
351
352 class Waiter:
353
354 def __init__(self):
355 self.sleeping = True
356 import signal
357 self.oldhandler = signal.signal(signal.SIGTERM,
358 self._SIGTERMHandler)
359
360 def _SIGTERMHandler(self, number, frame):
361 self.sleeping = False
362
363 def sleep(self):
364 while self.sleeping:
365 time.sleep(0.1)
366
367 waiter = Waiter()
368 waiter.sleep()
369