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 if self.options.logpath:
118 if not os.path.isdir(os.path.dirname(self.options.logpath)):
119 raise SystemExit("logpath:%s doesn't exist" %
120 self.options.logpath)
121 logdir = self.options.logpath
122 else:
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 "%Y-%m-%d %H:%M:%S"))
131 rlog.addHandler(h)
132 else:
133 logging.basicConfig()
134 if self.options.weblog:
135 [ h.setFormatter(HtmlFormatter()) for h in rlog.handlers ]
136
137
139 """
140 Switch identity to the appropriate Unix user
141 """
142 if not self.keeproot:
143 try:
144 cname = pwd.getpwuid(os.getuid())[0]
145 pwrec = pwd.getpwnam(self.options.uid)
146 os.setuid(pwrec.pw_uid)
147 os.environ['HOME'] = pwrec.pw_dir
148 except (KeyError, OSError):
149 print >>sys.stderr, "WARN: user:%s not found running as:%s"%(
150 self.options.uid,cname)
151
152
154 """Code below comes from the excellent recipe by Chad J. Schroeder.
155 """
156 try:
157 pid = os.fork()
158 except OSError, e:
159 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
160
161 if (pid == 0):
162 os.setsid()
163 try:
164 pid = os.fork()
165 except OSError, e:
166 raise Exception( "%s [%d]" % (e.strerror, e.errno) )
167
168 if (pid == 0):
169 os.chdir(WORKDIR)
170 os.umask(UMASK)
171 else:
172 os._exit(0)
173 else:
174 os._exit(0)
175
176
177 for fd in range(0, MAXFD):
178 try:
179 os.close(fd)
180 except OSError:
181 pass
182
183 os.open(REDIRECT_TO, os.O_RDWR)
184
185 os.dup2(0, 1)
186 os.dup2(0, 2)
187
188
189 - def sigTerm(self, signum=None, frame=None):
204
205
207 """
208 Watch the specified daemon and restart it if necessary.
209 """
210 from Products.ZenUtils.Watchdog import Watcher, log
211 log.setLevel(self.options.logseverity)
212 cmd = sys.argv[:]
213 if '--watchdog' in cmd:
214 cmd.remove('--watchdog')
215 if '--daemon' in cmd:
216 cmd.remove('--daemon')
217
218 socketPath = '%s/.%s-watchdog-%d' % (
219 zenPath('var'), self.__class__.__name__, os.getpid())
220
221
222 cycleTime = getattr(self.options, 'cycleTime', 1200)
223
224 startTimeout = getattr(self.options, 'starttimeout', cycleTime + 120)
225 maxTime = getattr(self.options, 'maxRestartTime', 600)
226 watchdog = Watcher(socketPath,
227 cmd,
228 startTimeout,
229 cycleTime,
230 maxTime)
231 watchdog.run()
232 sys.exit(0)
233
235
236
237
238 if not self.reporter and self.options.watchdogPath:
239 from Watchdog import Reporter
240 self.reporter = Reporter(self.options.watchdogPath)
241 if self.reporter:
242 self.reporter.niceDoggie(timeout)
243
245 """
246 Standard set of command-line options.
247 """
248 CmdBase.buildOptions(self)
249 self.parser.add_option('--uid',dest='uid',default="zenoss",
250 help='User to become when running default:zenoss')
251 self.parser.add_option('-c', '--cycle',dest='cycle',
252 action="store_true", default=False,
253 help="Cycle continuously on cycleInterval from Zope")
254 self.parser.add_option('-D', '--daemon', default=False,
255 dest='daemon',action="store_true",
256 help="Launch into the background")
257 self.parser.add_option('--weblog', default=False,
258 dest='weblog',action="store_true",
259 help="output log info in HTML table format")
260 self.parser.add_option('--watchdog', default=False,
261 dest='watchdog', action="store_true",
262 help="Run under a supervisor which will restart it")
263 self.parser.add_option('--watchdogPath', default=None,
264 dest='watchdogPath',
265 help="The path to the watchdog reporting socket")
266 self.parser.add_option('--starttimeout',
267 dest='starttimeout',
268 type="int",
269 help="Wait seconds for initial heartbeat")
270