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 |
| | 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.
- "or" - indicates at least one expression must be satisfied in order for the
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.
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 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
--------------------------------
@ -379,17 +404,6 @@ This can be used along with nova notifier to call force_down for a host
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
----------
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.
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 VertexProperties as VProps
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.graph.algo_driver.sub_graph_matching import NEG_CONDITION
from vitrage.graph.driver.networkx_graph import NXGraph
from vitrage.graph import Edge
from vitrage.graph import Vertex
ConditionVar = namedtuple('ConditionVar', ['symbol_name', 'positive'])
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
Scenario = namedtuple('Scenario', ['id',
'condition',
@ -37,8 +35,6 @@ Scenario = namedtuple('Scenario', ['id',
'entities',
'relationships'
])
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
ENTITY = 'entity'
RELATIONSHIP = 'relationship'
@ -195,8 +191,8 @@ class TemplateData(object):
self._relationships = {}
self.scenario_id = scenario_id
self.condition = self._parse_condition(
scenario_dict[TFields.CONDITION])
self.condition = parse_condition(scenario_dict[TFields.CONDITION])
self.valid_target = self._calculate_missing_action_target()
self.actions = self._build_actions(scenario_dict[TFields.ACTIONS])
self.subgraphs = TemplateData.SubGraph.from_condition(
self.condition,
@ -266,90 +262,21 @@ class TemplateData(object):
target=target,
edge=relationship.edge)
@staticmethod
def _build_actions(actions_def):
def _build_actions(self, actions_def):
actions = []
for action_def in actions_def:
action_dict = action_def[TFields.ACTION]
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, {})
actions.append(ActionSpecs(action_type, targets, properties))
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):
if symbol_name in self._template_relationships:
@ -365,6 +292,36 @@ class TemplateData(object):
self._entities[symbol_name] = 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):
@classmethod
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.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_validation.content. \
add_causal_relationship_validator import AddCausalRelationshipValidator
@ -30,6 +34,8 @@ from vitrage.evaluator.template_validation.content.base import \
get_content_fault_result
from vitrage.evaluator.template_validation.content.base import \
validate_template_id
from vitrage.evaluator.template_validation.content.execute_mistral_validator \
import ExecuteMistralValidator
from vitrage.evaluator.template_validation.content.mark_down_validator \
import MarkDownValidator
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):
try:
dnf_result = TemplateData.ScenarioData.convert_to_dnf_format(condition)
dnf_result = convert_to_dnf_format(condition)
except Exception:
LOG.error('%s status code: %s' % (status_msgs[85], 85))
return get_content_fault_result(85)
@ -163,11 +169,11 @@ def _validate_scenario_condition(condition, definitions_index):
# template id validation
values_to_replace = ' and ', ' or ', ' not ', 'not ', '(', ')'
condition = reduce(lambda cond, v: cond.replace(v, ' '),
values_to_replace,
condition)
condition_vars = reduce(lambda cond, v: cond.replace(v, ' '),
values_to_replace,
condition)
for condition_var in condition.split(' '):
for condition_var in condition_vars.split(' '):
if len(condition_var.strip()) == 0:
continue
@ -176,9 +182,49 @@ def _validate_scenario_condition(condition, definitions_index):
if not result.is_valid_config:
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()
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):
"""Not operator validation
@ -233,6 +279,7 @@ def _validate_scenario_action(action, definitions_index):
ActionType.SET_STATE: SetStateValidator(),
ActionType.ADD_CAUSAL_RELATIONSHIP: AddCausalRelationshipValidator(),
ActionType.MARK_DOWN: MarkDownValidator(),
ActionType.EXECUTE_MISTRAL: ExecuteMistralValidator(),
}
if action_type not in action_validators:

View File

@ -76,6 +76,7 @@ status_msgs = {
' \'target_action\' block.',
132: 'add_causal_relationship action requires action_target to be ALARM',
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
scenarios:
- scenario:
condition: not alarm_on_port
condition: port and not alarm_on_port
actions:
- action:
action_type: raise_alarm

View File

@ -11,20 +11,20 @@ definitions:
- entity:
category: RESOURCE
type: nova.host
template_id: resource
template_id: host
relationships:
- relationship:
source: alarm
target: resource
target: host
relationship_type: on
template_id : alarm_on_host
scenarios:
- scenario:
condition: not alarm_on_host
condition: host and not alarm_on_host
actions:
- action:
action_type: set_state
properties:
state: SUBOPTIMAL
action_target:
target: resource
target: host

View File

@ -47,7 +47,7 @@ definitions:
template_id : alarm_on_instance
scenarios:
- 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:
- action:
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
CONDITION_TEMPLATES_DIR = '%s/templates/evaluator/conditions/%s'
class TemplateContentValidatorTest(ValidatorTest):
# noinspection PyPep8Naming
@ -148,6 +151,72 @@ class TemplateContentValidatorTest(ValidatorTest):
scenario_dict[TemplateFields.CONDITION] = 'resource and aaa'
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):
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.entity_graph.mappings.operational_resource_state import \
OperationalResourceState
from vitrage.evaluator.condition import ConditionVar
from vitrage.evaluator.scenario_evaluator import ActionType
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 Scenario
from vitrage.evaluator.template_data import TemplateData