*Remove a leftover test file
[shinken.git] / shinken / action.py
blob78a46344c7853b314c23631a3a8935d0a9101037
1 #!/usr/bin/env python
2 #Copyright (C) 2009-2010 :
3 # Gabes Jean, naparuba@gmail.com
4 # Gerhard Lausser, Gerhard.Lausser@consol.de
6 #This file is part of Shinken.
8 #Shinken is free software: you can redistribute it and/or modify
9 #it under the terms of the GNU Affero General Public License as published by
10 #the Free Software Foundation, either version 3 of the License, or
11 #(at your option) any later version.
13 #Shinken is distributed in the hope that it will be useful,
14 #but WITHOUT ANY WARRANTY; without even the implied warranty of
15 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 #GNU Affero General Public License for more details.
18 #You should have received a copy of the GNU Affero General Public License
19 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
21 import os, time, copy
22 import shlex
23 import sys
24 import subprocess
26 __all__ = ( 'Action' )
28 valid_exit_status = (0, 1, 2, 3)
30 only_copy_prop = ('id', 'status', 'command', 't_to_go', 'timeout', 'env')
32 shellchars = ( '!', '$', '^', '&', '*', '(', ')', '~', '[', ']',
33 '|', '{', '}', ';', '<', '>', '?', '`')
36 # This abstract class is use just for having a common id between actions and checks
37 class __Action:
38 id = 0
40 # Mix the env into the environnment variables
41 # into a new local env dict
42 # rmq : we cannot just update os.environ because
43 # it will be modified for all other checks too
44 def get_local_environnement(self):
45 local_env = copy.copy(os.environ)
46 for p in self.env:
47 local_env[p] = self.env[p]
48 return local_env
51 def execute(self):
52 """ Start this action command ; the command will be executed in a subprocess """
53 self.status = 'launched'
54 self.check_time = time.time()
55 self.wait_time = 0.0001
56 self.last_poll = self.check_time
57 # Get a local env variables with our additionnal values
58 self.local_env = self.get_local_environnement()
60 return self.execute__() ## OS specific part
63 def get_outputs(self, out, max_plugins_output_length):
64 #print "Get only," , max_plugins_output_length, "bytes"
65 # Squize all output after max_plugins_output_length
66 out = out[:max_plugins_output_length]
67 # Then cuts by lines
68 elts = out.split('\n')
69 # For perf data
70 elts_line1 = elts[0].split('|')
71 # First line before | is output, and strip it
72 self.output = elts_line1[0].strip()
73 # After | is perfdata, and strip it
74 if len(elts_line1) > 1:
75 self.perf_data = elts_line1[1].strip()
76 # The others lines are long_output
77 # but others are are not stripped
78 if len(elts) > 1:
79 self.long_output = '\n'.join(elts[1:])
82 def check_finished(self, max_plugins_output_length):
83 # We must wait, but checks are variable in time
84 # so we do not wait the same for an little check
85 # than a long ping. So we do like TCP : slow start with *2
86 # but do not wait more than 0.1s.
87 self.last_poll = time.time()
88 if self.process.poll() is None:
89 self.wait_time = min(self.wait_time*2, 0.1)
90 #time.sleep(wait_time)
91 now = time.time()
92 if (now - self.check_time) > self.timeout:
93 self.kill__()
94 #print "Kill", self.process.pid, self.command, now - self.check_time
95 self.status = 'timeout'
96 self.execution_time = now - self.check_time
97 self.exit_status = 3
98 return
99 return
100 # Get standards outputs
101 self.exit_status = self.process.returncode
102 (stdoutdata, stderrdata) = self.process.communicate()
104 #we should not keep the process now
105 del self.process
107 # if the exit status is anormal, we add stderr to the output
108 if self.exit_status not in valid_exit_status:
109 stdoutdata = stdoutdata + stderrdata
110 # Now grep what we want in the output
111 self.get_outputs(stdoutdata, max_plugins_output_length)
113 self.status = 'done'
114 self.execution_time = time.time() - self.check_time
117 def copy_shell__(self, new_i):
118 """ This will assign the attributes present in 'only_copy_prop' from self to new_i """
119 for prop in only_copy_prop:
120 setattr(new_i, prop, getattr(self, prop))
121 return new_i
124 def got_shell_characters(self):
125 for c in self.command:
126 if c in shellchars:
127 return True
128 return False
132 ## OS specific "execute__" & "kill__" are defined by "Action" class definition:
134 if os.name != 'nt':
136 class Action(__Action):
138 # We allow direct launch only for 2.7 and higher version
139 # because if a direct launch crash, under this the file hanldes
140 # are not releases, it's not good.
141 def execute__(self, force_shell=sys.version_info < (2, 7)):
142 # If the command line got shell characters, we should go in a shell
143 # mode. So look at theses parameters
144 force_shell |= self.got_shell_characters()
146 if force_shell:
147 cmd = self.command
148 else :
149 shlex.split(self.command)
151 try:
152 self.process = subprocess.Popen(cmd,
153 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
154 close_fds=True, shell=force_shell, env=self.local_env)
155 except OSError , exp:
156 print "Debug : Error in launching command:", self.command, exp, force_shell
157 # Maybe it's just a shell we try to exec. So we must retry
158 if not force_shell and exp.errno == 8 and exp.strerror == 'Exec format error':
159 return self.execute__(True)
160 self.output = exp.__str__()
161 self.exit_status = 2
162 self.status = 'done'
163 self.execution_time = time.time() - self.check_time
165 # Maybe we run out of file descriptor. It's not good at all!
166 if exp.errno == 24 and exp.strerror == 'Too many open files':
167 return 'toomanyopenfiles'
169 def kill__(self):
170 os.kill(self.process.pid, 9)
172 else:
174 import ctypes
175 TerminateProcess = ctypes.windll.kernel32.TerminateProcess
177 class Action(__Action):
178 def execute__(self):
179 try:
180 self.process = subprocess.Popen(shlex.split(self.command),
181 stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.local_env, shell=True)
182 except WindowsError, exp:
183 print "We kill the process : ", exp, self.command
184 self.status = 'timeout'
185 self.execution_time = time.time() - self.check_time
187 def kill__(self):
188 TerminateProcess(int(self.process._handle), -1)