*Fix a bug in host.parents livestatus representation to make thruk happy
[shinken.git] / shinken / util.py
blob32dff3643aaf1e18d3dacf26223fc0206953333e
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 time, calendar, re
22 try:
23 from ClusterShell.NodeSet import NodeSet
24 except ImportError:
25 NodeSet = None
27 from shinken.macroresolver import MacroResolver
28 #from memoized import memoized
31 ################################### TIME ##################################
32 #@memoized
33 def get_end_of_day(year, month_id, day):
34 end_time = (year, month_id, day, 23, 59, 59, 0, 0, -1)
35 end_time_epoch = time.mktime(end_time)
36 return end_time_epoch
39 #@memoized
40 def print_date(t):
41 return time.asctime(time.localtime(t))
44 #@memoized
45 def get_day(t):
46 return int(t - get_sec_from_morning(t))
49 #@memoized
50 def get_sec_from_morning(t):
51 t_lt = time.localtime(t)
52 h = t_lt.tm_hour
53 m = t_lt.tm_min
54 s = t_lt.tm_sec
55 return h * 3600 + m * 60 + s
58 #@memoized
59 def get_start_of_day(year, month_id, day):
60 start_time = (year, month_id, day, 00, 00, 00, 0, 0, -1)
61 start_time_epoch = time.mktime(start_time)
62 return start_time_epoch
65 #change a time in seconds like 3600 into a format : 0d 1h 0m 0s
66 def format_t_into_dhms_format(t):
67 s = t
68 m,s=divmod(s,60)
69 h,m=divmod(m,60)
70 d,h=divmod(h,24)
71 return '%sd %sh %sm %ss' % (d, h, m, s)
74 ################################# Pythonization ###########################
75 #first change to foat so manage for example 25.0 to 25
76 def to_int(val):
77 return int(float(val))
79 def to_float(val):
80 return float(val)
82 def to_char(val):
83 return val[0]
85 def to_split(val):
86 val = val.split(',')
87 if val == ['']:
88 val = []
89 return val
91 #bool('0') = true, so...
92 def to_bool(val):
93 if val == '1':
94 return True
95 else:
96 return False
98 def from_bool_to_string(b):
99 if b :
100 return '1'
101 else:
102 return '0'
104 def from_bool_to_int(b):
105 if b :
106 return 1
107 else:
108 return 0
110 def from_list_to_split(val):
111 val = ','.join(['%s' % v for v in val])
112 return val
114 def from_float_to_int(val):
115 val = int(val)
116 return val
119 ### Functions for brok_transformations
120 ### They take 2 parameters : ref, and a value
121 ### ref is the item like a service, and value
122 ### if the value to preprocess
124 # Just a string list of all names, with ,
125 def to_list_string_of_names(ref, tab):
126 return ",".join([e.get_name() for e in tab])
128 # take a list of hosts and return a list
129 # of all host_names
130 def to_hostnames_list(ref, tab):
131 r = []
132 for h in tab:
133 if hasattr(h, 'host_name'):
134 r.append(h.host_name)
135 return r
137 # Will create a dict with 2 lists:
138 # *services : all services of the tab
139 # *hosts : all hosts of the tab
140 def to_svc_hst_distinct_lists(ref, tab):
141 r = {'hosts' : [], 'services' : []}
142 for e in tab:
143 cls = e.__class__
144 if cls.my_type == 'service':
145 name = e.get_dbg_name()
146 r['services'].append(name)
147 else:
148 name = e.get_dbg_name()
149 r['hosts'].append(name)
150 return r
153 # Will expaand the value with macros from the
154 # host/service ref before brok it
155 def expand_with_macros(ref, value):
156 return MacroResolver().resolve_simple_macros_in_string(value, ref.get_data_for_checks())
159 # Just get the string name of the object
160 # (like for realm)
161 def get_obj_name(obj):
162 return obj.get_name()
164 # return the list of keys of the custom dict
165 # but without the _ before
166 def get_customs_keys(d):
167 return [k[1:] for k in d.keys()]
170 # return the values of the dict
171 def get_customs_values(d):
172 return d.values()
175 ###################### Sorting ################
176 def scheduler_no_spare_first(x, y):
177 if x.spare and not y.spare:
178 return 1
179 elif x.spare and y.spare:
180 return 0
181 else:
182 return -1
185 #-1 is x first, 0 equal, 1 is y first
186 def alive_then_spare_then_deads(x, y):
187 #First are alive
188 if x.alive and not y.alive:
189 return -1
190 if y.alive and not x.alive:
191 return 0
192 #if not alive both, I really don't care...
193 if not x.alive and not y.alive:
194 return -1
195 #Ok, both are alive... now spare after no spare
196 if not x.spare:
197 return -1
198 #x is a spare, so y must be before, even if
199 #y is a spare
200 if not y.spare:
201 return 1
202 return 0
204 #-1 is x first, 0 equal, 1 is y first
205 def sort_by_ids(x, y):
206 if x.id < y.id:
207 return -1
208 if x.id > y.id:
209 return 1
210 #So is equal
211 return 0
215 ##################### Cleaning ##############
216 def strip_and_uniq(tab):
217 new_tab = set()
218 for elt in tab:
219 new_tab.add(elt.strip())
220 return list(new_tab)
224 #################### Patern change application (mainly for host) #######
226 def expand_xy_patern(pattern):
227 ns = NodeSet(pattern)
228 if len(ns) > 1:
229 for elem in ns:
230 for a in expand_xy_patern(elem):
231 yield a
232 else:
233 yield pattern
238 #This function is used to generate all patern change as
239 #recursive list.
240 #for example, for a [(1,3),(1,4),(1,5)] xy_couples,
241 #it will generate a 60 item list with:
242 #Rule: [1, '[1-5]', [1, '[1-4]', [1, '[1-3]', []]]]
243 #Rule: [1, '[1-5]', [1, '[1-4]', [2, '[1-3]', []]]]
244 #...
245 def got_generation_rule_patern_change(xy_couples):
246 res = []
247 xy_cpl = xy_couples
248 if xy_couples == []:
249 return []
250 (x, y) = xy_cpl[0]
251 for i in xrange(x, y+1):
252 n = got_generation_rule_patern_change(xy_cpl[1:])
253 if n != []:
254 for e in n:
255 res.append( [i, '[%d-%d]'%(x,y), e])
256 else:
257 res.append( [i, '[%d-%d]'%(x,y), []])
258 return res
261 #this fuction apply a recursive patern change
262 #generate by the got_generation_rule_patern_change
263 #function.
264 #It take one entry of this list, and apply
265 #recursivly the change to s like :
266 #s = "Unit [1-3] Port [1-4] Admin [1-5]"
267 #rule = [1, '[1-5]', [2, '[1-4]', [3, '[1-3]', []]]]
268 #output = Unit 3 Port 2 Admin 1
269 def apply_change_recursive_patern_change(s, rule):
270 #print "Try to change %s" % s, 'with', rule
271 new_s = s
272 (i, m, t) = rule
273 #print "replace %s by %s" % (r'%s' % m, str(i)), 'in', s
274 s = s.replace(r'%s' % m, str(i))
275 #print "And got", s
276 if t == []:
277 return s
278 return apply_change_recursive_patern_change(s, t)
281 #For service generator, get dict from a _custom properties
282 #as _disks C$(80%!90%),D$(80%!90%)$,E$(80%!90%)$
283 #return {'C' : '80%!90%', 'D' : '80%!90%', 'E' : '80%!90%'}
284 #And if we have a key that look like [X-Y] we will expand it
285 #into Y-X+1 keys
286 GET_KEY_VALUE_SEQUENCE_ERROR_NOERROR = 0
287 GET_KEY_VALUE_SEQUENCE_ERROR_SYNTAX = 1
288 GET_KEY_VALUE_SEQUENCE_ERROR_NODEFAULT = 2
289 GET_KEY_VALUE_SEQUENCE_ERROR_NODE= 3
290 def get_key_value_sequence(entry, default_value=None):
291 array1 = []
292 array2 = []
293 conf_entry = entry
295 # match a key$(value1..n)$
296 keyval_pattern_txt = r"""
297 \s*(?P<key>[^,]+?)(?P<values>(\$\(.*?\)\$)*)(?:[,]|$)
299 keyval_pattern = re.compile('(?x)' + keyval_pattern_txt)
300 # match a whole sequence of key$(value1..n)$
301 all_keyval_pattern = re.compile('(?x)^(' + keyval_pattern_txt + ')+$')
302 # match a single value
303 value_pattern = re.compile('(?:\$\((?P<val>.*?)\)\$)')
304 # match a sequence of values
305 all_value_pattern = re.compile('^(?:\$\(.*?\)\$)+$')
307 if all_keyval_pattern.match(conf_entry):
308 for mat in re.finditer(keyval_pattern, conf_entry):
309 r = { 'KEY' : mat.group('key') }
310 # The key is in mat.group('key')
311 # If there are also value(s)...
312 if mat.group('values'):
313 if all_value_pattern.match(mat.group('values')):
314 # If there are multiple values, loop over them
315 valnum = 1
316 for val in re.finditer(value_pattern, mat.group('values')):
317 r['VALUE' + str(valnum)] = val.group('val')
318 valnum += 1
319 else:
320 # Value syntax error
321 return (None, GET_KEY_VALUE_SEQUENCE_ERROR_SYNTAX)
322 else:
323 r['VALUE1'] = None
324 array1.append(r)
325 else:
326 # Something is wrong with the values. (Maybe unbalanced '$(')
327 # TODO: count opening and closing brackets in the pattern
328 return (None, GET_KEY_VALUE_SEQUENCE_ERROR_SYNTAX)
330 # now fill the empty values with the default value
331 for r in array1:
332 if r['VALUE1'] == None:
333 if default_value == None:
334 return (None, GET_KEY_VALUE_SEQUENCE_ERROR_NODEFAULT)
335 else:
336 r['VALUE1'] = default_value
337 r['VALUE'] = r['VALUE1']
339 #Now create new one but for [X-Y] matchs
340 # array1 holds the original entries. Some of the keys may contain wildcards
341 # array2 is filled with originals and inflated wildcards
342 #import time
343 #t0 = time.time()
344 #NodeSet = None
345 if NodeSet == None:
346 #The patern that will say if we have a [X-Y] key.
347 pat = re.compile('\[(\d*)-(\d*)\]')
349 for r in array1:
351 key = r['KEY']
352 orig_key = r['KEY']
354 #We have no choice, we cannot use NodeSet, so we use the
355 #simple regexp
356 if NodeSet == None:
357 m = pat.search(key)
358 got_xy = (m != None)
359 else: # Try to look with a nodeset check directly
360 try:
361 ns = NodeSet(key)
362 #If we have more than 1 element, we have a xy thing
363 got_xy = (len(ns) != 1)
364 except NodeSetParseRangeError:
365 return (None, GET_KEY_VALUE_SEQUENCE_ERROR_NODE)
366 pass # go in the next key
368 #Now we've got our couples of X-Y. If no void,
369 #we were with a "key generator"
371 if got_xy:
372 #Ok 2 cases : we have the NodeSet lib or not.
373 #if not, we use the dumb algo (quick, but manage less
374 #cases like /N or , in paterns)
375 if NodeSet == None: #us the old algo
376 still_loop = True
377 xy_couples = [] # will get all X-Y couples
378 while still_loop:
379 m = pat.search(key)
380 if m != None: # we've find one X-Y
381 (x,y) = m.groups()
382 (x,y) = (int(x), int(y))
383 xy_couples.append((x,y))
384 #We must search if we've gotother X-Y, so
385 #we delete this one, and loop
386 key = key.replace('[%d-%d]' % (x,y), 'Z'*10)
387 else:#no more X-Y in it
388 still_loop = False
390 #Now we have our xy_couples, we can manage them
392 #We search all patern change rules
393 rules = got_generation_rule_patern_change(xy_couples)
395 #Then we apply them all to get ours final keys
396 for rule in rules:
397 res = apply_change_recursive_patern_change(orig_key, rule)
398 new_r = {}
399 for key in r:
400 new_r[key] = r[key]
401 new_r['KEY'] = res
402 array2.append(new_r)
404 else:
405 #The key was just a generator, we can remove it
406 #keys_to_del.append(orig_key)
408 #We search all patern change rules
409 #rules = got_generation_rule_patern_change(xy_couples)
410 nodes_set = expand_xy_patern(orig_key)
411 new_keys = list(nodes_set)
413 #Then we apply them all to get ours final keys
414 for new_key in new_keys:
415 #res = apply_change_recursive_patern_change(orig_key, rule)
416 new_r = {}
417 for key in r:
418 new_r[key] = r[key]
419 new_r['KEY'] = new_key
420 array2.append(new_r)
421 else:
422 # There were no wildcards
423 array2.append(r)
426 #t1 = time.time()
427 #print "***********Diff", t1 -t0
429 return (array2, GET_KEY_VALUE_SEQUENCE_ERROR_NOERROR)