Package Products :: Package ZenUtils :: Module ZenDaemon
[hide private]
[frames] | no frames]

Source Code for Module Products.ZenUtils.ZenDaemon

  1  ########################################################################### 
  2  # 
  3  # This program is part of Zenoss Core, an open source monitoring platform. 
  4  # Copyright (C) 2007, Zenoss Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify it 
  7  # under the terms of the GNU General Public License version 2 as published by 
  8  # the Free Software Foundation. 
  9  # 
 10  # For complete information please visit: http://www.zenoss.com/oss/ 
 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  # Daemon creation code below based on Recipe by Chad J. Schroeder 
 31  # File mode creation mask of the daemon. 
 32  UMASK = 0022 
 33  # Default working directory for the daemon. 
 34  WORKDIR = "/" 
 35   
 36  # only close stdin/out/err 
 37  MAXFD = 3  
 38   
 39  # The standard I/O file descriptors are redirected to /dev/null by default. 
 40  if (hasattr(os, "devnull")): 
 41     REDIRECT_TO = os.devnull 
 42  else: 
 43     REDIRECT_TO = "/dev/null" 
 44   
 45   
46 -class ZenDaemon(CmdBase):
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
80 - def openPrivilegedPort(self, *address):
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
89 - def writePidFile(self):
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
105 - def setupLogging(self):
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 # Allow the user to dynamically lower and raise the logging 137 # level without restarts. 138 import signal 139 try: 140 signal.signal(signal.SIGUSR1, self.sighandler_USR1) 141 except ValueError: 142 # If we get called multiple times, this will generate an exception: 143 # ValueError: signal only works in main thread 144 # Ignore it as we've already set up the signal handler. 145 pass
146
147 - def sighandler_USR1(self, signum, frame):
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
172 - def changeUser(self):
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
187 - def becomeDaemon(self):
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): # The first child. 196 os.setsid() 197 try: 198 pid = os.fork() # Fork a second child. 199 except OSError, e: 200 raise Exception( "%s [%d]" % (e.strerror, e.errno) ) 201 202 if (pid == 0): # The second child. 203 os.chdir(WORKDIR) 204 os.umask(UMASK) 205 else: 206 os._exit(0) # Exit parent (the first child) of the second child. 207 else: 208 os._exit(0) # Exit parent of the first child. 209 210 # Iterate through and close all stdin/out/err 211 for fd in range(0, MAXFD): 212 try: 213 os.close(fd) 214 except OSError: # ERROR, fd wasn't open to begin with (ignored) 215 pass 216 217 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) 218 # Duplicate standard input to standard output and standard error. 219 os.dup2(0, 1) # standard output (1) 220 os.dup2(0, 2) # standard error (2)
221 222
223 - def sigTerm(self, signum=None, frame=None):
224 """ 225 Signal handler for the SIGTERM signal. 226 """ 227 # This probably won't be called when running as daemon. 228 # See ticket #1757 229 from Products.ZenUtils.Utils import unused 230 unused(signum, frame) 231 stop = getattr(self, "stop", None) 232 if callable(stop): stop() 233 if self.pidfile and os.path.exists(self.pidfile): 234 self.log.info("Deleting PID file %s ...", self.pidfile) 235 os.remove(self.pidfile) 236 self.log.info('Daemon %s shutting down' % self.__class__.__name__) 237 raise SystemExit
238 239
240 - def watchdogCycleTime(self):
241 """ 242 Return our cycle time (in minutes) 243 244 @return: cycle time 245 @rtype: integer 246 """ 247 # time between child reports: default to 2x the default cycle time 248 default = 1200 249 cycleTime = getattr(self.options, 'cycleTime', default) 250 if not cycleTime: 251 cycleTime = default 252 return cycleTime
253
254 - def watchdogStartTimeout(self):
255 """ 256 Return our watchdog start timeout (in minutes) 257 258 @return: start timeout 259 @rtype: integer 260 """ 261 # Default start timeout should be cycle time plus a couple of minutes 262 default = self.watchdogCycleTime() + 120 263 startTimeout = getattr(self.options, 'starttimeout', default) 264 if not startTimeout: 265 startTimeout = default 266 return startTimeout
267 268
269 - def watchdogMaxRestartTime(self):
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
283 - def becomeWatchdog(self):
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
312 - def niceDoggie(self, timeout):
313 # defer creation of the reporter until we know we're not going 314 # through zensocket or other startup that results in closing 315 # this socket 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
322 - def buildOptions(self):
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