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

Source Code for Module 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 logging 
 24  from logging import handlers 
 25   
 26  from CmdBase import CmdBase 
 27  from Utils import zenPath, HtmlFormatter, binPath 
 28   
 29  # Daemon creation code below based on Recipe by Chad J. Schroeder 
 30  # File mode creation mask of the daemon. 
 31  UMASK = 0022 
 32  # Default working directory for the daemon. 
 33  WORKDIR = "/" 
 34   
 35  # only close stdin/out/err 
 36  MAXFD = 3  
 37   
 38  # The standard I/O file descriptors are redirected to /dev/null by default. 
 39  if (hasattr(os, "devnull")): 
 40     REDIRECT_TO = os.devnull 
 41  else: 
 42     REDIRECT_TO = "/dev/null" 
 43   
 44   
45 -class ZenDaemon(CmdBase):
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
79 - def openPrivilegedPort(self, *address):
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
88 - def writePidFile(self):
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
104 - def setupLogging(self):
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
134 - def changeUser(self):
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
149 - def becomeDaemon(self):
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): # The first child. 158 os.setsid() 159 try: 160 pid = os.fork() # Fork a second child. 161 except OSError, e: 162 raise Exception( "%s [%d]" % (e.strerror, e.errno) ) 163 164 if (pid == 0): # The second child. 165 os.chdir(WORKDIR) 166 os.umask(UMASK) 167 else: 168 os._exit(0) # Exit parent (the first child) of the second child. 169 else: 170 os._exit(0) # Exit parent of the first child. 171 172 # Iterate through and close all stdin/out/err 173 for fd in range(0, MAXFD): 174 try: 175 os.close(fd) 176 except OSError: # ERROR, fd wasn't open to begin with (ignored) 177 pass 178 179 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) 180 # Duplicate standard input to standard output and standard error. 181 os.dup2(0, 1) # standard output (1) 182 os.dup2(0, 2) # standard error (2)
183 184
185 - def sigTerm(self, signum=None, frame=None):
186 """ 187 Signal handler for the SIGTERM signal. 188 """ 189 # This probably won't be called when running as daemon. 190 # See ticket #1757 191 from Products.ZenUtils.Utils import unused 192 unused(signum, frame) 193 stop = getattr(self, "stop", None) 194 if callable(stop): stop() 195 if self.pidfile and os.path.exists(self.pidfile): 196 self.log.info("Deleting PID file %s ...", self.pidfile) 197 os.remove(self.pidfile) 198 self.log.info('Daemon %s shutting down' % self.__class__.__name__) 199 raise SystemExit
200 201
202 - def becomeWatchdog(self):
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 # time between child reports: default to 2x the default cycle time 218 cycleTime = getattr(self.options, 'cycleTime', 1200) 219 # Default start timeout should be cycle time plus a couple of minutes 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
230 - def niceDoggie(self, timeout):
231 # defer creation of the reporter until we know we're not going 232 # through zensocket or other startup that results in closing 233 # this socket 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
240 - def buildOptions(self):
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