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:
Ifat Afek 2017-07-20 11:00:58 +00:00
parent b8e20918e9
commit df289c2933
27 changed files with 1222 additions and 108 deletions

View File

@ -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 | |
+------------------+---------------------------------------------------------+-------------------------------+

View File

@ -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

View 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)

View File

@ -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):

View File

@ -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:

View File

@ -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',
} }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View 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

View File

@ -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