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

@ -22,3 +22,4 @@ keystonemiddleware>=2.3.0
stevedore>=1.5.0 # Apache-2.0
exrex>=0.9.4
voluptuous>=0.8.8
sympy>=0.7.6.1

View File

@ -24,3 +24,4 @@ testtools>=1.4.0
exrex>=0.9.4
stevedore>=1.5.0 # Apache-2.0
voluptuous>=0.8.8
sympy>=0.7.6.1

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)

View File

@ -39,13 +39,13 @@ scenarios:
condition: alarm_on_host and host_contains_instance
actions:
- action:
action_type: raise_alarm
action_type: RAISE_ALARM
properties:
alarm_type: VM_CPU_SUBOPTIMAL_PERFORMANCE
action_target:
target: 4
- action:
action_type: set_state
action_type: SET_STATE
properties:
state: SUBOPTIMAL
action_target:
@ -54,7 +54,7 @@ scenarios:
condition: alarm_on_host and alarm_on_instance and host_contains_instance
actions:
- action:
action_type: add_causal_relationship
action_type: ADD_CAUSAL_RELATIONSHIP
action_target:
source: 1
target: 2

View File

@ -11,10 +11,10 @@
# 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_config import cfg
from oslo_log import log as logging
from vitrage.common import file_utils
from vitrage.evaluator.scenario_repository import ScenarioRepository
from vitrage.tests import base
from vitrage.tests.mocks import utils
@ -22,7 +22,7 @@ from vitrage.tests.mocks import utils
LOG = logging.getLogger(__name__)
class TemplateLoaderTest(base.BaseTest):
class ScenarioRepositoryTest(base.BaseTest):
OPTS = [
cfg.StrOpt('templates_dir',
@ -30,17 +30,12 @@ class TemplateLoaderTest(base.BaseTest):
),
]
def setUp(self):
super(TemplateLoaderTest, self).setUp()
@classmethod
def setUpClass(cls):
self.template_dir_path = utils.get_resources_dir() + '/templates'
self.conf = cfg.ConfigOpts()
self.conf.register_opts(self.OPTS, group='evaluator')
self.template_yamls = file_utils.load_yaml_files(
self.template_dir_path
)
cls.conf = cfg.ConfigOpts()
cls.conf.register_opts(cls.OPTS, group='evaluator')
def test_template_loader(self):
pass
repository = ScenarioRepository(self.conf)
print(repository)

View File

@ -17,7 +17,7 @@ from oslo_log import log as logging
from vitrage.common import file_utils
from vitrage.evaluator.template_fields import TemplateFields
from vitrage.evaluator import template_validator
from vitrage.evaluator import template_syntax_validator
from vitrage.tests import base
from vitrage.tests.mocks import utils
@ -40,37 +40,38 @@ class TemplateValidatorTest(base.BaseTest):
return copy.deepcopy(self.first_template)
def test_template_validator(self):
self.assertTrue(template_validator.validate(self.first_template))
self.assertTrue(template_syntax_validator.syntax_validate(
self.first_template))
def test_validate_template_without_metadata_section(self):
template = self.clone_template
template.pop(TemplateFields.METADATA)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_without_id_in_metadata_section(self):
template = self.clone_template
template[TemplateFields.METADATA].pop(TemplateFields.ID)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_without_definitions_section(self):
template = self.clone_template
template.pop(TemplateFields.DEFINITIONS)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_without_entities(self):
template = self.clone_template
template[TemplateFields.DEFINITIONS].pop(TemplateFields.ENTITIES)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_with_empty_entities(self):
template = self.clone_template
template[TemplateFields.DEFINITIONS][TemplateFields.ENTITIES] = []
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_entity_without_required_fields(self):
@ -78,13 +79,13 @@ class TemplateValidatorTest(base.BaseTest):
definitions = template[TemplateFields.DEFINITIONS]
entity = definitions[TemplateFields.ENTITIES][0]
entity[TemplateFields.ENTITY].pop(TemplateFields.CATEGORY)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
template = self.clone_template
definitions = template[TemplateFields.DEFINITIONS]
entity = definitions[TemplateFields.ENTITIES][0]
entity[TemplateFields.ENTITY].pop(TemplateFields.TEMPLATE_ID)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_relationships_without_required_fields(self):
@ -92,13 +93,13 @@ class TemplateValidatorTest(base.BaseTest):
definitions = template[TemplateFields.DEFINITIONS]
relationship = definitions[TemplateFields.RELATIONSHIPS][0]
relationship[TemplateFields.RELATIONSHIP].pop(TemplateFields.SOURCE)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
template = self.clone_template
definitions = template[TemplateFields.DEFINITIONS]
relationship = definitions[TemplateFields.RELATIONSHIPS][0]
relationship[TemplateFields.RELATIONSHIP].pop(TemplateFields.TARGET)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
template = self.clone_template
definitions = template[TemplateFields.DEFINITIONS]
@ -106,37 +107,37 @@ class TemplateValidatorTest(base.BaseTest):
relationship[TemplateFields.RELATIONSHIP].pop(
TemplateFields.TEMPLATE_ID
)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_without_scenarios(self):
template = self.clone_template
template.pop(TemplateFields.SCENARIOS)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_with_empty_scenarios(self):
template = self.clone_template
template[TemplateFields.SCENARIOS] = []
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_scenario_without_required_fields(self):
template = self.clone_template
scenario = template[TemplateFields.SCENARIOS][0]
scenario[TemplateFields.SCENARIO].pop(TemplateFields.CONDITION)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
template = self.clone_template
scenario = template[TemplateFields.SCENARIOS][0]
scenario[TemplateFields.SCENARIO].pop(TemplateFields.ACTIONS)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_template_with_empty_actions(self):
template = self.clone_template
scenario = template[TemplateFields.SCENARIOS][0]
scenario[TemplateFields.SCENARIO][TemplateFields.ACTIONS] = []
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
def test_validate_action_without_required_fields(self):
@ -144,10 +145,10 @@ class TemplateValidatorTest(base.BaseTest):
scenario = template[TemplateFields.SCENARIOS][0]
action = scenario[TemplateFields.SCENARIO][TemplateFields.ACTIONS][0]
action[TemplateFields.ACTION].pop(TemplateFields.ACTION_TYPE)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))
template = self.clone_template
scenario = template[TemplateFields.SCENARIOS][0]
action = scenario[TemplateFields.SCENARIO][TemplateFields.ACTIONS][0]
action[TemplateFields.ACTION].pop(TemplateFields.ACTION_TARGET)
self.assertFalse(template_validator.validate(template))
self.assertFalse(template_syntax_validator.syntax_validate(template))