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
27 from CmdBase import CmdBase
28 from Utils import zenPath, HtmlFormatter, binPath
29
30
31
32 UMASK = 0022
33
34 WORKDIR = "/"
35
36
37 MAXFD = 3
38
39
40 if (hasattr(os, "devnull")):
41 REDIRECT_TO = os.devnull
42 else:
43 REDIRECT_TO = "/dev/null"
44
45
47 """
48 Base class for creating daemons
49 """
50
51 pidfile = None
52
53 - def __init__(self, noopts=0, keeproot=False):
54 """
55 Initializer that takes care of basic daemon options.
56 Creates a PID file.
57 """
58 super(ZenDaemon, self).__init__(noopts)
59 self.pidfile = None
60 self.keeproot=keeproot
61 self.reporter = None
62 self.fqdn = socket.getfqdn()
63 from twisted.internet import reactor
64 reactor.addSystemEventTrigger('before', 'shutdown', self.sigTerm)
65 if not noopts:
66 if self.options.daemon:
67 self.changeUser()
68 self.becomeDaemon()
69 if self.options.daemon or self.options.watchdogPath:
70 try:
71 self.writePidFile()
72 except OSError:
73 msg= "ERROR: unable to open PID file %s" % \
74 (self.pidfile or '(unknown)')
75 raise SystemExit(msg)
76
77 if self.options.watchdog and not self.options.watchdogPath:
78 self.becomeWatchdog()
79
81 """Execute under zensocket, providing the args to zensocket"""
82 zensocket = binPath('zensocket')
83 cmd = [zensocket, zensocket] + list(address) + ['--'] + \
84 [sys.executable] + sys.argv + \
85 ['--useFileDescriptor=$privilegedSocket']
86 os.execlp(*cmd)
87
88
90 """
91 Write the PID file to disk
92 """
93 myname = sys.argv[0].split(os.sep)[-1]
94 if myname.endswith('.py'): myname = myname[:-3]
95 monitor = getattr(self.options, 'monitor', 'localhost')
96 myname = "%s-%s.pid" % (myname, monitor)
97 if self.options.watchdog and not self.options.watchdogPath:
98 self.pidfile = zenPath("var", 'watchdog-%s' % myname)
99 else:
100 self.pidfile = zenPath("var", myname)
101 fp = open(self.pidfile, 'w')
102 fp.write(str(os.getpid()))
103 fp.close()
104
106 """
107 Create formating for log entries and set default log level
108 """
109 rlog = logging.getLogger()
110 rlog.setLevel(logging.WARN)
111 if hasattr(self, 'mname'): mname = self.mname
112 else: mname = self.__class__.__name__
113 self.log = logging.getLogger("zen."+ mname)
114 zlog = logging.getLogger("zen")
115 zlog.setLevel(self.options.logseverity)
116 if self.options.watchdogPath or \
117 self.options.daemon:
118 logdir = self.checkLogpath()
119 if not logdir:
120 logdir = zenPath("log")
121 logfile = os.path.join(logdir, mname.lower()+".log")
122 maxBytes = self.options.maxLogKiloBytes * 1024
123 backupCount = self.options.maxBackupLogs
124 h = logging.handlers.RotatingFileHandler(filename=logfile, maxBytes=maxBytes, backupCount=backupCount)
125 h.setFormatter(logging.Formatter(
126 "%(asctime)s %(levelname)s %(name)s: %(message)s"))
127 rlog.addHandler(h)
128 else:
129 logging.basicConfig()
130 if self.options.weblog:
131 [ h.setFormatter(HtmlFormatter()) for h in rlog.handlers ]
132 else:
133 f = logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
134 [ h.setFormatter(f) for h in rlog.handlers ]
135
136
137
138 import signal
139 try:
140 signal.signal(signal.SIGUSR1, self.sighandler_USR1)
141 except ValueError:
142
143
144
145 pass
146
148 """
149 Switch to debug level if signaled by the user, and to
150 default when signaled again.
151 """
152 levelNames = {
153 logging.DEBUG:"DEBUG",
154 logging.INFO:"INFO",
155 logging.WARN:"WARN",
156 logging.ERROR:"ERROR",
157 logging.CRITICAL:"CRITICAL",
158 }
159 log = logging.getLogger('zen')
160 currentLevel = log.getEffectiveLevel()
161 if currentLevel == logging.DEBUG:
162 log.setLevel(self.options.logseverity)
163 log.info("Restoring logging level back to %s (%d)",
164 levelNames.get(self.options.logseverity,
165 "unknown"),
166 self.options.logseverity)
167 else:
168 log.setLevel(logging.DEBUG)
169 log.info("Setting logging level to DEBUG")
170
171
173 """
174 Switch identity to the appropriate Unix user
175 """
176 if not self.keeproot:
177 try:
178 cname = pwd.getpwuid(os.getuid())[0]
179 pwrec = pwd.getpwnam(self.options.uid)
180 os.setuid(pwrec.pw_uid)
181 os.environ['HOME'] = pwrec.pw_dir
182 except (KeyError, OSError):
183 print >>sys.stderr, "WARN: user:%s not found running as:%s"%(
184 self.options.uid,cname)
185
186
188 """Code below comes from the excellent recipe by Chad J. Schroeder.
189 """
190 try:
191 pid = os.fork()
192 except OSError, e:
193 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
194
195 if (pid == 0):
196 os.setsid()
197 try:
198 pid = os.fork()
199 except OSError, e:
200 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
201
202 if (pid == 0):
203 os.chdir(WORKDIR)
204 os.umask(UMASK)
205 else:
206 os._exit(0)
207 else:
208 os._exit(0)
209
210
211 for fd in range(0, MAXFD):
212 try:
213 os.close(fd)
214 except OSError:
215 pass
216
217 os.open(REDIRECT_TO, os.O_RDWR)
218
219 os.dup2(0, 1)
220 os.dup2(0, 2)
221
222
223 - def sigTerm(self, signum=None, frame=None):
238
239
241 """
242 Return our cycle time (in minutes)
243
244 @return: cycle time
245 @rtype: integer
246 """
247
248 default = 1200
249 cycleTime = getattr(self.options, 'cycleTime', default)
250 if not cycleTime:
251 cycleTime = default
252 return cycleTime
253
267
268
270 """
271 Return our watchdog max restart time (in minutes)
272
273 @return: maximum restart time
274 @rtype: integer
275 """
276 default = 600
277 maxTime = getattr(self.options, 'maxRestartTime', default)
278 if not maxTime:
279 maxTime = default
280 return default
281
282
284 """
285 Watch the specified daemon and restart it if necessary.
286 """
287 from Products.ZenUtils.Watchdog import Watcher, log
288 log.setLevel(self.options.logseverity)
289 cmd = sys.argv[:]
290 if '--watchdog' in cmd:
291 cmd.remove('--watchdog')
292 if '--daemon' in cmd:
293 cmd.remove('--daemon')
294
295 socketPath = '%s/.%s-watchdog-%d' % (
296 zenPath('var'), self.__class__.__name__, os.getpid())
297
298 cycleTime = self.watchdogCycleTime()
299 startTimeout = self.watchdogStartTimeout()
300 maxTime = self.watchdogMaxRestartTime()
301 self.log.debug("Watchdog cycleTime=%d startTimeout=%d maxTime=%d",
302 cycleTime, startTimeout, maxTime)
303
304 watchdog = Watcher(socketPath,
305 cmd,
306 startTimeout,
307 cycleTime,
308 maxTime)
309 watchdog.run()
310 sys.exit(0)
311
313
314
315
316 if not self.reporter and self.options.watchdogPath:
317 from Watchdog import Reporter
318 self.reporter = Reporter(self.options.watchdogPath)
319 if self.reporter:
320 self.reporter.niceDoggie(timeout)
321
323 """
324 Standard set of command-line options.
325 """
326 CmdBase.buildOptions(self)
327 self.parser.add_option('--uid',dest='uid',default="zenoss",
328 help='User to become when running default:zenoss')
329 self.parser.add_option('-c', '--cycle',dest='cycle',
330 action="store_true", default=False,
331 help="Cycle continuously on cycleInterval from Zope")
332 self.parser.add_option('-D', '--daemon', default=False,
333 dest='daemon',action="store_true",
334 help="Launch into the background")
335 self.parser.add_option('--weblog', default=False,
336 dest='weblog',action="store_true",
337 help="output log info in HTML table format")
338 self.parser.add_option('--watchdog', default=False,
339 dest='watchdog', action="store_true",
340 help="Run under a supervisor which will restart it")
341 self.parser.add_option('--watchdogPath', default=None,
342 dest='watchdogPath',
343 help="The path to the watchdog reporting socket")
344 self.parser.add_option('--starttimeout',
345 dest='starttimeout',
346 type="int",
347 help="Wait seconds for initial heartbeat")
348