Add template validations, to handle the case of actions that don't have an action_target
Two new validations are added: 1. A condition can not be only "negative". For example, instead of: "not alarm_on_instance" one should use "instance and not_alarm_on_instance" 2. There should be an entity that is common to all parts of an "or" condition. For example, this is illegal: "alarm1_on_host or alarm2_on_instance" Change-Id: I209155ade48ba740642670891c11aeef0868197c Implements: blueprint support-external-actions
This commit is contained in:
parent
b8e20918e9
commit
df289c2933
@ -104,3 +104,8 @@ The following describes all the possible status code and their messages:
|
|||||||
| 133 | execute_mistral action must contain workflow field in | content |
|
| 133 | execute_mistral action must contain workflow field in | content |
|
||||||
| | properties block | |
|
| | properties block | |
|
||||||
+------------------+---------------------------------------------------------+-------------------------------+
|
+------------------+---------------------------------------------------------+-------------------------------+
|
||||||
|
| 134 | condition can not contain only 'not' clauses | content |
|
||||||
|
+------------------+---------------------------------------------------------+-------------------------------+
|
||||||
|
| 135 | condition must contain a common entity for all 'or' | content |
|
||||||
|
| | clauses | |
|
||||||
|
+------------------+---------------------------------------------------------+-------------------------------+
|
||||||
|
@ -54,6 +54,8 @@ Expression can be combined using the following logical operators:
|
|||||||
condition to be met.
|
condition to be met.
|
||||||
- "or" - indicates at least one expression must be satisfied in order for the
|
- "or" - indicates at least one expression must be satisfied in order for the
|
||||||
condition to be met (non-exclusive or).
|
condition to be met (non-exclusive or).
|
||||||
|
- "not" - indicates that the expression must not be satisfied in order for the
|
||||||
|
condition to be met.
|
||||||
- parentheses "()" - clause indicating the scope of an expression.
|
- parentheses "()" - clause indicating the scope of an expression.
|
||||||
|
|
||||||
The following are examples of valid expressions, where X, Y and Z are
|
The following are examples of valid expressions, where X, Y and Z are
|
||||||
@ -66,6 +68,29 @@ relationships:
|
|||||||
- X and not (Y or Z)
|
- X and not (Y or Z)
|
||||||
- X and not X
|
- X and not X
|
||||||
|
|
||||||
|
|
||||||
|
A few restrictions regarding the condition format:
|
||||||
|
|
||||||
|
* A condition can not be entirely "negative", i.e. it must have at least one
|
||||||
|
part that does not have a "not" in front of it.
|
||||||
|
|
||||||
|
For example, instead of:
|
||||||
|
not alarm_on_instance
|
||||||
|
use:
|
||||||
|
instance and not alarm_on_instance
|
||||||
|
|
||||||
|
* There must be at least one entity that is common to all "or" clauses.
|
||||||
|
|
||||||
|
For example, this condition is illegal:
|
||||||
|
alarm1_on_host or alarm2_on_instance
|
||||||
|
This condition is legal:
|
||||||
|
alarm1_on_instance or alarm2_on_instance
|
||||||
|
|
||||||
|
|
||||||
|
For more information, see the 'Calculate the action_target' section in
|
||||||
|
`External Actions Spec <https://specs.openstack.org/openstack/vitrage-specs/specs/pike/external-actions.html>`_
|
||||||
|
|
||||||
|
|
||||||
Template validation status codes
|
Template validation status codes
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
@ -379,17 +404,6 @@ This can be used along with nova notifier to call force_down for a host
|
|||||||
Future support & Open Issues
|
Future support & Open Issues
|
||||||
============================
|
============================
|
||||||
|
|
||||||
Negation
|
|
||||||
--------
|
|
||||||
We need to support a "not" operator, that indicates the following expression
|
|
||||||
must not be satisfied in order for the condition to be met. "not" should apply
|
|
||||||
to relationships, not entities. Then we could have a condition like
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
condition: host_contains_instance and not alarm_on_instance
|
|
||||||
|
|
||||||
|
|
||||||
Inequality
|
Inequality
|
||||||
----------
|
----------
|
||||||
Consider a template that has two entities of the same category+type, say E1 and
|
Consider a template that has two entities of the same category+type, say E1 and
|
||||||
|
189
vitrage/evaluator/condition.py
Normal file
189
vitrage/evaluator/condition.py
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# 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_dfn
|
||||||
|
from sympy import Symbol
|
||||||
|
|
||||||
|
|
||||||
|
ConditionVar = namedtuple('ConditionVar', ['symbol_name', 'positive'])
|
||||||
|
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
|
||||||
|
|
||||||
|
|
||||||
|
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(' or ', '|')
|
||||||
|
condition_str = condition_str.replace(' not ', '~')
|
||||||
|
condition_str = condition_str.replace('not ', '~')
|
||||||
|
|
||||||
|
return sympy_to_dfn(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)
|
@ -13,22 +13,20 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from collections import namedtuple
|
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_dfn
|
|
||||||
from sympy import Symbol
|
|
||||||
|
|
||||||
from vitrage.common.constants import EdgeProperties as EProps
|
from vitrage.common.constants import EdgeProperties as EProps
|
||||||
from vitrage.common.constants import VertexProperties as VProps
|
from vitrage.common.constants import VertexProperties as VProps
|
||||||
from vitrage.common.exception import VitrageError
|
from vitrage.common.exception import VitrageError
|
||||||
|
from vitrage.evaluator.condition import EdgeDescription
|
||||||
|
from vitrage.evaluator.condition import get_condition_common_targets
|
||||||
|
from vitrage.evaluator.condition import parse_condition
|
||||||
|
from vitrage.evaluator.condition import SymbolResolver
|
||||||
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
||||||
from vitrage.graph.algo_driver.sub_graph_matching import NEG_CONDITION
|
from vitrage.graph.algo_driver.sub_graph_matching import NEG_CONDITION
|
||||||
from vitrage.graph.driver.networkx_graph import NXGraph
|
from vitrage.graph.driver.networkx_graph import NXGraph
|
||||||
from vitrage.graph import Edge
|
from vitrage.graph import Edge
|
||||||
from vitrage.graph import Vertex
|
from vitrage.graph import Vertex
|
||||||
|
|
||||||
ConditionVar = namedtuple('ConditionVar', ['symbol_name', 'positive'])
|
|
||||||
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
|
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
|
||||||
Scenario = namedtuple('Scenario', ['id',
|
Scenario = namedtuple('Scenario', ['id',
|
||||||
'condition',
|
'condition',
|
||||||
@ -37,8 +35,6 @@ Scenario = namedtuple('Scenario', ['id',
|
|||||||
'entities',
|
'entities',
|
||||||
'relationships'
|
'relationships'
|
||||||
])
|
])
|
||||||
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
|
|
||||||
|
|
||||||
ENTITY = 'entity'
|
ENTITY = 'entity'
|
||||||
RELATIONSHIP = 'relationship'
|
RELATIONSHIP = 'relationship'
|
||||||
|
|
||||||
@ -195,8 +191,8 @@ class TemplateData(object):
|
|||||||
self._relationships = {}
|
self._relationships = {}
|
||||||
|
|
||||||
self.scenario_id = scenario_id
|
self.scenario_id = scenario_id
|
||||||
self.condition = self._parse_condition(
|
self.condition = parse_condition(scenario_dict[TFields.CONDITION])
|
||||||
scenario_dict[TFields.CONDITION])
|
self.valid_target = self._calculate_missing_action_target()
|
||||||
self.actions = self._build_actions(scenario_dict[TFields.ACTIONS])
|
self.actions = self._build_actions(scenario_dict[TFields.ACTIONS])
|
||||||
self.subgraphs = TemplateData.SubGraph.from_condition(
|
self.subgraphs = TemplateData.SubGraph.from_condition(
|
||||||
self.condition,
|
self.condition,
|
||||||
@ -266,90 +262,21 @@ class TemplateData(object):
|
|||||||
target=target,
|
target=target,
|
||||||
edge=relationship.edge)
|
edge=relationship.edge)
|
||||||
|
|
||||||
@staticmethod
|
def _build_actions(self, actions_def):
|
||||||
def _build_actions(actions_def):
|
|
||||||
|
|
||||||
actions = []
|
actions = []
|
||||||
for action_def in actions_def:
|
for action_def in actions_def:
|
||||||
|
|
||||||
action_dict = action_def[TFields.ACTION]
|
action_dict = action_def[TFields.ACTION]
|
||||||
action_type = action_dict[TFields.ACTION_TYPE]
|
action_type = action_dict[TFields.ACTION_TYPE]
|
||||||
targets = action_dict.get(TFields.ACTION_TARGET, {})
|
targets = action_dict.get(TFields.ACTION_TARGET,
|
||||||
|
self.valid_target)
|
||||||
properties = action_dict.get(TFields.PROPERTIES, {})
|
properties = action_dict.get(TFields.PROPERTIES, {})
|
||||||
|
|
||||||
actions.append(ActionSpecs(action_type, targets, properties))
|
actions.append(ActionSpecs(action_type, targets, properties))
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def _parse_condition(self, 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 = self.convert_to_dnf_format(condition_str)
|
|
||||||
|
|
||||||
if isinstance(condition_dnf, Or):
|
|
||||||
return self._extract_or_condition(condition_dnf)
|
|
||||||
|
|
||||||
if isinstance(condition_dnf, And):
|
|
||||||
return [self._extract_and_condition(condition_dnf)]
|
|
||||||
|
|
||||||
if isinstance(condition_dnf, Not):
|
|
||||||
return [(self._extract_not_condition_var(condition_dnf))]
|
|
||||||
|
|
||||||
if isinstance(condition_dnf, Symbol):
|
|
||||||
return [[(self._extract_condition_var(condition_dnf, True))]]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def convert_to_dnf_format(condition_str):
|
|
||||||
|
|
||||||
condition_str = condition_str.replace(' and ', '&')
|
|
||||||
condition_str = condition_str.replace(' or ', '|')
|
|
||||||
condition_str = condition_str.replace(' not ', '~')
|
|
||||||
condition_str = condition_str.replace('not ', '~')
|
|
||||||
|
|
||||||
return sympy_to_dfn(condition_str)
|
|
||||||
|
|
||||||
def _extract_or_condition(self, or_condition):
|
|
||||||
|
|
||||||
vars_ = []
|
|
||||||
for var in or_condition.args:
|
|
||||||
|
|
||||||
if isinstance(var, And):
|
|
||||||
vars_.append(self._extract_and_condition(var))
|
|
||||||
else:
|
|
||||||
is_symbol = isinstance(var, Symbol)
|
|
||||||
vars_.append([self._extract_condition_var(var, is_symbol)])
|
|
||||||
|
|
||||||
return vars_
|
|
||||||
|
|
||||||
def _extract_and_condition(self, and_condition):
|
|
||||||
return [self._extract_condition_var(arg, isinstance(arg, Symbol))
|
|
||||||
for arg in and_condition.args]
|
|
||||||
|
|
||||||
def _extract_not_condition_var(self, not_condition):
|
|
||||||
return [self._extract_condition_var(arg, False)
|
|
||||||
for arg in not_condition.args]
|
|
||||||
|
|
||||||
def _extract_condition_var(self, symbol, positive):
|
|
||||||
if isinstance(symbol, Not):
|
|
||||||
return self._extract_not_condition_var(symbol)[0]
|
|
||||||
return ConditionVar(symbol.name, positive)
|
|
||||||
|
|
||||||
def _extract_var_and_update_index(self, symbol_name):
|
def _extract_var_and_update_index(self, symbol_name):
|
||||||
|
|
||||||
if symbol_name in self._template_relationships:
|
if symbol_name in self._template_relationships:
|
||||||
@ -365,6 +292,36 @@ class TemplateData(object):
|
|||||||
self._entities[symbol_name] = entity
|
self._entities[symbol_name] = entity
|
||||||
return entity, ENTITY
|
return entity, ENTITY
|
||||||
|
|
||||||
|
def _calculate_missing_action_target(self):
|
||||||
|
"""Return a vertex that can be used as an action target.
|
||||||
|
|
||||||
|
External actions like execute_mistral do not have an explicit
|
||||||
|
action target. This parameter is a must for the sub-graph matching
|
||||||
|
algorithm. If it is missing, we would like to select an arbitrary
|
||||||
|
target from the condition.
|
||||||
|
|
||||||
|
"""
|
||||||
|
definition_index = self._template_entities.copy()
|
||||||
|
definition_index.update(self._template_relationships)
|
||||||
|
targets = \
|
||||||
|
get_condition_common_targets(self.condition,
|
||||||
|
definition_index,
|
||||||
|
self.TemplateDataSymbolResolver())
|
||||||
|
return {TFields.TARGET: targets.pop()} if targets else None
|
||||||
|
|
||||||
|
class TemplateDataSymbolResolver(SymbolResolver):
|
||||||
|
def is_relationship(self, symbol):
|
||||||
|
return isinstance(symbol, EdgeDescription)
|
||||||
|
|
||||||
|
def get_relationship_source_id(self, relationship):
|
||||||
|
return relationship.source.vertex_id
|
||||||
|
|
||||||
|
def get_relationship_target_id(self, relationship):
|
||||||
|
return relationship.target.vertex_id
|
||||||
|
|
||||||
|
def get_entity_id(self, entity):
|
||||||
|
return entity.vertex_id
|
||||||
|
|
||||||
class SubGraph(object):
|
class SubGraph(object):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_condition(cls, condition, extract_var):
|
def from_condition(cls, condition, extract_var):
|
||||||
|
@ -20,7 +20,11 @@ from six.moves import reduce
|
|||||||
|
|
||||||
from vitrage.common.constants import EdgeProperties as EProps
|
from vitrage.common.constants import EdgeProperties as EProps
|
||||||
from vitrage.evaluator.actions.base import ActionType
|
from vitrage.evaluator.actions.base import ActionType
|
||||||
from vitrage.evaluator.template_data import TemplateData
|
from vitrage.evaluator.condition import convert_to_dnf_format
|
||||||
|
from vitrage.evaluator.condition import get_condition_common_targets
|
||||||
|
from vitrage.evaluator.condition import is_condition_include_positive_clause
|
||||||
|
from vitrage.evaluator.condition import parse_condition
|
||||||
|
from vitrage.evaluator.condition import SymbolResolver
|
||||||
from vitrage.evaluator.template_fields import TemplateFields
|
from vitrage.evaluator.template_fields import TemplateFields
|
||||||
from vitrage.evaluator.template_validation.content. \
|
from vitrage.evaluator.template_validation.content. \
|
||||||
add_causal_relationship_validator import AddCausalRelationshipValidator
|
add_causal_relationship_validator import AddCausalRelationshipValidator
|
||||||
@ -30,6 +34,8 @@ from vitrage.evaluator.template_validation.content.base import \
|
|||||||
get_content_fault_result
|
get_content_fault_result
|
||||||
from vitrage.evaluator.template_validation.content.base import \
|
from vitrage.evaluator.template_validation.content.base import \
|
||||||
validate_template_id
|
validate_template_id
|
||||||
|
from vitrage.evaluator.template_validation.content.execute_mistral_validator \
|
||||||
|
import ExecuteMistralValidator
|
||||||
from vitrage.evaluator.template_validation.content.mark_down_validator \
|
from vitrage.evaluator.template_validation.content.mark_down_validator \
|
||||||
import MarkDownValidator
|
import MarkDownValidator
|
||||||
from vitrage.evaluator.template_validation.content.raise_alarm_validator \
|
from vitrage.evaluator.template_validation.content.raise_alarm_validator \
|
||||||
@ -150,7 +156,7 @@ def _validate_scenarios(scenarios, definitions_index):
|
|||||||
|
|
||||||
def _validate_scenario_condition(condition, definitions_index):
|
def _validate_scenario_condition(condition, definitions_index):
|
||||||
try:
|
try:
|
||||||
dnf_result = TemplateData.ScenarioData.convert_to_dnf_format(condition)
|
dnf_result = convert_to_dnf_format(condition)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error('%s status code: %s' % (status_msgs[85], 85))
|
LOG.error('%s status code: %s' % (status_msgs[85], 85))
|
||||||
return get_content_fault_result(85)
|
return get_content_fault_result(85)
|
||||||
@ -163,11 +169,11 @@ def _validate_scenario_condition(condition, definitions_index):
|
|||||||
|
|
||||||
# template id validation
|
# template id validation
|
||||||
values_to_replace = ' and ', ' or ', ' not ', 'not ', '(', ')'
|
values_to_replace = ' and ', ' or ', ' not ', 'not ', '(', ')'
|
||||||
condition = reduce(lambda cond, v: cond.replace(v, ' '),
|
condition_vars = reduce(lambda cond, v: cond.replace(v, ' '),
|
||||||
values_to_replace,
|
values_to_replace,
|
||||||
condition)
|
condition)
|
||||||
|
|
||||||
for condition_var in condition.split(' '):
|
for condition_var in condition_vars.split(' '):
|
||||||
|
|
||||||
if len(condition_var.strip()) == 0:
|
if len(condition_var.strip()) == 0:
|
||||||
continue
|
continue
|
||||||
@ -176,9 +182,49 @@ def _validate_scenario_condition(condition, definitions_index):
|
|||||||
if not result.is_valid_config:
|
if not result.is_valid_config:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# condition structure validation
|
||||||
|
condition_structure_result = \
|
||||||
|
validate_condition_structure(parse_condition(condition),
|
||||||
|
definitions_index)
|
||||||
|
if not condition_structure_result.is_valid_config:
|
||||||
|
return condition_structure_result
|
||||||
|
|
||||||
return get_content_correct_result()
|
return get_content_correct_result()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_condition_structure(condition_dnf, definitions_index):
|
||||||
|
result = validate_condition_includes_positive_clause(condition_dnf)
|
||||||
|
if not result.is_valid_config:
|
||||||
|
return result
|
||||||
|
|
||||||
|
common_targets = get_condition_common_targets(condition_dnf,
|
||||||
|
definitions_index,
|
||||||
|
TemplateSymbolResolver())
|
||||||
|
|
||||||
|
return get_content_correct_result() if common_targets \
|
||||||
|
else get_content_fault_result(135)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_condition_includes_positive_clause(condition):
|
||||||
|
return get_content_correct_result() if \
|
||||||
|
is_condition_include_positive_clause(condition) \
|
||||||
|
else get_content_fault_result(134)
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateSymbolResolver(SymbolResolver):
|
||||||
|
def is_relationship(self, symbol):
|
||||||
|
return TemplateFields.RELATIONSHIP_TYPE in symbol
|
||||||
|
|
||||||
|
def get_relationship_source_id(self, relationship):
|
||||||
|
return relationship[TemplateFields.SOURCE]
|
||||||
|
|
||||||
|
def get_relationship_target_id(self, relationship):
|
||||||
|
return relationship[TemplateFields.TARGET]
|
||||||
|
|
||||||
|
def get_entity_id(self, entity):
|
||||||
|
return entity[TemplateFields.TEMPLATE_ID]
|
||||||
|
|
||||||
|
|
||||||
def _validate_not_condition(dnf_result, definitions_index):
|
def _validate_not_condition(dnf_result, definitions_index):
|
||||||
"""Not operator validation
|
"""Not operator validation
|
||||||
|
|
||||||
@ -233,6 +279,7 @@ def _validate_scenario_action(action, definitions_index):
|
|||||||
ActionType.SET_STATE: SetStateValidator(),
|
ActionType.SET_STATE: SetStateValidator(),
|
||||||
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator(),
|
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator(),
|
||||||
ActionType.MARK_DOWN: MarkDownValidator(),
|
ActionType.MARK_DOWN: MarkDownValidator(),
|
||||||
|
ActionType.EXECUTE_MISTRAL: ExecuteMistralValidator(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if action_type not in action_validators:
|
if action_type not in action_validators:
|
||||||
|
@ -76,6 +76,7 @@ status_msgs = {
|
|||||||
' \'target_action\' block.',
|
' \'target_action\' block.',
|
||||||
132: 'add_causal_relationship action requires action_target to be ALARM',
|
132: 'add_causal_relationship action requires action_target to be ALARM',
|
||||||
133: 'execute_mistral action must contain workflow field in properties '
|
133: 'execute_mistral action must contain workflow field in properties '
|
||||||
'block'
|
'block',
|
||||||
|
134: 'condition can not contain only \'not\' clauses',
|
||||||
|
135: 'condition must contain a common entity for all \'or\' clauses',
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
metadata:
|
||||||
|
name: complex1
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm6
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm6
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm4_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm5_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm6
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm6_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_instance or (alarm5_on_instance and alarm6_on_instance)
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,55 @@
|
|||||||
|
metadata:
|
||||||
|
name: complex2
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm4_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : alarm5_on_host
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: host
|
||||||
|
template_id : alarm4_on_host
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_host or (alarm4_on_instance and alarm5_on_host)
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,57 @@
|
|||||||
|
metadata:
|
||||||
|
name: complex_not
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm6
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm6
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm4_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm5_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm6
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm6_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_instance or (alarm5_on_instance and not alarm6_on_instance)
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,57 @@
|
|||||||
|
metadata:
|
||||||
|
name: complex_not_unsupported
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm6
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm6
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm4_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm5_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm6
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm6_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_instance or (not alarm5_on_instance and not alarm6_on_instance)
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,35 @@
|
|||||||
|
metadata:
|
||||||
|
name: not_edge_unsupported
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm1
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm1
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm1
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm1_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: not alarm1_on_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarm_x
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_instance
|
@ -0,0 +1,46 @@
|
|||||||
|
metadata:
|
||||||
|
name: not_or_unsupported
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm2
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm3
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm3
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance3
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm2
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm2_on_instance3
|
||||||
|
- relationship:
|
||||||
|
source: alarm3
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm3_on_instance3
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: not alarm2_on_instance3 or not alarm3_on_instance3
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance3
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,46 @@
|
|||||||
|
metadata:
|
||||||
|
name: not_or_unsupported2
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm2
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm3
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm3
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance3
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm2
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm2_on_instance3
|
||||||
|
- relationship:
|
||||||
|
source: alarm3
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm3_on_instance3
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm2_on_instance3 or not alarm3_on_instance3
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance3
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,35 @@
|
|||||||
|
metadata:
|
||||||
|
name: one_edge
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm1
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm1
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm1
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm1_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm1_on_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarm_x
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_instance
|
@ -0,0 +1,24 @@
|
|||||||
|
metadata:
|
||||||
|
name: one_vertex
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance2
|
||||||
|
relationships:
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: instance2
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarm_x
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance2
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_instance
|
@ -0,0 +1,44 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_and
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm2
|
||||||
|
template_id: alarm2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm3
|
||||||
|
template_id: alarm3
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm2
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm2_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm3
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm3_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm2_on_instance and alarm3_on_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,53 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_and2
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm2
|
||||||
|
template_id: alarm2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm3
|
||||||
|
template_id: alarm3
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm2
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm2_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm3
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm3_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: host
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : host_contains_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm2_on_instance and alarm3_on_instance and host_contains_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,46 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_or
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm2
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm2
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: alarm3
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm3
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance3
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm2
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm2_on_instance3
|
||||||
|
- relationship:
|
||||||
|
source: alarm3
|
||||||
|
relationship_type: on
|
||||||
|
target: instance3
|
||||||
|
template_id : alarm3_on_instance3
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm2_on_instance3 or alarm3_on_instance3
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance3
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,57 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_or2
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm6
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm6
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm4_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm5_on_instance
|
||||||
|
- relationship:
|
||||||
|
source: alarm6
|
||||||
|
relationship_type: on
|
||||||
|
target: instance
|
||||||
|
template_id : alarm6_on_instance
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_instance or alarm5_on_instance or alarm6_on_instance
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,46 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_or3
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm7
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm7
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm8
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm8
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance4
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm7
|
||||||
|
relationship_type: on
|
||||||
|
target: instance4
|
||||||
|
template_id : alarm7_on_instance4
|
||||||
|
- relationship:
|
||||||
|
source: alarm8
|
||||||
|
relationship_type: on
|
||||||
|
target: instance4
|
||||||
|
template_id : alarm8_on_instance4
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: instance4 or alarm7_on_instance4 or alarm8_on_instance4
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance4
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -0,0 +1,55 @@
|
|||||||
|
metadata:
|
||||||
|
name: simple_or_unsupported
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm4
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm4
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: alarm5
|
||||||
|
severity: WARNING
|
||||||
|
template_id: alarm5
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance1
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.instance
|
||||||
|
template_id: instance2
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: alarm4
|
||||||
|
relationship_type: on
|
||||||
|
target: instance1
|
||||||
|
template_id : alarm4_on_instance1
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance1
|
||||||
|
template_id : alarm5_on_instance1
|
||||||
|
- relationship:
|
||||||
|
source: alarm5
|
||||||
|
relationship_type: on
|
||||||
|
target: instance2
|
||||||
|
template_id : alarm5_on_instance2
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: alarm4_on_instance1 or alarm5_on_instance1 or alarm5_on_instance2
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: raise_alarm
|
||||||
|
properties:
|
||||||
|
alarm_name: alarmx
|
||||||
|
severity: WARNING
|
||||||
|
action_target:
|
||||||
|
target: instance1
|
||||||
|
- action:
|
||||||
|
action_type: execute_mistral
|
||||||
|
properties:
|
||||||
|
workflow: wf_3
|
@ -20,7 +20,7 @@ definitions:
|
|||||||
template_id : alarm_on_port
|
template_id : alarm_on_port
|
||||||
scenarios:
|
scenarios:
|
||||||
- scenario:
|
- scenario:
|
||||||
condition: not alarm_on_port
|
condition: port and not alarm_on_port
|
||||||
actions:
|
actions:
|
||||||
- action:
|
- action:
|
||||||
action_type: raise_alarm
|
action_type: raise_alarm
|
||||||
|
@ -11,20 +11,20 @@ definitions:
|
|||||||
- entity:
|
- entity:
|
||||||
category: RESOURCE
|
category: RESOURCE
|
||||||
type: nova.host
|
type: nova.host
|
||||||
template_id: resource
|
template_id: host
|
||||||
relationships:
|
relationships:
|
||||||
- relationship:
|
- relationship:
|
||||||
source: alarm
|
source: alarm
|
||||||
target: resource
|
target: host
|
||||||
relationship_type: on
|
relationship_type: on
|
||||||
template_id : alarm_on_host
|
template_id : alarm_on_host
|
||||||
scenarios:
|
scenarios:
|
||||||
- scenario:
|
- scenario:
|
||||||
condition: not alarm_on_host
|
condition: host and not alarm_on_host
|
||||||
actions:
|
actions:
|
||||||
- action:
|
- action:
|
||||||
action_type: set_state
|
action_type: set_state
|
||||||
properties:
|
properties:
|
||||||
state: SUBOPTIMAL
|
state: SUBOPTIMAL
|
||||||
action_target:
|
action_target:
|
||||||
target: resource
|
target: host
|
||||||
|
@ -47,7 +47,7 @@ definitions:
|
|||||||
template_id : alarm_on_instance
|
template_id : alarm_on_instance
|
||||||
scenarios:
|
scenarios:
|
||||||
- scenario:
|
- scenario:
|
||||||
condition: zone_contains_host or host_contains_instance and not host_contains_instance or not port_attached_instance
|
condition: alarm_on_instance or host_contains_instance and not zone_contains_host or port_attached_instance and not zone_contains_host
|
||||||
actions:
|
actions:
|
||||||
- action:
|
- action:
|
||||||
action_type: set_state
|
action_type: set_state
|
||||||
|
@ -23,6 +23,9 @@ from vitrage.tests.unit.evaluator.template_validation.content.base import \
|
|||||||
from vitrage.utils import file as file_utils
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
|
|
||||||
|
CONDITION_TEMPLATES_DIR = '%s/templates/evaluator/conditions/%s'
|
||||||
|
|
||||||
|
|
||||||
class TemplateContentValidatorTest(ValidatorTest):
|
class TemplateContentValidatorTest(ValidatorTest):
|
||||||
|
|
||||||
# noinspection PyPep8Naming
|
# noinspection PyPep8Naming
|
||||||
@ -148,6 +151,72 @@ class TemplateContentValidatorTest(ValidatorTest):
|
|||||||
scenario_dict[TemplateFields.CONDITION] = 'resource and aaa'
|
scenario_dict[TemplateFields.CONDITION] = 'resource and aaa'
|
||||||
self._execute_and_assert_with_fault_result(template, 3)
|
self._execute_and_assert_with_fault_result(template, 3)
|
||||||
|
|
||||||
|
def test_validate_scenario_target_one_edge_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('one_edge.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_one_vertex_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('one_vertex.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('simple_or.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or2_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('simple_or2.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or3_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('simple_or3.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or_unsupported_condition(self):
|
||||||
|
self._execute_condition_template_with_fault_result(
|
||||||
|
'simple_or_unsupported.yaml', 135)
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_and_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('simple_and.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_and2_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result(
|
||||||
|
'simple_and2.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex1_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('complex1.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex2_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result('complex2.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_edge_unsupported_condition(self):
|
||||||
|
self._execute_condition_template_with_fault_result(
|
||||||
|
'not_edge_unsupported.yaml', 134)
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_or_unsupported__condition(self):
|
||||||
|
self._execute_condition_template_with_fault_result(
|
||||||
|
'not_or_unsupported.yaml', 134)
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_or_unsupported2_condition(self):
|
||||||
|
self._execute_condition_template_with_fault_result(
|
||||||
|
'not_or_unsupported2.yaml', 135)
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex_not_condition(self):
|
||||||
|
self._execute_condition_template_with_correct_result(
|
||||||
|
'complex_not.yaml')
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex_not_unsupported_condition(self):
|
||||||
|
self._execute_condition_template_with_fault_result(
|
||||||
|
'complex_not_unsupported.yaml', 135)
|
||||||
|
|
||||||
|
def _execute_condition_template_with_correct_result(self, template_name):
|
||||||
|
template_path = CONDITION_TEMPLATES_DIR % (utils.get_resources_dir(),
|
||||||
|
template_name)
|
||||||
|
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||||
|
self._execute_and_assert_with_correct_result(template_definition)
|
||||||
|
|
||||||
|
def _execute_condition_template_with_fault_result(
|
||||||
|
self, template_name, status_code):
|
||||||
|
template_path = CONDITION_TEMPLATES_DIR % (utils.get_resources_dir(),
|
||||||
|
template_name)
|
||||||
|
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||||
|
self._execute_and_assert_with_fault_result(
|
||||||
|
template_definition, status_code)
|
||||||
|
|
||||||
def _execute_and_assert_with_fault_result(self, template, status_code):
|
def _execute_and_assert_with_fault_result(self, template, status_code):
|
||||||
|
|
||||||
result = validator.content_validation(template)
|
result = validator.content_validation(template)
|
||||||
|
119
vitrage/tests/unit/evaluator/test_condition.py
Normal file
119
vitrage/tests/unit/evaluator/test_condition.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from vitrage.evaluator.condition import EdgeDescription
|
||||||
|
from vitrage.evaluator.condition import SymbolResolver
|
||||||
|
from vitrage.evaluator.template_data import TemplateData
|
||||||
|
from vitrage.evaluator.template_validation.content.template_content_validator \
|
||||||
|
import get_condition_common_targets
|
||||||
|
from vitrage.tests import base
|
||||||
|
from vitrage.tests.mocks import utils
|
||||||
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
|
|
||||||
|
CONDITION_TEMPLATES_DIR = '%s/templates/evaluator/conditions/%s'
|
||||||
|
|
||||||
|
|
||||||
|
class ConditionTest(base.BaseTest):
|
||||||
|
|
||||||
|
def test_validate_scenario_target_one_edge_condition(self):
|
||||||
|
self._check_get_condition_common_targets('one_edge.yaml',
|
||||||
|
['alarm1', 'instance'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_one_vertex_condition(self):
|
||||||
|
self._check_get_condition_common_targets('one_vertex.yaml',
|
||||||
|
['instance2'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or_condition(self):
|
||||||
|
self._check_get_condition_common_targets('simple_or.yaml',
|
||||||
|
['instance3'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or2_condition(self):
|
||||||
|
self._check_get_condition_common_targets('simple_or2.yaml',
|
||||||
|
['instance'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or3_condition(self):
|
||||||
|
self._check_get_condition_common_targets('simple_or3.yaml',
|
||||||
|
['instance4'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_or_unsupported_condition(self):
|
||||||
|
self._check_get_condition_common_targets('simple_or_unsupported.yaml',
|
||||||
|
[])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_and_condition(self):
|
||||||
|
self._check_get_condition_common_targets(
|
||||||
|
'simple_and.yaml', ['alarm2', 'alarm3', 'instance'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_simple_and2_condition(self):
|
||||||
|
self._check_get_condition_common_targets(
|
||||||
|
'simple_and2.yaml', ['alarm2', 'alarm3', 'instance', 'host'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex1_condition(self):
|
||||||
|
self._check_get_condition_common_targets('complex1.yaml', ['instance'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex2_condition(self):
|
||||||
|
self._check_get_condition_common_targets('complex2.yaml',
|
||||||
|
['alarm4', 'host'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_edge_unsupported_condition(self):
|
||||||
|
self._check_get_condition_common_targets('not_edge_unsupported.yaml',
|
||||||
|
[])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_or_unsupported__condition(self):
|
||||||
|
self._check_get_condition_common_targets('not_or_unsupported.yaml',
|
||||||
|
[])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_not_or_unsupported2_condition(self):
|
||||||
|
self._check_get_condition_common_targets('not_or_unsupported2.yaml',
|
||||||
|
[])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex_not_condition(self):
|
||||||
|
self._check_get_condition_common_targets('complex_not.yaml',
|
||||||
|
['instance'])
|
||||||
|
|
||||||
|
def test_validate_scenario_target_complex_not_unsupported_condition(self):
|
||||||
|
self._check_get_condition_common_targets(
|
||||||
|
'complex_not_unsupported.yaml', [])
|
||||||
|
|
||||||
|
def _check_get_condition_common_targets(self,
|
||||||
|
template_name,
|
||||||
|
valid_targets):
|
||||||
|
template_path = CONDITION_TEMPLATES_DIR % (utils.get_resources_dir(),
|
||||||
|
template_name)
|
||||||
|
template_definition = file_utils.load_yaml_file(template_path, True)
|
||||||
|
|
||||||
|
template_data = TemplateData(template_definition)
|
||||||
|
definitions_index = template_data.entities.copy()
|
||||||
|
definitions_index.update(template_data.relationships)
|
||||||
|
|
||||||
|
common_targets = get_condition_common_targets(
|
||||||
|
template_data.scenarios[0].condition,
|
||||||
|
definitions_index,
|
||||||
|
self.ConditionSymbolResolver())
|
||||||
|
|
||||||
|
self.assertIsNotNone(common_targets)
|
||||||
|
self.assertTrue(common_targets == set(valid_targets))
|
||||||
|
|
||||||
|
class ConditionSymbolResolver(SymbolResolver):
|
||||||
|
def is_relationship(self, symbol):
|
||||||
|
return isinstance(symbol, EdgeDescription)
|
||||||
|
|
||||||
|
def get_relationship_source_id(self, relationship):
|
||||||
|
return relationship.source.vertex_id
|
||||||
|
|
||||||
|
def get_relationship_target_id(self, relationship):
|
||||||
|
return relationship.target.vertex_id
|
||||||
|
|
||||||
|
def get_entity_id(self, entity):
|
||||||
|
return entity.vertex_id
|
@ -20,9 +20,9 @@ from vitrage.datasources.nagios import NAGIOS_DATASOURCE
|
|||||||
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
|
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
|
||||||
from vitrage.entity_graph.mappings.operational_resource_state import \
|
from vitrage.entity_graph.mappings.operational_resource_state import \
|
||||||
OperationalResourceState
|
OperationalResourceState
|
||||||
|
from vitrage.evaluator.condition import ConditionVar
|
||||||
from vitrage.evaluator.scenario_evaluator import ActionType
|
from vitrage.evaluator.scenario_evaluator import ActionType
|
||||||
from vitrage.evaluator.template_data import ActionSpecs
|
from vitrage.evaluator.template_data import ActionSpecs
|
||||||
from vitrage.evaluator.template_data import ConditionVar
|
|
||||||
from vitrage.evaluator.template_data import EdgeDescription
|
from vitrage.evaluator.template_data import EdgeDescription
|
||||||
from vitrage.evaluator.template_data import Scenario
|
from vitrage.evaluator.template_data import Scenario
|
||||||
from vitrage.evaluator.template_data import TemplateData
|
from vitrage.evaluator.template_data import TemplateData
|
||||||
|
Loading…
x
Reference in New Issue
Block a user