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 0000000..73d795c --- /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 | ||