From df289c293347e05f79bdeb6389dca7d760e15bef Mon Sep 17 00:00:00 2001 From: Ifat Afek Date: Thu, 20 Jul 2017 11:00:58 +0000 Subject: [PATCH] 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 --- .../template_validation_status_code.rst | 5 + doc/source/vitrage-template-format.rst | 36 +++- vitrage/evaluator/condition.py | 189 ++++++++++++++++++ vitrage/evaluator/template_data.py | 121 ++++------- .../content/template_content_validator.py | 59 +++++- .../template_validation/status_messages.py | 5 +- .../evaluator/conditions/complex1.yaml | 57 ++++++ .../evaluator/conditions/complex2.yaml | 55 +++++ .../evaluator/conditions/complex_not.yaml | 57 ++++++ .../conditions/complex_not_unsupported.yaml | 57 ++++++ .../conditions/not_edge_unsupported.yaml | 35 ++++ .../conditions/not_or_unsupported.yaml | 46 +++++ .../conditions/not_or_unsupported2.yaml | 46 +++++ .../evaluator/conditions/one_edge.yaml | 35 ++++ .../evaluator/conditions/one_vertex.yaml | 24 +++ .../evaluator/conditions/simple_and.yaml | 44 ++++ .../evaluator/conditions/simple_and2.yaml | 53 +++++ .../evaluator/conditions/simple_or.yaml | 46 +++++ .../evaluator/conditions/simple_or2.yaml | 57 ++++++ .../evaluator/conditions/simple_or3.yaml | 46 +++++ .../conditions/simple_or_unsupported.yaml | 55 +++++ .../simple_not_operator_deduced_alarm.yaml | 2 +- .../basic_correct_not_condition.yaml | 8 +- .../complicated_correct_not_condition.yaml | 2 +- .../test_template_content_validator.py | 69 +++++++ .../tests/unit/evaluator/test_condition.py | 119 +++++++++++ .../unit/evaluator/test_template_data.py | 2 +- 27 files changed, 1222 insertions(+), 108 deletions(-) create mode 100644 vitrage/evaluator/condition.py create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/complex1.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/complex2.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/complex_not.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/complex_not_unsupported.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/not_edge_unsupported.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported2.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/one_edge.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/one_vertex.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_and.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_and2.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_or.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_or2.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_or3.yaml create mode 100644 vitrage/tests/resources/templates/evaluator/conditions/simple_or_unsupported.yaml create mode 100644 vitrage/tests/unit/evaluator/test_condition.py diff --git a/doc/source/template_validation_status_code.rst b/doc/source/template_validation_status_code.rst index be70b8220..a12b9a7c8 100644 --- a/doc/source/template_validation_status_code.rst +++ b/doc/source/template_validation_status_code.rst @@ -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 | | ++------------------+---------------------------------------------------------+-------------------------------+ diff --git a/doc/source/vitrage-template-format.rst b/doc/source/vitrage-template-format.rst index a6a2da3c9..e853dacea 100644 --- a/doc/source/vitrage-template-format.rst +++ b/doc/source/vitrage-template-format.rst @@ -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 `_ + + 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 diff --git a/vitrage/evaluator/condition.py b/vitrage/evaluator/condition.py new file mode 100644 index 000000000..10ce0d654 --- /dev/null +++ b/vitrage/evaluator/condition.py @@ -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) diff --git a/vitrage/evaluator/template_data.py b/vitrage/evaluator/template_data.py index cf7740c4b..86a21a22f 100644 --- a/vitrage/evaluator/template_data.py +++ b/vitrage/evaluator/template_data.py @@ -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): diff --git a/vitrage/evaluator/template_validation/content/template_content_validator.py b/vitrage/evaluator/template_validation/content/template_content_validator.py index cf49698c8..32de1c1e5 100644 --- a/vitrage/evaluator/template_validation/content/template_content_validator.py +++ b/vitrage/evaluator/template_validation/content/template_content_validator.py @@ -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: diff --git a/vitrage/evaluator/template_validation/status_messages.py b/vitrage/evaluator/template_validation/status_messages.py index dfddab409..2bb57e57b 100644 --- a/vitrage/evaluator/template_validation/status_messages.py +++ b/vitrage/evaluator/template_validation/status_messages.py @@ -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', } diff --git a/vitrage/tests/resources/templates/evaluator/conditions/complex1.yaml b/vitrage/tests/resources/templates/evaluator/conditions/complex1.yaml new file mode 100644 index 000000000..ac63eed67 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/complex1.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/complex2.yaml b/vitrage/tests/resources/templates/evaluator/conditions/complex2.yaml new file mode 100644 index 000000000..3ec7d335f --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/complex2.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/complex_not.yaml b/vitrage/tests/resources/templates/evaluator/conditions/complex_not.yaml new file mode 100644 index 000000000..5e1e87305 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/complex_not.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/complex_not_unsupported.yaml b/vitrage/tests/resources/templates/evaluator/conditions/complex_not_unsupported.yaml new file mode 100644 index 000000000..591c7ecbc --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/complex_not_unsupported.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/not_edge_unsupported.yaml b/vitrage/tests/resources/templates/evaluator/conditions/not_edge_unsupported.yaml new file mode 100644 index 000000000..3fac0c874 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/not_edge_unsupported.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported.yaml b/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported.yaml new file mode 100644 index 000000000..1712b9d94 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported2.yaml b/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported2.yaml new file mode 100644 index 000000000..c65513f92 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/not_or_unsupported2.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/one_edge.yaml b/vitrage/tests/resources/templates/evaluator/conditions/one_edge.yaml new file mode 100644 index 000000000..62ab8f7dd --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/one_edge.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/one_vertex.yaml b/vitrage/tests/resources/templates/evaluator/conditions/one_vertex.yaml new file mode 100644 index 000000000..02d9bde9f --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/one_vertex.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_and.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_and.yaml new file mode 100644 index 000000000..daac1f784 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_and.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_and2.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_and2.yaml new file mode 100644 index 000000000..f3dea3310 --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_and2.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_or.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_or.yaml new file mode 100644 index 000000000..e3bf78eeb --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_or.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_or2.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_or2.yaml new file mode 100644 index 000000000..a245e8d2a --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_or2.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_or3.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_or3.yaml new file mode 100644 index 000000000..ae97f8d4d --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_or3.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/conditions/simple_or_unsupported.yaml b/vitrage/tests/resources/templates/evaluator/conditions/simple_or_unsupported.yaml new file mode 100644 index 000000000..6a126144d --- /dev/null +++ b/vitrage/tests/resources/templates/evaluator/conditions/simple_or_unsupported.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/evaluator/simple_not_operator_deduced_alarm.yaml b/vitrage/tests/resources/templates/evaluator/simple_not_operator_deduced_alarm.yaml index 4dc8a0f5d..3bb7b5590 100644 --- a/vitrage/tests/resources/templates/evaluator/simple_not_operator_deduced_alarm.yaml +++ b/vitrage/tests/resources/templates/evaluator/simple_not_operator_deduced_alarm.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/not_operator/basic_correct_not_condition.yaml b/vitrage/tests/resources/templates/not_operator/basic_correct_not_condition.yaml index bc5ccd1a7..a5a9ca538 100644 --- a/vitrage/tests/resources/templates/not_operator/basic_correct_not_condition.yaml +++ b/vitrage/tests/resources/templates/not_operator/basic_correct_not_condition.yaml @@ -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 diff --git a/vitrage/tests/resources/templates/not_operator/complicated_correct_not_condition.yaml b/vitrage/tests/resources/templates/not_operator/complicated_correct_not_condition.yaml index 8ad74d5ff..2888ce63f 100644 --- a/vitrage/tests/resources/templates/not_operator/complicated_correct_not_condition.yaml +++ b/vitrage/tests/resources/templates/not_operator/complicated_correct_not_condition.yaml @@ -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 diff --git a/vitrage/tests/unit/evaluator/template_validation/content/test_template_content_validator.py b/vitrage/tests/unit/evaluator/template_validation/content/test_template_content_validator.py index 5e1cbdadd..5b1ac01dd 100644 --- a/vitrage/tests/unit/evaluator/template_validation/content/test_template_content_validator.py +++ b/vitrage/tests/unit/evaluator/template_validation/content/test_template_content_validator.py @@ -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) diff --git a/vitrage/tests/unit/evaluator/test_condition.py b/vitrage/tests/unit/evaluator/test_condition.py new file mode 100644 index 000000000..096782c0c --- /dev/null +++ b/vitrage/tests/unit/evaluator/test_condition.py @@ -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 diff --git a/vitrage/tests/unit/evaluator/test_template_data.py b/vitrage/tests/unit/evaluator/test_template_data.py index a41ae79ac..057b40aad 100644 --- a/vitrage/tests/unit/evaluator/test_template_data.py +++ b/vitrage/tests/unit/evaluator/test_template_data.py @@ -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