support for not operator in templates condition

Change-Id: I434b1967ee0cb91b54ab93e680a2a11bab3f8c3e
This commit is contained in:
Alexey Weyl 2017-03-19 16:51:23 +00:00
parent d59abab2c1
commit 8f327e3662
22 changed files with 2148 additions and 248 deletions

View File

@ -0,0 +1,310 @@
==============================
Templates Not Operator Support
==============================
Overview
--------
The Templates language supports the "or" and "and" operators at the moment.
Many scenarios can't be described by using only those two operators and thus
we would like to add support for "not" operator as well.
Template Structure
==================
The template is written in YAML language, with the following structure.
::
metadata:
name: <unique template identifier>
description: <what this template does>
definitions:
entities:
- entity: ...
- entity: ...
relationships:
- relationship: ...
- relationship: ...
scenarios:
- scenario:
condition: <if statement true do the action>
actions:
- action: ...
All the sections are in use as described in the "vitrage-template-format.rst" file.
But in the condition section it will be possible to write the "not" operator in addition to the "and" and "or" operators.
The "not" operator can be used only before a relationship expression.
Condition Format
----------------
The condition which needs to be met will be phrased using the entities and
relationships previously defined. The condition details are described in the
"vitrage-template-format.rst" and the addition here is the new logical operator "not":
- "not" - indicates that the expression must not be satisfied in order for the
condition to be met.
The following are examples of valid expressions, where X, Y and Z are
relationships:
- X
- X and Y
- X and Y and Z
- X and not Y
- X and not (Y or Z)
- X and not X
Supported Use Cases
===================
Use Case 1:
-----------
There exists an instance on Host but there is no Alarm on the instance.
+--------+ +--------+ Not +---------+
| Host | ------> | Vm | < - - - - | Alarm |
+--------+ +--------+ +---------+
::
metadata:
name: no_alarm_on_instance_that_contained_in_host
description: when host contains vm that has no alarm on it, show implications on the host
definitions:
entities:
- entity:
category: ALARM
type: instance_mem_performance_problem
template_id: instance_alarm # some string
- entity:
category: RESOURCE
type: nova.host
template_id: host
- entity:
category: RESOURCE
type: nova.instance
template_id: instance
relationships:
- relationship:
source: instance_alarm
target: instance
relationship_type: on
template_id : alarm_on_instance
- relationship:
source: host
target: instance
relationship_type: contains
template_id : host_contains_instance
scenarios:
- scenario:
condition: host_contains_instance and not alarm_on_instance
actions:
- action:
action_type: set_state
properties:
state: available
action_target:
target: host
Use Case 2:
-----------
There exists a host with no alarm.
+--------+ Not +---------+
| Host | < - - - - | Alarm |
+--------+ +---------+
::
metadata:
name: no_alarm_on_host
description: when there is no alarm on the host, show implications on the host
definitions:
entities:
- entity:
category: ALARM
type: host_high_mem_load
template_id: host_alarm # some string
- entity:
category: RESOURCE
type: nova.host
template_id: host
relationships:
- relationship:
source: host_alarm # source and target from entities section
target: host
relationship_type: on
template_id : alarm_on_host
scenarios:
- scenario:
condition: not alarm_on_host
actions:
- action:
action_type: set_state
properties:
state: available
action_target:
target: instance
Use Case 3:
-----------
The Switch is attached to a Host that contains a Vm.
The Switch is also comprised to a Network which has a Port.
There is no edge between the Vm and the Port.
+---------+ +---------+
+----------- | Host | --------> | Vm |
| +---------+ +---------+
|
v |
+----------+ N
| Switch | | o
+----------+ t
|
| v
|
| +---------+ +---------+
+----------> | Network | <--------- | Port |
+---------+ +---------+
::
metadata:
name: no_connection_between_vm_and_port
description: when there is no edge between the port and the vm, show implications on the instances
definitions:
entities:
- entity:
category: RESOURCE
type: nova.host
template_id: host
- entity:
category: RESOURCE
type: nova.instance
template_id: instance
- entity:
category: RESOURCE
type: switch
template_id: switch
- entity:
category: RESOURCE
type: neutron.network
template_id: network
- entity:
category: RESOURCE
type: neutron.port
template_id: port
relationships:
- relationship:
source: host
target: instance
relationship_type: contains
template_id : host_contains_instance
- relationship:
source: switch
target: host
relationship_type: connected
template_id : host_connected_switch
- relationship:
source: switch
target: network
relationship_type: has
template_id : switch_has_network
- relationship:
source: port
target: network
relationship_type: attached
template_id : port_attached_network
- relationship:
source: vm
target: port
relationship_type: connected
template_id : vm_connected_port
scenarios:
- scenario:
condition: host_contains_instance and host_connected_switch and switch_has_network and port_attached_network and not vm_connected_port
actions:
- action:
action_type: raise_alarm
properties:
alarm_name: instance_mem_performance_problem
severity: warning
action_target:
target: instance
Unsupported Use Cases
=====================
Use Case 1:
-----------
There is a Host contains Vm, which has no edge ("connection") to a stack that has an alarm on it.
Difference: The difference here from the graphs above, is that here there are
two connected component subgraphs (the first is host contains vm, the second is alarm on stack),
and the current mechanism doesn't support such a use case of not operator between many connected component subgraphs.
In the subgraphs above, we had only one vertex which was not connected to the main connected component subgraph.
+---------+ +---------+ Not +---------+ +---------+
| Host | --------> | Vm | - - - - - -> | Stack | <--------- | Alarm |
+---------+ +---------+ +---------+ +---------+
::
metadata:
name: host_contains_vm_with_no_edge_to_stack_that_has_alarm_on_it
description: when host contains vm without and edge to a stack that has no alarms, show implications on the instances
definitions:
entities:
- entity:
category: RESOURCE
type: nova.host
template_id: host
- entity:
category: RESOURCE
type: nova.instance
template_id: instance
- entity:
category: RESOURCE
type: heat.stack
template_id: stack
- entity:
category: ALARM
type: stack_high_mem_load
template_id: stack_alarm
relationships:
- relationship:
source: host
target: instance
relationship_type: contains
template_id : host_contains_instance
- relationship:
source: stack_alarm
target: stack
relationship_type: on
template_id : alarm_on_stack
- relationship:
source: instance
target: stack
relationship_type: attached
template_id : instance_attached_stack
scenarios:
- scenario:
condition: host_contains_instance and alarm_on_stack and not instance_attached_stack
actions:
- action:
action_type: set_state
properties:
state: available
action_target:
target: instance

View File

@ -111,7 +111,7 @@ class NotifierEventTypes(object):
DEACTIVATE_MARK_DOWN_EVENT = 'vitrage.mark_down.deactivate' DEACTIVATE_MARK_DOWN_EVENT = 'vitrage.mark_down.deactivate'
class TopologyFields(object): class TemplateTopologyFields(object):
"""yaml fields for topology definitions""" """yaml fields for topology definitions"""
METADATA = 'metadata' METADATA = 'metadata'
DESCRIPTION = 'description' DESCRIPTION = 'description'

View File

@ -14,7 +14,6 @@
from oslo_config import cfg from oslo_config import cfg
from vitrage.common.constants import DatasourceOpts as DSOpts from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import TopologyFields
from vitrage.common.constants import UpdateMethod from vitrage.common.constants import UpdateMethod
STATIC_DATASOURCE = 'static' STATIC_DATASOURCE = 'static'
@ -48,5 +47,16 @@ OPTS = [
help='static data sources configuration directory')] help='static data sources configuration directory')]
class StaticFields(TopologyFields): class StaticFields(object):
"""yaml fields for static definitions"""
METADATA = 'metadata'
DEFINITIONS = 'definitions'
RELATIONSHIPS = 'relationships'
ENTITIES = 'entities'
RELATIONSHIP_TYPE = 'relationship_type'
SOURCE = 'source'
TARGET = 'target'
STATIC_ID = 'static_id' STATIC_ID = 'static_id'
TYPE = 'type'
ID = 'id'
NAME = 'name'

View File

@ -18,8 +18,6 @@ from six.moves import reduce
from oslo_log import log from oslo_log import log
from vitrage.common.constants import TopologyFields
from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.driver_base import DriverBase from vitrage.datasources.driver_base import DriverBase
from vitrage.datasources.static import STATIC_DATASOURCE from vitrage.datasources.static import STATIC_DATASOURCE
from vitrage.datasources.static import StaticFields from vitrage.datasources.static import StaticFields
@ -30,7 +28,7 @@ LOG = log.getLogger(__name__)
class StaticDriver(DriverBase): class StaticDriver(DriverBase):
# base fields are required for all entities, others are treated as metadata # base fields are required for all entities, others are treated as metadata
BASE_FIELDS = {StaticFields.STATIC_ID, StaticFields.TYPE, VProps.ID} BASE_FIELDS = {StaticFields.STATIC_ID, StaticFields.TYPE, StaticFields.ID}
def __init__(self, conf): def __init__(self, conf):
super(StaticDriver, self).__init__() super(StaticDriver, self).__init__()
@ -40,7 +38,7 @@ class StaticDriver(DriverBase):
def _is_valid_config(config): def _is_valid_config(config):
"""check for validity of configuration""" """check for validity of configuration"""
# TODO(yujunz) check with yaml schema or reuse template validation # TODO(yujunz) check with yaml schema or reuse template validation
return TopologyFields.DEFINITIONS in config return StaticFields.DEFINITIONS in config
@staticmethod @staticmethod
def get_event_types(): def get_event_types():
@ -77,10 +75,10 @@ class StaticDriver(DriverBase):
.format(path)) .format(path))
return [] return []
definitions = config[TopologyFields.DEFINITIONS] definitions = config[StaticFields.DEFINITIONS]
entities = definitions[TopologyFields.ENTITIES] entities = definitions[StaticFields.ENTITIES]
relationships = definitions[TopologyFields.RELATIONSHIPS] relationships = definitions[StaticFields.RELATIONSHIPS]
return cls._pack(entities, relationships) return cls._pack(entities, relationships)
@ -100,22 +98,23 @@ class StaticDriver(DriverBase):
metadata = {key: value for key, value in iteritems(entity) metadata = {key: value for key, value in iteritems(entity)
if key not in cls.BASE_FIELDS} if key not in cls.BASE_FIELDS}
entities_dict[static_id] = entity entities_dict[static_id] = entity
entity[TopologyFields.RELATIONSHIPS] = [] entity[StaticFields.RELATIONSHIPS] = []
entity[TopologyFields.METADATA] = metadata entity[StaticFields.METADATA] = metadata
else: else:
LOG.warning("Skipped duplicated entity: {}".format(entity)) LOG.warning("Skipped duplicated entity: {}".format(entity))
@classmethod @classmethod
def _pack_rel(cls, entities_dict, rel): def _pack_rel(cls, entities_dict, rel):
source_id = rel[TopologyFields.SOURCE] source_id = rel[StaticFields.SOURCE]
target_id = rel[TopologyFields.TARGET] target_id = rel[StaticFields.TARGET]
if source_id == target_id: if source_id == target_id:
# self pointing relationship # self pointing relationship
entities_dict[source_id][TopologyFields.RELATIONSHIPS].append(rel) entities_dict[source_id]
[StaticFields.RELATIONSHIPS].append(rel)
else: else:
source, target = entities_dict[source_id], entities_dict[target_id] source, target = entities_dict[source_id], entities_dict[target_id]
source[TopologyFields.RELATIONSHIPS].append( source[StaticFields.RELATIONSHIPS].append(
cls._expand_neighbor(rel, target)) cls._expand_neighbor(rel, target))
@staticmethod @staticmethod

View File

@ -18,7 +18,6 @@ from oslo_log import log as logging
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import TopologyFields
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.resource_transformer_base import \ from vitrage.datasources.resource_transformer_base import \
ResourceTransformerBase ResourceTransformerBase
@ -58,7 +57,7 @@ class StaticTransformer(ResourceTransformerBase):
return STATIC_DATASOURCE return STATIC_DATASOURCE
def _create_vertex(self, entity_event): def _create_vertex(self, entity_event):
metadata = entity_event.get(TopologyFields.METADATA, {}) metadata = entity_event.get(StaticFields.METADATA, {})
entity_type = entity_event[VProps.TYPE] entity_type = entity_event[VProps.TYPE]
entity_id = entity_event[VProps.ID] entity_id = entity_event[VProps.ID]
@ -101,13 +100,15 @@ class StaticTransformer(ResourceTransformerBase):
return None return None
if rel[StaticFields.SOURCE] == rel[StaticFields.TARGET]: if rel[StaticFields.SOURCE] == rel[StaticFields.TARGET]:
neighbor = entity_event neighbor = entity_event
return self._create_neighbor(entity_event, return self._create_neighbor(
entity_event,
neighbor[VProps.ID], neighbor[VProps.ID],
neighbor[VProps.TYPE], neighbor[VProps.TYPE],
rel[TopologyFields.RELATIONSHIP_TYPE], rel[StaticFields.RELATIONSHIP_TYPE],
is_entity_source=is_entity_source) is_entity_source=is_entity_source)
def _create_static_neighbors(self, entity_event): def _create_static_neighbors(self, entity_event):
relationships = entity_event.get(TopologyFields.RELATIONSHIPS, []) relationships = entity_event.get(StaticFields.RELATIONSHIPS,
[])
return [self._create_static_neighbor(entity_event, rel) return [self._create_static_neighbor(entity_event, rel)
for rel in relationships] for rel in relationships]

View File

@ -26,9 +26,9 @@ from vitrage.evaluator.actions.base import ActionType
import vitrage.evaluator.actions.priority_tools as pt import vitrage.evaluator.actions.priority_tools as pt
from vitrage.evaluator.template_data import ActionSpecs from vitrage.evaluator.template_data import ActionSpecs
from vitrage.evaluator.template_data import EdgeDescription from vitrage.evaluator.template_data import EdgeDescription
from vitrage.evaluator.template_data import ENTITY
from vitrage.graph.algo_driver.algorithm import Mapping from vitrage.graph.algo_driver.algorithm import Mapping
from vitrage.graph.driver.networkx_graph import NXGraph from vitrage.graph.algo_driver.sub_graph_matching import \
NEG_CONDITION
from vitrage.graph.driver import Vertex from vitrage.graph.driver import Vertex
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -87,7 +87,6 @@ class ScenarioEvaluator(object):
str(before), str(before),
str(current)) str(current))
# todo (erosensw): support for NOT conditions - reverse logic
before_scenarios = self._get_element_scenarios(before, is_vertex) before_scenarios = self._get_element_scenarios(before, is_vertex)
current_scenarios = self._get_element_scenarios(current, is_vertex) current_scenarios = self._get_element_scenarios(current, is_vertex)
before_scenarios, current_scenarios = \ before_scenarios, current_scenarios = \
@ -157,15 +156,52 @@ class ScenarioEvaluator(object):
actions = {} actions = {}
for action in scenario.actions: for action in scenario.actions:
for scenario_element in scenario_elements: for scenario_element in scenario_elements:
matches = self._evaluate_full_condition(scenario.condition, matches = self._evaluate_subgraphs(scenario.subgraphs,
element,
scenario_element,
action.targets['target'])
actions.update(self._get_actions_from_matches(matches,
mode,
action,
scenario))
return actions
def _evaluate_subgraphs(self,
subgraphs,
element,
scenario_element,
action_target):
if isinstance(element, Vertex):
return self._find_vertex_subgraph_matching(subgraphs,
action_target,
element, element,
scenario_element) scenario_element)
if matches: else:
return self._find_edge_subgraph_matching(subgraphs,
action_target,
element,
scenario_element)
def _get_actions_from_matches(self,
combined_matches,
mode,
action_spec,
scenario):
actions = {}
for is_switch_mode, matches in combined_matches:
new_mode = mode
if is_switch_mode:
new_mode = ActionMode.UNDO \
if mode == ActionMode.DO else ActionMode.DO
for match in matches: for match in matches:
spec, action_id = self._get_action_spec(action, match) spec, action_id = self._get_action_spec(action_spec, match)
match_hash = hash(tuple(sorted(match.items()))) match_hash = hash(tuple(sorted(match.items())))
actions[action_id] = \ actions[action_id] = ActionInfo(spec, new_mode,
ActionInfo(spec, mode, scenario.id, match_hash) scenario.id, match_hash)
return actions return actions
@staticmethod @staticmethod
@ -190,53 +226,6 @@ class ScenarioEvaluator(object):
tuple(sorted(action_spec.properties.items()))) tuple(sorted(action_spec.properties.items())))
) )
def _evaluate_full_condition(self, condition, element, scenario_element):
condition_matches = []
for clause in condition:
# OR condition means aggregation of matches, without duplicates
and_condition_matches = \
self._evaluate_and_condition(clause, element, scenario_element)
condition_matches += and_condition_matches
return condition_matches
def _evaluate_and_condition(self, condition, element, scenario_element):
condition_g = NXGraph("scenario condition")
for term in condition:
if not term.positive:
# todo(erosensw): add support for NOT clauses
LOG.error('Template with NOT operator current not supported')
return []
if term.type == ENTITY:
term.variable[VProps.IS_DELETED] = False
condition_g.add_vertex(term.variable)
else: # type = relationship
edge_desc = term.variable
self._set_relationship_not_deleted(edge_desc)
self._add_relationship(condition_g, edge_desc)
if isinstance(element, Vertex):
initial_map = Mapping(scenario_element, element, True)
else:
initial_map = Mapping(scenario_element.edge, element, False)
return self._entity_graph.algo.sub_graph_matching(condition_g,
[initial_map])
@staticmethod
def _set_relationship_not_deleted(edge_description):
edge_description.source[VProps.IS_DELETED] = False
edge_description.target[VProps.IS_DELETED] = False
edge_description.edge[EProps.IS_DELETED] = False
@staticmethod
def _add_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 _analyze_and_filter_actions(self, actions): def _analyze_and_filter_actions(self, actions):
actions_to_perform = {} actions_to_perform = {}
@ -260,6 +249,102 @@ class ScenarioEvaluator(object):
actions_to_perform[key] = new_dominant actions_to_perform[key] = new_dominant
return actions_to_perform.values() return actions_to_perform.values()
def _find_vertex_subgraph_matching(self,
subgraphs,
action_target,
vertex,
scenario_vertex):
"""calculates subgraph matching for vertex
iterates over all the subgraphs, and checks if the triggered vertex is
in the same connected component as the action then run subgraph
matching on the vertex and return its result, otherwise return an
empty list of matches.
"""
matches = []
for subgraph in subgraphs:
connected_component = subgraph.algo.graph_query_vertices(
root_id=action_target,
edge_query_dict={'!=': {NEG_CONDITION: True}})
is_switch_mode = \
connected_component.get_vertex(scenario_vertex.vertex_id)
if is_switch_mode:
initial_map = Mapping(scenario_vertex, vertex, True)
mat = self._entity_graph.algo.sub_graph_matching(subgraph,
initial_map)
matches.append((False, mat))
else:
matches.append((True, []))
return matches
def _find_edge_subgraph_matching(self,
subgraphs,
action_target,
vertex,
scenario_edge):
"""calculates subgraph matching for edge
iterates over all the subgraphs, and checks if the triggered edge is a
negative edge then mark it as deleted=false and negative=false so that
subgraph matching on that edge will work correctly. after running
subgraph matching, we need to remove the negative vertices that were
added due to the change above.
"""
matches = []
for subgraph in subgraphs:
is_switch_mode = \
subgraph.get_edge(scenario_edge.source.vertex_id,
scenario_edge.target.vertex_id,
scenario_edge.edge.label).get(NEG_CONDITION,
False)
connected_component = subgraph.algo.graph_query_vertices(
root_id=action_target,
edge_query_dict={'!=': {NEG_CONDITION: True}})
# change the is_deleted and negative_condition props to false when
# is_switch_mode=true so that when we have an event on a
# negative_condition=true edge it will find the correct subgraph
self._switch_edge_negative_props(is_switch_mode, scenario_edge,
subgraph, False)
initial_map = Mapping(scenario_edge.edge, vertex, False)
curr_matches = \
self._entity_graph.algo.sub_graph_matching(subgraph,
initial_map)
# switch back to the original values
self._switch_edge_negative_props(is_switch_mode, scenario_edge,
subgraph, True)
self._remove_negative_vertices_from_matches(curr_matches,
connected_component)
matches.append((is_switch_mode, curr_matches))
return matches
@staticmethod
def _switch_edge_negative_props(is_switch_mode,
scenario_edge,
subgraph,
status):
if is_switch_mode:
scenario_edge.edge[NEG_CONDITION] = status
scenario_edge.edge[EProps.IS_DELETED] = status
subgraph.update_edge(scenario_edge.edge)
@staticmethod
def _remove_negative_vertices_from_matches(matches, connected_component):
for match in matches:
ver_ids = [v.vertex_id for v in connected_component.get_vertices()]
ver_to_remove = [id for id in match.keys() if id not in ver_ids]
for v_id in ver_to_remove:
del match[v_id]
class ActionTracker(object): class ActionTracker(object):
"""Keeps track of all active actions and relative dominance/priority. """Keeps track of all active actions and relative dominance/priority.

View File

@ -11,6 +11,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# 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 collections import namedtuple from collections import namedtuple
from sympy.logic.boolalg import And from sympy.logic.boolalg import And
from sympy.logic.boolalg import Not from sympy.logic.boolalg import Not
@ -19,14 +20,17 @@ 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 VertexProperties as VProps
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.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', ['variable', 'type', 'positive'])
ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties']) ActionSpecs = namedtuple('ActionSpecs', ['type', 'targets', 'properties'])
Scenario = namedtuple('Scenario', ['id', 'condition', 'actions']) Scenario = namedtuple('Scenario', ['id', 'condition', 'actions', 'subgraphs'])
EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target']) EdgeDescription = namedtuple('EdgeDescription', ['edge', 'source', 'target'])
@ -132,12 +136,13 @@ class TemplateData(object):
scenarios = [] scenarios = []
for counter, scenarios_def in enumerate(scenarios_defs): for counter, scenarios_def in enumerate(scenarios_defs):
scenario_dict = scenarios_def[TFields.SCENARIO] scenario_dict = scenarios_def[TFields.SCENARIO]
condition = self._parse_condition(scenario_dict[TFields.CONDITION]) condition = self._parse_condition(scenario_dict[TFields.CONDITION])
action_specs = self._build_actions(scenario_dict[TFields.ACTIONS]) 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, action_specs)) scenarios.append(Scenario(scenario_id, condition,
action_specs, subgraphs))
return scenarios return scenarios
@ -156,6 +161,41 @@ 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
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.source[VProps.IS_DELETED] = False
edge_description.target[VProps.IS_DELETED] = 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
@ -181,7 +221,7 @@ class TemplateData(object):
return [self._extract_and_condition(condition_dnf)] return [self._extract_and_condition(condition_dnf)]
if isinstance(condition_dnf, Not): if isinstance(condition_dnf, Not):
return [[(self._extract_condition_var(condition_dnf, False))]] return [(self._extract_not_condition_var(condition_dnf))]
if isinstance(condition_dnf, Symbol): if isinstance(condition_dnf, Symbol):
return [[(self._extract_condition_var(condition_dnf, True))]] return [[(self._extract_condition_var(condition_dnf, True))]]
@ -213,8 +253,14 @@ class TemplateData(object):
return [self._extract_condition_var(arg, isinstance(arg, Symbol)) return [self._extract_condition_var(arg, isinstance(arg, Symbol))
for arg in and_condition.args] for arg in and_condition.args]
def _extract_condition_var(self, symbol, positive): 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]
else:
var, var_type = self._extract_var(str(symbol)) var, var_type = self._extract_var(str(symbol))
return ConditionVar(var, var_type, positive) return ConditionVar(var, var_type, positive)

View File

@ -12,10 +12,10 @@
# 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 TopologyFields from vitrage.common.constants import TemplateTopologyFields
class TemplateFields(TopologyFields): class TemplateFields(TemplateTopologyFields):
SCENARIOS = 'scenarios' SCENARIOS = 'scenarios'

View File

@ -17,7 +17,10 @@ from networkx.algorithms import simple_paths
from oslo_log import log as logging from oslo_log import log as logging
from vitrage.common.constants import EdgeProperties as EProps
from vitrage.graph.algo_driver.algorithm import GraphAlgorithm from vitrage.graph.algo_driver.algorithm import GraphAlgorithm
from vitrage.graph.algo_driver.algorithm import Mapping
from vitrage.graph.algo_driver.sub_graph_matching import NEG_CONDITION
from vitrage.graph.algo_driver.sub_graph_matching import subgraph_matching from vitrage.graph.algo_driver.sub_graph_matching import subgraph_matching
from vitrage.graph.driver import Direction from vitrage.graph.driver import Direction
from vitrage.graph.driver import Edge from vitrage.graph.driver import Edge
@ -97,23 +100,41 @@ class NXAlgorithm(GraphAlgorithm):
str(self.graph._g.edges(data=True))) str(self.graph._g.edges(data=True)))
return graph return graph
@staticmethod def sub_graph_matching(self,
def _edge_result_to_list(edge_result): subgraph,
d = dict() known_match,
for source_id, target_id, label, data in edge_result: validate=False):
d[(source_id, target_id, label)] = \ """Finds all the matching subgraphs in the graph
Edge(source_id, target_id, label, properties=data)
return d.values()
@staticmethod In case the known_match has a subgraph edge with property
def _vertex_result_to_list(vertex_result): "negative_condition" then run subgraph matching on the edge vertices
d = dict() and unite the results.
for v_id, data in vertex_result: Otherwise just run subgraph matching and return its result.
d[v_id] = Vertex(vertex_id=v_id, properties=data)
return d.values()
def sub_graph_matching(self, subgraph, known_matches, validate=False): :param subgraph: the subgraph to match
return subgraph_matching(self.graph, subgraph, known_matches, validate) :param known_match: starting point at the subgraph and the graph
:param validate:
:return: all the matching subgraphs in the graph
"""
sge = known_match.subgraph_element
ge = known_match.graph_element
if not known_match.is_vertex and sge.get(NEG_CONDITION):
source_matches = self._filtered_subgraph_matching(ge.source_id,
sge.source_id,
subgraph,
validate)
target_matches = self._filtered_subgraph_matching(ge.target_id,
sge.target_id,
subgraph,
validate)
return self._list_union(source_matches, target_matches)
else:
return subgraph_matching(self.graph,
subgraph,
[known_match],
validate)
def create_graph_from_matching_vertices(self, def create_graph_from_matching_vertices(self,
vertex_attr_filter=None, vertex_attr_filter=None,
@ -159,3 +180,53 @@ class NXAlgorithm(GraphAlgorithm):
return simple_paths.all_simple_paths(self.graph._g, return simple_paths.all_simple_paths(self.graph._g,
source=source, source=source,
target=target) target=target)
def _filtered_subgraph_matching(self,
ge_v_id,
sge_v_id,
subgraph,
validate):
"""Runs subgraph_matching on edges vertices with filtering
Runs subgraph_matching on edges vertices after checking if that vertex
has real neighbors in the entity graph.
"""
if self.graph.neighbors(ge_v_id,
edge_attr_filter={EProps.IS_DELETED: False}):
template_vertex = subgraph.get_vertex(sge_v_id)
graph_vertex = self.graph.get_vertex(ge_v_id)
match = Mapping(template_vertex, graph_vertex, True)
return subgraph_matching(self.graph, subgraph, [match], validate)
return []
@staticmethod
def _edge_result_to_list(edge_result):
d = dict()
for source_id, target_id, label, data in edge_result:
d[(source_id, target_id, label)] = \
Edge(source_id, target_id, label, properties=data)
return d.values()
@staticmethod
def _vertex_result_to_list(vertex_result):
d = dict()
for v_id, data in vertex_result:
d[v_id] = Vertex(vertex_id=v_id, properties=data)
return d.values()
@staticmethod
def _list_union(list_1, list_2):
"""Union of list that aren't hashable
Can't use here set union because the items in the lists are
dictionaries and they are not hashable for set.
:return: list - union list
"""
for target_item in list_2:
if target_item not in list_1:
list_1.append(target_item)
return list_1

View File

@ -11,7 +11,9 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# 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 oslo_log import log as logging from oslo_log import log as logging
import six
from vitrage.common.exception import VitrageAlgorithmError from vitrage.common.exception import VitrageAlgorithmError
from vitrage.graph.filter import check_filter from vitrage.graph.filter import check_filter
@ -22,6 +24,8 @@ LOG = logging.getLogger(__name__)
MAPPED_V_ID = 'mapped_v_id' MAPPED_V_ID = 'mapped_v_id'
NEIGHBORS_MAPPED = 'neighbors_mapped' NEIGHBORS_MAPPED = 'neighbors_mapped'
GRAPH_VERTEX = 'graph_vertex' GRAPH_VERTEX = 'graph_vertex'
NEG_VERTEX = 'negative vertex'
NEG_CONDITION = 'negative_condition'
def subgraph_matching(base_graph, subgraph, matches, validate=False): def subgraph_matching(base_graph, subgraph, matches, validate=False):
@ -94,7 +98,10 @@ def subgraph_matching(base_graph, subgraph, matches, validate=False):
continue continue
# STEP 3: FIND A SUB-GRAPH VERTEX TO MAP # STEP 3: FIND A SUB-GRAPH VERTEX TO MAP
v_with_unmapped_neighbors = vertices_with_unmapped_neighbors.pop(0) v_with_unmapped_neighbors = _choose_vertex(
vertices_with_unmapped_neighbors,
curr_subgraph)
unmapped_neighbors = list(filter( unmapped_neighbors = list(filter(
lambda v: not v.get(MAPPED_V_ID), lambda v: not v.get(MAPPED_V_ID),
curr_subgraph.neighbors(v_with_unmapped_neighbors.vertex_id))) curr_subgraph.neighbors(v_with_unmapped_neighbors.vertex_id)))
@ -104,7 +111,10 @@ def subgraph_matching(base_graph, subgraph, matches, validate=False):
curr_subgraph.update_vertex(v_with_unmapped_neighbors) curr_subgraph.update_vertex(v_with_unmapped_neighbors)
queue.append(curr_subgraph) queue.append(curr_subgraph)
continue continue
subgraph_vertex_to_map = unmapped_neighbors.pop(0) subgraph_vertex_to_map = _choose_vertex(
unmapped_neighbors,
curr_subgraph,
curr_v=v_with_unmapped_neighbors)
# STEP 4: PROPERTIES CHECK # STEP 4: PROPERTIES CHECK
graph_candidate_vertices = base_graph.neighbors( graph_candidate_vertices = base_graph.neighbors(
@ -114,25 +124,66 @@ def subgraph_matching(base_graph, subgraph, matches, validate=False):
# STEP 5: STRUCTURE CHECK # STEP 5: STRUCTURE CHECK
edges = _get_edges_to_mapped_vertices(curr_subgraph, edges = _get_edges_to_mapped_vertices(curr_subgraph,
subgraph_vertex_to_map.vertex_id) subgraph_vertex_to_map.vertex_id)
neg_edges = set(e for e in edges if e.get(NEG_CONDITION))
pos_edges = edges.difference(neg_edges)
if not graph_candidate_vertices and neg_edges and not pos_edges:
subgraph_vertex_to_map[MAPPED_V_ID] = NEG_VERTEX
curr_subgraph.update_vertex(subgraph_vertex_to_map)
queue.append(curr_subgraph.copy())
continue
found_subgraphs = []
for graph_vertex in graph_candidate_vertices: for graph_vertex in graph_candidate_vertices:
subgraph_vertex_to_map[MAPPED_V_ID] = graph_vertex.vertex_id subgraph_vertex_to_map[MAPPED_V_ID] = graph_vertex.vertex_id
subgraph_vertex_to_map[GRAPH_VERTEX] = graph_vertex subgraph_vertex_to_map[GRAPH_VERTEX] = graph_vertex
curr_subgraph.update_vertex(subgraph_vertex_to_map) curr_subgraph.update_vertex(subgraph_vertex_to_map)
if _graph_contains_subgraph_edges(base_graph, if not _graph_contains_subgraph_edges(base_graph,
curr_subgraph, curr_subgraph,
edges): pos_edges):
queue.append(curr_subgraph.copy()) continue
if not _graph_contains_subgraph_edges(base_graph,
curr_subgraph,
neg_edges):
del found_subgraphs[:]
break
if neg_edges and not pos_edges:
subgraph_vertex_to_map[MAPPED_V_ID] = NEG_VERTEX
curr_subgraph.update_vertex(subgraph_vertex_to_map)
found_subgraphs.append(curr_subgraph.copy())
queue.extend(found_subgraphs)
# Last thing: Convert results to the expected format! # Last thing: Convert results to the expected format!
return _generate_result(final_subgraphs)
def _generate_result(final_subgraphs):
result = [] result = []
for mapping in final_subgraphs: for mapping in final_subgraphs:
# TODO(ihefetz) If needed, Here we can easily extract the edge subgraph_vertices = dict()
# matches from the mapping graph for v in mapping.get_vertices():
a = {v.vertex_id: v[GRAPH_VERTEX] for v in mapping.get_vertices()} v_id = v[MAPPED_V_ID]
result.append(a) if isinstance(v_id, six.string_types) and v_id is not NEG_VERTEX:
subgraph_vertices[v.vertex_id] = v[GRAPH_VERTEX]
if subgraph_vertices not in result:
result.append(subgraph_vertices)
return result return result
def _choose_vertex(vertices, subgraph, curr_v=None):
"""Return a vertex with a positive edge if exists, otherwise the first one.
"""
for v in vertices:
curr_vertex_id = curr_v.vertex_id if curr_v else None
if not subgraph.get_edges(v.vertex_id, curr_vertex_id,
attr_filter={NEG_CONDITION: True}):
return v
return vertices.pop(0)
def _get_edges_to_mapped_vertices(graph, vertex_id): def _get_edges_to_mapped_vertices(graph, vertex_id):
"""Get all edges (to/from) vertex where neighbor has a MAPPED_V_ID """Get all edges (to/from) vertex where neighbor has a MAPPED_V_ID
@ -169,7 +220,12 @@ def _graph_contains_subgraph_edges(graph, subgraph, subgraph_edges):
found_graph_edge = graph.get_edge(graph_v_id_source, found_graph_edge = graph.get_edge(graph_v_id_source,
graph_v_id_target, graph_v_id_target,
e.label) e.label)
if not found_graph_edge or not check_filter(found_graph_edge, e):
if not found_graph_edge and e.get(NEG_CONDITION):
continue
if not found_graph_edge or not check_filter(found_graph_edge, e,
NEG_CONDITION):
return False return False
return True return True

View File

@ -158,3 +158,6 @@ class Edge(PropertiesElement):
:return: the other vertex id :return: the other vertex id
""" """
return self.source_id if self.target_id == v_id else self.target_id return self.source_id if self.target_id == v_id else self.target_id
def has_vertex(self, v_id):
return self.source_id == v_id or self.target_id == v_id

View File

@ -202,11 +202,18 @@ class Graph(object):
pass pass
@abc.abstractmethod @abc.abstractmethod
def get_edges(self, v_id, direction=Direction.BOTH, def get_edges(self,
v1_id,
v2_id=None,
direction=Direction.BOTH,
attr_filter=None): attr_filter=None):
"""Fetch multiple edges from the graph, """Fetch multiple edges from the graph,
Fetch an edge from the graph, according to its two vertices and label Fetch all edges from the graph, according to its two vertices.
If only one vertex id is given it finds all the edges from this vertex
to all other vertices.
If two vertices ids are given it finds all the edges between those two
vertices.
EXAMPLE EXAMPLE
------- -------
@ -218,8 +225,11 @@ class Graph(object):
v_id=v2.vertex_id, v_id=v2.vertex_id,
attr_filter={'LABEL': ['ON', 'WITH']}) attr_filter={'LABEL': ['ON', 'WITH']})
:param v_id: vertex id a vertex :param v1_id: first vertex id of vertex
:type v_id: str :type v1_id: str
:param v2_id: second vertex id of vertex
:type v2_id: str
:param direction: specify In/Out/Both for edge direction :param direction: specify In/Out/Both for edge direction
:type direction: int :type direction: int

View File

@ -118,7 +118,10 @@ class NXGraph(Graph):
return edge_copy(source_id, target_id, label, properties) return edge_copy(source_id, target_id, label, properties)
return None return None
def get_edges(self, v_id, direction=Direction.BOTH, def get_edges(self,
v1_id,
v2_id=None,
direction=Direction.BOTH,
attr_filter=None): attr_filter=None):
"""Fetch multiple edges from the graph """Fetch multiple edges from the graph
@ -128,9 +131,14 @@ class NXGraph(Graph):
return check_filter(edge_data, attr_filter) return check_filter(edge_data, attr_filter)
nodes, edges = self._neighboring_nodes_edges_query( nodes, edges = self._neighboring_nodes_edges_query(
v_id, edge_predicate=check_edge, direction=direction) v1_id, edge_predicate=check_edge, direction=direction)
edge_copies = set(edge_copy(u, v, label, data) edge_copies = set(edge_copy(u, v, label, data)
for u, v, label, data in edges) for u, v, label, data in edges)
if v2_id:
edge_copies = [e for e in edge_copies if e.has_vertex(v2_id)]
return edge_copies return edge_copies
def _get_edges_by_direction(self, v_id, direction): def _get_edges_by_direction(self, v_id, direction):

View File

@ -12,22 +12,32 @@
# 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 six.moves import queue
from oslo_config import cfg from oslo_config import cfg
from six.moves import queue
from vitrage.common.constants import DatasourceAction from vitrage.common.constants import DatasourceAction
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EdgeProperties as EProps from vitrage.common.constants import EdgeProperties as EProps
from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nagios.properties import NagiosProperties
from vitrage.datasources.nagios.properties import NagiosTestStatus
from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE
from vitrage.datasources.neutron.port import NEUTRON_PORT_DATASOURCE
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE
from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator from vitrage.evaluator.scenario_evaluator import ScenarioEvaluator
from vitrage.evaluator.scenario_repository import ScenarioRepository from vitrage.evaluator.scenario_repository import ScenarioRepository
from vitrage.graph import create_edge
from vitrage.tests.functional.base import \ from vitrage.tests.functional.base import \
TestFunctionalBase TestFunctionalBase
import vitrage.tests.mocks.mock_driver as mock_driver import vitrage.tests.mocks.mock_driver as mock_driver
from vitrage.tests.mocks import utils from vitrage.tests.mocks import utils
_TARGET_HOST = 'host-2' _TARGET_HOST = 'host-2'
_TARGET_ZONE = 'zone-1'
_NAGIOS_TEST_INFO = {'resource_name': _TARGET_HOST, _NAGIOS_TEST_INFO = {'resource_name': _TARGET_HOST,
DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT} DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT}
@ -58,7 +68,8 @@ class TestScenarioEvaluator(TestFunctionalBase):
event_queue, processor, evaluator = self._init_system() event_queue, processor, evaluator = self._init_system()
host_v = self._get_host_from_graph(_TARGET_HOST, host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE,
_TARGET_HOST,
processor.entity_graph) processor.entity_graph)
self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE],
'host should be AVAILABLE when starting') 'host should be AVAILABLE when starting')
@ -85,7 +96,8 @@ class TestScenarioEvaluator(TestFunctionalBase):
event_queue, processor, evaluator = self._init_system() event_queue, processor, evaluator = self._init_system()
host_v = self._get_host_from_graph(_TARGET_HOST, host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE,
_TARGET_HOST,
processor.entity_graph) processor.entity_graph)
self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE],
'host should be AVAILABLE when starting') 'host should be AVAILABLE when starting')
@ -130,7 +142,8 @@ class TestScenarioEvaluator(TestFunctionalBase):
event_queue, processor, evaluator = self._init_system() event_queue, processor, evaluator = self._init_system()
host_v = self._get_host_from_graph(_TARGET_HOST, host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE,
_TARGET_HOST,
processor.entity_graph) processor.entity_graph)
self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE],
'host should be AVAILABLE when starting') 'host should be AVAILABLE when starting')
@ -168,7 +181,8 @@ class TestScenarioEvaluator(TestFunctionalBase):
event_queue, processor, evaluator = self._init_system() event_queue, processor, evaluator = self._init_system()
host_v = self._get_host_from_graph(_TARGET_HOST, host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE,
_TARGET_HOST,
processor.entity_graph) processor.entity_graph)
self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE],
'host should be AVAILABLE when starting') 'host should be AVAILABLE when starting')
@ -197,7 +211,6 @@ class TestScenarioEvaluator(TestFunctionalBase):
self._get_deduced_alarms_on_host(host_v, processor.entity_graph) self._get_deduced_alarms_on_host(host_v, processor.entity_graph)
self.assertEqual(0, len(alarms)) self.assertEqual(0, len(alarms))
# todo: (erosensw) uncomment this test
def test_overlapping_deduced_alarm_1(self): def test_overlapping_deduced_alarm_1(self):
event_queue, processor, evaluator = self._init_system() event_queue, processor, evaluator = self._init_system()
@ -293,12 +306,333 @@ class TestScenarioEvaluator(TestFunctionalBase):
self.assertEqual(1, len(alarms)) self.assertEqual(1, len(alarms))
self.assertEqual('WARNING', alarms[0]['severity']) self.assertEqual('WARNING', alarms[0]['severity'])
def test_simple_not_operator_deduced_alarm(self):
"""Handles a simple not operator use case
We have created the following template: if there is a neutron.port that
doesn't have a nagios alarm of type PORT_PROBLEM on it, then raise a
deduced alarm on the port called simple_port_deduced_alarm.
The test has 3 steps in it:
1. create neutron.network and neutron.port and check that the
simple_port_deduced_alarm is raised on the neutron.port because it
doesn't have a nagios alarm on it.
2. create a nagios alarm called PORT_PROBLEM on the port and check
that the alarm simple_port_deduced_alarm doesn't appear on the
neutron.port.
3. delete the nagios alarm called PORT_PROBLEM from the port, and
check that the alarm alarm simple_port_deduced_alarm appear on the
neutron.port.
"""
event_queue, processor, evaluator = self._init_system()
entity_graph = processor.entity_graph
# constants
num_orig_vertices = entity_graph.num_vertices()
num_orig_edges = entity_graph.num_edges()
num_added_vertices = 2
num_added_edges = 2
num_deduced_vertices = 1
num_deduced_edges = 1
num_nagios_alarm_vertices = 1
num_nagios_alarm_edges = 1
# find instances
query = {
VProps.CATEGORY: 'RESOURCE',
VProps.TYPE: NOVA_INSTANCE_DATASOURCE
}
instance_ver = entity_graph.get_vertices(vertex_attr_filter=query)[0]
# update network
network_event = {
'tenant_id': 'admin',
'name': 'net-0',
'updated_at': '2015-12-01T12:46:41Z',
'status': 'active',
'id': '12345',
'vitrage_entity_type': 'neutron.network',
'vitrage_datasource_action': 'snapshot',
'vitrage_sample_date': '2015-12-01T12:46:41Z',
}
# update port
port_event = {
'tenant_id': 'admin',
'name': 'port-0',
'updated_at': '2015-12-01T12:46:41Z',
'status': 'active',
'id': '54321',
'vitrage_entity_type': 'neutron.port',
'vitrage_datasource_action': 'snapshot',
'vitrage_sample_date': '2015-12-01T12:46:41Z',
'network_id': '12345',
'device_id': instance_ver.get(VProps.ID),
'device_owner': 'compute:nova',
'fixed_ips': {}
}
processor.process_event(network_event)
processor.process_event(port_event)
port_vertex = entity_graph.get_vertices(
vertex_attr_filter={VProps.TYPE: NEUTRON_PORT_DATASOURCE})[0]
while not event_queue.empty():
processor.process_event(event_queue.get())
# test asserts
query = {VProps.CATEGORY: EntityCategory.ALARM}
port_neighbors = entity_graph.neighbors(port_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices, entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges,
entity_graph.num_edges())
self.assertEqual(1, len(port_neighbors))
self.assertEqual(port_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(port_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(port_neighbors[0][VProps.NAME],
'simple_port_deduced_alarm')
# Add PORT_PROBLEM alarm
test_vals = {'status': 'WARNING',
'service': 'PORT_PROBLEM',
'name': 'PORT_PROBLEM',
DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT,
VProps.RESOURCE_ID: port_vertex.get(VProps.ID),
'resource_name': port_vertex.get(VProps.ID),
'resource_type': NEUTRON_PORT_DATASOURCE}
generator = mock_driver.simple_nagios_alarm_generators(1, 1, test_vals)
nagios_event = mock_driver.generate_random_events_list(generator)[0]
processor.process_event(nagios_event)
while not event_queue.empty():
processor.process_event(event_queue.get())
# test asserts
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices + num_nagios_alarm_vertices,
entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges +
num_nagios_alarm_edges, entity_graph.num_edges())
query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'vitrage'}
port_neighbors = entity_graph.neighbors(port_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(port_neighbors))
self.assertEqual(port_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(port_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(port_neighbors[0][VProps.NAME],
'simple_port_deduced_alarm')
self.assertEqual(port_neighbors[0][VProps.IS_DELETED], True)
query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'nagios'}
port_neighbors = entity_graph.neighbors(port_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(port_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(port_neighbors[0][VProps.TYPE], 'nagios')
self.assertEqual(port_neighbors[0][VProps.NAME], 'PORT_PROBLEM')
self.assertEqual(port_neighbors[0][VProps.IS_DELETED], False)
self.assertEqual(port_neighbors[0][VProps.IS_PLACEHOLDER], False)
# disable PORT_PROBLEM alarm
nagios_event[NagiosProperties.STATUS] = NagiosTestStatus.OK
processor.process_event(nagios_event)
while not event_queue.empty():
processor.process_event(event_queue.get())
# test asserts
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices + num_nagios_alarm_vertices,
entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges +
num_nagios_alarm_edges, entity_graph.num_edges())
query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'vitrage'}
port_neighbors = entity_graph.neighbors(port_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(port_neighbors))
self.assertEqual(port_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(port_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(port_neighbors[0][VProps.NAME],
'simple_port_deduced_alarm')
self.assertEqual(port_neighbors[0][VProps.IS_DELETED], False)
query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'nagios'}
port_neighbors = entity_graph.neighbors(port_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(port_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(port_neighbors[0][VProps.TYPE], 'nagios')
self.assertEqual(port_neighbors[0][VProps.NAME], 'PORT_PROBLEM')
self.assertEqual(port_neighbors[0][VProps.IS_DELETED], True)
def test_complex_not_operator_deduced_alarm(self):
"""Handles a complex not operator use case
We have created the following template: if there is a openstack.cluster
that has a nova.zone which is connected to a neutron.network and also
there is no nagios alarm of type CLUSTER_PROBLEM on the cluster and no
nagios alarm of type NETWORK_PROBLEM on the neutron.network, then raise
a deduced alarm on the nova.zone called complex_zone_deduced_alarm.
The test has 3 steps in it:
1. create a neutron.network and connect it to a zone, and check that
the complex_zone_deduced_alarm is raised on the nova.zone because it
doesn't have nagios alarms the openstack.cluster and on the
neutron.network.
2. create a nagios alarm called NETWORK_PROBLEM on the network and
check that the alarm complex_zone_deduced_alarm doesn't appear on
the nova.zone.
3. delete the nagios alarm called NETWORK_PROBLEM from the port, and
check that the alarm alarm complex_zone_deduced_alarm appear on the
nova.zone.
"""
event_queue, processor, evaluator = self._init_system()
entity_graph = processor.entity_graph
# constants
num_orig_vertices = entity_graph.num_vertices()
num_orig_edges = entity_graph.num_edges()
num_added_vertices = 2
num_added_edges = 3
num_deduced_vertices = 1
num_deduced_edges = 1
num_network_alarm_vertices = 1
num_network_alarm_edges = 1
# update zone
generator = mock_driver.simple_zone_generators(1, 1, snapshot_events=1)
zone_event = mock_driver.generate_random_events_list(generator)[0]
zone_event['zoneName'] = 'zone-7'
# update network
network_event = {
'tenant_id': 'admin',
'name': 'net-0',
'updated_at': '2015-12-01T12:46:41Z',
'status': 'active',
'id': '12345',
'vitrage_entity_type': 'neutron.network',
'vitrage_datasource_action': 'snapshot',
'vitrage_sample_date': '2015-12-01T12:46:41Z',
}
# process events
processor.process_event(zone_event)
query = {VProps.TYPE: NOVA_ZONE_DATASOURCE, VProps.ID: 'zone-7'}
zone_vertex = entity_graph.get_vertices(vertex_attr_filter=query)[0]
processor.process_event(network_event)
query = {VProps.TYPE: NEUTRON_NETWORK_DATASOURCE}
network_vertex = entity_graph.get_vertices(vertex_attr_filter=query)[0]
# add edge between network and zone
edge = create_edge(network_vertex.vertex_id, zone_vertex.vertex_id,
'attached')
entity_graph.add_edge(edge)
while not event_queue.empty():
processor.process_event(event_queue.get())
# test asserts
query = {VProps.CATEGORY: EntityCategory.ALARM}
zone_neighbors = entity_graph.neighbors(zone_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices, entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges,
entity_graph.num_edges())
self.assertEqual(1, len(zone_neighbors))
self.assertEqual(zone_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(zone_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(zone_neighbors[0][VProps.NAME],
'complex_zone_deduced_alarm')
# Add NETWORK_PROBLEM alarm
test_vals = {'status': 'WARNING',
'service': 'NETWORK_PROBLEM',
'name': 'NETWORK_PROBLEM',
DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT,
VProps.RESOURCE_ID: network_vertex[VProps.ID],
'resource_name': network_vertex[VProps.ID],
'resource_type': NEUTRON_NETWORK_DATASOURCE}
generator = mock_driver.simple_nagios_alarm_generators(1, 1, test_vals)
nagios_event = mock_driver.generate_random_events_list(generator)[0]
processor.process_event(nagios_event)
while not event_queue.empty():
processor.process_event(event_queue.get())
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices + num_network_alarm_vertices,
entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges +
num_network_alarm_edges, entity_graph.num_edges())
query = {VProps.CATEGORY: EntityCategory.ALARM}
network_neighbors = entity_graph.neighbors(network_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(network_neighbors))
self.assertEqual(network_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(network_neighbors[0][VProps.TYPE], 'nagios')
self.assertEqual(network_neighbors[0][VProps.NAME], 'NETWORK_PROBLEM')
self.assertEqual(network_neighbors[0][VProps.IS_DELETED], False)
self.assertEqual(network_neighbors[0][VProps.IS_PLACEHOLDER], False)
zone_neighbors = entity_graph.neighbors(zone_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(zone_neighbors))
self.assertEqual(zone_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(zone_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(zone_neighbors[0][VProps.NAME],
'complex_zone_deduced_alarm')
self.assertEqual(zone_neighbors[0][VProps.IS_DELETED], True)
# delete NETWORK_PROBLEM alarm
nagios_event[NagiosProperties.STATUS] = NagiosTestStatus.OK
processor.process_event(nagios_event)
while not event_queue.empty():
processor.process_event(event_queue.get())
self.assertEqual(num_orig_vertices + num_added_vertices +
num_deduced_vertices + num_network_alarm_vertices,
entity_graph.num_vertices())
self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges +
num_network_alarm_edges, entity_graph.num_edges())
query = {VProps.CATEGORY: EntityCategory.ALARM}
network_neighbors = entity_graph.neighbors(network_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(network_neighbors))
self.assertEqual(network_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(network_neighbors[0][VProps.TYPE], 'nagios')
self.assertEqual(network_neighbors[0][VProps.NAME], 'NETWORK_PROBLEM')
self.assertEqual(network_neighbors[0][VProps.IS_DELETED], True)
zone_neighbors = entity_graph.neighbors(zone_vertex.vertex_id,
vertex_attr_filter=query)
self.assertEqual(1, len(zone_neighbors))
self.assertEqual(zone_neighbors[0][VProps.CATEGORY],
EntityCategory.ALARM)
self.assertEqual(zone_neighbors[0][VProps.TYPE], 'vitrage')
self.assertEqual(zone_neighbors[0][VProps.NAME],
'complex_zone_deduced_alarm')
self.assertEqual(zone_neighbors[0][VProps.IS_DELETED], False)
def get_host_after_event(self, event_queue, nagios_event, def get_host_after_event(self, event_queue, nagios_event,
processor, target_host): processor, target_host):
processor.process_event(nagios_event) processor.process_event(nagios_event)
while not event_queue.empty(): while not event_queue.empty():
processor.process_event(event_queue.get()) processor.process_event(event_queue.get())
host_v = self._get_host_from_graph(target_host, host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE,
target_host,
processor.entity_graph) processor.entity_graph)
return host_v return host_v
@ -311,13 +645,12 @@ class TestScenarioEvaluator(TestFunctionalBase):
return event_queue, processor, evaluator return event_queue, processor, evaluator
@staticmethod @staticmethod
def _get_host_from_graph(host_name, entity_graph): def _get_entity_from_graph(entity_type, entity_name, entity_graph):
vertex_attrs = {VProps.TYPE: NOVA_HOST_DATASOURCE, vertex_attrs = {VProps.TYPE: entity_type,
VProps.NAME: host_name} VProps.NAME: entity_name}
host_vertices = entity_graph.get_vertices( vertices = entity_graph.get_vertices(vertex_attr_filter=vertex_attrs)
vertex_attr_filter=vertex_attrs) # assert len(vertices) == 1, "incorrect number of vertices"
assert len(host_vertices) == 1, "incorrect number of vertices" return vertices[0]
return host_vertices[0]
@staticmethod @staticmethod
def _get_deduced_alarms_on_host(host_v, entity_graph): def _get_deduced_alarms_on_host(host_v, entity_graph):

View File

@ -27,7 +27,6 @@ from random import randint
# noinspection PyPep8Naming # noinspection PyPep8Naming
from vitrage.common.constants import EdgeLabel from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import TopologyFields
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.static import StaticFields from vitrage.datasources.static import StaticFields
from vitrage.tests.mocks.entity_model import BasicEntityModel as Bem from vitrage.tests.mocks.entity_model import BasicEntityModel as Bem
@ -569,12 +568,12 @@ def _get_static_snapshot_driver_values(spec):
switch_id = "s{}".format(switch_index) switch_id = "s{}".format(switch_index)
relationship = { relationship = {
TopologyFields.SOURCE: switch_id, StaticFields.SOURCE: switch_id,
TopologyFields.TARGET: host_id, StaticFields.TARGET: host_id,
TopologyFields.RELATIONSHIP_TYPE: EdgeLabel.ATTACHED StaticFields.RELATIONSHIP_TYPE: EdgeLabel.ATTACHED
} }
rel = relationship.copy() rel = relationship.copy()
rel[TopologyFields.TARGET] = entities[host_id] rel[StaticFields.TARGET] = entities[host_id]
relationships[switch_id].append(rel) relationships[switch_id].append(rel)
for host_index, switch_index in host_switch_mapping: for host_index, switch_index in host_switch_mapping:

View File

@ -0,0 +1,59 @@
metadata:
name: complex_correct_not_condition_template
description: complex not condition template for general tests
definitions:
entities:
- entity:
category: ALARM
type: nagios
name: NETWORK_PROBLEM
template_id: network_alarm
- entity:
category: ALARM
type: nagios
name: CLUSTER_PROBLEM
template_id: cluster_alarm
- entity:
category: RESOURCE
type: nova.zone
template_id: nova_zone
- entity:
category: RESOURCE
type: openstack.cluster
template_id: openstack_cluster
- entity:
category: RESOURCE
type: neutron.network
template_id: neutron_network
relationships:
- relationship:
source: cluster_alarm
target: openstack_cluster
relationship_type: on
template_id : alarm_on_cluster
- relationship:
source: openstack_cluster
target: nova_zone
relationship_type: contains
template_id : cluster_contains_zone
- relationship:
source: neutron_network
target: nova_zone
relationship_type: attached
template_id : network_attached_zone
- relationship:
source: network_alarm
target: neutron_network
relationship_type: on
template_id : alarm_on_network
scenarios:
- scenario:
condition: cluster_contains_zone and network_attached_zone and not alarm_on_cluster and not alarm_on_network
actions:
- action:
action_type: raise_alarm
properties:
alarm_name: complex_zone_deduced_alarm
severity: WARNING
action_target:
target: nova_zone

View File

@ -0,0 +1,31 @@
metadata:
name: basic_correct_not_condition_template
description: basic not condition template for general tests
definitions:
entities:
- entity:
category: ALARM
type: nagios
name: PORT_PROBLEM
template_id: port_alarm
- entity:
category: RESOURCE
type: neutron.port
template_id: port
relationships:
- relationship:
source: port_alarm
target: port
relationship_type: on
template_id : alarm_on_port
scenarios:
- scenario:
condition: not alarm_on_port
actions:
- action:
action_type: raise_alarm
properties:
alarm_name: simple_port_deduced_alarm
severity: WARNING
action_target:
target: port

View File

@ -17,7 +17,6 @@ from oslo_config import cfg
from vitrage.common.constants import DatasourceAction from vitrage.common.constants import DatasourceAction
from vitrage.common.constants import DatasourceOpts as DSOpts from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import GraphAction from vitrage.common.constants import GraphAction
from vitrage.common.constants import TopologyFields
from vitrage.datasources.static import driver from vitrage.datasources.static import driver
from vitrage.datasources.static import STATIC_DATASOURCE from vitrage.datasources.static import STATIC_DATASOURCE
from vitrage.datasources.static import StaticFields from vitrage.datasources.static import StaticFields
@ -99,8 +98,9 @@ class TestStaticDriver(base.BaseTest):
self._validate_static_entity(entity) self._validate_static_entity(entity)
def _validate_static_entity(self, entity): def _validate_static_entity(self, entity):
self.assertTrue(isinstance(entity[TopologyFields.METADATA], dict)) self.assertTrue(isinstance(entity[StaticFields.METADATA],
for rel in entity[TopologyFields.RELATIONSHIPS]: dict))
for rel in entity[StaticFields.RELATIONSHIPS]:
self._validate_static_rel(entity, rel) self._validate_static_rel(entity, rel)
def _validate_static_rel(self, entity, rel): def _validate_static_rel(self, entity, rel):

View File

@ -19,12 +19,12 @@ from oslo_config import cfg
from vitrage.common.constants import DatasourceOpts as DSOpts from vitrage.common.constants import DatasourceOpts as DSOpts
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import TopologyFields
from vitrage.common.constants import UpdateMethod from vitrage.common.constants import UpdateMethod
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.host.transformer import HostTransformer from vitrage.datasources.nova.host.transformer import HostTransformer
from vitrage.datasources.static import STATIC_DATASOURCE from vitrage.datasources.static import STATIC_DATASOURCE
from vitrage.datasources.static import StaticFields
from vitrage.datasources.static.transformer import StaticTransformer from vitrage.datasources.static.transformer import StaticTransformer
from vitrage.tests import base from vitrage.tests import base
from vitrage.tests.mocks import mock_driver from vitrage.tests.mocks import mock_driver
@ -109,7 +109,7 @@ class TestStaticTransformer(base.BaseTest):
self.assertEqual(vertex[VProps.SAMPLE_TIMESTAMP], self.assertEqual(vertex[VProps.SAMPLE_TIMESTAMP],
event[DSProps.SAMPLE_DATE]) event[DSProps.SAMPLE_DATE])
for k, v in event.get(TopologyFields.METADATA, {}): for k, v in event.get(StaticFields.METADATA, {}):
self.assertEqual(vertex[k], v) self.assertEqual(vertex[k], v)
def _validate_common_props(self, vertex, event): def _validate_common_props(self, vertex, event):
@ -120,19 +120,22 @@ class TestStaticTransformer(base.BaseTest):
def _validate_neighbors(self, neighbors, vertex_id, event): def _validate_neighbors(self, neighbors, vertex_id, event):
for i in range(len(neighbors)): for i in range(len(neighbors)):
self._validate_neighbor(neighbors[i], self._validate_neighbor(
event[TopologyFields.RELATIONSHIPS][i], neighbors[i],
event[StaticFields.RELATIONSHIPS][i],
vertex_id) vertex_id)
def _validate_neighbor(self, neighbor, rel, vertex_id): def _validate_neighbor(self, neighbor, rel, vertex_id):
vertex = neighbor.vertex vertex = neighbor.vertex
self._validate_neighbor_vertex_props(vertex, self._validate_neighbor_vertex_props(
rel[TopologyFields.TARGET]) vertex,
rel[StaticFields.TARGET])
edge = neighbor.edge edge = neighbor.edge
self.assertEqual(edge.source_id, vertex_id) self.assertEqual(edge.source_id, vertex_id)
self.assertEqual(edge.target_id, neighbor.vertex.vertex_id) self.assertEqual(edge.target_id, neighbor.vertex.vertex_id)
self.assertEqual(edge.label, rel[TopologyFields.RELATIONSHIP_TYPE]) self.assertEqual(edge.label,
rel[StaticFields.RELATIONSHIP_TYPE])
def _validate_neighbor_vertex_props(self, vertex, event): def _validate_neighbor_vertex_props(self, vertex, event):
self._validate_common_props(vertex, event) self._validate_common_props(vertex, event)

View File

@ -18,6 +18,8 @@ from vitrage.common.constants import DatasourceAction
from vitrage.common.constants import DatasourceProperties as DSProps from vitrage.common.constants import DatasourceProperties as DSProps
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.datasources.nagios import NAGIOS_DATASOURCE from vitrage.datasources.nagios import NAGIOS_DATASOURCE
from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE
from vitrage.datasources.neutron.port import NEUTRON_PORT_DATASOURCE
from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE from vitrage.datasources.nova.zone import NOVA_ZONE_DATASOURCE
@ -42,7 +44,9 @@ class TestEntityGraphUnitBase(base.BaseTest):
default=[NAGIOS_DATASOURCE, default=[NAGIOS_DATASOURCE,
NOVA_HOST_DATASOURCE, NOVA_HOST_DATASOURCE,
NOVA_INSTANCE_DATASOURCE, NOVA_INSTANCE_DATASOURCE,
NOVA_ZONE_DATASOURCE], NOVA_ZONE_DATASOURCE,
NEUTRON_NETWORK_DATASOURCE,
NEUTRON_PORT_DATASOURCE],
help='Names of supported data sources'), help='Names of supported data sources'),
cfg.ListOpt('path', cfg.ListOpt('path',

View File

@ -38,10 +38,11 @@ from vitrage.tests import base
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# number of vms and alarms on vms needs 7 or more
ENTITY_GRAPH_HOSTS_PER_CLUSTER = 8 ENTITY_GRAPH_HOSTS_PER_CLUSTER = 8
ENTITY_GRAPH_VMS_PER_HOST = 8 ENTITY_GRAPH_VMS_PER_HOST = 8
ENTITY_GRAPH_ALARMS_PER_HOST = 8 ENTITY_GRAPH_ALARMS_PER_HOST = 8
ENTITY_GRAPH_TESTS_PER_HOST = 20 ENTITY_GRAPH_TESTS_PER_HOST = 8
ENTITY_GRAPH_ALARMS_PER_VM = 8 ENTITY_GRAPH_ALARMS_PER_VM = 8
RESOURCE = EntityCategory.RESOURCE RESOURCE = EntityCategory.RESOURCE

File diff suppressed because it is too large Load Diff