Source code for shinken.dependencynode

#!/usr/bin/env python

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

# Copyright (C) 2009-2014:
#     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/>.

import re
from shinken.util import filter_any, filter_none
from shinken.util import filter_host_by_name, filter_host_by_regex, filter_host_by_group,\
    filter_host_by_tag
from shinken.util import filter_service_by_name
from shinken.util import filter_service_by_regex_name
from shinken.util import filter_service_by_regex_host_name
from shinken.util import filter_service_by_host_name
from shinken.util import filter_service_by_bp_rule_label
from shinken.util import filter_service_by_hostgroup_name
from shinken.util import filter_service_by_host_tag_name
from shinken.util import filter_service_by_servicegroup_name
from shinken.util import filter_host_by_bp_rule_label
from shinken.util import filter_service_by_host_bp_rule_label


"""
Here is a node class for dependency_node(s) and a factory to create them
"""
[docs]class DependencyNode(object): def __init__(self): self.operand = None self.sons = [] # Of: values are a triple OK,WARN,CRIT self.of_values = ('0', '0', '0') self.is_of_mul = False self.configuration_errors = [] self.not_value = False def __str__(self): return "Op:'%s' Val:'%s' Sons:'[%s]' IsNot:'%s'" % (self.operand, self.of_values, ','.join([str(s) for s in self.sons]), self.not_value)
[docs] def get_reverse_state(self, state): # Warning is still warning if state == 1: return 1 if state == 0: return 2 if state == 2: return 0 # should not go here... return state # We will get the state of this node, by looking at the state of # our sons, and apply our operand
[docs] def get_state(self): # print "Ask state of me", self # If we are a host or a service, wee just got the host/service # hard state if self.operand in ['host', 'service']: return self.get_simple_node_state() else: return self.get_complex_node_state() # Returns a simple node direct state (such as a host or a service). No # calculation is needed
[docs] def get_simple_node_state(self): state = self.sons[0].last_hard_state_id # print "Get the hard state (%s) for the object %s" % (state, self.sons[0].get_name()) # Make DOWN look as CRITICAL (2 instead of 1) if self.operand == 'host' and state == 1: state = 2 # Maybe we are a NOT node, so manage this if self.not_value: # We inverse our states if self.operand == 'host' and state == 1: return 0 if self.operand == 'host' and state == 0: return 1 # Critical -> OK if self.operand == 'service' and state == 2: return 0 # OK -> CRITICAL (warning is untouched) if self.operand == 'service' and state == 0: return 2 return state # Calculates a complex node state based on its sons state, and its operator
[docs] def get_complex_node_state(self): if self.operand == '|': return self.get_complex_or_node_state() elif self.operand == '&': return self.get_complex_and_node_state() # It's an Xof rule else: return self.get_complex_xof_node_state() # Calculates a complex node state with an | operand
[docs] def get_complex_or_node_state(self): # First we get the state of all our sons states = [s.get_state() for s in self.sons] # Next we calculate the best state best_state = min(states) # Then we handle eventual not value if self.not_value: return self.get_reverse_state(best_state) return best_state # Calculates a complex node state with an & operand
[docs] def get_complex_and_node_state(self): # First we get the state of all our sons states = [s.get_state() for s in self.sons] # Next we calculate the worst state if 2 in states: worst_state = 2 else: worst_state = max(states) # Then we handle eventual not value if self.not_value: return self.get_reverse_state(worst_state) return worst_state # Calculates a complex node state with an Xof operand
[docs] def get_complex_xof_node_state(self): # First we get the state of all our sons states = [s.get_state() for s in self.sons] # We search for OK, WARN or CRIT applications # And we will choice between them nb_search_ok = self.of_values[0] nb_search_warn = self.of_values[1] nb_search_crit = self.of_values[2] # We look for each application nb_sons = len(states) nb_ok = len([s for s in states if s == 0]) nb_warn = len([s for s in states if s == 1]) nb_crit = len([s for s in states if s == 2]) # print "NB:", nb_ok, nb_warn, nb_crit # Ok and Crit apply with their own values # Warn can apply with warn or crit values # so a W C can raise a Warning, but not enough for # a critical def get_state_for(nb_tot, nb_real, nb_search): if nb_search.endswith('%'): nb_search = int(nb_search[:-1]) if nb_search < 0: # nb_search is negative, so + nb_search = max(100 + nb_search, 0) apply_for = float(nb_real) / nb_tot * 100 >= nb_search else: nb_search = int(nb_search) if nb_search < 0: # nb_search is negative, so + nb_search = max(nb_tot + nb_search, 0) apply_for = nb_real >= nb_search return apply_for ok_apply = get_state_for(nb_sons, nb_ok, nb_search_ok) warn_apply = get_state_for(nb_sons, nb_warn + nb_crit, nb_search_warn) crit_apply = get_state_for(nb_sons, nb_crit, nb_search_crit) # print "What apply?", ok_apply, warn_apply, crit_apply # return the worst state that apply if crit_apply: if self.not_value: return self.get_reverse_state(2) return 2 if warn_apply: if self.not_value: return self.get_reverse_state(1) return 1 if ok_apply: if self.not_value: return self.get_reverse_state(0) return 0 # Maybe even OK is not possible, if so, it depends if the admin # ask a simple form Xof: or a multiple one A,B,Cof: # the simple should give OK, the mult should give the worst state if self.is_of_mul: # print "Is mul, send 0" if self.not_value: return self.get_reverse_state(0) return 0 else: # print "not mul, return worst", worse_state if 2 in states: worst_state = 2 else: worst_state = max(states) if self.not_value: return self.get_reverse_state(worst_state) return worst_state # return a list of all host/service in our node and below
[docs] def list_all_elements(self): r = [] # We are a host/service if self.operand in ['host', 'service']: return [self.sons[0]] for s in self.sons: r.extend(s.list_all_elements()) # and uniq the result return list(set(r)) # If we are a of: rule, we can get some 0 in of_values, # if so, change them with NB sons instead
[docs] def switch_zeros_of_values(self): nb_sons = len(self.sons) # Need a list for assignment self.of_values = list(self.of_values) for i in [0, 1, 2]: if self.of_values[i] == '0': self.of_values[i] = str(nb_sons) self.of_values = tuple(self.of_values) # Check for empty (= not found) leaf nodes
[docs] def is_valid(self): valid = True if not self.sons: valid = False else: for s in self.sons: if isinstance(s, DependencyNode) and not s.is_valid(): self.configuration_errors.extend(s.configuration_errors) valid = False return valid
""" TODO: Add some comment about this class for the doc"""
[docs]class DependencyNodeFactory(object): host_flags = "grlt" service_flags = "grl" def __init__(self, bound_item): self.bound_item = bound_item # the () will be eval in a recursiv way, only one level of ()
[docs] def eval_cor_pattern(self, pattern, hosts, services, running=False): pattern = pattern.strip() # print "***** EVAL ", pattern complex_node = False # Look if it's a complex pattern (with rule) or # if it's a leaf ofit, like a host/service for m in '()&|': if m in pattern: complex_node = True # If it's a simple node, evaluate it directly if complex_node is False: return self.eval_simple_cor_pattern(pattern, hosts, services, running) else: return self.eval_complex_cor_pattern(pattern, hosts, services, running) # Checks if an expression is an Xof pattern, and parses its components if # so. In such a case, once parsed, returns the cleaned patten.
[docs] def eval_xof_pattern(self, node, pattern): p = "^(-?\d+%?),*(-?\d*%?),*(-?\d*%?) *of: *(.+)" r = re.compile(p) m = r.search(pattern) if m is not None: # print "Match the of: thing N=", m.groups() node.operand = 'of:' g = m.groups() # We can have a Aof: rule, or a multiple A,B,Cof: rule. mul_of = (g[1] != u'' and g[2] != u'') # If multi got (A,B,C) if mul_of: node.is_of_mul = True node.of_values = (g[0], g[1], g[2]) else: # if not, use A,0,0, we will change 0 after to put MAX node.of_values = (g[0], '0', '0') pattern = m.groups()[3] return pattern # Evaluate a complex correlation expression, such as an &, |, nested # expressions in par, and so on.
[docs] def eval_complex_cor_pattern(self, pattern, hosts, services, running=False): node = DependencyNode() pattern = self.eval_xof_pattern(node, pattern) in_par = False tmp = '' son_is_not = False # We keep is the next son will be not or not stacked_par = 0 for c in pattern: if c == '(': stacked_par += 1 # print "INCREASING STACK TO", stacked_par in_par = True tmp = tmp.strip() # Maybe we just start a par, but we got some things in tmp # that should not be good in fact ! if stacked_par == 1 and tmp != '': # TODO : real error print "ERROR : bad expression near", tmp continue # If we are already in a par, add this ( # but not if it's the first one so if stacked_par > 1: tmp += c elif c == ')': # print "Need closeing a sub expression?", tmp stacked_par -= 1 if stacked_par < 0: # TODO : real error print "Error : bad expression near", tmp, "too much ')'" continue if stacked_par == 0: # print "THIS is closing a sub compress expression", tmp tmp = tmp.strip() o = self.eval_cor_pattern(tmp, hosts, services, running) # Maybe our son was notted if son_is_not: o.not_value = True son_is_not = False node.sons.append(o) in_par = False # OK now clean the tmp so we start clean tmp = '' continue # ok here we are still in a huge par, we just close one sub one tmp += c # Expressions in par will be parsed in a sub node after. So just # stack pattern elif in_par: tmp += c # Until here, we're not in par # Manage the NOT for an expression. Only allow ! at the beginning # of a host or a host,service expression. elif c == '!': tmp = tmp.strip() if tmp and tmp[0] != '!': print "Error : bad expression near", tmp, "wrong position for '!'" continue # Flags next node not state son_is_not = True # DO NOT keep the c in tmp, we consumed it # print "MATCHING", c, pattern elif c == '&' or c == '|': # Oh we got a real cut in an expression, if so, cut it # print "REAL & for cutting" tmp = tmp.strip() # Look at the rule viability if node.operand is not None and node.operand != 'of:' and c != node.operand: # Should be logged as a warning / info? :) return None if node.operand != 'of:': node.operand = c if tmp != '': # print "Will analyse the current str", tmp o = self.eval_cor_pattern(tmp, hosts, services, running) # Maybe our son was notted if son_is_not: o.not_value = True son_is_not = False node.sons.append(o) tmp = '' # Maybe it's a classic character or we're in par, if so, continue else: tmp += c # Be sure to manage the trainling part when the line is done tmp = tmp.strip() if tmp != '': # print "Managing trainling part", tmp o = self.eval_cor_pattern(tmp, hosts, services, running) # Maybe our son was notted if son_is_not: o.not_value = True son_is_not = False # print "4end I've %s got new sons" % pattern , o node.sons.append(o) # We got our nodes, so we can update 0 values of of_values # with the number of sons node.switch_zeros_of_values() return node # Evaluate a simple correlation expression, such as a host, a host + a # service, or expand a host or service expression.
[docs] def eval_simple_cor_pattern(self, pattern, hosts, services, running=False): node = DependencyNode() pattern = self.eval_xof_pattern(node, pattern) # print "Try to find?", pattern # If it's a not value, tag the node and find # the name without this ! operator if pattern.startswith('!'): node.not_value = True pattern = pattern[1:] # Is the pattern an expression to be expanded? if re.search(r"^([%s]+|\*):" % self.host_flags, pattern) or \ re.search(r",\s*([%s]+:.*|\*)$" % self.service_flags, pattern): # o is just extracted its attributes, then trashed. o = self.expand_expression(pattern, hosts, services, running) if node.operand != 'of:': node.operand = '&' node.sons.extend(o.sons) node.configuration_errors.extend(o.configuration_errors) node.switch_zeros_of_values() else: node.operand = 'object' obj, error = self.find_object(pattern, hosts, services) if obj is not None: # Set host or service node.operand = obj.__class__.my_type node.sons.append(obj) else: if running is False: node.configuration_errors.append(error) else: # As business rules are re-evaluated at run time on # each scheduling loop, if the rule becomes invalid # because of a badly written macro modulation, it # should be notified upper for the error to be # displayed in the check output. raise Exception(error) return node # We've got an object, like h1,db1 that mean the # db1 service of the host db1, or just h1, that mean # the host h1.
[docs] def find_object(self, pattern, hosts, services): # print "Finding object", pattern obj = None error = None is_service = False # h_name, service_desc are , separated elts = pattern.split(',') host_name = elts[0].strip() # If host_name is empty, use the host_name the business rule is bound to if not host_name: host_name = self.bound_item.host_name # Look if we have a service if len(elts) > 1: is_service = True service_description = elts[1].strip() if is_service: obj = services.find_srv_by_name_and_hostname(host_name, service_description) if not obj: error = "Business rule uses unknown service %s/%s"\ % (host_name, service_description) else: obj = hosts.find_by_name(host_name) if not obj: error = "Business rule uses unknown host %s" % (host_name,) return obj, error # Tries to expand a host or service expression into a dependency node tree # using (host|service)group membership, regex, or labels as item selector.
[docs] def expand_expression(self, pattern, hosts, services, running=False): error = None node = DependencyNode() node.operand = '&' elts = [e.strip() for e in pattern.split(',')] # If host_name is empty, use the host_name the business rule is bound to if not elts[0]: elts[0] = self.bound_item.host_name filters = [] # Looks for hosts/services using appropriate filters try: if len(elts) > 1: # We got a service expression host_expr, service_expr = elts filters.extend(self.get_srv_host_filters(host_expr)) filters.extend(self.get_srv_service_filters(service_expr)) items = services.find_by_filter(filters) else: # We got a host expression host_expr = elts[0] filters.extend(self.get_host_filters(host_expr)) items = hosts.find_by_filter(filters) except re.error, e: error = "Business rule uses invalid regex %s: %s" % (pattern, e) else: if not items: error = "Business rule got an empty result for pattern %s" % pattern # Checks if we got result if error: if running is False: node.configuration_errors.append(error) else: # As business rules are re-evaluated at run time on # each scheduling loop, if the rule becomes invalid # because of a badly written macro modulation, it # should be notified upper for the error to be # displayed in the check output. raise Exception(error) return node # Creates dependency node subtree for item in items: # Creates a host/service node son = DependencyNode() son.operand = item.__class__.my_type son.sons.append(item) # Appends it to wrapping node node.sons.append(son) node.switch_zeros_of_values() return node # Generates filter list on a hosts host_name
[docs] def get_host_filters(self, expr): if expr == "*": return [filter_any] match = re.search(r"^([%s]+):(.*)" % self.host_flags, expr) if match is None: return [filter_host_by_name(expr)] flags, expr = match.groups() if "g" in flags: return [filter_host_by_group(expr)] elif "r" in flags: return [filter_host_by_regex(expr)] elif "l" in flags: return [filter_host_by_bp_rule_label(expr)] elif "t" in flags: return [filter_host_by_tag(expr)] else: return [filter_none] # Generates filter list on services host_name
[docs] def get_srv_host_filters(self, expr): if expr == "*": return [filter_any] match = re.search(r"^([%s]+):(.*)" % self.host_flags, expr) if match is None: return [filter_service_by_host_name(expr)] flags, expr = match.groups() if "g" in flags: return [filter_service_by_hostgroup_name(expr)] elif "r" in flags: return [filter_service_by_regex_host_name(expr)] elif "l" in flags: return [filter_service_by_host_bp_rule_label(expr)] elif "t" in flags: return [filter_service_by_host_tag_name(expr)] else: return [filter_none] # Generates filter list on services service_description
[docs] def get_srv_service_filters(self, expr): if expr == "*": return [filter_any] match = re.search(r"^([%s]+):(.*)" % self.service_flags, expr) if match is None: return [filter_service_by_name(expr)] flags, expr = match.groups() if "g" in flags: return [filter_service_by_servicegroup_name(expr)] elif "r" in flags: return [filter_service_by_regex_name(expr)] elif "l" in flags: return [filter_service_by_bp_rule_label(expr)] else: return [filter_none]
Read the Docs v: latest
Versions
latest
stable
branch-1.4
2.4.1
2.2
2.0.3
1.4.2
Downloads
pdf
htmlzip
epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.