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 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
138 - def changeUser(self):
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
153 - def becomeDaemon(self):
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): # The first child. 162 os.setsid() 163 try: 164 pid = os.fork() # Fork a second child. 165 except OSError, e: 166 raise Exception( "%s [%d]" % (e.strerror, e.errno) ) 167 168 if (pid == 0): # The second child. 169 os.chdir(WORKDIR) 170 os.umask(UMASK) 171 else: 172 os._exit(0) # Exit parent (the first child) of the second child. 173 else: 174 os._exit(0) # Exit parent of the first child. 175 176 # Iterate through and close all stdin/out/err 177 for fd in range(0, MAXFD): 178 try: 179 os.close(fd) 180 except OSError: # ERROR, fd wasn't open to begin with (ignored) 181 pass 182 183 os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) 184 # Duplicate standard input to standard output and standard error. 185 os.dup2(0, 1) # standard output (1) 186 os.dup2(0, 2) # standard error (2)
187 188
189 - def sigTerm(self, signum=None, frame=None):
190 """ 191 Signal handler for the SIGTERM signal. 192 """ 193 # This probably won't be called when running as daemon. 194 # See ticket #1757 195 from Products.ZenUtils.Utils import unused 196 unused(signum, frame) 197 stop = getattr(self, "stop", None) 198 if callable(stop): stop() 199 if self.pidfile and os.path.exists(self.pidfile): 200 self.log.info("Deleting PID file %s ...", self.pidfile) 201 os.remove(self.pidfile) 202 self.log.info('Daemon %s shutting down' % self.__class__.__name__) 203 raise SystemExit
204 205
206 - def becomeWatchdog(self):
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 # time between child reports: default to 2x the default cycle time 222 cycleTime = getattr(self.options, 'cycleTime', 1200) 223 # Default start timeout should be cycle time plus a couple of minutes 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
234 - def niceDoggie(self, timeout):
235 # defer creation of the reporter until we know we're not going 236 # through zensocket or other startup that results in closing 237 # this socket 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
244 - def buildOptions(self):
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