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"
45
46
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 """Execute under zensocket, providing the args to zensocket"""
83 zensocket = binPath('zensocket')
84 cmd = [zensocket, zensocket] + list(address) + ['--'] + \
85 [sys.executable] + sys.argv + \
86 ['--useFileDescriptor=$privilegedSocket']
87 os.execlp(*cmd)
88
89
91 """
92 Write the PID file to disk
93 """
94 myname = sys.argv[0].split(os.sep)[-1]
95 if myname.endswith('.py'): myname = myname[:-3]
96 monitor = getattr(self.options, 'monitor', 'localhost')
97 myname = "%s-%s.pid" % (myname, monitor)
98 if self.options.watchdog and not self.options.watchdogPath:
99 self.pidfile = zenPath("var", 'watchdog-%s' % myname)
100 else:
101 self.pidfile = zenPath("var", myname)
102 fp = open(self.pidfile, 'w')
103 fp.write(str(os.getpid()))
104 fp.close()
105
107 """
108 Create formating for log entries and set default log level
109 """
110
111
112 rlog = logging.getLogger()
113 rlog.setLevel(logging.WARN)
114 if hasattr(self, 'mname'): mname = self.mname
115 else: mname = self.__class__.__name__
116 self.log = logging.getLogger("zen."+ mname)
117 zlog = logging.getLogger("zen")
118 zlog.setLevel(self.options.logseverity)
119 if self.options.watchdogPath or \
120 self.options.daemon:
121 logdir = self.checkLogpath()
122 if not logdir:
123 logdir = zenPath("log")
124 logfile = os.path.join(logdir, mname.lower()+".log")
125 maxBytes = self.options.maxLogKiloBytes * 1024
126 backupCount = self.options.maxBackupLogs
127 h = logging.handlers.RotatingFileHandler(filename=logfile, maxBytes=maxBytes, backupCount=backupCount)
128 h.setFormatter(logging.Formatter(
129 "%(asctime)s %(levelname)s %(name)s: %(message)s"))
130 rlog.addHandler(h)
131 else:
132 logging.basicConfig()
133 if self.options.weblog:
134 [ h.setFormatter(HtmlFormatter()) for h in rlog.handlers ]
135 else:
136 f = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
137 [ h.setFormatter(f) for h in rlog.handlers ]
138
139
140
141 import signal
142 try:
143 signal.signal(signal.SIGUSR1, self.sighandler_USR1)
144 except ValueError:
145
146
147
148 pass
149
151 """
152 Switch to debug level if signaled by the user, and to
153 default when signaled again.
154 """
155 log = logging.getLogger('zen')
156 currentLevel = log.getEffectiveLevel()
157 if currentLevel == logging.DEBUG:
158 log.setLevel(self.options.logseverity)
159 log.info("Restoring logging level back to %s (%d)",
160 logging.getLevelName(self.options.logseverity) or "unknown",
161 self.options.logseverity)
162
163 if hasattr(self, 'mname'): mname = self.mname
164 else: mname = self.__class__.__name__
165 observer = twisted_log.PythonLoggingObserver(loggerName="zen."+ mname)
166 observer.stop()
167 else:
168 log.setLevel(logging.DEBUG)
169 log.info("Setting logging level to DEBUG")
170
171
172 if hasattr(self, 'mname'): mname = self.mname
173 else: mname = self.__class__.__name__
174 observer = twisted_log.PythonLoggingObserver(loggerName="zen."+ mname)
175 observer.start()
176
177
179 """
180 Switch identity to the appropriate Unix user
181 """
182 if not self.keeproot:
183 try:
184 cname = pwd.getpwuid(os.getuid())[0]
185 pwrec = pwd.getpwnam(self.options.uid)
186 os.setuid(pwrec.pw_uid)
187 os.environ['HOME'] = pwrec.pw_dir
188 except (KeyError, OSError):
189 print >>sys.stderr, "WARN: user:%s not found running as:%s"%(
190 self.options.uid,cname)
191
192
194 """Code below comes from the excellent recipe by Chad J. Schroeder.
195 """
196 try:
197 pid = os.fork()
198 except OSError, e:
199 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
200
201 if (pid == 0):
202 os.setsid()
203 try:
204 pid = os.fork()
205 except OSError, e:
206 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
207
208 if (pid == 0):
209 os.chdir(WORKDIR)
210 os.umask(UMASK)
211 else:
212 os._exit(0)
213 else:
214 os._exit(0)
215
216
217 for fd in range(0, MAXFD):
218 try:
219 os.close(fd)
220 except OSError:
221 pass
222
223 os.open(REDIRECT_TO, os.O_RDWR)
224
225 os.dup2(0, 1)
226 os.dup2(0, 2)
227
228
229 - def sigTerm(self, signum=None, frame=None):
244
245
259
273
274
276 """
277 Return our watchdog max restart time (in minutes)
278
279 @return: maximum restart time
280 @rtype: integer
281 """
282 default = 600
283 maxTime = getattr(self.options, 'maxRestartTime', default)
284 if not maxTime:
285 maxTime = default
286 return default
287
288
290 """
291 Watch the specified daemon and restart it if necessary.
292 """
293 from Products.ZenUtils.Watchdog import Watcher, log
294 log.setLevel(self.options.logseverity)
295 cmd = sys.argv[:]
296 if '--watchdog' in cmd:
297 cmd.remove('--watchdog')
298 if '--daemon' in cmd:
299 cmd.remove('--daemon')
300
301 socketPath = '%s/.%s-watchdog-%d' % (
302 zenPath('var'), self.__class__.__name__, os.getpid())
303
304 cycleTime = self.watchdogCycleTime()
305 startTimeout = self.watchdogStartTimeout()
306 maxTime = self.watchdogMaxRestartTime()
307 self.log.debug("Watchdog cycleTime=%d startTimeout=%d maxTime=%d",
308 cycleTime, startTimeout, maxTime)
309
310 watchdog = Watcher(socketPath,
311 cmd,
312 startTimeout,
313 cycleTime,
314 maxTime)
315 watchdog.run()
316 sys.exit(0)
317
319
320
321
322 if not self.reporter and self.options.watchdogPath:
323 from Watchdog import Reporter
324 self.reporter = Reporter(self.options.watchdogPath)
325 if self.reporter:
326 self.reporter.niceDoggie(timeout)
327
329 """
330 Standard set of command-line options.
331 """
332 CmdBase.buildOptions(self)
333 self.parser.add_option('--uid',dest='uid',default="zenoss",
334 help='User to become when running default:zenoss')
335 self.parser.add_option('-c', '--cycle',dest='cycle',
336 action="store_true", default=False,
337 help="Cycle continuously on cycleInterval from Zope")
338 self.parser.add_option('-D', '--daemon', default=False,
339 dest='daemon',action="store_true",
340 help="Launch into the background")
341 self.parser.add_option('--weblog', default=False,
342 dest='weblog',action="store_true",
343 help="output log info in HTML table format")
344 self.parser.add_option('--watchdog', default=False,
345 dest='watchdog', action="store_true",
346 help="Run under a supervisor which will restart it")
347 self.parser.add_option('--watchdogPath', default=None,
348 dest='watchdogPath',
349 help="The path to the watchdog reporting socket")
350 self.parser.add_option('--starttimeout',
351 dest='starttimeout',
352 type="int",
353 help="Wait seconds for initial heartbeat")
354