# Copyright 2017 - Nokia # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc from collections import namedtuple from sympy.logic.boolalg import And from sympy.logic.boolalg import Not from sympy.logic.boolalg import Or from sympy.logic.boolalg import to_dnf as sympy_to_dnf from sympy import Symbol ConditionVar = namedtuple('ConditionVar', ['symbol_name', 'positive']) class SymbolResolver(object): @abc.abstractmethod def is_relationship(self, symbol): pass @abc.abstractmethod def get_relationship_source_id(self, relationship): pass @abc.abstractmethod def get_relationship_target_id(self, relationship): pass @abc.abstractmethod def get_entity_id(self, entity): pass def get_condition_common_targets(condition, definitions_index, symbol_resolver): """Return the targets that are common to all clauses of the condition. Common targets include: * And condition - any vertex that is part of the condition can be a target * Not condition - no vertex that is part of the condition can be a target * Or condition - vertices that appear in any "positive" part (i.e. one that doesn't have a 'not' in front of it) of the Or condition A complete description of all options can be found in Vitrage 'external-actions' spec. The condition format: [[and_var1, and_var2, ...], or_list_2, ...] :return: A set of vertices that are common to all clauses of the condition """ clauses_targets = [] for clause in condition: clause_targets = set() for term in clause: if term.positive: symbol = definitions_index.get(term.symbol_name) if symbol and symbol_resolver.is_relationship(symbol): clause_targets.add( symbol_resolver.get_relationship_source_id(symbol)) clause_targets.add( symbol_resolver.get_relationship_target_id(symbol)) elif symbol: clause_targets.add(symbol_resolver.get_entity_id(symbol)) clauses_targets.append(clause_targets) return set.intersection(*clauses_targets) def is_condition_include_positive_clause(condition): """Check if a condition is positive A positive condition has at least one part that is not 'not' Positive conditions: host_contains_instance host and not host_contains_instance Negative conditions: not host_contains_instance not host_contains_instance or not alarm_on_host The condition format: [[and_var1, and_var2, ...], or_list_2, ...] :return: True if the condition is positive """ is_positive = False for clause in condition: for term in clause: if term.positive: is_positive = True return is_positive def parse_condition(condition_str): """Parse condition string into an object The condition string will be converted here into DNF (Disjunctive Normal Form), e.g., (X and Y) or (X and Z) or (X and V and not W) ... where X, Y, Z, V, W are either entities or relationships more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form The condition variable lists is then extracted from the DNF object. It is a list of lists. Each inner list represents an AND expression compound condition variables. The outer list presents the OR expression [[and_var1, and_var2, ...], or_list_2, ...] :param condition_str: the string as it written in the template :return: condition_vars_lists """ condition_dnf = convert_to_dnf_format(condition_str) if isinstance(condition_dnf, Or): return extract_or_condition(condition_dnf) if isinstance(condition_dnf, And): return [extract_and_condition(condition_dnf)] if isinstance(condition_dnf, Not): return [(extract_not_condition_var(condition_dnf))] if isinstance(condition_dnf, Symbol): return [[(extract_condition_var(condition_dnf, True))]] def convert_to_dnf_format(condition_str): condition_str = condition_str.replace(' and ', '&') condition_str = condition_str.replace(' AND ', '&') condition_str = condition_str.replace(' or ', '|') condition_str = condition_str.replace(' OR ', '|') condition_str = condition_str.replace(' not ', '~') condition_str = condition_str.replace(' NOT ', '~') condition_str = condition_str.replace('not ', '~') condition_str = condition_str.replace('NOT ', '~') return sympy_to_dnf(condition_str) def extract_or_condition(or_condition): vars_ = [] for var in or_condition.args: if isinstance(var, And): vars_.append(extract_and_condition(var)) else: is_symbol = isinstance(var, Symbol) vars_.append([extract_condition_var(var, is_symbol)]) return vars_ def extract_and_condition(and_condition): return [extract_condition_var(arg, isinstance(arg, Symbol)) for arg in and_condition.args] def extract_not_condition_var(not_condition): return [extract_condition_var(arg, False) for arg in not_condition.args] def extract_condition_var(symbol, positive): if isinstance(symbol, Not): return extract_not_condition_var(symbol)[0] return ConditionVar(symbol.name, positive)