Source code for shinken.macroresolver

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# Copyright (C) 2009-2012:
#     Gabes Jean, naparuba@gmail.com
#     Gerhard Lausser, Gerhard.Lausser@consol.de
#     Gregory Starck, g.starck@gmail.com
#     Hartmut Goebel, h.goebel@goebel-consult.de
#
# This file is part of Shinken.
#
# Shinken is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Shinken is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Shinken.  If not, see <http://www.gnu.org/licenses/>.


# This class resolve Macro in commands by looking at the macros list
# in Class of elements. It give a property that call be callable or not.
# It not callable, it's a simple property and replace the macro with the value
# If callable, it's a method that is called to get the value. for example, to
# get the number of service in a host, you call a method to get the
# len(host.services)

import re
import time

from shinken.borg import Borg


[docs]class MacroResolver(Borg): """Please Add a Docstring to describe the class here""" my_type = 'macroresolver' # Global macros macros = { 'TOTALHOSTSUP': '_get_total_hosts_up', 'TOTALHOSTSDOWN': '_get_total_hosts_down', 'TOTALHOSTSUNREACHABLE': '_get_total_hosts_unreachable', 'TOTALHOSTSDOWNUNHANDLED': '_get_total_hosts_unhandled', 'TOTALHOSTSUNREACHABLEUNHANDLED': '_get_total_hosts_unreachable_unhandled', 'TOTALHOSTPROBLEMS': '_get_total_host_problems', 'TOTALHOSTPROBLEMSUNHANDLED': '_get_total_host_problems_unhandled', 'TOTALSERVICESOK': '_get_total_service_ok', 'TOTALSERVICESWARNING': '_get_total_services_warning', 'TOTALSERVICESCRITICAL': '_get_total_services_critical', 'TOTALSERVICESUNKNOWN': '_get_total_services_unknown', 'TOTALSERVICESWARNINGUNHANDLED': '_get_total_services_warning_unhandled', 'TOTALSERVICESCRITICALUNHANDLED': '_get_total_services_critical_unhandled', 'TOTALSERVICESUNKNOWNUNHANDLED': '_get_total_services_unknown_unhandled', 'TOTALSERVICEPROBLEMS': '_get_total_service_problems', 'TOTALSERVICEPROBLEMSUNHANDLED': '_get_total_service_problems_unhandled', 'LONGDATETIME': '_get_long_date_time', 'SHORTDATETIME': '_get_short_date_time', 'DATE': '_get_date', 'TIME': '_get_time', 'TIMET': '_get_timet', 'PROCESSSTARTTIME': '_get_process_start_time', 'EVENTSTARTTIME': '_get_events_start_time', } # This must be called ONCE. It just put links for elements # by scheduler
[docs] def init(self, conf): # For searching class and elements for ondemand # we need link to types self.conf = conf self.lists_on_demand = [] self.hosts = conf.hosts # For special void host_name handling... self.host_class = self.hosts.inner_class self.lists_on_demand.append(self.hosts) self.services = conf.services self.contacts = conf.contacts self.lists_on_demand.append(self.contacts) self.hostgroups = conf.hostgroups self.lists_on_demand.append(self.hostgroups) self.commands = conf.commands self.servicegroups = conf.servicegroups self.lists_on_demand.append(self.servicegroups) self.contactgroups = conf.contactgroups self.lists_on_demand.append(self.contactgroups) self.illegal_macro_output_chars = conf.illegal_macro_output_chars self.output_macros = ['HOSTOUTPUT', 'HOSTPERFDATA', 'HOSTACKAUTHOR', 'HOSTACKCOMMENT', 'SERVICEOUTPUT', 'SERVICEPERFDATA', 'SERVICEACKAUTHOR', 'SERVICEACKCOMMENT'] # Try cache :) #self.cache = {} # Return all macros of a string, so cut the $ # And create a dict with it: # val: value, not set here # type: type of macro, like class one, or ARGN one
def _get_macros(self, s): #if s in self.cache: # return self.cache[s] p = re.compile(r'(\$)') elts = p.split(s) macros = {} in_macro = False for elt in elts: if elt == '$': in_macro = not in_macro elif in_macro: macros[elt] = {'val': '', 'type': 'unknown'} #self.cache[s] = macros if '' in macros: del macros[''] return macros # Get a value from a property of a element # Prop can be a function or a property # So we call it or not def _get_value_from_element(self, elt, prop): try: value = getattr(elt, prop) if callable(value): return unicode(value()) else: return unicode(value) except AttributeError, exp: # Return no value return '' except UnicodeError, exp: if isinstance(value, str): return unicode(value, 'utf8', errors='ignore') else: return '' # For some macros, we need to delete unwanted characters def _delete_unwanted_caracters(self, s): for c in self.illegal_macro_output_chars: s = s.replace(c, '') return s # return a dict with all environment variable came from # the macros of the datas object
[docs] def get_env_macros(self, data): env = {} for o in data: cls = o.__class__ macros = cls.macros for macro in macros: if macro.startswith("USER"): break #print "Macro in %s: %s" % (o.__class__, macro) prop = macros[macro] value = self._get_value_from_element(o, prop) env['NAGIOS_%s' % macro] = value if hasattr(o, 'customs'): # make NAGIOS__HOSTMACADDR from _MACADDR for cmacro in o.customs: env['NAGIOS__' + o.__class__.__name__.upper() + cmacro[1:].upper()] = o.customs[cmacro] return env # This function will look at elements in data (and args if it filled) # to replace the macros in c_line with real value.
[docs] def resolve_simple_macros_in_string(self, c_line, data, args=None): # Now we prepare the classes for looking at the class.macros data.append(self) # For getting global MACROS if hasattr(self, 'conf'): data.append(self.conf) # For USERN macros clss = [d.__class__ for d in data] # we should do some loops for nested macros # like $USER1$ hiding like a ninja in a $ARG2$ Macro. And if # $USER1$ is pointing to $USER34$ etc etc, we should loop # until we reach the bottom. So the last loop is when we do # not still have macros :) still_got_macros = True nb_loop = 0 while still_got_macros: nb_loop += 1 # Ok, we want the macros in the command line macros = self._get_macros(c_line) # We can get out if we do not have macros this loop still_got_macros = (len(macros) != 0) #print "Still go macros:", still_got_macros # Put in the macros the type of macro for all macros self._get_type_of_macro(macros, clss) # Now we get values from elements for macro in macros: # If type ARGN, look at ARGN cutting if macros[macro]['type'] == 'ARGN' and args is not None: macros[macro]['val'] = self._resolve_argn(macro, args) macros[macro]['type'] = 'resolved' # If class, get value from properties if macros[macro]['type'] == 'class': cls = macros[macro]['class'] for elt in data: if elt is not None and elt.__class__ == cls: prop = cls.macros[macro] macros[macro]['val'] = self._get_value_from_element(elt, prop) # Now check if we do not have a 'output' macro. If so, we must # delete all special characters that can be dangerous if macro in self.output_macros: macros[macro]['val'] = self._delete_unwanted_caracters(macros[macro]['val']) if macros[macro]['type'] == 'CUSTOM': cls_type = macros[macro]['class'] # Beware : only cut the first _HOST value, so the macro name can have it on it... macro_name = re.split('_' + cls_type, macro, 1)[1].upper() # Ok, we've got the macro like MAC_ADDRESS for _HOSTMAC_ADDRESS # Now we get the element in data that have the type HOST # and we check if it got the custom value for elt in data: if elt is not None and elt.__class__.my_type.upper() == cls_type: if '_' + macro_name in elt.customs: macros[macro]['val'] = elt.customs['_' + macro_name] # Then look on the macromodulations, in reserver order, so # the last to set, will be the firt to have. (yes, don't want to play # with break and such things sorry...) mms = getattr(elt, 'macromodulations', []) for mm in mms[::-1]: # Look if the modulation got the value, but also if it's currently active if '_' + macro_name in mm.customs and mm.is_active(): macros[macro]['val'] = mm.customs['_' + macro_name] if macros[macro]['type'] == 'ONDEMAND': macros[macro]['val'] = self._resolve_ondemand(macro, data) # We resolved all we can, now replace the macro in the command call for macro in macros: c_line = c_line.replace('$'+macro+'$', macros[macro]['val']) # A $$ means we want a $, it's not a macro! # We replace $$ by a big dirty thing to be sure to not misinterpret it c_line = c_line.replace("$$", "DOUBLEDOLLAR") if nb_loop > 32: # too much loop, we exit still_got_macros = False # We now replace the big dirty token we made by only a simple $ c_line = c_line.replace("DOUBLEDOLLAR", "$") #print "Retuning c_line", c_line.strip() return c_line.strip() # Resolve a command with macro by looking at data classes.macros # And get macro from item properties.
[docs] def resolve_command(self, com, data): c_line = com.command.command_line return self.resolve_simple_macros_in_string(c_line, data, args=com.args) # For all Macros in macros, set the type by looking at the # MACRO name (ARGN? -> argn_type, # HOSTBLABLA -> class one and set Host in class) # _HOSTTOTO -> HOST CUSTOM MACRO TOTO # $SERVICESTATEID:srv-1:Load$ -> MACRO SERVICESTATEID of # the service Load of host srv-1
def _get_type_of_macro(self, macros, clss): for macro in macros: # ARGN Macros if re.match('ARG\d', macro): macros[macro]['type'] = 'ARGN' continue # USERN macros # are managed in the Config class, so no # need to look that here elif re.match('_HOST\w', macro): macros[macro]['type'] = 'CUSTOM' macros[macro]['class'] = 'HOST' continue elif re.match('_SERVICE\w', macro): macros[macro]['type'] = 'CUSTOM' macros[macro]['class'] = 'SERVICE' # value of macro: re.split('_HOST', '_HOSTMAC_ADDRESS')[1] continue elif re.match('_CONTACT\w', macro): macros[macro]['type'] = 'CUSTOM' macros[macro]['class'] = 'CONTACT' continue # On demand macro elif len(macro.split(':')) > 1: macros[macro]['type'] = 'ONDEMAND' continue # OK, classical macro... for cls in clss: if macro in cls.macros: macros[macro]['type'] = 'class' macros[macro]['class'] = cls continue # Resolve MACROS for the ARGN def _resolve_argn(self, macro, args): # first, get the number of args id = None r = re.search('ARG(?P<id>\d+)', macro) if r is not None: id = int(r.group('id')) - 1 try: return args[id] except IndexError: return '' # Resolve on-demand macro, quite hard in fact def _resolve_ondemand(self, macro, data): #print "\nResolving macro", macro elts = macro.split(':') nb_parts = len(elts) macro_name = elts[0] # Len 3 == service, 2 = all others types... if nb_parts == 3: val = '' #print "Got a Service on demand asking...", elts (host_name, service_description) = (elts[1], elts[2]) # host_name can be void, so it's the host in data # that is important. We use our self.host_class to # find the host in the data :) if host_name == '': for elt in data: if elt is not None and elt.__class__ == self.host_class: host_name = elt.host_name # Ok now we get service s = self.services.find_srv_by_name_and_hostname(host_name, service_description) if s is not None: cls = s.__class__ prop = cls.macros[macro_name] val = self._get_value_from_element(s, prop) #print "Got val:", val return val # Ok, service was easy, now hard part else: val = '' elt_name = elts[1] # Special case: elt_name can be void # so it's the host where it apply if elt_name == '': for elt in data: if elt is not None and elt.__class__ == self.host_class: elt_name = elt.host_name for list in self.lists_on_demand: cls = list.inner_class # We search our type by looking at the macro if macro_name in cls.macros: prop = cls.macros[macro_name] i = list.find_by_name(elt_name) if i is not None: val = self._get_value_from_element(i, prop) # Ok we got our value :) break return val return '' # Get Fri 15 May 11:42:39 CEST 2009 def _get_long_date_time(self): return time.strftime("%a %d %b %H:%M:%S %Z %Y").decode('UTF-8', 'ignore') # Get 10-13-2000 00:30:28 def _get_short_date_time(self): return time.strftime("%d-%m-%Y %H:%M:%S") # Get 10-13-2000 def _get_date(self): return time.strftime("%d-%m-%Y") # Get 00:30:28 def _get_time(self): return time.strftime("%H:%M:%S") # Get epoch time def _get_timet(self): return str(int(time.time())) def _get_total_hosts_up(self): return len([h for h in self.hosts if h.state == 'UP']) def _get_total_hosts_down(self): return len([h for h in self.hosts if h.state == 'DOWN']) def _get_total_hosts_unreachable(self): return len([h for h in self.hosts if h.state == 'UNREACHABLE']) # TODO def _get_total_hosts_unreachable_unhandled(self): return 0 def _get_total_hosts_problems(self): return len([h for h in self.hosts if h.is_problem]) def _get_total_hosts_problems_unhandled(self): return 0 def _get_total_service_ok(self): return len([s for s in self.services if s.state == 'OK']) def _get_total_services_warning(self): return len([s for s in self.services if s.state == 'WARNING']) def _get_total_services_critical(self): return len([s for s in self.services if s.state == 'CRITICAL']) def _get_total_services_unknown(self): return len([s for s in self.services if s.state == 'UNKNOWN']) # TODO def _get_total_services_warning_unhandled(self): return 0 def _get_total_services_critical_unhandled(self): return 0 def _get_total_services_unknown_unhandled(self): return 0 def _get_total_service_problems(self): return len([s for s in self.services if s.is_problem]) def _get_total_service_problems_unhandled(self): return 0 def _get_process_start_time(self): return 0 def _get_events_start_time(self): return 0
Read the Docs v: documentation
Versions
latest
documentation
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.