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