Back to index

nagios-plugins  1.4.16
utils.py
Go to the documentation of this file.
00001 #
00002 #
00003 # Util classes for Nagios plugins
00004 #
00005 #
00006 
00007 
00008 
00009 #==========================================================================
00010 #
00011 # Version: = '$Id: utils.py 2 2002-02-28 06:42:51Z egalstad $'
00012 #
00013 # (C) Rob W.W. Hooft, Nonius BV, 1998
00014 #
00015 # Contact r.hooft@euromail.net for questions/suggestions.
00016 # See: <http://starship.python.net/crew/hooft/>
00017 # Distribute freely.
00018 #
00019 # jaclu@galdrion.com 2000-07-14
00020 #   Some changes in error handling of Run() to avoid error garbage
00021 #   when used from Nagios plugins
00022 #   I also removed the following functions: AbortableWait() and _buttonkill() 
00023 #   since they are only usable with Tkinter
00024 
00025 import sys,os,signal,time,string
00026 
00027 class error(Exception):
00028     pass
00029 
00030 class _ready(Exception):
00031     pass
00032 
00033 def which(filename):
00034     """Find the file 'filename' in the execution path. If no executable
00035        file is found, return None"""
00036     for dir in string.split(os.environ['PATH'],os.pathsep):
00037         fn=os.path.join(dir,filename)
00038         if os.path.exists(fn):
00039             if os.stat(fn)[0]&0111:
00040                 return fn
00041     else:
00042         return None
00043     
00044 class Task:
00045     """Manage asynchronous subprocess tasks.
00046        This differs from the 'subproc' package!
00047         - 'subproc' connects to the subprocess via pipes
00048         - 'task' lets the subprocess run autonomously.
00049        After starting the task, we can just:
00050         - ask whether it is finished yet
00051         - wait until it is finished
00052         - perform an 'idle' task (e.g. Tkinter's mainloop) while waiting for
00053           subprocess termination
00054         - kill the subprocess with a specific signal
00055         - ask for the exit code.
00056        Summarizing:
00057         - 'subproc' is a sophisticated os.popen()
00058         - 'task' is a sophisticated os.system()
00059        Another difference of task with 'subproc':
00060         - If the Task() object is deleted, before the subprocess status
00061           was retrieved, the child process will stay.
00062           It will never be waited for (i.e., the process will turn into
00063           a zombie. Not a good idea in general).
00064 
00065        Public data:
00066            None.
00067 
00068        Public methods:
00069            __init__, __str__, Run, Wait, Kill, Done, Status.
00070     """
00071     def __init__(self,command):
00072         """Constructor.
00073            arguments:
00074                command: the command to run, in the form of a string,
00075                         or a tuple or list of words.
00076            """
00077         if type(command)==type(''):
00078             self.cmd=command
00079             self.words=string.split(command)
00080         elif type(command)==type([]) or type(command)==type(()):
00081             # Surround each word by ' '. Limitation: words cannot contain ' chars
00082             self.cmd="'"+string.join(command,"' '")+"'"
00083             self.words=tuple(command)
00084         else:
00085             raise error("command must be tuple, list, or string")
00086         self.pid=None
00087         self.status=None
00088 
00089     def Run(self,usesh=0,detach=0,stdout=None,stdin=None,stderr=None):
00090         """Actually run the process.
00091            This method should be called exactly once.
00092            optional arguments:
00093                usesh=0: if 1, run 'sh -c command', if 0, split the
00094                         command into words, and run it by ourselves.
00095                         If usesh=1, the 'Kill' method might not do what
00096                         you want (it will kill the 'sh' process, not the
00097                         command).
00098                detach=0: if 1, run 'sh -c 'command&' (regardless of
00099                          'usesh'). Since the 'sh' process will immediately
00100                          terminate, the task created will be inherited by
00101                          'init', so you can safely forget it.  Remember that if
00102                          detach=1, Kill(), Done() and Status() will manipulate
00103                          the 'sh' process; there is no way to find out about the
00104                          detached process.
00105                stdout=None: filename to use as stdout for the child process.
00106                             If None, the stdout of the parent will be used.
00107                stdin= None: filename to use as stdin for the child process.
00108                             If None, the stdin of the parent will be used.
00109                stderr=None: filename to use as stderr for the child process.
00110                             If None, the stderr of the parent will be used.
00111            return value:                            
00112                None
00113         """
00114         if self.pid!=None:
00115             raise error("Second run on task forbidden")
00116         self.pid=os.fork()
00117         if not self.pid:
00118             for fn in range(3,256): # Close all non-standard files in a safe way
00119                 try:
00120                     os.close(fn)
00121                 except os.error:
00122                     pass
00123            #
00124            # jaclu@galdrion.com 2000-07-14
00125            #
00126            # I changed this bit somewhat, since Nagios plugins
00127            # should send only limited errors to the caller
00128            # The original setup here corupted output when there was an error.
00129            # Instead the caller should check result of Wait() and anything
00130            # not zero should be reported as a failure.
00131            #
00132            try:
00133               if stdout: # Replace stdout by file
00134                   os.close(1)
00135                   i=os.open(stdout,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666)
00136                   if i!=1:
00137                      sys.stderr.write("stdout not opened on 1!\n")
00138               if stdin: # Replace stdin by file
00139                   os.close(0)
00140                   i=os.open(stdin,os.O_RDONLY)
00141                   if i!=0:
00142                      sys.stderr.write("stdin not opened on 0!\n")
00143               if stderr: # Replace stderr by file
00144                   os.close(2)
00145                   i=os.open(stderr,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666)
00146                   if i!=2:
00147                      sys.stdout.write("stderr not opened on 2!\n")
00148               #try:
00149                 if detach:
00150                     os.execv('/bin/sh',('sh','-c',self.cmd+'&'))
00151                 elif usesh:
00152                     os.execv('/bin/sh',('sh','-c',self.cmd))
00153                 else:
00154                     os.execvp(self.words[0],self.words)
00155             except:
00156                 #print self.words
00157                 #sys.stderr.write("Subprocess '%s' execution failed!\n"%self.cmd)
00158                 sys.exit(1)
00159         else:
00160             # Mother process
00161             if detach:
00162                 # Should complete "immediately"
00163                 self.Wait()
00164 
00165     def Wait(self,idlefunc=None,interval=0.1):
00166         """Wait for the subprocess to terminate.
00167            If the process has already terminated, this function will return
00168            immediately without raising an error.
00169            optional arguments:
00170                idlefunc=None: a callable object (function, class, bound method)
00171                               that will be called every 0.1 second (or see
00172                               the 'interval' variable) while waiting for
00173                               the subprocess to terminate. This can be the
00174                               Tkinter 'update' procedure, such that the GUI
00175                               doesn't die during the run. If this is set to
00176                               'None', the process will really wait. idlefunc
00177                               should ideally not take a very long time to
00178                               complete...
00179                interval=0.1: The interval (in seconds) with which the 'idlefunc'
00180                              (if any) will be called.
00181            return value:
00182                the exit status of the subprocess (0 if successful).
00183         """
00184         if self.status!=None:
00185             # Already finished
00186             return self.status
00187         if callable(idlefunc):
00188             while 1:
00189                 try:
00190                     pid,status=os.waitpid(self.pid,os.WNOHANG)
00191                     if pid==self.pid:
00192                         self.status=status
00193                         return status
00194                     else:
00195                         idlefunc()
00196                         time.sleep(interval)
00197                 except KeyboardInterrupt:
00198                     # Send the interrupt to the inferior process.
00199                     self.Kill(signal=signal.SIGINT)
00200         elif idlefunc:
00201             raise error("Non-callable idle function")
00202         else:
00203             while 1:
00204                 try:
00205                     pid,status=os.waitpid(self.pid,0)
00206                     self.status=status
00207                     return status
00208                 except KeyboardInterrupt:
00209                     # Send the interrupt to the inferior process.
00210                     self.Kill(signal=signal.SIGINT)
00211 
00212     def Kill(self,signal=signal.SIGTERM):
00213         """Send a signal to the running subprocess.
00214            optional arguments:
00215                signal=SIGTERM: number of the signal to send.
00216                                (see os.kill)
00217            return value:
00218                see os.kill()
00219         """
00220         if self.status==None:
00221             # Only if it is not already finished
00222             return os.kill(self.pid,signal)
00223 
00224     def Done(self):
00225         """Ask whether the process has already finished.
00226            return value:
00227                1: yes, the process has finished.
00228                0: no, the process has not finished yet.
00229         """
00230         if self.status!=None:
00231             return 1
00232         else:
00233             pid,status=os.waitpid(self.pid,os.WNOHANG)
00234             if pid==self.pid:
00235                 #print "OK:",pid,status
00236                 self.status=status
00237                 return 1
00238             else:
00239                 #print "NOK:",pid,status
00240                 return 0
00241 
00242     def Status(self):
00243         """Ask for the status of the task.
00244            return value:
00245                None: process has not finished yet (maybe not even started).
00246                any integer: process exit status.
00247         """
00248         self.Done()
00249         return self.status
00250 
00251     def __str__(self):
00252         if self.pid!=None:
00253             if self.status!=None:
00254                 s2="done, exit status=%d"%self.status
00255             else:
00256                 s2="running"
00257         else:
00258             s2="prepared"
00259         return "<%s: '%s', %s>"%(self.__class__.__name__,self.cmd,s2)
00260 
00261 
00262 #==========================================================================
00263 #
00264 #
00265 # Class: TimeoutHandler
00266 # License: GPL
00267 # Copyright (c)  2000 Jacob Lundqvist (jaclu@galdrion.com)
00268 #
00269 # Version: 1.0  2000-07-14
00270 #
00271 # Description:
00272 #  On init, suply a call-back kill_func that should be called on timeout
00273 #
00274 #  Make sure that what ever you are doing is calling Check periodically
00275 # 
00276 #  To check if timeout was triggered call WasTimeOut returns (true/false)
00277 #
00278 
00279 import time,sys
00280 
00281 class TimeoutHandler:
00282     def __init__(self,kill_func,time_to_live=10,debug=0):
00283        'Generic time-out handler.'
00284        self.kill_func=kill_func
00285        self.start_time=time.time()
00286        self.stop_time=+self.start_time+int(time_to_live)
00287        self.debug=debug
00288        self.aborted=0
00289     
00290     def Check(self):
00291        'Call this periodically to check for time-out.'
00292        if self.debug:
00293            sys.stdout.write('.')
00294            sys.stdout.flush()
00295        if time.time()>=self.stop_time:
00296            self.TimeOut()
00297            
00298     def TimeOut(self):
00299        'Trigger the time-out callback.'
00300        self.aborted=1
00301        if self.debug:
00302            print 'Timeout, aborting'
00303        self.kill_func()
00304        
00305     def WasTimeOut(self):
00306        'Indicates if timeout was triggered 1=yes, 0=no.'
00307        if self.debug:
00308            print ''
00309            print 'call duration: %.2f seconds' % (time.time()-self.start_time)
00310        return self.aborted