*Change the jenkins/hudson test scripts
[shinken.git] / shinken / macroresolver.py
blob57de04f9f6d419b7c3a412673ab2b44a7b6af0e5
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
7 #This file is part of Shinken.
9 #Shinken is free software: you can redistribute it and/or modify
10 #it under the terms of the GNU Affero General Public License as published by
11 #the Free Software Foundation, either version 3 of the License, or
12 #(at your option) any later version.
14 #Shinken is distributed in the hope that it will be useful,
15 #but WITHOUT ANY WARRANTY; without even the implied warranty of
16 #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 #GNU Affero General Public License for more details.
19 #You should have received a copy of the GNU Affero General Public License
20 #along with Shinken. If not, see <http://www.gnu.org/licenses/>.
23 #This class revolv Macro in commands by looking at the macros list
24 #in Class of elements. It give a propertie that call be callable or not.
25 #It not callable, it's a simple properties and remplace the macro with the value
26 #If callable, it's a method that is call for getting the value. for exemple, to
27 #get the number of service in a host, you call a method to get the
28 # len(host.services)
30 import re
31 from shinken.borg import Borg
32 #from singleton import Singleton
33 import time
34 #from contact import Contact
37 class MacroResolver(Borg):
39 my_type = 'macroresolver'
40 #Global macros
41 macros = {
42 'TOTALHOSTSUP' : 'get_total_hosts_up',
43 'TOTALHOSTSDOWN' : 'get_total_hosts_down',
44 'TOTALHOSTSUNREACHABLE' : 'get_total_hosts_unreacheable',
45 'TOTALHOSTSDOWNUNHANDLED' : 'get_total_hosts_unhandled',
46 'TOTALHOSTSUNREACHABLEUNHANDLED' : 'get_total_hosts_unreacheable_unhandled',
47 'TOTALHOSTPROBLEMS' : 'get_total_host_problems',
48 'TOTALHOSTPROBLEMSUNHANDLED' : 'get_total_host_problems_unhandled',
49 'TOTALSERVICESOK' : 'get_total_service_ok',
50 'TOTALSERVICESWARNING' : 'get_total_services_warning',
51 'TOTALSERVICESCRITICAL' : 'get_total_services_critical',
52 'TOTALSERVICESUNKNOWN' : 'get_total_services_unknown',
53 'TOTALSERVICESWARNINGUNHANDLED' : 'get_total_services_warning_unhandled',
54 'TOTALSERVICESCRITICALUNHANDLED' : 'get_total_services_critical_unhandled',
55 'TOTALSERVICESUNKNOWNUNHANDLED' : 'get_total_services_unknown_unhandled',
56 'TOTALSERVICEPROBLEMS' : 'get_total_service_problems',
57 'TOTALSERVICEPROBLEMSUNHANDLED' : 'get_total_service_problems_unhandled',
59 'LONGDATETIME' : 'get_long_date_time',
60 'SHORTDATETIME' : 'get_short_date_time',
61 'DATE' : 'get_date',
62 'TIME' : 'get_time',
63 'TIMET' : 'get_timet',
65 'PROCESSSTARTTIME' : 'get_process_start_time',
66 'EVENTSTARTTIME' : 'get_events_start_time',
71 #This shall be call ONE TIME. It just put links for elements
72 #by scheduler
73 def init(self, conf):
74 # For searching class and elements for ondemand
75 #we need link to types
76 self.conf = conf
77 self.lists_on_demand = []
78 self.hosts = conf.hosts
79 #For special void host_name handling...
80 self.host_class = self.hosts.inner_class
81 self.lists_on_demand.append(self.hosts)
82 self.services = conf.services
83 self.contacts = conf.contacts
84 self.lists_on_demand.append(self.contacts)
85 self.hostgroups = conf.hostgroups
86 self.lists_on_demand.append(self.hostgroups)
87 self.commands = conf.commands
88 self.servicegroups = conf.servicegroups
89 self.lists_on_demand.append(self.servicegroups)
90 self.contactgroups = conf.contactgroups
91 self.lists_on_demand.append(self.contactgroups)
92 self.illegal_macro_output_chars = conf.illegal_macro_output_chars
93 self.output_macros = ['HOSTOUTPUT', 'HOSTPERFDATA', 'HOSTACKAUTHOR', 'HOSTACKCOMMENT', 'SERVICEOUTPUT', 'SERVICEPERFDATA', 'SERVICEACKAUTHOR', 'SERVICEACKCOMMENT']
94 #Try cache :)
95 #self.cache = {}
98 #Return all macros of a string, so cut the $
99 #And create a dict with it:
100 #val : value, not set here
101 #type : type of macro, like class one, or ARGN one
102 def get_macros(self, s):
103 #if s in self.cache:
104 # return self.cache[s]
106 p = re.compile(r'(\$)')
107 elts = p.split(s)
108 macros = {}
109 in_macro = False
110 for elt in elts:
111 if elt == '$':
112 in_macro = not in_macro
113 elif in_macro:
114 macros[elt] = {'val' : '', 'type' : 'unknown'}
116 #self.cache[s] = macros
117 return macros
120 #Get a value from a propertie of a element
121 #Prop can ba a function or a propertie
122 #So we call it or no
123 def get_value_from_element(self, elt, prop):
124 try:
125 value = getattr(elt, prop)
126 if callable(value):
127 return str(value())
128 else:
129 return str(value)
130 except AttributeError , exp:
131 return str(exp)
134 #For some macros, we need to delete unwanted caracters
135 def delete_unwanted_caracters(self, s):
136 for c in self.illegal_macro_output_chars:
137 s = s.replace(c, '')
138 return s
141 #return a dict with all environement variable came from
142 #the macros of the datas object
143 def get_env_macros(self, data):
144 env = {}
146 clss = [d.__class__ for d in data]
147 for o in data:
148 for cls in clss:
149 if o.__class__ == cls:
150 macros = cls.macros
151 for macro in macros:
152 # print "Macro in %s : %s" % (o.__class__, macro)
153 prop = macros[macro]
154 value = self.get_value_from_element(o, prop)
155 # print "Value: %s" % value
156 env['NAGIOS_'+macro] = value
158 return env
161 # This function will look at elements in data (and args if it filled)
162 # to replace the macros in c_line with real value.
163 def resolve_simple_macros_in_string(self, c_line, data, args=None):
164 #Now we prepare the classes for looking at the class.macros
165 data.append(self) #For getting global MACROS
166 if hasattr(self, 'conf'):
167 data.append(self.conf) # For USERN macros
168 clss = [d.__class__ for d in data]
170 #we should do some loops for nested macros
171 #like $USER1$ hiding like a ninja in a $ARG2$ Macro. And if
172 #$USER1$ is pointing to $USER34$ etc etc, we should loop
173 #until we reach the botom. So the last loop is when we do
174 #not still have macros :)
175 still_got_macros = True
176 nb_loop = 0
177 while still_got_macros:
178 nb_loop += 1
179 #Ok, we want the macros in the command line
180 macros = self.get_macros(c_line)
182 #We can get out if we do not have macros this loop
183 still_got_macros = (len(macros)!=0)
184 #print "Still go macros:", still_got_macros
186 #Put in the macros the type of macro for all macros
187 self.get_type_of_macro(macros, clss)
188 #Now we get values from elements
189 for macro in macros:
190 #If type ARGN, look at ARGN cutting
191 if macros[macro]['type'] == 'ARGN' and args!= None:
192 macros[macro]['val'] = self.resolve_argn(macro, args)
193 macros[macro]['type'] = 'resolved'
194 #If class, get value from properties
195 if macros[macro]['type'] == 'class':
196 cls = macros[macro]['class']
197 for elt in data:
198 if elt is not None and elt.__class__ == cls:
199 prop = cls.macros[macro]
200 macros[macro]['val'] = self.get_value_from_element(elt, prop)
201 #Now check if we do not have a 'output' macro. If so, we must
202 #delete all special caracters that can be dangerous
203 if macro in self.output_macros:
204 macros[macro]['val'] = self.delete_unwanted_caracters(macros[macro]['val'])
205 if macros[macro]['type'] == 'CUSTOM':
206 cls_type = macros[macro]['class']
207 macro_name = re.split('_'+cls_type, macro)[1].upper()
208 #Ok, we've got the macro like MAC_ADDRESS for _HOSTMAC_ADDRESS
209 #Now we get the element in data that have the type HOST
210 #and we check if it gots the custom value
211 for elt in data:
212 if elt is not None and elt.__class__.my_type.upper() == cls_type:
213 if '_'+macro_name in elt.customs:
214 macros[macro]['val'] = elt.customs['_'+macro_name]
215 if macros[macro]['type'] == 'ONDEMAND':
216 macros[macro]['val'] = self.resolve_ondemand(macro, data)
218 #We resolved all we can, now replace the macro in the command call
219 for macro in macros:
220 c_line = c_line.replace('$'+macro+'$', macros[macro]['val'])
222 if nb_loop > 32: #too mouch loop, we exit
223 still_got_macros = False
225 #print "Retuning c_line", c_line.strip()
226 return c_line.strip()
229 #Resolve a command with macro by looking at data classes.macros
230 #And get macro from item properties.
231 def resolve_command(self, com, data):
232 c_line = com.command.command_line
233 return self.resolve_simple_macros_in_string(c_line, data, args=com.args)
236 #For all Macros in macros, set the type by looking at the
237 #MACRO name (ARGN? -> argn_type,
238 #HOSTBLABLA -> class one and set Host in class)
239 #_HOSTTOTO -> HOST CUSTOM MACRO TOTO
240 #$SERVICESTATEID:srv-1:Load$ -> MACRO SERVICESTATEID of
241 #the service Load of host srv-1
242 def get_type_of_macro(self, macros, clss):
243 for macro in macros:
244 #ARGN Macros
245 if re.match('ARG\d', macro):
246 macros[macro]['type'] = 'ARGN'
247 continue
248 #USERN macros
249 #are managed in the Config class, so no
250 #need to look that here
251 elif re.match('_HOST\w', macro):
252 macros[macro]['type'] = 'CUSTOM'
253 macros[macro]['class'] = 'HOST'
254 continue
255 elif re.match('_SERVICE\w', macro):
256 macros[macro]['type'] = 'CUSTOM'
257 macros[macro]['class'] = 'SERVICE'
258 #value of macro : re.split('_HOST', '_HOSTMAC_ADDRESS')[1]
259 continue
260 elif re.match('_CONTACT\w', macro):
261 macros[macro]['type'] = 'CUSTOM'
262 macros[macro]['class'] = 'CONTACT'
263 continue
264 #On demand macro
265 elif len(macro.split(':')) > 1:
266 macros[macro]['type'] = 'ONDEMAND'
267 continue
268 #OK, classical macro...
269 for cls in clss:
270 if macro in cls.macros:
271 macros[macro]['type'] = 'class'
272 macros[macro]['class'] = cls
273 continue
276 #Resolv MACROS for the ARGN
277 def resolve_argn(self, macro, args):
278 #first, get number of arg
279 id = None
280 r = re.search('ARG(?P<id>\d+)', macro)
281 if r is not None:
282 id = int(r.group('id')) - 1
283 try:
284 return args[id]
285 except IndexError:
286 return ''
289 #Resolved on demande macro, quite hard on fact
290 def resolve_ondemand(self, macro, data):
291 #print "\nResolving macro", macro
292 elts = macro.split(':')
293 nb_parts = len(elts)
294 macro_name = elts[0]
295 #Len 3 == service, 2 = all others types...
296 if nb_parts == 3:
297 val = ''
298 #print "Got a Service on demand asking...", elts
299 (host_name, service_description) = (elts[1], elts[2])
300 #host_name can be void, so it's the host in data
301 #that is important. We use our self.host_class to
302 #find the host in the data :)
303 if host_name == '':
304 for elt in data:
305 if elt is not None and elt.__class__ == self.host_class:
306 host_name = elt.host_name
307 #Okn now we get service
308 s = self.services.find_srv_by_name_and_hostname(host_name, service_description)
309 if s != None:
310 cls = s.__class__
311 prop = cls.macros[macro_name]
312 val = self.get_value_from_element(s, prop)
313 #print "Got val:", val
314 return val
315 #Ok, service was easy, now hard part
316 else:
317 val = ''
318 elt_name = elts[1]
319 #Special case : elt_name can be void
320 #so it's the host where it apply
321 if elt_name == '':
322 for elt in data:
323 if elt is not None and elt.__class__ == self.host_class:
324 elt_name = elt.host_name
325 for list in self.lists_on_demand:
326 cls = list.inner_class
327 #We search our type by look at the macro
328 if macro_name in cls.macros:
329 prop = cls.macros[macro_name]
330 i = list.find_by_name(elt_name)
331 if i != None:
332 val = self.get_value_from_element(i, prop)
333 #print "Got val:", val
334 return val
335 return ''
338 #Get Fri 15 May 11:42:39 CEST 2009
339 def get_long_date_time(self):
340 return time.strftime("%a %d %b %H:%M:%S %Z %Y", time.localtime())
343 #Get 10-13-2000 00:30:28
344 def get_short_date_time(self):
345 return time.strftime("%d-%m-%Y %H:%M:%S", time.localtime())
348 #Get 10-13-2000
349 def get_date(self):
350 return time.strftime("%d-%m-%Y", time.localtime())
353 #Get 00:30:28
354 def get_time(self):
355 return time.strftime("%H:%M:%S", time.localtime())
358 #Get epoch time
359 def get_timet(self):
360 return str(int(time.time()))
363 def get_total_hosts_up(self):
364 return len([h for h in self.hosts if h.state == 'UP'])
366 def get_total_hosts_down(self):
367 return len([h for h in self.hosts if h.state == 'DOWN'])
369 def get_total_hosts_unreacheable(self):
370 return len([h for h in self.hosts if h.state == 'UNREACHABLE'])
372 #TODO
373 def get_total_hosts_unreacheable_unhandled(self):
374 return 0
377 def get_total_host_problems(self):
378 return len([h for h in self.hosts if h.is_problem])
380 def get_total_host_problems_unhandled(self):
381 return 0
383 def get_total_service_ok(self):
384 return len([s for s in self.services if s.state == 'OK'])
386 def get_total_services_warning(self):
387 return len([s for s in self.services if s.state == 'WARNING'])
389 def get_total_services_critical(self):
390 return len([s for s in self.services if s.state == 'CRITICAL'])
392 def get_total_services_unknown(self):
393 return len([s for s in self.services if s.state == 'UNKNOWN'])
395 #TODO
396 def get_total_services_warning_unhandled(self):
397 return 0
399 def get_total_services_critical_unhandled(self):
400 return 0
402 def get_total_services_unknown_unhandled(self):
403 return 0
405 def get_total_service_problems(self):
406 return len([s for s in self.services if s.is_problem])
408 def get_total_service_problems_unhandled(self):
409 return 0
411 def get_process_start_time(self):
412 return 0
414 def get_events_start_time(self):
415 return 0