Add support for building equivalent scenario
TemplateData is refactored to make it possible - split SubGraph and ScenarioData from TemplateData - build entity index and relationship index in TemplateData - delay variable extraction to subgraph build - simplify the process of adding scenario to repository Implement: blueprint entity-equivalence Change-Id: I7192e3a82880dc3c3995000c029af4e0aabd8d6c
This commit is contained in:
parent
6278d815ad
commit
2cb1fafe2b
@ -62,28 +62,20 @@ Let's take a basic template as example
|
|||||||
'alarm': Vertex(vertex_id='alarm',
|
'alarm': Vertex(vertex_id='alarm',
|
||||||
properties={'category': 'ALARM',
|
properties={'category': 'ALARM',
|
||||||
'type': 'nagios',
|
'type': 'nagios',
|
||||||
'name': 'HOST_HIGH_CPU_LOAD',
|
'name': 'HOST_HIGH_CPU_LOAD'
|
||||||
'is_deleted': False,
|
|
||||||
'is_placeholder': False
|
|
||||||
}),
|
}),
|
||||||
'resource': Vertex(vertex_id='resource',
|
'resource': Vertex(vertex_id='resource',
|
||||||
properties={'category': 'RESOURCE',
|
properties={'category': 'RESOURCE',
|
||||||
'type': 'nova.host',
|
'type': 'nova.host'
|
||||||
'is_deleted': False,
|
|
||||||
'is_placeholder': False
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_relationships = {
|
expected_relationships = {
|
||||||
'alarm_on_host': EdgeDescription(
|
'alarm_on_host': EdgeDescription(
|
||||||
edge=Edge(
|
edge=Edge(source_id='alarm',
|
||||||
source_id='alarm',
|
|
||||||
target_id='resource',
|
target_id='resource',
|
||||||
label='on',
|
label='on',
|
||||||
properties={
|
properties={'relationship_type': 'on'}),
|
||||||
'relationship_type': 'on',
|
|
||||||
'is_deleted': False,
|
|
||||||
'negative_condition': False}),
|
|
||||||
source=expected_entities['alarm'],
|
source=expected_entities['alarm'],
|
||||||
target=expected_entities['resource']
|
target=expected_entities['resource']
|
||||||
)
|
)
|
||||||
@ -92,19 +84,16 @@ Let's take a basic template as example
|
|||||||
expected_scenario = Scenario(
|
expected_scenario = Scenario(
|
||||||
id='basic_template-scenario0',
|
id='basic_template-scenario0',
|
||||||
condition=[
|
condition=[
|
||||||
[ConditionVar(
|
[ConditionVar(symbol_name='alarm_on_host',
|
||||||
variable=expected_relationships['alarm_on_host'],
|
positive=True)]],
|
||||||
type='relationship',
|
|
||||||
positive=True)]
|
|
||||||
],
|
|
||||||
actions=[
|
actions=[
|
||||||
ActionSpecs(
|
ActionSpecs(
|
||||||
type='set_state',
|
type='set_state',
|
||||||
targets={
|
targets={'target': 'resource'},
|
||||||
'target': 'resource'},
|
properties={'state': 'SUBOPTIMAL'})],
|
||||||
properties={
|
subgraphs=template_data.scenarios[0].subgraphs, # ignore subgraphs
|
||||||
'state': 'SUBOPTIMAL'})],
|
entities=expected_entities,
|
||||||
subgraphs=[object] # [<vitrage.graph.driver.networkx_graph.NXGraph object>]
|
relationships=expected_relationships
|
||||||
)
|
)
|
||||||
|
|
||||||
Entities and relationships
|
Entities and relationships
|
||||||
@ -170,26 +159,34 @@ operators. As explained in embedded comment:
|
|||||||
:param condition_str: the string as it written in the template itself
|
:param condition_str: the string as it written in the template itself
|
||||||
:return: condition_vars_lists
|
:return: condition_vars_lists
|
||||||
|
|
||||||
Each condition variable refers either an entity or relationship by ``variable``.
|
|
||||||
The same variable in different conditions share an identical object. It is not
|
|
||||||
duplicated.
|
|
||||||
|
|
||||||
actions
|
actions
|
||||||
-------
|
-------
|
||||||
|
|
||||||
``actions`` is a list of ``ActionSpecs``.
|
``actions`` is a list of ``ActionSpecs``.
|
||||||
|
|
||||||
Note that the values of action ``targets`` are **string**. It is different from
|
The action targets in the spec must be referenced in the condition definition.
|
||||||
how relationships and entities are referred in ``condition``, which are object
|
They are either linked to ``vertex_id`` of entity condition variables or
|
||||||
references.
|
``source_id`` and ``target_id`` in relationship condition variable extracted.
|
||||||
|
|
||||||
The targets values are linked to ``vertex_id`` of entity condition variables or
|
In each matched subgraph in the entity graph, the targets will be resolved as
|
||||||
``source_id`` and ``target_id`` in a relationship condition variable. It will
|
concrete vertices or edges.
|
||||||
be resolved to real targets from matched sub-graph in entity graph.
|
|
||||||
|
|
||||||
subgraphs
|
subgraphs
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Sub graphs are compiled from conditions for pattern matching in the entity
|
Sub graphs are built from conditions for pattern matching in the entity graph.
|
||||||
graph. Each sub-list in condition variables list is compiled into one sub
|
Each sub-list in condition variables list is compiled into one sub graph. The
|
||||||
graph. The actions will be triggered if any of the subgraph is matched.
|
actions will be triggered if any of the subgraph is matched.
|
||||||
|
|
||||||
|
entities & relationships
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Dicts of **touched** entities and relationships during subgraph building are
|
||||||
|
saved in scenario.
|
||||||
|
|
||||||
|
This makes creation of the scenarios repository index on related entities and
|
||||||
|
relationships easier and more efficient. You don't need to traverse the
|
||||||
|
condition object again, which is already done once during subgraphs building.
|
||||||
|
It also eliminate the necessity of duplication check because there is no
|
||||||
|
duplicate entities or relationships in these dicts compared to the condition
|
||||||
|
variables lists.
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from vitrage.common.constants import TemplateTopologyFields
|
from vitrage.common.constants import TemplateTopologyFields
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
|
|
||||||
|
|
||||||
class Fields(TemplateTopologyFields):
|
class Fields(TemplateTopologyFields):
|
||||||
@ -37,18 +38,23 @@ class EquivalenceData(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_equivalences(equivalence_defs):
|
def _build_equivalences(equivalence_defs):
|
||||||
"""equivalence stored as arrays of frozen entity sets
|
"""equivalence stored as arrays of frozen entity props sets
|
||||||
|
|
||||||
equivalences:: [equivalence, ...]
|
equivalences:: [equivalence, ...]
|
||||||
equivalence:: {entity, ...}
|
equivalence:: {entity_props, ...}
|
||||||
entity:: {(k,v), ...}
|
entity_props:: {(k,v), ...}
|
||||||
"""
|
"""
|
||||||
equivalences = []
|
equivalences = []
|
||||||
for equivalence_def in equivalence_defs:
|
for equivalence_def in equivalence_defs:
|
||||||
equivalent_entities = equivalence_def[Fields.EQUIVALENCE]
|
equivalent_entities = equivalence_def[Fields.EQUIVALENCE]
|
||||||
equivalence = []
|
equivalence = set()
|
||||||
for entity_def in equivalent_entities:
|
for entity_def in equivalent_entities:
|
||||||
equivalence.append(frozenset(entity_def[Fields.ENTITY]
|
entity_props = {(k, v)
|
||||||
.items()))
|
for k, v in entity_def[Fields.ENTITY].items()}
|
||||||
|
entity_key = frozenset(entity_props)
|
||||||
|
if entity_key in equivalence:
|
||||||
|
raise VitrageError('duplicated entities found in '
|
||||||
|
'equivalence')
|
||||||
|
equivalence.add(entity_key)
|
||||||
equivalences.append(frozenset(equivalence))
|
equivalences.append(frozenset(equivalence))
|
||||||
return equivalences
|
return equivalences
|
||||||
|
39
vitrage/evaluator/equivalence_repository.py
Normal file
39
vitrage/evaluator/equivalence_repository.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Copyright 2017 - ZTE Corporation
|
||||||
|
#
|
||||||
|
# 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.common.exception import VitrageError
|
||||||
|
from vitrage.evaluator.equivalence_data import EquivalenceData
|
||||||
|
from vitrage.utils import file as file_utils
|
||||||
|
|
||||||
|
|
||||||
|
class EquivalenceRepository(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.entity_equivalences = {}
|
||||||
|
|
||||||
|
def load_files(self, directory):
|
||||||
|
equivalence_defs = file_utils.load_yaml_files(directory)
|
||||||
|
|
||||||
|
for equivalence_def in equivalence_defs:
|
||||||
|
equivalence_data = EquivalenceData(equivalence_def)
|
||||||
|
for equivalence in equivalence_data.equivalences:
|
||||||
|
self._add_equivalence(equivalence)
|
||||||
|
return self.entity_equivalences
|
||||||
|
|
||||||
|
def _add_equivalence(self, equivalence):
|
||||||
|
for entity in equivalence:
|
||||||
|
if entity in self.entity_equivalences:
|
||||||
|
# TODO(yujunz): log error and continue processing the rest
|
||||||
|
raise VitrageError('one entity should not be included in '
|
||||||
|
'multiple equivalence definitions')
|
||||||
|
self.entity_equivalences[entity] = equivalence
|
@ -19,7 +19,7 @@ from oslo_log import log
|
|||||||
from oslo_utils import uuidutils
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
from vitrage.evaluator.base import Template
|
from vitrage.evaluator.base import Template
|
||||||
from vitrage.evaluator.template_data import RELATIONSHIP
|
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
||||||
from vitrage.evaluator.template_data import TemplateData
|
from vitrage.evaluator.template_data import TemplateData
|
||||||
from vitrage.evaluator.template_validation.template_content_validator import \
|
from vitrage.evaluator.template_validation.template_content_validator import \
|
||||||
content_validation
|
content_validation
|
||||||
@ -36,6 +36,8 @@ EdgeKeyScenario = namedtuple('EdgeKeyScenario', ['label', 'source', 'target'])
|
|||||||
class ScenarioRepository(object):
|
class ScenarioRepository(object):
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
self._templates = {}
|
self._templates = {}
|
||||||
|
self.entity_equivalences = \
|
||||||
|
EquivalenceRepository().load_files(conf.evaluator.equivalences_dir)
|
||||||
self.relationship_scenarios = defaultdict(list)
|
self.relationship_scenarios = defaultdict(list)
|
||||||
self.entity_scenarios = defaultdict(list)
|
self.entity_scenarios = defaultdict(list)
|
||||||
self._load_templates_files(conf)
|
self._load_templates_files(conf)
|
||||||
@ -93,7 +95,39 @@ class ScenarioRepository(object):
|
|||||||
result)
|
result)
|
||||||
if result.is_valid_config:
|
if result.is_valid_config:
|
||||||
template_data = TemplateData(template_def)
|
template_data = TemplateData(template_def)
|
||||||
self._add_template_scenarios(template_data)
|
for scenario in template_data.scenarios:
|
||||||
|
for equivalent_scenario in self._expand_equivalence(scenario):
|
||||||
|
self._add_scenario(equivalent_scenario)
|
||||||
|
|
||||||
|
def _expand_equivalence(self, scenario):
|
||||||
|
equivalent_scenarios = [scenario]
|
||||||
|
for symbol_name, entity in scenario.entities.items():
|
||||||
|
entity_key = frozenset(entity.properties.items())
|
||||||
|
if entity_key not in self.entity_equivalences:
|
||||||
|
continue
|
||||||
|
equivalent_scenarios = self._expand_on_symbol(
|
||||||
|
equivalent_scenarios,
|
||||||
|
symbol_name,
|
||||||
|
self.entity_equivalences[entity_key] - {entity_key})
|
||||||
|
return equivalent_scenarios
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _expand_on_symbol(scenarios_in, symbol_name, entity_keys):
|
||||||
|
scenarios_out = list(scenarios_in)
|
||||||
|
for entity_key in entity_keys:
|
||||||
|
for scenario in scenarios_in:
|
||||||
|
equivalent_scenario = TemplateData.ScenarioData.\
|
||||||
|
build_equivalent_scenario(scenario,
|
||||||
|
symbol_name,
|
||||||
|
entity_key)
|
||||||
|
scenarios_out.append(equivalent_scenario)
|
||||||
|
return scenarios_out
|
||||||
|
|
||||||
|
def _add_scenario(self, scenario):
|
||||||
|
for entity in scenario.entities.values():
|
||||||
|
self._add_entity_scenario(scenario, entity)
|
||||||
|
for relationship in scenario.relationships.values():
|
||||||
|
self._add_relationship_scenario(scenario, relationship)
|
||||||
|
|
||||||
def _load_templates_files(self, conf):
|
def _load_templates_files(self, conf):
|
||||||
|
|
||||||
@ -103,34 +137,13 @@ class ScenarioRepository(object):
|
|||||||
for template_def in template_defs:
|
for template_def in template_defs:
|
||||||
self.add_template(template_def)
|
self.add_template(template_def)
|
||||||
|
|
||||||
def _add_template_scenarios(self, template):
|
|
||||||
for scenario in template.scenarios:
|
|
||||||
self._handle_condition(scenario)
|
|
||||||
|
|
||||||
def _handle_condition(self, scenario):
|
|
||||||
for clause in scenario.condition:
|
|
||||||
self._handle_clause(clause, scenario)
|
|
||||||
|
|
||||||
def _handle_clause(self, clause, scenario):
|
|
||||||
for condition_var in clause:
|
|
||||||
if condition_var.type == RELATIONSHIP:
|
|
||||||
edge_desc = condition_var.variable
|
|
||||||
self._add_relationship(scenario, edge_desc)
|
|
||||||
self._add_entity(scenario, edge_desc.source)
|
|
||||||
self._add_entity(scenario, edge_desc.target)
|
|
||||||
else: # Entity
|
|
||||||
self._add_entity(scenario, condition_var.variable)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _create_scenario_key(properties):
|
def _create_scenario_key(properties):
|
||||||
return frozenset(properties)
|
return frozenset(properties)
|
||||||
|
|
||||||
def _add_relationship(self, scenario, edge_desc):
|
def _add_relationship_scenario(self, scenario, edge_desc):
|
||||||
|
|
||||||
key = self._create_edge_scenario_key(edge_desc)
|
key = self._create_edge_scenario_key(edge_desc)
|
||||||
scenarios = self.relationship_scenarios[key]
|
|
||||||
|
|
||||||
if not self._edge_contains(scenarios, scenario, edge_desc):
|
|
||||||
self.relationship_scenarios[key].append((edge_desc, scenario))
|
self.relationship_scenarios[key].append((edge_desc, scenario))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -139,22 +152,7 @@ class ScenarioRepository(object):
|
|||||||
frozenset(edge_desc.source.properties.items()),
|
frozenset(edge_desc.source.properties.items()),
|
||||||
frozenset(edge_desc.target.properties.items()))
|
frozenset(edge_desc.target.properties.items()))
|
||||||
|
|
||||||
def _add_entity(self, scenario, entity):
|
def _add_entity_scenario(self, scenario, entity):
|
||||||
|
|
||||||
key = frozenset(list(entity.properties.items()))
|
key = frozenset(list(entity.properties.items()))
|
||||||
scenarios = self.entity_scenarios[key]
|
|
||||||
|
|
||||||
if not self._entity_contains(scenarios, scenario, entity):
|
|
||||||
self.entity_scenarios[key].append((entity, scenario))
|
self.entity_scenarios[key].append((entity, scenario))
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _edge_contains(scenarios, scenario, edge):
|
|
||||||
return any(e.edge.source_id == edge.edge.source_id and
|
|
||||||
e.edge.target_id == edge.edge.target_id and
|
|
||||||
e.edge.label == edge.edge.label and s.id == scenario.id
|
|
||||||
for e, s in scenarios)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _entity_contains(scenarios, scenario, entity):
|
|
||||||
return any(e.vertex_id == entity.vertex_id and s.id == scenario.id
|
|
||||||
for e, s in scenarios)
|
|
||||||
|
@ -19,25 +19,36 @@ from sympy.logic.boolalg import Or
|
|||||||
from sympy.logic.boolalg import to_dnf as sympy_to_dfn
|
from sympy.logic.boolalg import to_dnf as sympy_to_dfn
|
||||||
from sympy import Symbol
|
from sympy import Symbol
|
||||||
|
|
||||||
|
|
||||||
from vitrage.common.constants import EdgeProperties as EProps
|
from vitrage.common.constants import EdgeProperties as EProps
|
||||||
from vitrage.common.constants import VertexProperties as VProps
|
from vitrage.common.constants import VertexProperties as VProps
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
from vitrage.evaluator.template_fields import TemplateFields as TFields
|
||||||
from vitrage.graph.algo_driver.sub_graph_matching import NEG_CONDITION
|
from vitrage.graph.algo_driver.sub_graph_matching import NEG_CONDITION
|
||||||
from vitrage.graph.driver.networkx_graph import NXGraph
|
from vitrage.graph.driver.networkx_graph import NXGraph
|
||||||
from vitrage.graph import Edge
|
from vitrage.graph import Edge
|
||||||
from vitrage.graph import Vertex
|
from vitrage.graph import Vertex
|
||||||
|
|
||||||
ConditionVar = namedtuple('ConditionVar', ['variable', 'type', 'positive'])
|
ConditionVar = namedtuple('ConditionVar', ['symbol_name', 'positive'])
|
||||||
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
|
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
|
||||||
Scenario = namedtuple('Scenario', ['id', 'condition', 'actions', 'subgraphs'])
|
Scenario = namedtuple('Scenario', ['id',
|
||||||
|
'condition',
|
||||||
|
'actions',
|
||||||
|
'subgraphs',
|
||||||
|
'entities',
|
||||||
|
'relationships'
|
||||||
|
])
|
||||||
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
|
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
|
||||||
|
|
||||||
|
|
||||||
ENTITY = 'entity'
|
ENTITY = 'entity'
|
||||||
RELATIONSHIP = 'relationship'
|
RELATIONSHIP = 'relationship'
|
||||||
|
|
||||||
|
|
||||||
|
def copy_edge_desc(edge_desc):
|
||||||
|
return EdgeDescription(edge=edge_desc.edge.copy(),
|
||||||
|
source=edge_desc.source.copy(),
|
||||||
|
target=edge_desc.target.copy())
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyAttributeOutsideInit
|
# noinspection PyAttributeOutsideInit
|
||||||
class TemplateData(object):
|
class TemplateData(object):
|
||||||
|
|
||||||
@ -135,17 +146,94 @@ class TemplateData(object):
|
|||||||
def _build_scenarios(self, scenarios_defs):
|
def _build_scenarios(self, scenarios_defs):
|
||||||
|
|
||||||
scenarios = []
|
scenarios = []
|
||||||
for counter, scenarios_def in enumerate(scenarios_defs):
|
for counter, scenario_def in enumerate(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])
|
|
||||||
subgraphs = self._build_subgraphs(condition)
|
|
||||||
scenario_id = "%s-scenario%s" % (self.name, str(counter))
|
scenario_id = "%s-scenario%s" % (self.name, str(counter))
|
||||||
scenarios.append(Scenario(scenario_id, condition,
|
scenario_dict = scenario_def[TFields.SCENARIO]
|
||||||
action_specs, subgraphs))
|
scenarios.append(TemplateData.ScenarioData(
|
||||||
|
scenario_id,
|
||||||
|
scenario_dict, self).to_tuple())
|
||||||
return scenarios
|
return scenarios
|
||||||
|
|
||||||
|
class ScenarioData(object):
|
||||||
|
def __init__(self, scenario_id, scenario_dict, template_data):
|
||||||
|
self._template_entities = template_data.entities
|
||||||
|
self._template_relationships = template_data.relationships
|
||||||
|
|
||||||
|
self._entities = {}
|
||||||
|
self._relationships = {}
|
||||||
|
|
||||||
|
self.scenario_id = scenario_id
|
||||||
|
self.condition = self._parse_condition(
|
||||||
|
scenario_dict[TFields.CONDITION])
|
||||||
|
self.actions = self._build_actions(scenario_dict[TFields.ACTIONS])
|
||||||
|
self.subgraphs = TemplateData.SubGraph.from_condition(
|
||||||
|
self.condition,
|
||||||
|
self._extract_var_and_update_index)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.scenario_id == other.scenario_id \
|
||||||
|
and self.condition == other.condition \
|
||||||
|
and self.actions == other.actions
|
||||||
|
|
||||||
|
def to_tuple(self):
|
||||||
|
return Scenario(id=self.scenario_id,
|
||||||
|
condition=self.condition,
|
||||||
|
actions=self.actions,
|
||||||
|
subgraphs=self.subgraphs,
|
||||||
|
entities=self._entities,
|
||||||
|
relationships=self._relationships)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_equivalent_scenario(cls,
|
||||||
|
scenario,
|
||||||
|
template_id,
|
||||||
|
entity_props):
|
||||||
|
entities = scenario.entities.copy()
|
||||||
|
entities[template_id] = Vertex(
|
||||||
|
vertex_id=entities[template_id].vertex_id,
|
||||||
|
properties={k: v for k, v in entity_props})
|
||||||
|
relationships = {
|
||||||
|
rel_id: cls.build_equivalent_relationship(rel,
|
||||||
|
template_id,
|
||||||
|
entity_props)
|
||||||
|
for rel_id, rel in scenario.relationships.items()}
|
||||||
|
|
||||||
|
def extract_var(symbol_name):
|
||||||
|
if symbol_name in entities:
|
||||||
|
return entities[symbol_name], ENTITY
|
||||||
|
elif symbol_name in relationships:
|
||||||
|
return relationships[symbol_name], RELATIONSHIP
|
||||||
|
else:
|
||||||
|
raise VitrageError('invalid symbol name: {}'
|
||||||
|
.format(symbol_name))
|
||||||
|
|
||||||
|
subgraphs = TemplateData.SubGraph.from_condition(
|
||||||
|
scenario.condition, extract_var)
|
||||||
|
|
||||||
|
return Scenario(id=scenario.id + '_equivalence',
|
||||||
|
condition=scenario.condition,
|
||||||
|
actions=scenario.actions,
|
||||||
|
subgraphs=subgraphs,
|
||||||
|
entities=entities,
|
||||||
|
relationships=relationships)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_equivalent_relationship(cls,
|
||||||
|
relationship,
|
||||||
|
template_id,
|
||||||
|
entity_props):
|
||||||
|
source = relationship.source
|
||||||
|
target = relationship.target
|
||||||
|
if relationship.edge.source_id == template_id:
|
||||||
|
source = Vertex(vertex_id=source.vertex_id,
|
||||||
|
properties={k: v for k, v in entity_props})
|
||||||
|
elif relationship.edge.target_id == template_id:
|
||||||
|
target = Vertex(vertex_id=target.vertex_id,
|
||||||
|
properties={k: v for k, v in entity_props})
|
||||||
|
return EdgeDescription(source=source,
|
||||||
|
target=target,
|
||||||
|
edge=relationship.edge)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_actions(actions_def):
|
def _build_actions(actions_def):
|
||||||
|
|
||||||
@ -161,60 +249,22 @@ class TemplateData(object):
|
|||||||
|
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def _build_subgraphs(self, condition):
|
|
||||||
return [self._build_subgraph(clause) for clause in condition]
|
|
||||||
|
|
||||||
def _build_subgraph(self, clause):
|
|
||||||
condition_g = NXGraph("scenario condition")
|
|
||||||
|
|
||||||
for term in clause:
|
|
||||||
if term.type == ENTITY:
|
|
||||||
term.variable[VProps.IS_DELETED] = False
|
|
||||||
term.variable[VProps.IS_PLACEHOLDER] = False
|
|
||||||
condition_g.add_vertex(term.variable)
|
|
||||||
|
|
||||||
else: # type = relationship
|
|
||||||
edge_desc = term.variable
|
|
||||||
self._set_edge_relationship_info(edge_desc, term.positive)
|
|
||||||
self._add_edge_relationship(condition_g, edge_desc)
|
|
||||||
|
|
||||||
return condition_g
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _set_edge_relationship_info(edge_description, is_positive_condition):
|
|
||||||
if not is_positive_condition:
|
|
||||||
edge_description.edge[NEG_CONDITION] = True
|
|
||||||
edge_description.edge[EProps.IS_DELETED] = True
|
|
||||||
else:
|
|
||||||
edge_description.edge[EProps.IS_DELETED] = False
|
|
||||||
edge_description.edge[NEG_CONDITION] = False
|
|
||||||
|
|
||||||
edge_description.source[VProps.IS_DELETED] = False
|
|
||||||
edge_description.source[VProps.IS_PLACEHOLDER] = False
|
|
||||||
edge_description.target[VProps.IS_DELETED] = False
|
|
||||||
edge_description.target[VProps.IS_PLACEHOLDER] = False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _add_edge_relationship(condition_graph, edge_description):
|
|
||||||
condition_graph.add_vertex(edge_description.source)
|
|
||||||
condition_graph.add_vertex(edge_description.target)
|
|
||||||
condition_graph.add_edge(edge_description.edge)
|
|
||||||
|
|
||||||
def _parse_condition(self, condition_str):
|
def _parse_condition(self, condition_str):
|
||||||
"""Parse condition string into an object
|
"""Parse condition string into an object
|
||||||
|
|
||||||
The condition string will be converted here into DNF (Disjunctive
|
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)...
|
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
|
... where X, Y, Z, V, W are either entities or relationships
|
||||||
more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
|
more details: https://en.wikipedia.org/wiki/Disjunctive_normal_form
|
||||||
|
|
||||||
The condition variable lists is then extracted from the DNF object. It
|
The condition variable lists is then extracted from the DNF object.
|
||||||
is a list of lists. Each inner list represents an AND expression
|
It is a list of lists. Each inner list represents an AND expression
|
||||||
compound condition variables. The outer list presents the OR expression
|
compound condition variables. The outer list presents the OR
|
||||||
|
expression
|
||||||
|
|
||||||
[[and_var1, and_var2, ...], or_list_2, ...]
|
[[and_var1, and_var2, ...], or_list_2, ...]
|
||||||
|
|
||||||
:param condition_str: the string as it written in the template itself
|
:param condition_str: the string as it written in the template
|
||||||
:return: condition_vars_lists
|
:return: condition_vars_lists
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -266,13 +316,68 @@ class TemplateData(object):
|
|||||||
def _extract_condition_var(self, symbol, positive):
|
def _extract_condition_var(self, symbol, positive):
|
||||||
if isinstance(symbol, Not):
|
if isinstance(symbol, Not):
|
||||||
return self._extract_not_condition_var(symbol)[0]
|
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:
|
||||||
|
relationship = self._template_relationships[symbol_name]
|
||||||
|
self._relationships[symbol_name] = relationship
|
||||||
|
self._entities.update({
|
||||||
|
relationship.edge.source_id: relationship.source,
|
||||||
|
relationship.edge.target_id: relationship.target
|
||||||
|
})
|
||||||
|
return relationship, RELATIONSHIP
|
||||||
|
|
||||||
|
entity = self._template_entities[symbol_name]
|
||||||
|
self._entities[symbol_name] = entity
|
||||||
|
return entity, ENTITY
|
||||||
|
|
||||||
|
class SubGraph(object):
|
||||||
|
@classmethod
|
||||||
|
def from_condition(cls, condition, extract_var):
|
||||||
|
return [cls.from_clause(clause, extract_var)
|
||||||
|
for clause in condition]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_clause(cls, clause, extract_var):
|
||||||
|
condition_g = NXGraph("scenario condition")
|
||||||
|
|
||||||
|
for term in clause:
|
||||||
|
variable, var_type = extract_var(term.symbol_name)
|
||||||
|
if var_type == ENTITY:
|
||||||
|
vertex = variable.copy()
|
||||||
|
vertex[VProps.IS_DELETED] = False
|
||||||
|
vertex[VProps.IS_PLACEHOLDER] = False
|
||||||
|
condition_g.add_vertex(vertex)
|
||||||
|
|
||||||
|
else: # type = relationship
|
||||||
|
# prevent overwritten of NEG_CONDITION and IS_DELETED
|
||||||
|
# property when there are both "not A" and "A" in same
|
||||||
|
# template
|
||||||
|
edge_desc = copy_edge_desc(variable)
|
||||||
|
cls._set_edge_relationship_info(edge_desc, term.positive)
|
||||||
|
cls._add_edge_relationship(condition_g, edge_desc)
|
||||||
|
|
||||||
|
return condition_g
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _set_edge_relationship_info(edge_description,
|
||||||
|
is_positive_condition):
|
||||||
|
if not is_positive_condition:
|
||||||
|
edge_description.edge[NEG_CONDITION] = True
|
||||||
|
edge_description.edge[EProps.IS_DELETED] = True
|
||||||
else:
|
else:
|
||||||
var, var_type = self._extract_var(str(symbol))
|
edge_description.edge[EProps.IS_DELETED] = False
|
||||||
return ConditionVar(var, var_type, positive)
|
edge_description.edge[NEG_CONDITION] = False
|
||||||
|
|
||||||
def _extract_var(self, template_id):
|
edge_description.source[VProps.IS_DELETED] = False
|
||||||
|
edge_description.source[VProps.IS_PLACEHOLDER] = False
|
||||||
|
edge_description.target[VProps.IS_DELETED] = False
|
||||||
|
edge_description.target[VProps.IS_PLACEHOLDER] = False
|
||||||
|
|
||||||
if template_id in self.relationships:
|
@staticmethod
|
||||||
return self.relationships[template_id], RELATIONSHIP
|
def _add_edge_relationship(condition_graph, edge_description):
|
||||||
|
condition_graph.add_vertex(edge_description.source)
|
||||||
return self.entities[template_id], ENTITY
|
condition_graph.add_vertex(edge_description.target)
|
||||||
|
condition_graph.add_edge(edge_description.edge)
|
||||||
|
@ -142,7 +142,7 @@ def _validate_scenarios(scenarios, definitions_index):
|
|||||||
|
|
||||||
def _validate_scenario_condition(condition, definitions_index):
|
def _validate_scenario_condition(condition, definitions_index):
|
||||||
try:
|
try:
|
||||||
dnf_result = TemplateData.convert_to_dnf_format(condition)
|
dnf_result = TemplateData.ScenarioData.convert_to_dnf_format(condition)
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.error('%s status code: %s' % (status_msgs[85], 85))
|
LOG.error('%s status code: %s' % (status_msgs[85], 85))
|
||||||
return get_fault_result(RESULT_DESCRIPTION, 85)
|
return get_fault_result(RESULT_DESCRIPTION, 85)
|
||||||
|
@ -42,6 +42,9 @@ class PropertiesElement(object):
|
|||||||
def items(self):
|
def items(self):
|
||||||
return self.properties.items()
|
return self.properties.items()
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return PropertiesElement(self.properties.copy())
|
||||||
|
|
||||||
|
|
||||||
class Vertex(PropertiesElement):
|
class Vertex(PropertiesElement):
|
||||||
"""Class Vertex
|
"""Class Vertex
|
||||||
@ -85,6 +88,10 @@ class Vertex(PropertiesElement):
|
|||||||
return self.__dict__ == other.__dict__ and \
|
return self.__dict__ == other.__dict__ and \
|
||||||
self.properties == other.properties
|
self.properties == other.properties
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return Vertex(vertex_id=self.vertex_id,
|
||||||
|
properties=self.properties.copy())
|
||||||
|
|
||||||
|
|
||||||
class Edge(PropertiesElement):
|
class Edge(PropertiesElement):
|
||||||
"""Class Edge represents a directional edge between two vertices
|
"""Class Edge represents a directional edge between two vertices
|
||||||
@ -161,3 +168,9 @@ class Edge(PropertiesElement):
|
|||||||
|
|
||||||
def has_vertex(self, v_id):
|
def has_vertex(self, v_id):
|
||||||
return self.source_id == v_id or self.target_id == v_id
|
return self.source_id == v_id or self.target_id == v_id
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return Edge(source_id=self.source_id,
|
||||||
|
target_id=self.target_id,
|
||||||
|
label=self.label,
|
||||||
|
properties=self.properties.copy())
|
||||||
|
@ -3,14 +3,18 @@ metadata:
|
|||||||
equivalences:
|
equivalences:
|
||||||
- equivalence:
|
- equivalence:
|
||||||
- entity:
|
- entity:
|
||||||
|
# matches an entity in templates/general/basic.yaml
|
||||||
category: ALARM
|
category: ALARM
|
||||||
type: nagios
|
type: nagios
|
||||||
name: host_problem
|
name: host_problem
|
||||||
- entity:
|
- entity:
|
||||||
|
# specify an equivalent entity
|
||||||
|
# note that the `name` does not need to be identical
|
||||||
category: ALARM
|
category: ALARM
|
||||||
type: zabbix
|
type: zabbix
|
||||||
name: host_fail # equivalence should work for unequal property values
|
name: host_fail
|
||||||
- entity:
|
- entity:
|
||||||
|
# allow multiple equivalent entities in same group
|
||||||
category: ALARM
|
category: ALARM
|
||||||
type: vitrage
|
type: vitrage
|
||||||
name: host_down
|
name: host_down
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
metadata:
|
||||||
|
name: duplicated entities in multiple equivalences
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: host_problem
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: host_problem
|
@ -0,0 +1,12 @@
|
|||||||
|
metadata:
|
||||||
|
name: first occurrence of zabbix host_problem
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: host_problem
|
@ -0,0 +1,12 @@
|
|||||||
|
metadata:
|
||||||
|
name: second occurrence of zabbix host_problem
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: zabbix
|
||||||
|
name: host_problem
|
@ -0,0 +1,12 @@
|
|||||||
|
metadata:
|
||||||
|
name: duplicated entities in same equivalence
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: host_problem
|
@ -0,0 +1,21 @@
|
|||||||
|
metadata:
|
||||||
|
name: entity equivalence example
|
||||||
|
equivalences:
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: cpu_high
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: cpu_high
|
||||||
|
- equivalence:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: mem_low
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: vitrage
|
||||||
|
name: mem_low
|
@ -0,0 +1,40 @@
|
|||||||
|
metadata:
|
||||||
|
name: basic_template
|
||||||
|
description: basic template for general tests
|
||||||
|
definitions:
|
||||||
|
entities:
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: cpu_high
|
||||||
|
template_id: cpu_alarm
|
||||||
|
- entity:
|
||||||
|
category: ALARM
|
||||||
|
type: nagios
|
||||||
|
name: mem_low
|
||||||
|
template_id: mem_alarm
|
||||||
|
- entity:
|
||||||
|
category: RESOURCE
|
||||||
|
type: nova.host
|
||||||
|
template_id: host
|
||||||
|
relationships:
|
||||||
|
- relationship:
|
||||||
|
source: cpu_alarm
|
||||||
|
target: host
|
||||||
|
relationship_type: on
|
||||||
|
template_id : cpu_alarm_on_host
|
||||||
|
- relationship:
|
||||||
|
source: mem_alarm
|
||||||
|
target: host
|
||||||
|
relationship_type: on
|
||||||
|
template_id : mem_alarm_on_host
|
||||||
|
scenarios:
|
||||||
|
- scenario:
|
||||||
|
condition: cpu_alarm_on_host or mem_alarm_on_host
|
||||||
|
actions:
|
||||||
|
- action:
|
||||||
|
action_type: set_state
|
||||||
|
properties:
|
||||||
|
state: SUBOPTIMAL
|
||||||
|
action_target:
|
||||||
|
target: host
|
@ -4,9 +4,11 @@ metadata:
|
|||||||
definitions:
|
definitions:
|
||||||
entities:
|
entities:
|
||||||
- entity:
|
- entity:
|
||||||
|
# an equivalence of this entity is defined in equivalences/basic.yaml
|
||||||
|
# be sure keep them sync when modifying test cases
|
||||||
category: ALARM
|
category: ALARM
|
||||||
type: nagios
|
type: nagios
|
||||||
name: HOST_HIGH_CPU_LOAD
|
name: host_problem
|
||||||
template_id: alarm
|
template_id: alarm
|
||||||
- entity:
|
- entity:
|
||||||
category: RESOURCE
|
category: RESOURCE
|
||||||
|
35
vitrage/tests/unit/evaluator/test_equivalence_repository.py
Normal file
35
vitrage/tests/unit/evaluator/test_equivalence_repository.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Copyright 2017 - ZTE Corporation
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
from vitrage.common.exception import VitrageError
|
||||||
|
from vitrage.evaluator.equivalence_repository import EquivalenceRepository
|
||||||
|
from vitrage.tests import base
|
||||||
|
from vitrage.tests.mocks import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestEquivalenceRepository(base.BaseTest):
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
def setUp(self):
|
||||||
|
super(TestEquivalenceRepository, self).setUp()
|
||||||
|
self.equivalence_repository = EquivalenceRepository()
|
||||||
|
|
||||||
|
def test_duplicate_entities_in_equivalence(self):
|
||||||
|
equivalences_dup_dir = utils.get_resources_dir() + '/equivalences_dup'
|
||||||
|
for directory in os.listdir(equivalences_dup_dir):
|
||||||
|
self.assertRaises(VitrageError,
|
||||||
|
self.equivalence_repository.load_files,
|
||||||
|
os.path.join(equivalences_dup_dir, directory))
|
@ -73,6 +73,20 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
scenario_templates = self.scenario_repository.templates
|
scenario_templates = self.scenario_repository.templates
|
||||||
self.assertEqual(valid_template_counter, len(scenario_templates))
|
self.assertEqual(valid_template_counter, len(scenario_templates))
|
||||||
|
|
||||||
|
entity_equivalences = self.scenario_repository.entity_equivalences
|
||||||
|
for entity_props, equivalence in entity_equivalences.items():
|
||||||
|
# Example structure of entity_equivalences
|
||||||
|
# { A: (A, B, C),
|
||||||
|
# B: (A, B, C),
|
||||||
|
# C: (A, B, C)}
|
||||||
|
# Verify entity itself is also included. It is not required, but
|
||||||
|
# worth noting when handling equivalence
|
||||||
|
self.assertTrue(entity_props in equivalence)
|
||||||
|
for equivalent_props in equivalence:
|
||||||
|
# Verify equivalent scenarios are present in repository
|
||||||
|
self.assertTrue(equivalent_props in
|
||||||
|
self.scenario_repository.entity_scenarios)
|
||||||
|
|
||||||
def test_get_scenario_by_edge(self):
|
def test_get_scenario_by_edge(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -81,3 +95,37 @@ class ScenarioRepositoryTest(base.BaseTest):
|
|||||||
|
|
||||||
def test_add_template(self):
|
def test_add_template(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ScenarioExpansionTest(base.BaseTest):
|
||||||
|
BASE_DIR = utils.get_resources_dir() + '/scenario_expansion/'
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt('templates_dir',
|
||||||
|
default=BASE_DIR + 'templates'),
|
||||||
|
cfg.StrOpt('equivalences_dir',
|
||||||
|
default=BASE_DIR + '/equivalences')]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
|
||||||
|
cls.conf = cfg.ConfigOpts()
|
||||||
|
cls.conf.register_opts(cls.OPTS, group='evaluator')
|
||||||
|
|
||||||
|
templates_dir_path = cls.conf.evaluator.templates_dir
|
||||||
|
cls.template_defs = file_utils.load_yaml_files(templates_dir_path)
|
||||||
|
|
||||||
|
cls.scenario_repository = ScenarioRepository(cls.conf)
|
||||||
|
|
||||||
|
def test_expansion(self):
|
||||||
|
entity_scenarios = self.scenario_repository.entity_scenarios
|
||||||
|
for entity_key, scenarios in entity_scenarios.items():
|
||||||
|
if ('category', 'ALARM') in entity_key:
|
||||||
|
# scenarios expanded on the other alarm
|
||||||
|
self.assertEqual(len(scenarios), 2)
|
||||||
|
if ('category', 'RESOURCE') in entity_key:
|
||||||
|
# Scenarios expanded on the two alarms. Each alarm is expanded
|
||||||
|
# to two equivalent alarms. Thus 2 x 2 = 4 in total
|
||||||
|
self.assertEqual(len(scenarios), 4)
|
||||||
|
# each relationship is expand to two. Thus 2 x 2 = 4 in total
|
||||||
|
relationships = self.scenario_repository.relationship_scenarios.keys()
|
||||||
|
self.assertEqual(len(relationships), 4)
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from vitrage.common.constants import EdgeLabel
|
|
||||||
from vitrage.evaluator.template_data import ActionSpecs
|
from vitrage.evaluator.template_data import ActionSpecs
|
||||||
from vitrage.evaluator.template_data import ConditionVar
|
from vitrage.evaluator.template_data import ConditionVar
|
||||||
from vitrage.evaluator.template_data import EdgeDescription
|
from vitrage.evaluator.template_data import EdgeDescription
|
||||||
@ -55,15 +54,11 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
'alarm': Vertex(vertex_id='alarm',
|
'alarm': Vertex(vertex_id='alarm',
|
||||||
properties={'category': 'ALARM',
|
properties={'category': 'ALARM',
|
||||||
'type': 'nagios',
|
'type': 'nagios',
|
||||||
'name': 'HOST_HIGH_CPU_LOAD',
|
'name': 'host_problem'
|
||||||
'is_deleted': False,
|
|
||||||
'is_placeholder': False
|
|
||||||
}),
|
}),
|
||||||
'resource': Vertex(vertex_id='resource',
|
'resource': Vertex(vertex_id='resource',
|
||||||
properties={'category': 'RESOURCE',
|
properties={'category': 'RESOURCE',
|
||||||
'type': 'nova.host',
|
'type': 'nova.host'
|
||||||
'is_deleted': False,
|
|
||||||
'is_placeholder': False
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +67,7 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
edge=Edge(source_id='alarm',
|
edge=Edge(source_id='alarm',
|
||||||
target_id='resource',
|
target_id='resource',
|
||||||
label='on',
|
label='on',
|
||||||
properties={'relationship_type': 'on',
|
properties={'relationship_type': 'on'}),
|
||||||
'is_deleted': False,
|
|
||||||
'negative_condition': False}),
|
|
||||||
source=expected_entities['alarm'],
|
source=expected_entities['alarm'],
|
||||||
target=expected_entities['resource']
|
target=expected_entities['resource']
|
||||||
)
|
)
|
||||||
@ -83,15 +76,19 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
expected_scenario = Scenario(
|
expected_scenario = Scenario(
|
||||||
id='basic_template-scenario0',
|
id='basic_template-scenario0',
|
||||||
condition=[
|
condition=[
|
||||||
[ConditionVar(variable=expected_relationships['alarm_on_host'],
|
[ConditionVar(symbol_name='alarm_on_host',
|
||||||
type='relationship',
|
|
||||||
positive=True)]],
|
positive=True)]],
|
||||||
actions=[
|
actions=[
|
||||||
ActionSpecs(
|
ActionSpecs(
|
||||||
type='set_state',
|
type='set_state',
|
||||||
targets={'target': 'resource'},
|
targets={'target': 'resource'},
|
||||||
properties={'state': 'SUBOPTIMAL'})],
|
properties={'state': 'SUBOPTIMAL'})],
|
||||||
subgraphs=template_data.scenarios[0].subgraphs
|
# TODO(yujunz): verify the built subgraph is consistent with
|
||||||
|
# scenario definition. For now the observed value is
|
||||||
|
# assigned to make test passing
|
||||||
|
subgraphs=template_data.scenarios[0].subgraphs,
|
||||||
|
entities=expected_entities,
|
||||||
|
relationships=expected_relationships
|
||||||
)
|
)
|
||||||
|
|
||||||
self._validate_strict_equal(template_data,
|
self._validate_strict_equal(template_data,
|
||||||
@ -186,19 +183,8 @@ class BasicTemplateTest(base.BaseTest):
|
|||||||
condition_var = condition[0][0]
|
condition_var = condition[0][0]
|
||||||
self.assertIsInstance(condition_var, ConditionVar)
|
self.assertIsInstance(condition_var, ConditionVar)
|
||||||
|
|
||||||
variable = condition_var.variable
|
symbol_name = condition_var.symbol_name
|
||||||
self.assertIsInstance(variable, EdgeDescription)
|
self.assertIsInstance(symbol_name, str)
|
||||||
|
|
||||||
edge = variable[0]
|
|
||||||
self.assertEqual(edge.source_id, 'alarm')
|
|
||||||
self.assertEqual(edge.target_id, 'resource')
|
|
||||||
self.assertEqual(edge.label, EdgeLabel.ON)
|
|
||||||
|
|
||||||
source = variable[1]
|
|
||||||
self.assertEqual(source, entities[source.vertex_id])
|
|
||||||
|
|
||||||
target = variable[2]
|
|
||||||
self.assertEqual(target, entities[target.vertex_id])
|
|
||||||
|
|
||||||
actions = scenario.actions
|
actions = scenario.actions
|
||||||
self.assert_is_not_empty(scenario.actions)
|
self.assert_is_not_empty(scenario.actions)
|
||||||
|
Loading…
Reference in New Issue
Block a user