evaluator - loading template files

Change-Id: I588306430296d8df5df102eed257a41713a087e3
This commit is contained in:
Liat Har-Tal
2016-02-21 10:54:56 +00:00
parent 30ffba8565
commit 1e6bacf0fd
11 changed files with 331 additions and 162 deletions

View File

@@ -1,76 +0,0 @@
# Copyright 2016 - 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.
class Scenario(object):
TYPE_ENTITY = 'entity'
TYPE_RELATE = 'relationship'
def __init__(self):
pass
def get_condition(self):
"""Returns the condition for this scenario.
Each condition should be formatted in 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
For details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
:return: condition
"""
entity = 'replace with vertex'
relationship = 'replace with edge'
mock_entity = (entity, self.TYPE_ENTITY, True)
mock_relationship = (relationship, self.TYPE_RELATE, False)
# single "and" clause between entity and relationship
return [(mock_entity, mock_relationship)]
def get_actions(self):
"""Returns the action specifications for this scenario.
:return: list of actions to perform
:rtype: ActionSpecs
"""
action_spec = ActionSpecs()
return [action_spec]
class ActionSpecs(object):
def get_type(self):
return 'action type str'
def get_targets(self):
"""Returns dict of template_ids to apply action on
:return: dict of string:template_id
:rtype: dict
"""
# e.g., for adding edge, need two ids. for alarms, will need only one.
return {'source': 'source template_id',
'target': 'target template_id'}
def get_properties(self):
"""Returns the properties relevant to the action.
:return: dictionary of properties relevant to the action.
:rtype: dict
"""
return {'prop_key': 'prop_val'}

View File

@@ -0,0 +1,73 @@
# Copyright 2016 - 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 oslo_log import log
from vitrage.common import file_utils
from vitrage.evaluator.template import Template
from vitrage.evaluator.template_syntax_validator import syntax_validate
LOG = log.getLogger(__name__)
action_types = {
'RAISE_ALARM': 'raise_alarm',
'ADD_CAUSAL_RELATIONSHIP': 'add_causal_relationship',
'SET_STATE': 'set_state'
}
class ScenarioRepository(object):
def __init__(self, conf):
self._load_templates_files(conf)
self.scenarios = {}
def add_template(self, template_definition):
if syntax_validate(template_definition):
template = Template(template_definition)
print(template)
def get_relevant_scenarios(self, element_before, element_now):
"""Returns scenarios triggered by an event.
Returned scenarios are divided into two disjoint lists, based on the
element state (before/now) that triggered the scenario condition.
Note that this should intuitively mean that the "before" scenarios will
activate their "undo" operation, while the "now" will activate the
"execute" operation.
:param element_before:
:param element_now:
:return:
:rtype: dict
"""
# trigger_id_before = 'template_id of trigger for before scenario'
# trigger_id_now = 'template_id of trigger for now scenario'
# return {'before': [(scenario., trigger_id_before)],
# 'now': [(scenario.Scenario, trigger_id_now)]}
pass
def _load_templates_files(self, conf):
templates_dir_path = conf.evaluator.templates_dir
template_definitions = file_utils.load_yaml_files(templates_dir_path)
for template_definition in template_definitions:
self.add_template(template_definition)

View File

@@ -11,11 +11,217 @@
# 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 collections import namedtuple
from oslo_log import log
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
from sympy import Symbol
from vitrage.evaluator.template_fields import TemplateFields as TFields
from vitrage.graph import Edge
from vitrage.graph import Vertex
LOG = log.getLogger(__name__)
ConditionVar = namedtuple('ConditionVar', ['element', 'type', 'positive'])
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
Scenario = namedtuple('Scenario', ['condition', 'actions'])
TYPE_ENTITY = 'entity'
TYPE_RELATIONSHIP = 'relationship'
class Template(object):
pass
def __init__(self, template_def):
super(Template, self).__init__()
self.template_name = template_def[TFields.METADATA][TFields.ID]
definitions = template_def[TFields.DEFINITIONS]
self.entities = self._build_entities(definitions[TFields.ENTITIES])
self.relationships = self._build_relationships(
definitions[TFields.RELATIONSHIPS])
self.scenarios = self._build_scenarios(template_def[TFields.SCENARIOS])
@property
def entities(self):
return self._entities
@entities.setter
def entities(self, entities):
self._entities = entities
@property
def relationships(self):
return self._relationships
@relationships.setter
def relationships(self, relationships):
self._relationships = relationships
def _build_entities(self, entities_definitions):
entities = {}
for entity_definition in entities_definitions:
entity_dict = entity_definition[TFields.ENTITY]
template_id = entity_dict[TFields.TEMPLATE_ID]
entities[template_id] = Vertex(template_id, entity_dict)
return entities
def _build_relationships(self, relationships_defs):
relationships = {}
for relationship_def in relationships_defs:
relationship_dict = relationship_def[TFields.RELATIONSHIP]
relationship = self._create_edge(relationship_dict)
template_id = relationship_dict[TFields.TEMPLATE_ID]
relationships[template_id] = relationship
return relationships
def _create_edge(self, relationship_dict):
return Edge(relationship_dict[TFields.SOURCE],
relationship_dict[TFields.TARGET],
relationship_dict[TFields.RELATIONSHIP_TYPE],
relationship_dict)
def _build_scenarios(self, scenarios_defs):
scenarios = []
for scenarios_def in scenarios_defs:
scenario_dict = scenarios_def[TFields.SCENARIO]
condition = self._parse_condition(scenario_dict[TFields.CONDITION])
action_specs = self._build_actions(scenario_dict[TFields.ACTIONS])
scenarios.append(Scenario(condition, action_specs))
return scenarios
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]
target_def = action_dict[TFields.ACTION_TARGET]
targets = self._extract_action_target(target_def)
properties = {}
if TFields.PROPERTIES in action_dict:
properties = action_dict[TFields.PROPERTIES]
actions.append(ActionSpecs(action_type, targets, properties))
return actions
def _extract_action_target(self, action_target):
targets = {}
for key, value in action_target.iteritems():
targets[key] = self._extract_variable(value)
return targets
def _parse_condition(self, condition_str):
"""Parse condition string into an object
The condition object is formatted in 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 object itself is a list of tuples. each tuple represents
an AND expression compound ConditionElements. The list presents the
OR expression e.g. [(condition_element1, condition_element2)]
:param condition_str: the string as it written in the template itself
:return: Condition object
"""
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(condition_dnf))]
if isinstance(condition_dnf, Symbol):
return [(self._extract_condition_variable(condition_dnf, False))]
def convert_to_dnf_format(self, condition_str):
condition_str = condition_str.replace('and', '&')
condition_str = condition_str.replace('or', '|')
condition_str = condition_str.replace('not ', '~')
return to_dnf(condition_str)
def _extract_or_condition(self, or_condition):
variables = []
for variable in or_condition.args:
if isinstance(variable, And):
variables.append((self._extract_and_condition(variable)),)
elif isinstance(variable, Not):
variables.append((self._extract_not_condition(variable),))
else:
variables.append((self._extract_condition_variable(variable,
False),))
return variables
def _extract_and_condition(self, and_condition):
variables = ()
for arg in and_condition.args:
if isinstance(arg, Not):
condition_var = self._extract_not_condition(arg)
else:
condition_var = self._extract_condition_variable(arg, False)
variables = variables + condition_var
return variables
def _extract_not_condition(self, not_condition):
self._extract_condition_variable(not_condition.args, True)
def _extract_condition_variable(self, symbol, not_):
template_id = symbol.__str__()
variable = self._extract_variable(template_id)
if variable:
return ConditionVar(variable[0], variable[1], not_)
return None
def _extract_variable(self, template_id):
if template_id in self.relationships:
return self.relationships[template_id], TYPE_RELATIONSHIP
if template_id in self.entities:
return self.entities[template_id], TYPE_ENTITY
LOG.error('Cannot find template_id = %s in template named: %s' %
(template_id, self.template_name))
return None

View File

@@ -1,27 +1,35 @@
# Copyright 2016 - Nokia
# Copyright 2015 - 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
# 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 oslo_log import log
from vitrage.common import file_utils
LOG = log.getLogger(__name__)
def load_templates_files(conf):
def syntax_validate(template_conf):
pass
templates_dir_path = conf.evaluator.templates_dir
template_files = file_utils.load_yaml_files(templates_dir_path)
for template_file in template_files:
pass
def validate_scenario_condition(condition_str):
"""Validate the condition content.
Check:
1. The brackets are valid
:param condition: the condition string
:return: True if the condition itself is valid, otherwise returns False
:rtype: bool
"""
pass

View File

@@ -1,40 +0,0 @@
# Copyright 2016 - 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 vitrage.evaluator.scenario as scenario
class ScenarioManager(object):
def get_relevant_scenarios(self, element_before, element_now):
"""Returns scenarios triggered by an event.
Returned scenarios are divided into two disjoint lists, based on the
element state (before/now) that triggered the scenario condition.
Note that this should intuitively mean that the "before" scenarios will
activate their "undo" operation, while the "now" will activate the
"execute" operation.
:param element_before:
:param element_now:
:return:
:rtype: dict
"""
trigger_id_before = 'template_id of trigger for before scenario'
trigger_id_now = 'template_id of trigger for now scenario'
return {'before': [(scenario.Scenario(), trigger_id_before)],
'now': [(scenario.Scenario(), trigger_id_now)]}

View File

@@ -32,7 +32,7 @@ DICT_STRUCTURE_SCHEMA_ERROR = '%s must refer to dictionary.'
SCHEMA_CONTENT_ERROR = '%s must contain %s Fields.'
def validate(template_conf):
def syntax_validate(template_conf):
is_valid = validate_template_sections(template_conf)