#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015-2015: Alignak team, see AUTHORS.txt file for contributors
#
# This file is part of Alignak.
#
# Alignak 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.
#
# Alignak 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 Alignak. If not, see <http://www.gnu.org/licenses/>.
#
#
# This file incorporates work covered by the following copyright and
# permission notice:
#
# Copyright (C) 2009-2014:
# aviau, alexandre.viau@savoirfairelinux.com
# Jean Gabes, naparuba@gmail.com
# Sebastien Coavoux, s.coavoux@free.fr
# 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 module provides ComplexExpressionNode and ComplexExpressionFactory used for parsing
expression (business rules)
"""
from alignak.util import strip_and_uniq
from alignak.dependencynode import DependencyNode
[docs]class ComplexExpressionNode(object):
"""
ComplexExpressionNode is a node class for complex_expression(s)
"""
def __init__(self):
self.operand = None
self.sons = []
self.configuration_errors = []
self.not_value = False
# If leaf, the content will be the hostgroup or hosts
# that are selected with this node
self.leaf = False
self.content = None
def __str__(self):
if not self.leaf:
return "Op:'%s' Leaf:%s Sons:'[%s] IsNot:%s'" % \
(self.operand, self.leaf, ','.join([str(s) for s in self.sons]), self.not_value)
else:
return 'IS LEAF %s' % self.content
[docs] def resolve_elements(self):
"""Get element of this node recursively
Compute rules with OR or AND rule then NOT rules.
:return: set of element
:rtype: set
"""
# If it's a leaf, we just need to dump a set with the content of the node
if self.leaf:
# print "Is a leaf", self.content
if not self.content:
return set()
return set(self.content)
# first got the not ones in a list, and the other in the other list
not_nodes = [s for s in self.sons if s.not_value]
positiv_nodes = [s for s in self.sons if not s.not_value] # ok a not not is hard to read..
# print "Not nodes", not_nodes
# print "Positiv nodes", positiv_nodes
# By default we are using a OR rule
if not self.operand:
self.operand = '|'
res = set()
# print "Will now merge all of this", self.operand
# The operand will change the positiv loop only
i = 0
for node in positiv_nodes:
node_members = node.resolve_elements()
if self.operand == '|':
# print "OR rule", node_members
res = res.union(node_members)
elif self.operand == '&':
# print "AND RULE", node_members
# The first elements of an AND rule should be used
if i == 0:
res = node_members
else:
res = res.intersection(node_members)
i += 1
# And we finally remove all NOT elements from the result
for node in not_nodes:
node_members = node.resolve_elements()
res = res.difference(node_members)
return res
[docs] def is_valid(self):
"""
Check if all leaves are correct (no error)
:return: True if correct, else False
:rtype: bool
TODO: Fix this function and use it.
DependencyNode should be ComplexExpressionNode
Should return true on a leaf
"""
valid = True
if not self.sons:
valid = False
else:
for son in self.sons:
if isinstance(son, DependencyNode) and not son.is_valid():
self.configuration_errors.extend(son.configuration_errors)
valid = False
return valid
[docs]class ComplexExpressionFactory(object):
"""ComplexExpressionFactory provides complex expression parsing functions
"""
def __init__(self, ctx='hostgroups', grps=None, all_elements=None):
self.ctx = ctx
self.grps = grps
self.all_elements = all_elements
[docs] def eval_cor_pattern(self, pattern):
"""Parse and build recursively a tree of ComplexExpressionNode from pattern
:param pattern: pattern to parse
:type pattern: str
:return: root node of parsed tree
:type: alignak.complexexpression.ComplexExpressionNode
"""
pattern = pattern.strip()
# print "eval_cor_pattern::", 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 char in '()+&|,':
if char in pattern:
complex_node = True
node = ComplexExpressionNode()
# print "Is so complex?", complex_node, pattern, node
# if it's a single expression like !linux or production
# (where "linux" and "production" are hostgroup names)
# we will get the objects from it and return a leaf node
if not complex_node:
# 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:]
node.operand = self.ctx
node.leaf = True
obj, error = self.find_object(pattern)
if obj is not None:
node.content = obj
else:
node.configuration_errors.append(error)
return node
in_par = False
tmp = ''
stacked_par = 0
for char in pattern:
# print "MATCHING", c
if char == ',' or char == '|':
# Maybe we are in a par, if so, just stack it
if in_par:
# print ", in a par, just staking it"
tmp += char
else:
# Oh we got a real cut in an expression, if so, cut it
# print "REAL , for cutting"
tmp = tmp.strip()
node.operand = '|'
if tmp != '':
# print "Will analyse the current str", tmp
son = self.eval_cor_pattern(tmp)
node.sons.append(son)
tmp = ''
elif char == '&' or char == '+':
# Maybe we are in a par, if so, just stack it
if in_par:
# print " & in a par, just staking it"
tmp += char
else:
# Oh we got a real cut in an expression, if so, cut it
# print "REAL & for cutting"
tmp = tmp.strip()
node.operand = '&'
if tmp != '':
# print "Will analyse the current str", tmp
son = self.eval_cor_pattern(tmp)
node.sons.append(son)
tmp = ''
elif char == '(':
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 += char
# o = self.eval_cor_pattern(tmp)
# print "1( I've %s got new sons" % pattern , o
# node.sons.append(o)
elif char == ')':
# 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()
son = self.eval_cor_pattern(tmp)
node.sons.append(son)
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 += char
# Maybe it's a classic character, if so, continue
else:
tmp += char
# Be sure to manage the trainling part when the line is done
tmp = tmp.strip()
if tmp != '':
# print "Managing trainling part", tmp
son = self.eval_cor_pattern(tmp)
# print "4end I've %s got new sons" % pattern , o
node.sons.append(son)
# print "End, tmp", tmp
# print "R %s:" % pattern, node
return node
[docs] def find_object(self, pattern):
"""Get a list of host corresponding to the pattern regarding the context
:param pattern: pattern to find
:type pattern: str
:return: Host list matching pattern (hostgroup name, template, all)
:rtype: list[alignak.objects.host.Host]
"""
obj = None
error = None
pattern = pattern.strip()
if pattern == '*':
obj = [h.host_name for h in self.all_elements.items.values()
if getattr(h, 'host_name', '') != '' and not h.is_tpl()]
return obj, error
# Ok a more classic way
# print "GRPS", self.grps
if self.ctx == 'hostgroups':
# Ok try to find this hostgroup
hgr = self.grps.find_by_name(pattern)
# Maybe it's an known one?
if not hgr:
error = "Error : cannot find the %s of the expression '%s'" % (self.ctx, pattern)
return hgr, error
# Ok the group is found, get the elements!
elts = hgr.get_hosts()
elts = strip_and_uniq(elts)
# Maybe the hostgroup memebrs is '*', if so expand with all hosts
if '*' in elts:
elts.extend([h.host_name for h in self.all_elements.items.values()
if getattr(h, 'host_name', '') != '' and not h.is_tpl()])
# And remove this strange hostname too :)
elts.remove('*')
return elts, error
else: # templates
obj = self.grps.find_hosts_that_use_template(pattern)
return obj, error