diff options
Diffstat (limited to 'contrib/utils.py')
| -rw-r--r-- | contrib/utils.py | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/contrib/utils.py b/contrib/utils.py new file mode 100644 index 00000000..73d795c9 --- /dev/null +++ b/contrib/utils.py | |||
| @@ -0,0 +1,310 @@ | |||
| 1 | # | ||
| 2 | # | ||
| 3 | # Util classes for Nagios plugins | ||
| 4 | # | ||
| 5 | # | ||
| 6 | |||
| 7 | |||
| 8 | |||
| 9 | #========================================================================== | ||
| 10 | # | ||
| 11 | # Version: = '$Id$' | ||
| 12 | # | ||
| 13 | # (C) Rob W.W. Hooft, Nonius BV, 1998 | ||
| 14 | # | ||
| 15 | # Contact r.hooft@euromail.net for questions/suggestions. | ||
| 16 | # See: <http://starship.python.net/crew/hooft/> | ||
| 17 | # Distribute freely. | ||
| 18 | # | ||
| 19 | # jaclu@galdrion.com 2000-07-14 | ||
| 20 | # Some changes in error handling of Run() to avoid error garbage | ||
| 21 | # when used from Nagios plugins | ||
| 22 | # I also removed the following functions: AbortableWait() and _buttonkill() | ||
| 23 | # since they are only usable with Tkinter | ||
| 24 | |||
| 25 | import sys,os,signal,time,string | ||
| 26 | |||
| 27 | class error(Exception): | ||
| 28 | pass | ||
| 29 | |||
| 30 | class _ready(Exception): | ||
| 31 | pass | ||
| 32 | |||
| 33 | def which(filename): | ||
| 34 | """Find the file 'filename' in the execution path. If no executable | ||
| 35 | file is found, return None""" | ||
| 36 | for dir in string.split(os.environ['PATH'],os.pathsep): | ||
| 37 | fn=os.path.join(dir,filename) | ||
| 38 | if os.path.exists(fn): | ||
| 39 | if os.stat(fn)[0]&0111: | ||
| 40 | return fn | ||
| 41 | else: | ||
| 42 | return None | ||
| 43 | |||
| 44 | class Task: | ||
| 45 | """Manage asynchronous subprocess tasks. | ||
| 46 | This differs from the 'subproc' package! | ||
| 47 | - 'subproc' connects to the subprocess via pipes | ||
| 48 | - 'task' lets the subprocess run autonomously. | ||
| 49 | After starting the task, we can just: | ||
| 50 | - ask whether it is finished yet | ||
| 51 | - wait until it is finished | ||
| 52 | - perform an 'idle' task (e.g. Tkinter's mainloop) while waiting for | ||
| 53 | subprocess termination | ||
| 54 | - kill the subprocess with a specific signal | ||
| 55 | - ask for the exit code. | ||
| 56 | Summarizing: | ||
| 57 | - 'subproc' is a sophisticated os.popen() | ||
| 58 | - 'task' is a sophisticated os.system() | ||
| 59 | Another difference of task with 'subproc': | ||
| 60 | - If the Task() object is deleted, before the subprocess status | ||
| 61 | was retrieved, the child process will stay. | ||
| 62 | It will never be waited for (i.e., the process will turn into | ||
| 63 | a zombie. Not a good idea in general). | ||
| 64 | |||
| 65 | Public data: | ||
| 66 | None. | ||
| 67 | |||
| 68 | Public methods: | ||
| 69 | __init__, __str__, Run, Wait, Kill, Done, Status. | ||
| 70 | """ | ||
| 71 | def __init__(self,command): | ||
| 72 | """Constructor. | ||
| 73 | arguments: | ||
| 74 | command: the command to run, in the form of a string, | ||
| 75 | or a tuple or list of words. | ||
| 76 | """ | ||
| 77 | if type(command)==type(''): | ||
| 78 | self.cmd=command | ||
| 79 | self.words=string.split(command) | ||
| 80 | elif type(command)==type([]) or type(command)==type(()): | ||
| 81 | # Surround each word by ' '. Limitation: words cannot contain ' chars | ||
| 82 | self.cmd="'"+string.join(command,"' '")+"'" | ||
| 83 | self.words=tuple(command) | ||
| 84 | else: | ||
| 85 | raise error("command must be tuple, list, or string") | ||
| 86 | self.pid=None | ||
| 87 | self.status=None | ||
| 88 | |||
| 89 | def Run(self,usesh=0,detach=0,stdout=None,stdin=None,stderr=None): | ||
| 90 | """Actually run the process. | ||
| 91 | This method should be called exactly once. | ||
| 92 | optional arguments: | ||
| 93 | usesh=0: if 1, run 'sh -c command', if 0, split the | ||
| 94 | command into words, and run it by ourselves. | ||
| 95 | If usesh=1, the 'Kill' method might not do what | ||
| 96 | you want (it will kill the 'sh' process, not the | ||
| 97 | command). | ||
| 98 | detach=0: if 1, run 'sh -c 'command&' (regardless of | ||
| 99 | 'usesh'). Since the 'sh' process will immediately | ||
| 100 | terminate, the task created will be inherited by | ||
| 101 | 'init', so you can safely forget it. Remember that if | ||
| 102 | detach=1, Kill(), Done() and Status() will manipulate | ||
| 103 | the 'sh' process; there is no way to find out about the | ||
| 104 | detached process. | ||
| 105 | stdout=None: filename to use as stdout for the child process. | ||
| 106 | If None, the stdout of the parent will be used. | ||
| 107 | stdin= None: filename to use as stdin for the child process. | ||
| 108 | If None, the stdin of the parent will be used. | ||
| 109 | stderr=None: filename to use as stderr for the child process. | ||
| 110 | If None, the stderr of the parent will be used. | ||
| 111 | return value: | ||
| 112 | None | ||
| 113 | """ | ||
| 114 | if self.pid!=None: | ||
| 115 | raise error("Second run on task forbidden") | ||
| 116 | self.pid=os.fork() | ||
| 117 | if not self.pid: | ||
| 118 | for fn in range(3,256): # Close all non-standard files in a safe way | ||
| 119 | try: | ||
| 120 | os.close(fn) | ||
| 121 | except os.error: | ||
| 122 | pass | ||
| 123 | # | ||
| 124 | # jaclu@galdrion.com 2000-07-14 | ||
| 125 | # | ||
| 126 | # I changed this bit somewhat, since Nagios plugins | ||
| 127 | # should send only limited errors to the caller | ||
| 128 | # The original setup here corupted output when there was an error. | ||
| 129 | # Instead the caller should check result of Wait() and anything | ||
| 130 | # not zero should be reported as a failure. | ||
| 131 | # | ||
| 132 | try: | ||
| 133 | if stdout: # Replace stdout by file | ||
| 134 | os.close(1) | ||
| 135 | i=os.open(stdout,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666) | ||
| 136 | if i!=1: | ||
| 137 | sys.stderr.write("stdout not opened on 1!\n") | ||
| 138 | if stdin: # Replace stdin by file | ||
| 139 | os.close(0) | ||
| 140 | i=os.open(stdin,os.O_RDONLY) | ||
| 141 | if i!=0: | ||
| 142 | sys.stderr.write("stdin not opened on 0!\n") | ||
| 143 | if stderr: # Replace stderr by file | ||
| 144 | os.close(2) | ||
| 145 | i=os.open(stderr,os.O_CREAT|os.O_WRONLY|os.O_TRUNC,0666) | ||
| 146 | if i!=2: | ||
| 147 | sys.stdout.write("stderr not opened on 2!\n") | ||
| 148 | #try: | ||
| 149 | if detach: | ||
| 150 | os.execv('/bin/sh',('sh','-c',self.cmd+'&')) | ||
| 151 | elif usesh: | ||
| 152 | os.execv('/bin/sh',('sh','-c',self.cmd)) | ||
| 153 | else: | ||
| 154 | os.execvp(self.words[0],self.words) | ||
| 155 | except: | ||
| 156 | #print self.words | ||
| 157 | #sys.stderr.write("Subprocess '%s' execution failed!\n"%self.cmd) | ||
| 158 | sys.exit(1) | ||
| 159 | else: | ||
| 160 | # Mother process | ||
| 161 | if detach: | ||
| 162 | # Should complete "immediately" | ||
| 163 | self.Wait() | ||
| 164 | |||
| 165 | def Wait(self,idlefunc=None,interval=0.1): | ||
| 166 | """Wait for the subprocess to terminate. | ||
| 167 | If the process has already terminated, this function will return | ||
| 168 | immediately without raising an error. | ||
| 169 | optional arguments: | ||
| 170 | idlefunc=None: a callable object (function, class, bound method) | ||
| 171 | that will be called every 0.1 second (or see | ||
| 172 | the 'interval' variable) while waiting for | ||
| 173 | the subprocess to terminate. This can be the | ||
| 174 | Tkinter 'update' procedure, such that the GUI | ||
| 175 | doesn't die during the run. If this is set to | ||
| 176 | 'None', the process will really wait. idlefunc | ||
| 177 | should ideally not take a very long time to | ||
| 178 | complete... | ||
| 179 | interval=0.1: The interval (in seconds) with which the 'idlefunc' | ||
| 180 | (if any) will be called. | ||
| 181 | return value: | ||
| 182 | the exit status of the subprocess (0 if successful). | ||
| 183 | """ | ||
| 184 | if self.status!=None: | ||
| 185 | # Already finished | ||
| 186 | return self.status | ||
| 187 | if callable(idlefunc): | ||
| 188 | while 1: | ||
| 189 | try: | ||
| 190 | pid,status=os.waitpid(self.pid,os.WNOHANG) | ||
| 191 | if pid==self.pid: | ||
| 192 | self.status=status | ||
| 193 | return status | ||
| 194 | else: | ||
| 195 | idlefunc() | ||
| 196 | time.sleep(interval) | ||
| 197 | except KeyboardInterrupt: | ||
| 198 | # Send the interrupt to the inferior process. | ||
| 199 | self.Kill(signal=signal.SIGINT) | ||
| 200 | elif idlefunc: | ||
| 201 | raise error("Non-callable idle function") | ||
| 202 | else: | ||
| 203 | while 1: | ||
| 204 | try: | ||
| 205 | pid,status=os.waitpid(self.pid,0) | ||
| 206 | self.status=status | ||
| 207 | return status | ||
| 208 | except KeyboardInterrupt: | ||
| 209 | # Send the interrupt to the inferior process. | ||
| 210 | self.Kill(signal=signal.SIGINT) | ||
| 211 | |||
| 212 | def Kill(self,signal=signal.SIGTERM): | ||
| 213 | """Send a signal to the running subprocess. | ||
| 214 | optional arguments: | ||
| 215 | signal=SIGTERM: number of the signal to send. | ||
| 216 | (see os.kill) | ||
| 217 | return value: | ||
| 218 | see os.kill() | ||
| 219 | """ | ||
| 220 | if self.status==None: | ||
| 221 | # Only if it is not already finished | ||
| 222 | return os.kill(self.pid,signal) | ||
| 223 | |||
| 224 | def Done(self): | ||
| 225 | """Ask whether the process has already finished. | ||
| 226 | return value: | ||
| 227 | 1: yes, the process has finished. | ||
| 228 | 0: no, the process has not finished yet. | ||
| 229 | """ | ||
| 230 | if self.status!=None: | ||
| 231 | return 1 | ||
| 232 | else: | ||
| 233 | pid,status=os.waitpid(self.pid,os.WNOHANG) | ||
| 234 | if pid==self.pid: | ||
| 235 | #print "OK:",pid,status | ||
| 236 | self.status=status | ||
| 237 | return 1 | ||
| 238 | else: | ||
| 239 | #print "NOK:",pid,status | ||
| 240 | return 0 | ||
| 241 | |||
| 242 | def Status(self): | ||
| 243 | """Ask for the status of the task. | ||
| 244 | return value: | ||
| 245 | None: process has not finished yet (maybe not even started). | ||
| 246 | any integer: process exit status. | ||
| 247 | """ | ||
| 248 | self.Done() | ||
| 249 | return self.status | ||
| 250 | |||
| 251 | def __str__(self): | ||
| 252 | if self.pid!=None: | ||
| 253 | if self.status!=None: | ||
| 254 | s2="done, exit status=%d"%self.status | ||
| 255 | else: | ||
| 256 | s2="running" | ||
| 257 | else: | ||
| 258 | s2="prepared" | ||
| 259 | return "<%s: '%s', %s>"%(self.__class__.__name__,self.cmd,s2) | ||
| 260 | |||
| 261 | |||
| 262 | #========================================================================== | ||
| 263 | # | ||
| 264 | # | ||
| 265 | # Class: TimeoutHandler | ||
| 266 | # License: GPL | ||
| 267 | # Copyright (c) 2000 Jacob Lundqvist (jaclu@galdrion.com) | ||
| 268 | # | ||
| 269 | # Version: 1.0 2000-07-14 | ||
| 270 | # | ||
| 271 | # Description: | ||
| 272 | # On init, suply a call-back kill_func that should be called on timeout | ||
| 273 | # | ||
| 274 | # Make sure that what ever you are doing is calling Check periodically | ||
| 275 | # | ||
| 276 | # To check if timeout was triggered call WasTimeOut returns (true/false) | ||
| 277 | # | ||
| 278 | |||
| 279 | import time,sys | ||
| 280 | |||
| 281 | class TimeoutHandler: | ||
| 282 | def __init__(self,kill_func,time_to_live=10,debug=0): | ||
| 283 | 'Generic time-out handler.' | ||
| 284 | self.kill_func=kill_func | ||
| 285 | self.start_time=time.time() | ||
| 286 | self.stop_time=+self.start_time+int(time_to_live) | ||
| 287 | self.debug=debug | ||
| 288 | self.aborted=0 | ||
| 289 | |||
| 290 | def Check(self): | ||
| 291 | 'Call this periodically to check for time-out.' | ||
| 292 | if self.debug: | ||
| 293 | sys.stdout.write('.') | ||
| 294 | sys.stdout.flush() | ||
| 295 | if time.time()>=self.stop_time: | ||
| 296 | self.TimeOut() | ||
| 297 | |||
| 298 | def TimeOut(self): | ||
| 299 | 'Trigger the time-out callback.' | ||
| 300 | self.aborted=1 | ||
| 301 | if self.debug: | ||
| 302 | print 'Timeout, aborting' | ||
| 303 | self.kill_func() | ||
| 304 | |||
| 305 | def WasTimeOut(self): | ||
| 306 | 'Indicates if timeout was triggered 1=yes, 0=no.' | ||
| 307 | if self.debug: | ||
| 308 | print '' | ||
| 309 | print 'call duration: %.2f seconds' % (time.time()-self.start_time) | ||
| 310 | return self.aborted | ||
