Add : service without host will be just droped, like Nagios.
[shinken.git] / shinken / macroresolver.py
blobb587199e91c7f0b9056d2664e476d6f52b8438eb
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/>.
24 #This class revolv Macro in commands by looking at the macros list
25 #in Class of elements. It give a propertie that call be callable or not.
26 #It not callable, it's a simple properties and remplace the macro with the value
27 #If callable, it's a method that is call for getting the value. for exemple, to
28 #get the number of service in a host, you call a method to get the
29 # len(host.services)
31 import re
32 import time
34 from shinken.borg import Borg
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',
58 'LONGDATETIME': 'get_long_date_time',
59 'SHORTDATETIME': 'get_short_date_time',
60 'DATE': 'get_date',
61 'TIME': 'get_time',
62 'TIMET': 'get_timet',
63 'PROCESSSTARTTIME': 'get_process_start_time',
64 'EVENTSTARTTIME': 'get_events_start_time',
68 #This shall be call ONE TIME. It just put links for elements
69 #by scheduler
70 def init(self, conf):
71 # For searching class and elements for ondemand
72 #we need link to types
73 self.conf = conf
74 self.lists_on_demand = []
75 self.hosts = conf.hosts
76 #For special void host_name handling...
77 self.host_class = self.hosts.inner_class
78 self.lists_on_demand.append(self.hosts)
79 self.services = conf.services
80 self.contacts = conf.contacts
81 self.lists_on_demand.append(self.contacts)
82 self.hostgroups = conf.hostgroups
83 self.lists_on_demand.append(self.hostgroups)
84 self.commands = conf.commands
85 self.servicegroups = conf.servicegroups
86 self.lists_on_demand.append(self.servicegroups)
87 self.contactgroups = conf.contactgroups
88 self.lists_on_demand.append(self.contactgroups)
89 self.illegal_macro_output_chars = conf.illegal_macro_output_chars
90 self.output_macros = ['HOSTOUTPUT', 'HOSTPERFDATA', 'HOSTACKAUTHOR', 'HOSTACKCOMMENT', 'SERVICEOUTPUT', 'SERVICEPERFDATA', 'SERVICEACKAUTHOR', 'SERVICEACKCOMMENT']
91 #Try cache :)
92 #self.cache = {}
95 #Return all macros of a string, so cut the $
96 #And create a dict with it:
97 #val : value, not set here
98 #type : type of macro, like class one, or ARGN one
99 def get_macros(self, s):
100 #if s in self.cache:
101 # return self.cache[s]
103 p = re.compile(r'(\$)')
104 elts = p.split(s)
105 macros = {}
106 in_macro = False
107 for elt in elts:
108 if elt == '$':
109 in_macro = not in_macro
110 elif in_macro:
111 macros[elt] = {'val' : '', 'type' : 'unknown'}
113 #self.cache[s] = macros
114 return macros
117 #Get a value from a propertie of a element
118 #Prop can ba a function or a propertie
119 #So we call it or no
120 def get_value_from_element(self, elt, prop):
121 try:
122 value = getattr(elt, prop)
123 if callable(value):
124 return str(value())
125 else:
126 return str(value)
127 except AttributeError , exp:
128 # Return no value
129 return ''
132 #For some macros, we need to delete unwanted caracters
133 def delete_unwanted_caracters(self, s):
134 for c in self.illegal_macro_output_chars:
135 s = s.replace(c, '')
136 return s
139 #return a dict with all environement variable came from
140 #the macros of the datas object
141 def get_env_macros(self, data):
142 env = {}
144 clss = [d.__class__ for d in data]
145 for o in data:
146 for cls in clss:
147 if o.__class__ == cls:
148 macros = cls.macros
149 for macro in macros:
150 # print "Macro in %s : %s" % (o.__class__, macro)
151 prop = macros[macro]
152 value = self.get_value_from_element(o, prop)
153 # print "Value: %s" % value
154 env['NAGIOS_'+macro] = value
156 return env
159 # This function will look at elements in data (and args if it filled)
160 # to replace the macros in c_line with real value.
161 def resolve_simple_macros_in_string(self, c_line, data, args=None):
162 #Now we prepare the classes for looking at the class.macros
163 data.append(self) #For getting global MACROS
164 if hasattr(self, 'conf'):
165 data.append(self.conf) # For USERN macros
166 clss = [d.__class__ for d in data]
168 #we should do some loops for nested macros
169 #like $USER1$ hiding like a ninja in a $ARG2$ Macro. And if
170 #$USER1$ is pointing to $USER34$ etc etc, we should loop
171 #until we reach the botom. So the last loop is when we do
172 #not still have macros :)
173 still_got_macros = True
174 nb_loop = 0
175 while still_got_macros:
176 nb_loop += 1
177 #Ok, we want the macros in the command line
178 macros = self.get_macros(c_line)
180 #We can get out if we do not have macros this loop
181 still_got_macros = (len(macros)!=0)
182 #print "Still go macros:", still_got_macros
184 #Put in the macros the type of macro for all macros
185 self.get_type_of_macro(macros, clss)
186 #Now we get values from elements
187 for macro in macros:
188 #If type ARGN, look at ARGN cutting
189 if macros[macro]['type'] == 'ARGN' and args is not None:
190 macros[macro]['val'] = self.resolve_argn(macro, args)
191 macros[macro]['type'] = 'resolved'
192 #If class, get value from properties
193 if macros[macro]['type'] == 'class':
194 cls = macros[macro]['class']
195 for elt in data:
196 if elt is not None and elt.__class__ == cls:
197 prop = cls.macros[macro]
198 macros[macro]['val'] = self.get_value_from_element(elt, prop)
199 #Now check if we do not have a 'output' macro. If so, we must
200 #delete all special caracters that can be dangerous
201 if macro in self.output_macros:
202 macros[macro]['val'] = self.delete_unwanted_caracters(macros[macro]['val'])
203 if macros[macro]['type'] == 'CUSTOM':
204 cls_type = macros[macro]['class']
205 macro_name = re.split('_'+cls_type, macro)[1].upper()
206 #Ok, we've got the macro like MAC_ADDRESS for _HOSTMAC_ADDRESS
207 #Now we get the element in data that have the type HOST
208 #and we check if it gots the custom value
209 for elt in data:
210 if elt is not None and elt.__class__.my_type.upper() == cls_type:
211 if '_'+macro_name in elt.customs:
212 macros[macro]['val'] = elt.customs['_'+macro_name]
213 if macros[macro]['type'] == 'ONDEMAND':
214 macros[macro]['val'] = self.resolve_ondemand(macro, data)
216 #We resolved all we can, now replace the macro in the command call
217 for macro in macros:
218 c_line = c_line.replace('$'+macro+'$', macros[macro]['val'])
220 if nb_loop > 32: #too mouch loop, we exit
221 still_got_macros = False
223 #print "Retuning c_line", c_line.strip()
224 return c_line.strip()
227 #Resolve a command with macro by looking at data classes.macros
228 #And get macro from item properties.
229 def resolve_command(self, com, data):
230 c_line = com.command.command_line
231 return self.resolve_simple_macros_in_string(c_line, data, args=com.args)
234 #For all Macros in macros, set the type by looking at the
235 #MACRO name (ARGN? -> argn_type,
236 #HOSTBLABLA -> class one and set Host in class)
237 #_HOSTTOTO -> HOST CUSTOM MACRO TOTO
238 #$SERVICESTATEID:srv-1:Load$ -> MACRO SERVICESTATEID of
239 #the service Load of host srv-1
240 def get_type_of_macro(self, macros, clss):
241 for macro in macros:
242 #ARGN Macros
243 if re.match('ARG\d', macro):
244 macros[macro]['type'] = 'ARGN'
245 continue
246 #USERN macros
247 #are managed in the Config class, so no
248 #need to look that here
249 elif re.match('_HOST\w', macro):
250 macros[macro]['type'] = 'CUSTOM'
251 macros[macro]['class'] = 'HOST'
252 continue
253 elif re.match('_SERVICE\w', macro):
254 macros[macro]['type'] = 'CUSTOM'
255 macros[macro]['class'] = 'SERVICE'
256 #value of macro : re.split('_HOST', '_HOSTMAC_ADDRESS')[1]
257 continue
258 elif re.match('_CONTACT\w', macro):
259 macros[macro]['type'] = 'CUSTOM'
260 macros[macro]['class'] = 'CONTACT'
261 continue
262 #On demand macro
263 elif len(macro.split(':')) > 1:
264 macros[macro]['type'] = 'ONDEMAND'
265 continue
266 #OK, classical macro...
267 for cls in clss:
268 if macro in cls.macros:
269 macros[macro]['type'] = 'class'
270 macros[macro]['class'] = cls
271 continue
274 #Resolv MACROS for the ARGN
275 def resolve_argn(self, macro, args):
276 #first, get number of arg
277 id = None
278 r = re.search('ARG(?P<id>\d+)', macro)
279 if r is not None:
280 id = int(r.group('id')) - 1
281 try:
282 return args[id]
283 except IndexError:
284 return ''
287 #Resolved on demande macro, quite hard on fact
288 def resolve_ondemand(self, macro, data):
289 #print "\nResolving macro", macro
290 elts = macro.split(':')
291 nb_parts = len(elts)
292 macro_name = elts[0]
293 #Len 3 == service, 2 = all others types...
294 if nb_parts == 3:
295 val = ''
296 #print "Got a Service on demand asking...", elts
297 (host_name, service_description) = (elts[1], elts[2])
298 #host_name can be void, so it's the host in data
299 #that is important. We use our self.host_class to
300 #find the host in the data :)
301 if host_name == '':
302 for elt in data:
303 if elt is not None and elt.__class__ == self.host_class:
304 host_name = elt.host_name
305 #Okn now we get service
306 s = self.services.find_srv_by_name_and_hostname(host_name, service_description)
307 if s is not None:
308 cls = s.__class__
309 prop = cls.macros[macro_name]
310 val = self.get_value_from_element(s, prop)
311 #print "Got val:", val
312 return val
313 #Ok, service was easy, now hard part
314 else:
315 val = ''
316 elt_name = elts[1]
317 #Special case : elt_name can be void
318 #so it's the host where it apply
319 if elt_name == '':
320 for elt in data:
321 if elt is not None and elt.__class__ == self.host_class:
322 elt_name = elt.host_name
323 for list in self.lists_on_demand:
324 cls = list.inner_class
325 #We search our type by look at the macro
326 if macro_name in cls.macros:
327 prop = cls.macros[macro_name]
328 i = list.find_by_name(elt_name)
329 if i is not None:
330 val = self.get_value_from_element(i, prop)
331 #print "Got val:", val
332 return val
333 return ''
336 #Get Fri 15 May 11:42:39 CEST 2009
337 def get_long_date_time(self):
338 return time.strftime("%a %d %b %H:%M:%S %Z %Y", time.localtime())
341 #Get 10-13-2000 00:30:28
342 def get_short_date_time(self):
343 return time.strftime("%d-%m-%Y %H:%M:%S", time.localtime())
346 #Get 10-13-2000
347 def get_date(self):
348 return time.strftime("%d-%m-%Y", time.localtime())
351 #Get 00:30:28
352 def get_time(self):
353 return time.strftime("%H:%M:%S", time.localtime())
356 #Get epoch time
357 def get_timet(self):
358 return str(int(time.time()))
361 def get_total_hosts_up(self):
362 return len([h for h in self.hosts if h.state == 'UP'])
364 def get_total_hosts_down(self):
365 return len([h for h in self.hosts if h.state == 'DOWN'])
367 def get_total_hosts_unreacheable(self):
368 return len([h for h in self.hosts if h.state == 'UNREACHABLE'])
370 #TODO
371 def get_total_hosts_unreacheable_unhandled(self):
372 return 0
375 def get_total_host_problems(self):
376 return len([h for h in self.hosts if h.is_problem])
378 def get_total_host_problems_unhandled(self):
379 return 0
381 def get_total_service_ok(self):
382 return len([s for s in self.services if s.state == 'OK'])
384 def get_total_services_warning(self):
385 return len([s for s in self.services if s.state == 'WARNING'])
387 def get_total_services_critical(self):
388 return len([s for s in self.services if s.state == 'CRITICAL'])
390 def get_total_services_unknown(self):
391 return len([s for s in self.services if s.state == 'UNKNOWN'])
393 #TODO
394 def get_total_services_warning_unhandled(self):
395 return 0
397 def get_total_services_critical_unhandled(self):
398 return 0
400 def get_total_services_unknown_unhandled(self):
401 return 0
403 def get_total_service_problems(self):
404 return len([s for s in self.services if s.is_problem])
406 def get_total_service_problems_unhandled(self):
407 return 0
409 def get_process_start_time(self):
410 return 0
412 def get_events_start_time(self):
413 return 0