1
2
3
4
5
6
7
8
9
10
11
12
13
14 __doc__="""ZenDaemon
15
16 Base class for making deamon programs
17 """
18
19
20 import sys
21 import os
22 import pwd
23 import socket
24 import logging
25 from logging import handlers
26 from twisted.python import log as twisted_log
27
28 from CmdBase import CmdBase
29 from Utils import zenPath, HtmlFormatter, binPath
30
31
32
33 UMASK = 0022
34
35 WORKDIR = "/"
36
37
38 MAXFD = 3
39
40
41 if (hasattr(os, "devnull")):
42 REDIRECT_TO = os.devnull
43 else:
44 REDIRECT_TO = "/dev/null"
48 """
49 Base class for creating daemons
50 """
51
52 pidfile = None
53
54 - def __init__(self, noopts=0, keeproot=False):
55 """
56 Initializer that takes care of basic daemon options.
57 Creates a PID file.
58 """
59 super(ZenDaemon, self).__init__(noopts)
60 self.pidfile = None
61 self.keeproot=keeproot
62 self.reporter = None
63 self.fqdn = socket.getfqdn()
64 from twisted.internet import reactor
65 reactor.addSystemEventTrigger('before', 'shutdown', self.sigTerm)
66 if not noopts:
67 if self.options.daemon:
68 self.changeUser()
69 self.becomeDaemon()
70 if self.options.daemon or self.options.watchdogPath:
71 try:
72 self.writePidFile()
73 except OSError:
74 msg= "ERROR: unable to open PID file %s" % \
75 (self.pidfile or '(unknown)')
76 raise SystemExit(msg)
77
78 if self.options.watchdog and not self.options.watchdogPath:
79 self.becomeWatchdog()
80
82 """
83 Given a socket option string (eg 'so_rcvbufforce=1') convert
84 to a C-friendly command-line option for passing to zensocket.
85 """
86 optString = optString.upper()
87 if '=' not in optString:
88 flag = optString
89 value = 1
90 else:
91 flag, value = optString.split('=', 1)
92 try:
93 value = int(value)
94 except ValueError:
95 self.log.warn("The value %s for flag %s cound not be converted",
96 value, flag)
97 return None
98
99
100 if flag not in dir(socket):
101 self.log.warn("The flag %s is not a valid socket option",
102 flag)
103 return None
104
105 numericFlag = getattr(socket, flag)
106 return '--socketOpt=%s:%s' % (numericFlag, value)
107
109 """
110 Execute under zensocket, providing the args to zensocket
111 """
112 socketOptions = []
113 for optString in set(self.options.socketOption):
114 arg = self.convertSocketOption(optString)
115 if arg:
116 socketOptions.append(arg)
117
118 zensocket = binPath('zensocket')
119 cmd = [zensocket, zensocket] + list(address) + socketOptions + ['--',
120 sys.executable] + sys.argv + \
121 ['--useFileDescriptor=$privilegedSocket']
122 self.log.debug(cmd)
123 os.execlp(*cmd)
124
125
127 """
128 Write the PID file to disk
129 """
130 myname = sys.argv[0].split(os.sep)[-1]
131 if myname.endswith('.py'): myname = myname[:-3]
132 monitor = getattr(self.options, 'monitor', 'localhost')
133 myname = "%s-%s.pid" % (myname, monitor)
134 if self.options.watchdog and not self.options.watchdogPath:
135 self.pidfile = zenPath("var", 'watchdog-%s' % myname)
136 else:
137 self.pidfile = zenPath("var", myname)
138 fp = open(self.pidfile, 'w')
139 fp.write(str(os.getpid()))
140 fp.close()
141
142 @property
144 return getattr(self, 'mname', self.__class__.__name__)
145
147 """
148 Create formating for log entries and set default log level
149 """
150
151
152 rootLog = logging.getLogger()
153 rootLog.setLevel(logging.WARN)
154
155 zenLog = logging.getLogger('zen')
156 zenLog.setLevel(self.options.logseverity)
157
158 formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s: %(message)s')
159
160 if self.options.watchdogPath or self.options.daemon or self.options.duallog:
161 logdir = self.checkLogpath() or zenPath("log")
162
163 handler = logging.handlers.RotatingFileHandler(
164 filename = os.path.join(logdir, '%s.log' % self.logname.lower()),
165 maxBytes = self.options.maxLogKiloBytes * 1024,
166 backupCount = self.options.maxBackupLogs
167 )
168 handler.setFormatter(formatter)
169 rootLog.addHandler(handler)
170 if not (self.options.watchdogPath or self.options.daemon):
171
172
173 if self.options.weblog:
174 formatter = HtmlFormatter()
175
176 if not rootLog.handlers:
177
178 consoleHandler = logging.StreamHandler(sys.stderr)
179 rootLog.addHandler(consoleHandler)
180
181 for handler in (h for h in rootLog.handlers if isinstance(h, logging.StreamHandler)):
182 handler.setLevel(self.options.logseverity)
183 handler.setFormatter(formatter)
184
185 self.log = logging.getLogger('zen.%s' % self.logname)
186
187
188
189 import signal
190 try:
191 signal.signal(signal.SIGUSR1, self.sighandler_USR1)
192 except ValueError:
193
194
195
196 pass
197
199 """
200 Switch to debug level if signaled by the user, and to
201 default when signaled again.
202 """
203 def getTwistedLogger():
204 loggerName = "zen.%s.twisted" % self.logname
205 return twisted_log.PythonLoggingObserver(loggerName=loggerName)
206
207 log = logging.getLogger('zen')
208 currentLevel = log.getEffectiveLevel()
209 if currentLevel == logging.DEBUG:
210 if self.options.logseverity == logging.DEBUG:
211 return
212 log.setLevel(self.options.logseverity)
213 log.info("Restoring logging level back to %s (%d)",
214 logging.getLevelName(self.options.logseverity) or "unknown",
215 self.options.logseverity)
216 try:
217 getTwistedLogger().stop()
218 except ValueError:
219 log.info("Unable to remove Twisted logger -- "
220 "expect Twisted logging to continue.")
221 else:
222 log.setLevel(logging.DEBUG)
223 log.info("Setting logging level to DEBUG")
224 getTwistedLogger().start()
225 self._sigUSR1_called(signum, frame)
226
229
231 """
232 Switch identity to the appropriate Unix user
233 """
234 if not self.keeproot:
235 try:
236 cname = pwd.getpwuid(os.getuid())[0]
237 pwrec = pwd.getpwnam(self.options.uid)
238 os.setuid(pwrec.pw_uid)
239 os.environ['HOME'] = pwrec.pw_dir
240 except (KeyError, OSError):
241 print >>sys.stderr, "WARN: user:%s not found running as:%s"%(
242 self.options.uid,cname)
243
244
246 """Code below comes from the excellent recipe by Chad J. Schroeder.
247 """
248
249 from platform import system
250 if system() == 'Darwin':
251 from urllib import getproxies
252 getproxies()
253 try:
254 pid = os.fork()
255 except OSError, e:
256 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
257
258 if (pid == 0):
259 os.setsid()
260 try:
261 pid = os.fork()
262 except OSError, e:
263 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
264
265 if (pid == 0):
266 os.chdir(WORKDIR)
267 os.umask(UMASK)
268 else:
269 os._exit(0)
270 else:
271 os._exit(0)
272
273
274 for fd in range(0, MAXFD):
275 try:
276 os.close(fd)
277 except OSError:
278 pass
279
280 os.open(REDIRECT_TO, os.O_RDWR)
281
282 os.dup2(0, 1)
283 os.dup2(0, 2)
284
285
286 - def sigTerm(self, signum=None, frame=None):
301
302
316
330
331
333 """
334 Return our watchdog max restart time (in minutes)
335
336 @return: maximum restart time
337 @rtype: integer
338 """
339 default = 600
340 maxTime = getattr(self.options, 'maxRestartTime', default)
341 if not maxTime:
342 maxTime = default
343 return default
344
345
347 """
348 Watch the specified daemon and restart it if necessary.
349 """
350 from Products.ZenUtils.Watchdog import Watcher, log
351 log.setLevel(self.options.logseverity)
352 cmd = sys.argv[:]
353 if '--watchdog' in cmd:
354 cmd.remove('--watchdog')
355 if '--daemon' in cmd:
356 cmd.remove('--daemon')
357
358 socketPath = '%s/.%s-watchdog-%d' % (
359 zenPath('var'), self.__class__.__name__, os.getpid())
360
361 cycleTime = self.watchdogCycleTime()
362 startTimeout = self.watchdogStartTimeout()
363 maxTime = self.watchdogMaxRestartTime()
364 self.log.debug("Watchdog cycleTime=%d startTimeout=%d maxTime=%d",
365 cycleTime, startTimeout, maxTime)
366
367 watchdog = Watcher(socketPath,
368 cmd,
369 startTimeout,
370 cycleTime,
371 maxTime)
372 watchdog.run()
373 sys.exit(0)
374
376
377
378
379 if not self.reporter and self.options.watchdogPath:
380 from Watchdog import Reporter
381 self.reporter = Reporter(self.options.watchdogPath)
382 if self.reporter:
383 self.reporter.niceDoggie(timeout)
384
386 """
387 Standard set of command-line options.
388 """
389 CmdBase.buildOptions(self)
390 self.parser.add_option('--uid',dest='uid',default="zenoss",
391 help='User to become when running default:zenoss')
392 self.parser.add_option('-c', '--cycle',dest='cycle',
393 action="store_true", default=False,
394 help="Cycle continuously on cycleInterval from Zope")
395 self.parser.add_option('-D', '--daemon', default=False,
396 dest='daemon',action="store_true",
397 help="Launch into the background")
398 self.parser.add_option('--duallog', default=False,
399 dest='duallog',action="store_true",
400 help="Log to console and log file")
401 self.parser.add_option('--weblog', default=False,
402 dest='weblog',action="store_true",
403 help="output log info in HTML table format")
404 self.parser.add_option('--watchdog', default=False,
405 dest='watchdog', action="store_true",
406 help="Run under a supervisor which will restart it")
407 self.parser.add_option('--watchdogPath', default=None,
408 dest='watchdogPath',
409 help="The path to the watchdog reporting socket")
410 self.parser.add_option('--starttimeout',
411 dest='starttimeout',
412 type="int",
413 help="Wait seconds for initial heartbeat")
414 self.parser.add_option('--socketOption',
415 dest='socketOption', default=[], action='append',
416 help="Set listener socket options." \
417 "For option details: man 7 socket")
418