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