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 logging
24 from logging import handlers
25
26 from CmdBase import CmdBase
27 from Utils import zenPath, HtmlFormatter, binPath
28
29
30
31 UMASK = 0022
32
33 WORKDIR = "/"
34
35
36 MAXFD = 3
37
38
39 if (hasattr(os, "devnull")):
40 REDIRECT_TO = os.devnull
41 else:
42 REDIRECT_TO = "/dev/null"
43
44
46 """
47 Base class for creating daemons
48 """
49
50 pidfile = None
51
52 - def __init__(self, noopts=0, keeproot=False):
53 """
54 Initializer that takes care of basic daemon options.
55 Creates a PID file.
56 """
57 CmdBase.__init__(self, noopts)
58 self.pidfile = None
59 self.keeproot=keeproot
60 self.reporter = None
61 from twisted.internet import reactor
62 reactor.addSystemEventTrigger('before', 'shutdown', self.sigTerm)
63 if not noopts:
64 if self.options.daemon:
65 self.changeUser()
66 self.becomeDaemon()
67 if self.options.daemon or self.options.watchdogPath:
68 try:
69 self.writePidFile()
70 except OSError:
71 msg= "ERROR: unable to open PID file %s" % \
72 (self.pidfile or '(unknown)')
73 raise SystemExit(msg)
74
75 if self.options.watchdog and not self.options.watchdogPath:
76 self.becomeWatchdog()
77
78
80 """Execute under zensocket, providing the args to zensocket"""
81 zensocket = binPath('zensocket')
82 cmd = [zensocket, zensocket] + list(address) + ['--'] + \
83 [sys.executable] + sys.argv + \
84 ['--useFileDescriptor=$privilegedSocket']
85 os.execlp(*cmd)
86
87
89 """
90 Write the PID file to disk
91 """
92 myname = sys.argv[0].split(os.sep)[-1]
93 if myname.endswith('.py'): myname = myname[:-3]
94 monitor = getattr(self.options, 'monitor', 'localhost')
95 myname = "%s-%s.pid" % (myname, monitor)
96 if self.options.watchdog and not self.options.watchdogPath:
97 self.pidfile = zenPath("var", 'watchdog-%s' % myname)
98 else:
99 self.pidfile = zenPath("var", myname)
100 fp = open(self.pidfile, 'w')
101 fp.write(str(os.getpid()))
102 fp.close()
103
105 """
106 Create formating for log entries and set default log level
107 """
108 rlog = logging.getLogger()
109 rlog.setLevel(logging.WARN)
110 if hasattr(self, 'mname'): mname = self.mname
111 else: mname = self.__class__.__name__
112 self.log = logging.getLogger("zen."+ mname)
113 zlog = logging.getLogger("zen")
114 zlog.setLevel(self.options.logseverity)
115 if self.options.watchdogPath or \
116 self.options.daemon:
117 logdir = self.checkLogpath()
118 if not logdir:
119 logdir = zenPath("log")
120 logfile = os.path.join(logdir, mname.lower()+".log")
121 maxBytes = self.options.maxLogKiloBytes * 1024
122 backupCount = self.options.maxBackupLogs
123 h = logging.handlers.RotatingFileHandler(filename=logfile, maxBytes=maxBytes, backupCount=backupCount)
124 h.setFormatter(logging.Formatter(
125 "%(asctime)s %(levelname)s %(name)s: %(message)s",
126 "%Y-%m-%d %H:%M:%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
133
135 """
136 Switch identity to the appropriate Unix user
137 """
138 if not self.keeproot:
139 try:
140 cname = pwd.getpwuid(os.getuid())[0]
141 pwrec = pwd.getpwnam(self.options.uid)
142 os.setuid(pwrec.pw_uid)
143 os.environ['HOME'] = pwrec.pw_dir
144 except (KeyError, OSError):
145 print >>sys.stderr, "WARN: user:%s not found running as:%s"%(
146 self.options.uid,cname)
147
148
150 """Code below comes from the excellent recipe by Chad J. Schroeder.
151 """
152 try:
153 pid = os.fork()
154 except OSError, e:
155 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
156
157 if (pid == 0):
158 os.setsid()
159 try:
160 pid = os.fork()
161 except OSError, e:
162 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
163
164 if (pid == 0):
165 os.chdir(WORKDIR)
166 os.umask(UMASK)
167 else:
168 os._exit(0)
169 else:
170 os._exit(0)
171
172
173 for fd in range(0, MAXFD):
174 try:
175 os.close(fd)
176 except OSError:
177 pass
178
179 os.open(REDIRECT_TO, os.O_RDWR)
180
181 os.dup2(0, 1)
182 os.dup2(0, 2)
183
184
185 - def sigTerm(self, signum=None, frame=None):
200
201
203 """
204 Watch the specified daemon and restart it if necessary.
205 """
206 from Products.ZenUtils.Watchdog import Watcher, log
207 log.setLevel(self.options.logseverity)
208 cmd = sys.argv[:]
209 if '--watchdog' in cmd:
210 cmd.remove('--watchdog')
211 if '--daemon' in cmd:
212 cmd.remove('--daemon')
213
214 socketPath = '%s/.%s-watchdog-%d' % (
215 zenPath('var'), self.__class__.__name__, os.getpid())
216
217
218 cycleTime = getattr(self.options, 'cycleTime', 1200)
219
220 startTimeout = getattr(self.options, 'starttimeout', cycleTime + 120)
221 maxTime = getattr(self.options, 'maxRestartTime', 600)
222 watchdog = Watcher(socketPath,
223 cmd,
224 startTimeout,
225 cycleTime,
226 maxTime)
227 watchdog.run()
228 sys.exit(0)
229
231
232
233
234 if not self.reporter and self.options.watchdogPath:
235 from Watchdog import Reporter
236 self.reporter = Reporter(self.options.watchdogPath)
237 if self.reporter:
238 self.reporter.niceDoggie(timeout)
239
241 """
242 Standard set of command-line options.
243 """
244 CmdBase.buildOptions(self)
245 self.parser.add_option('--uid',dest='uid',default="zenoss",
246 help='User to become when running default:zenoss')
247 self.parser.add_option('-c', '--cycle',dest='cycle',
248 action="store_true", default=False,
249 help="Cycle continuously on cycleInterval from Zope")
250 self.parser.add_option('-D', '--daemon', default=False,
251 dest='daemon',action="store_true",
252 help="Launch into the background")
253 self.parser.add_option('--weblog', default=False,
254 dest='weblog',action="store_true",
255 help="output log info in HTML table format")
256 self.parser.add_option('--watchdog', default=False,
257 dest='watchdog', action="store_true",
258 help="Run under a supervisor which will restart it")
259 self.parser.add_option('--watchdogPath', default=None,
260 dest='watchdogPath',
261 help="The path to the watchdog reporting socket")
262 self.parser.add_option('--starttimeout',
263 dest='starttimeout',
264 type="int",
265 help="Wait seconds for initial heartbeat")
266