diff --git a/vitrage/api_handler/apis/topology.py b/vitrage/api_handler/apis/topology.py index 552f02f11..86c1039e9 100644 --- a/vitrage/api_handler/apis/topology.py +++ b/vitrage/api_handler/apis/topology.py @@ -22,10 +22,9 @@ from vitrage.common.constants import EdgeProperties as EProps from vitrage.common.constants import EntityCategory from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE -from vitrage.datasources import OPENSTACK_CLUSTER -from vitrage.datasources.transformer_base import build_key -from vitrage.datasources.transformer_base import CLUSTER_ID - +from vitrage.datasources.transformer_base\ + import create_cluster_placeholder_vertex +from vitrage.entity_graph.processor import processor_utils LOG = log.getLogger(__name__) @@ -191,13 +190,19 @@ class TopologyApis(EntityGraphApisBase): entities = [] - if not root: - root = build_key([EntityCategory.RESOURCE, - OPENSTACK_CLUSTER, - CLUSTER_ID]) + if root: + root_vertex = \ + self.entity_graph.get_vertex(root) + else: + key_values_hash = processor_utils.get_defining_properties( + create_cluster_placeholder_vertex()) + tmp_vertices = self.entity_graph.get_vertices_by_key( + key_values_hash) + if not tmp_vertices: + LOG.debug("No root vertex found") + return set(entities) + root_vertex = tmp_vertices[0] - root_vertex = \ - self.entity_graph.get_vertex(root) local_connected_component_subgraphs = \ ga.connected_component_subgraphs(subgraph) diff --git a/vitrage/cmd/graph.py b/vitrage/cmd/graph.py index 50e67406a..c7c727397 100644 --- a/vitrage/cmd/graph.py +++ b/vitrage/cmd/graph.py @@ -69,7 +69,8 @@ def init(conf): evaluator_q = queue.Queue() e_graph = entity_graph.get_graph_driver(conf)( 'Entity Graph', - '%s:%s:%s' % (EntityCategory.RESOURCE, OPENSTACK_CLUSTER, CLUSTER_ID)) + '%s:%s:%s' % (EntityCategory.RESOURCE, OPENSTACK_CLUSTER, CLUSTER_ID), + uuid=True) scenario_repo = ScenarioRepository(conf) evaluator = ScenarioEvaluator(conf, e_graph, scenario_repo, evaluator_q) diff --git a/vitrage/common/constants.py b/vitrage/common/constants.py index 029db3b72..4bda0c73d 100644 --- a/vitrage/common/constants.py +++ b/vitrage/common/constants.py @@ -38,6 +38,7 @@ class VertexProperties(object): RAWTEXT = 'rawtext' RESOURCE_ID = 'resource_id' RESOURCE = 'resource' + IS_REAL_VITRAGE_ID = 'is_real_vitrage_id' class EdgeProperties(object): diff --git a/vitrage/datasources/aodh/transformer.py b/vitrage/datasources/aodh/transformer.py index ff29a56d0..b7d81268b 100644 --- a/vitrage/datasources/aodh/transformer.py +++ b/vitrage/datasources/aodh/transformer.py @@ -56,6 +56,7 @@ class AodhTransformer(AlarmTransformerBase): AodhProps.ENABLED: entity_event[AodhProps.ENABLED], VProps.PROJECT_ID: entity_event.get(AodhProps.PROJECT_ID, None), AodhProps.REPEAT_ACTIONS: entity_event[AodhProps.REPEAT_ACTIONS], + VProps.RESOURCE_ID: entity_event[AodhProps.RESOURCE_ID], 'alarm_type': entity_event[AodhProps.TYPE] } diff --git a/vitrage/datasources/collectd/transformer.py b/vitrage/datasources/collectd/transformer.py index 2de72aa58..dc5296c64 100644 --- a/vitrage/datasources/collectd/transformer.py +++ b/vitrage/datasources/collectd/transformer.py @@ -53,7 +53,8 @@ class CollectdTransformer(AlarmTransformerBase): metadata = { VProps.NAME: entity_event[CProps.MESSAGE], VProps.SEVERITY: entity_event[CProps.SEVERITY], - VProps.RAWTEXT: self.generate_raw_text(entity_event) + VProps.RAWTEXT: self.generate_raw_text(entity_event), + VProps.RESOURCE_ID: entity_event[CProps.RESOURCE_NAME] } return graph_utils.create_vertex( diff --git a/vitrage/datasources/consistency/transformer.py b/vitrage/datasources/consistency/transformer.py index 4768e6c86..983d7d1a6 100644 --- a/vitrage/datasources/consistency/transformer.py +++ b/vitrage/datasources/consistency/transformer.py @@ -40,9 +40,16 @@ class ConsistencyTransformer(ResourceTransformerBase): @staticmethod def _create_vertex(entity_event): + metadata = { + VProps.IS_REAL_VITRAGE_ID: + entity_event.get(VProps.IS_REAL_VITRAGE_ID, False) + } + return graph_utils.create_vertex( entity_event[VProps.VITRAGE_ID], - sample_timestamp=entity_event[DSProps.SAMPLE_DATE]) + sample_timestamp=entity_event[DSProps.SAMPLE_DATE], + metadata=metadata + ) def _create_entity_key(self, entity_event): return None diff --git a/vitrage/datasources/nagios/transformer.py b/vitrage/datasources/nagios/transformer.py index e65a0a7b5..53f648312 100644 --- a/vitrage/datasources/nagios/transformer.py +++ b/vitrage/datasources/nagios/transformer.py @@ -48,6 +48,7 @@ class NagiosTransformer(AlarmTransformerBase): metadata = { VProps.NAME: entity_event[NagiosProperties.SERVICE], + VProps.RESOURCE_ID: entity_event[NagiosProperties.RESOURCE_NAME], VProps.SEVERITY: entity_event[NagiosProperties.STATUS], VProps.INFO: entity_event[NagiosProperties.STATUS_INFO] } diff --git a/vitrage/datasources/zabbix/transformer.py b/vitrage/datasources/zabbix/transformer.py index 9f53abe1c..c722f0f92 100644 --- a/vitrage/datasources/zabbix/transformer.py +++ b/vitrage/datasources/zabbix/transformer.py @@ -64,7 +64,8 @@ class ZabbixTransformer(AlarmTransformerBase): VProps.NAME: entity_event[ZProps.DESCRIPTION], VProps.SEVERITY: TriggerSeverity.str( entity_event[ZProps.PRIORITY]), - VProps.RAWTEXT: entity_event[ZProps.RAWTEXT] + VProps.RAWTEXT: entity_event[ZProps.RAWTEXT], + VProps.RESOURCE_ID: entity_event[ZProps.RESOURCE_NAME] } return graph_utils.create_vertex( diff --git a/vitrage/entity_graph/consistency/consistency_enforcer.py b/vitrage/entity_graph/consistency/consistency_enforcer.py index a8eacad2d..23d22064c 100644 --- a/vitrage/entity_graph/consistency/consistency_enforcer.py +++ b/vitrage/entity_graph/consistency/consistency_enforcer.py @@ -157,7 +157,11 @@ class ConsistencyEnforcer(object): DSProps.DATASOURCE_ACTION: DatasourceAction.UPDATE, DSProps.SAMPLE_DATE: str(utcnow()), DSProps.EVENT_TYPE: action, - VProps.VITRAGE_ID: vertex[VProps.VITRAGE_ID] + VProps.VITRAGE_ID: vertex[VProps.VITRAGE_ID], + VProps.ID: vertex.get(VProps.ID, None), + VProps.TYPE: vertex[VProps.TYPE], + VProps.CATEGORY: vertex[VProps.CATEGORY], + VProps.IS_REAL_VITRAGE_ID: True } self.evaluator_queue.put(event) diff --git a/vitrage/entity_graph/processor/processor.py b/vitrage/entity_graph/processor/processor.py index 15f1be40e..3d18e706d 100644 --- a/vitrage/entity_graph/processor/processor.py +++ b/vitrage/entity_graph/processor/processor.py @@ -15,8 +15,12 @@ from oslo_log import log +from oslo_utils import uuidutils + from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps +from vitrage.common.exception import VitrageError +from vitrage.datasources import OPENSTACK_CLUSTER from vitrage.datasources.transformer_base import TransformerBase from vitrage.entity_graph.mappings.datasource_info_mapper import \ DatasourceInfoMapper @@ -32,7 +36,8 @@ LOG = log.getLogger(__name__) class Processor(processor.ProcessorBase): - def __init__(self, conf, initialization_status, e_graph=None): + def __init__(self, conf, initialization_status, e_graph=None, + uuid=False): super(Processor, self).__init__() self.conf = conf self.transformer_manager = TransformerManager(self.conf) @@ -40,7 +45,7 @@ class Processor(processor.ProcessorBase): self._initialize_events_actions() self.initialization_status = initialization_status self.entity_graph = e_graph if e_graph is not None\ - else NXGraph("Entity Graph") + else NXGraph("Entity Graph", uuid=uuid) self._notifier = GraphNotifier(conf) def process_event(self, event): @@ -74,6 +79,7 @@ class Processor(processor.ProcessorBase): """ LOG.debug('Add entity to entity graph:\n%s', new_vertex) + self._find_and_fix_graph_vertex(new_vertex, neighbors) self.entity_graph.add_vertex(new_vertex) self._connect_neighbors(neighbors, [], GraphAction.CREATE_ENTITY) @@ -92,6 +98,8 @@ class Processor(processor.ProcessorBase): LOG.debug('Update entity in entity graph:\n%s', updated_vertex) + if not updated_vertex.get(VProps.IS_REAL_VITRAGE_ID, False): + self._find_and_fix_graph_vertex(updated_vertex, neighbors) graph_vertex = self.entity_graph.get_vertex(updated_vertex.vertex_id) if (not graph_vertex) or \ @@ -117,7 +125,8 @@ class Processor(processor.ProcessorBase): """ LOG.debug('Delete entity from entity graph:\n%s', deleted_vertex) - + if not deleted_vertex.get(VProps.IS_REAL_VITRAGE_ID, False): + self._find_and_fix_graph_vertex(deleted_vertex, neighbors) graph_vertex = self.entity_graph.get_vertex(deleted_vertex.vertex_id) if graph_vertex and (not PUtils.is_deleted(graph_vertex)) and \ @@ -141,21 +150,38 @@ class Processor(processor.ProcessorBase): def update_relationship(self, entity_vertex, neighbors): LOG.debug('Update relationship in entity graph:\n%s', neighbors) + if not entity_vertex: + for neighbor in neighbors: + self.entity_graph.update_edge(neighbor.edge) + return + + self._find_and_fix_graph_vertex(entity_vertex, []) + self.entity_graph.update_vertex(entity_vertex) for neighbor in neighbors: - # TODO(Alexey): maybe to check if the vertices exists - if entity_vertex is not None: - self.entity_graph.update_vertex(entity_vertex) + self._find_and_fix_relationship(entity_vertex, neighbor) self.entity_graph.update_edge(neighbor.edge) def delete_relationship(self, updated_vertex, neighbors): LOG.debug('Delete relationship from entity graph:\n%s', neighbors) + if not updated_vertex: + for neighbor in neighbors: + graph_edge = self.entity_graph.get_edge( + neighbor.edge.source_id, + neighbor.edge.target_id, + neighbor.edge.label) + if graph_edge: + self.entity_graph.remove_edge(graph_edge) + + return + + self._find_and_fix_graph_vertex(updated_vertex, []) + self.entity_graph.update_vertex(updated_vertex) for neighbor in neighbors: + self._find_and_fix_relationship(updated_vertex, neighbor) graph_edge = self.entity_graph.get_edge(neighbor.edge.source_id, neighbor.edge.target_id, neighbor.edge.label) - if updated_vertex is not None: - self.entity_graph.update_vertex(updated_vertex) if graph_edge: PUtils.mark_deleted(self.entity_graph, graph_edge) @@ -168,7 +194,7 @@ class Processor(processor.ProcessorBase): :type vertex: Vertex :param neighbors: The neighbors of the deleted vertex - :type neighbors: List + :type neighbors: List - It's just a mock in this method """ LOG.debug('Remove deleted entity from entity graph:\n%s', vertex) @@ -308,7 +334,12 @@ class Processor(processor.ProcessorBase): if action in [GraphAction.UPDATE_ENTITY, GraphAction.DELETE_ENTITY, GraphAction.CREATE_ENTITY]: - graph_vertex = self.entity_graph.get_vertex(vertex.vertex_id) + if vertex.get(VProps.IS_REAL_VITRAGE_ID, False): + graph_vertex = self.entity_graph.get_vertex( + vertex.vertex_id) + else: + graph_vertex = self._get_single_graph_vertex_by_props( + vertex) elif action in [GraphAction.END_MESSAGE, GraphAction.REMOVE_DELETED_ENTITY, GraphAction.UPDATE_RELATIONSHIP, @@ -329,3 +360,97 @@ class Processor(processor.ProcessorBase): return result = self.entity_graph.get_vertices(attr) event[TransformerBase.QUERY_RESULT] = result + + def _find_and_fix_relationship(self, vertex, neighbor): + prev_neighbor_id = neighbor.vertex[VProps.VITRAGE_ID] + self._find_and_fix_graph_vertex(neighbor.vertex, []) + if neighbor.edge.source_id == prev_neighbor_id: + neighbor.edge.source_id = neighbor.vertex.vertex_id + neighbor.edge.target_id = vertex.vertex_id + else: + neighbor.edge.target_id = neighbor.vertex.vertex_id + neighbor.edge.source_id = vertex.vertex_id + + def _find_and_fix_graph_vertex(self, + vertex, + neighbors=None, + include_deleted=False): + """Search for vertex in graph, and update vertex id and vitrage ID + + Search for vertex in graph, and update vertex id and vitrage ID + Both in the Vertex itself, and in it's neighbors and edges + + :param new_vertex: The vertex to update + :type new_vertex: Vertex + + :param neighbors: The neighbors of the vertex + :type neighbors: list + + :param include_deleted: If True, Include deleted entities in the search + :type include_deleted: bool + """ + + previous_vitrage_id = vertex[VProps.VITRAGE_ID] + + graph_vertex = self._get_single_graph_vertex_by_props( + vertex, include_deleted) + + if not graph_vertex or (PUtils.is_deleted(graph_vertex) + and not include_deleted): + + vitrage_id = uuidutils.generate_uuid() + if vertex[VProps.TYPE] == OPENSTACK_CLUSTER: + self.entity_graph.root_id = vitrage_id + else: + vitrage_id = graph_vertex[VProps.VITRAGE_ID] + + vertex[VProps.VITRAGE_ID] = vitrage_id + vertex.vertex_id = vitrage_id + + if not neighbors: + return + + for neighbor_vertex, edge in neighbors: + if not neighbor_vertex.get(VProps.IS_REAL_VITRAGE_ID, False): + self._find_and_fix_graph_vertex(neighbor_vertex) + if edge.target_id == previous_vitrage_id: + edge.target_id = vitrage_id + edge.source_id = neighbor_vertex.vertex_id + else: + edge.source_id = vitrage_id + edge.target_id = neighbor_vertex.vertex_id + + def _get_single_graph_vertex_by_props(self, vertex, include_deleted=False): + """Returns a single vertex by it's defining properties + + Queries the graph DB for vertices according to the + vertice's "key" properties + In case multiple vertices return from the query, + an exception is issued + + :param updated_vertex: The vertex with the defining properties + :type vertex: Vertex + + :param include_deleted: Include deleted entities in the query + :type include_deleted: bool + """ + + received_graph_vertices = self.entity_graph.get_vertices_by_key( + PUtils.get_defining_properties(vertex)) + graph_vertices = [] + if include_deleted: + for tmp_vertex in received_graph_vertices: + graph_vertices.append(tmp_vertex) + else: + for tmp_vertex in received_graph_vertices: + if not tmp_vertex.get(VProps.IS_DELETED, False): + graph_vertices.append(tmp_vertex) + + if len(graph_vertices) > 1: + raise VitrageError( + 'found too many vertices with same properties: %s ', + vertex) + graph_vertex = None if not graph_vertices \ + else graph_vertices[0] + + return graph_vertex diff --git a/vitrage/entity_graph/processor/processor_utils.py b/vitrage/entity_graph/processor/processor_utils.py index 55a0ff5fb..6e438678f 100644 --- a/vitrage/entity_graph/processor/processor_utils.py +++ b/vitrage/entity_graph/processor/processor_utils.py @@ -16,7 +16,12 @@ from dateutil import parser from oslo_log import log +from pprint import pformat + +import six + from vitrage.common.constants import EdgeProperties as EProps +from vitrage.common.constants import EntityCategory from vitrage.common.constants import VertexProperties as VProps from vitrage.graph import Edge from vitrage.graph import Vertex @@ -83,6 +88,21 @@ def get_vertex_types(vertex): return category, type_ +def get_defining_properties(vertex): + defining_props = {VProps.TYPE: six.text_type(vertex[VProps.TYPE])} + + if VProps.ID in vertex.properties: + defining_props[VProps.ID] = vertex[VProps.ID] + + # In case the entity is an Alarm + if vertex[VProps.CATEGORY] == EntityCategory.ALARM: + if VProps.RESOURCE_ID in vertex.properties: + defining_props[VProps.RESOURCE_ID] = vertex[VProps.RESOURCE_ID] + defining_props[VProps.NAME] = vertex[VProps.NAME] + + return hash(pformat(defining_props)) + + def can_update_vertex(graph_vertex, new_vertex): return (not graph_vertex) or (not new_vertex[VProps.IS_PLACEHOLDER]) diff --git a/vitrage/evaluator/actions/evaluator_event_transformer.py b/vitrage/evaluator/actions/evaluator_event_transformer.py index e83d62b1e..9a36a3d4d 100644 --- a/vitrage/evaluator/actions/evaluator_event_transformer.py +++ b/vitrage/evaluator/actions/evaluator_event_transformer.py @@ -67,6 +67,9 @@ class EvaluatorEventTransformer(transformer_base.TransformerBase): VProps.IS_PLACEHOLDER: False, VProps.RESOURCE_ID: event.get(TFields.TARGET) } + if VProps.IS_REAL_VITRAGE_ID in event: + properties[VProps.IS_REAL_VITRAGE_ID] = \ + event.get(VProps.IS_REAL_VITRAGE_ID) if VProps.VITRAGE_STATE in event: properties[VProps.VITRAGE_STATE] = \ event.get(VProps.VITRAGE_STATE) @@ -129,7 +132,8 @@ class EvaluatorEventTransformer(transformer_base.TransformerBase): neighbor_props = { VProps.IS_PLACEHOLDER: True, VProps.UPDATE_TIMESTAMP: timestamp, - VProps.SAMPLE_TIMESTAMP: event[VProps.SAMPLE_TIMESTAMP] + VProps.SAMPLE_TIMESTAMP: event[VProps.SAMPLE_TIMESTAMP], + VProps.IS_REAL_VITRAGE_ID: True } neighbor = Vertex(event[TFields.TARGET], neighbor_props) return [Neighbor(neighbor, relation_edge)] diff --git a/vitrage/evaluator/actions/recipes/set_state.py b/vitrage/evaluator/actions/recipes/set_state.py index 20ad71d06..432969d8b 100644 --- a/vitrage/evaluator/actions/recipes/set_state.py +++ b/vitrage/evaluator/actions/recipes/set_state.py @@ -53,7 +53,8 @@ class SetState(base.Recipe): update_vertex_params = { VProps.VITRAGE_ID: target_id, - VProps.VITRAGE_STATE: vitrage_state + VProps.VITRAGE_STATE: vitrage_state, + VProps.IS_REAL_VITRAGE_ID: True } update_vertex_step = ActionStepWrapper(UPDATE_VERTEX, update_vertex_params) diff --git a/vitrage/evaluator/scenario_repository.py b/vitrage/evaluator/scenario_repository.py index ea6835b90..f7ab25305 100644 --- a/vitrage/evaluator/scenario_repository.py +++ b/vitrage/evaluator/scenario_repository.py @@ -13,10 +13,11 @@ # under the License. from collections import defaultdict from collections import namedtuple -from hashlib import md5 from oslo_log import log +from oslo_utils import uuidutils + from vitrage.evaluator.base import Template from vitrage.evaluator.template_data import RELATIONSHIP from vitrage.evaluator.template_data import TemplateData @@ -85,7 +86,7 @@ class ScenarioRepository(object): if not result.is_valid_config: LOG.info('Unable to load template: %s' % result.comment) - template_uuid = md5(str(template_def).encode()).hexdigest() + template_uuid = uuidutils.generate_uuid() self.templates[str(template_uuid)] = Template(template_uuid, template_def, current_time, diff --git a/vitrage/graph/driver/graph.py b/vitrage/graph/driver/graph.py index 43cf2cd61..674b3bf85 100644 --- a/vitrage/graph/driver/graph.py +++ b/vitrage/graph/driver/graph.py @@ -36,7 +36,8 @@ class Direction(object): @six.add_metaclass(abc.ABCMeta) class Graph(object): - def __init__(self, name, graph_type, vertices=None, edges=None): + def __init__(self, name, graph_type, vertices=None, edges=None, + uuid=False): """Create a Graph instance :type name: str @@ -48,6 +49,7 @@ class Graph(object): self.name = name self.graph_type = graph_type self.root_id = None + self.uuid = uuid self.notifier = Notifier() def subscribe(self, function): @@ -338,6 +340,20 @@ class Graph(object): """ pass + @abc.abstractmethod + def get_vertices_by_key(self, + key_values_hash): + """Get vertices list according to their hash key + + The hash key is derived from their properties : + See processor_utils - get_defining_properties + + + :param key_values_hash: hash key + :type key_values_hash str + """ + pass + @abc.abstractmethod def neighbors(self, v_id, vertex_attr_filter=None, edge_attr_filter=None, direction=Direction.BOTH): diff --git a/vitrage/graph/driver/networkx_graph.py b/vitrage/graph/driver/networkx_graph.py index fad39f675..92b17f4a0 100644 --- a/vitrage/graph/driver/networkx_graph.py +++ b/vitrage/graph/driver/networkx_graph.py @@ -11,6 +11,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from collections import defaultdict import copy import json @@ -21,6 +22,7 @@ from networkx.readwrite import json_graph from oslo_log import log as logging from vitrage.common.constants import VertexProperties as VProps +from vitrage.entity_graph.processor import processor_utils as PUtils from vitrage.graph.algo_driver.networkx_algorithm import NXAlgorithm from vitrage.graph.driver.elements import Edge from vitrage.graph.driver.elements import Vertex @@ -50,9 +52,12 @@ class NXGraph(Graph): name='networkx_graph', root_id=None, vertices=None, - edges=None): - super(NXGraph, self).__init__(name, NXGraph.GRAPH_TYPE) + edges=None, + uuid=False): + super(NXGraph, self).__init__(name, NXGraph.GRAPH_TYPE, uuid=uuid) self._g = nx.MultiDiGraph() + if uuid: + self.key_to_vertex_ids = defaultdict(list) self.root_id = root_id self.add_vertices(vertices) self.add_edges(edges) @@ -81,6 +86,13 @@ class NXGraph(Graph): def _add_vertex(self, v): properties_copy = copy.copy(v.properties) self._g.add_node(n=v.vertex_id, attr_dict=properties_copy) + if self.uuid: + self._update_keys_map(v) + + def _update_keys_map(self, v): + key_hash = PUtils.get_defining_properties(v) + if v.vertex_id not in self.key_to_vertex_ids[key_hash]: + self.key_to_vertex_ids[key_hash].append(v.vertex_id) @Notifier.update_notify def add_edge(self, e): @@ -197,6 +209,16 @@ class NXGraph(Graph): :type v: Vertex """ + if self.uuid: + vertex = self.get_vertex(v.vertex_id) + if vertex: + key_hash = PUtils.get_defining_properties(vertex) + if key_hash in self.key_to_vertex_ids and\ + v.vertex_id in self.key_to_vertex_ids[key_hash]: + self.key_to_vertex_ids[key_hash].remove(v.vertex_id) + if len(self.key_to_vertex_ids[key_hash]) == 0: + del self.key_to_vertex_ids[key_hash] + self._g.remove_node(n=v.vertex_id) def remove_edge(self, e): @@ -207,7 +229,7 @@ class NXGraph(Graph): self._g.remove_edge(u=e.source_id, v=e.target_id, key=e.label) def get_vertices(self, - vertex_attr_filter=None, + vertex_attr_filter=None, # Dictionary of key value query_dict=None): def check_vertex(vertex_data): return check_filter(vertex_data[1], vertex_attr_filter) @@ -226,6 +248,15 @@ class NXGraph(Graph): else: return [] + def get_vertices_by_key(self, key_values_hash): + + if key_values_hash in self.key_to_vertex_ids: + vertices = [] + for vertex_id in self.key_to_vertex_ids[key_values_hash]: + vertices.append(self.get_vertex(vertex_id)) + return vertices + return [] + def neighbors(self, v_id, vertex_attr_filter=None, edge_attr_filter=None, direction=Direction.BOTH): diff --git a/vitrage/tests/functional/api_handler/test_apis.py b/vitrage/tests/functional/api_handler/test_apis.py index 4cea8346b..dc00ec97d 100644 --- a/vitrage/tests/functional/api_handler/test_apis.py +++ b/vitrage/tests/functional/api_handler/test_apis.py @@ -23,7 +23,8 @@ from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources import NOVA_HOST_DATASOURCE from vitrage.datasources import NOVA_INSTANCE_DATASOURCE from vitrage.datasources import NOVA_ZONE_DATASOURCE -from vitrage.datasources import OPENSTACK_CLUSTER +from vitrage.datasources.transformer_base \ + import create_cluster_placeholder_vertex from vitrage.graph.driver.networkx_graph import NXGraph import vitrage.graph.utils as graph_utils from vitrage.tests.unit.entity_graph.base import TestEntityGraphUnitBase @@ -143,7 +144,7 @@ class TestApis(TestEntityGraphUnitBase): graph_type='graph', depth=10, query=None, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=None, all_tenants=False) graph_topology = json.loads(graph_topology) @@ -165,7 +166,7 @@ class TestApis(TestEntityGraphUnitBase): graph_type='graph', depth=10, query=None, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=None, all_tenants=False) graph_topology = json.loads(graph_topology) @@ -187,7 +188,7 @@ class TestApis(TestEntityGraphUnitBase): graph_type='graph', depth=10, query=None, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=None, all_tenants=True) graph_topology = json.loads(graph_topology) @@ -389,12 +390,10 @@ class TestApis(TestEntityGraphUnitBase): self.assertEqual(resource[VProps.PROJECT_ID], project_id) def _create_graph(self): - graph = NXGraph('Multi tenancy graph') + graph = NXGraph('Multi tenancy graph', uuid=True) # create vertices - cluster_vertex = self._create_resource( - 'RESOURCE:openstack.cluster:OpenStack Cluster', - OPENSTACK_CLUSTER) + cluster_vertex = create_cluster_placeholder_vertex() zone_vertex = self._create_resource('zone_1', NOVA_ZONE_DATASOURCE) host_vertex = self._create_resource('host_1', @@ -411,18 +410,38 @@ class TestApis(TestEntityGraphUnitBase): instance_4_vertex = self._create_resource('instance_4', NOVA_INSTANCE_DATASOURCE, project_id='project_2') - alarm_on_host_vertex = self._create_alarm('alarm_on_host', - 'alarm_on_host') - alarm_on_instance_1_vertex = self._create_alarm('alarm_on_instance_1', - 'deduced_alarm', - project_id='project_1') - alarm_on_instance_2_vertex = self._create_alarm('alarm_on_instance_2', - 'deduced_alarm') - alarm_on_instance_3_vertex = self._create_alarm('alarm_on_instance_3', - 'deduced_alarm', - project_id='project_2') - alarm_on_instance_4_vertex = self._create_alarm('alarm_on_instance_4', - 'deduced_alarm') + alarm_on_host_vertex = self._create_alarm( + 'alarm_on_host', + 'alarm_on_host', + metadata={'type': 'nova.host', + 'name': 'host_1', + 'resource_id': 'host_1'}) + alarm_on_instance_1_vertex = self._create_alarm( + 'alarm_on_instance_1', + 'deduced_alarm', + project_id='project_1', + metadata={'type': 'nova.instance', + 'name': 'instance_1', + 'resource_id': 'sdg7849ythksjdg'}) + alarm_on_instance_2_vertex = self._create_alarm( + 'alarm_on_instance_2', + 'deduced_alarm', + metadata={'type': 'nova.instance', + 'name': 'instance_2', + 'resource_id': 'nbfhsdugf'}) + alarm_on_instance_3_vertex = self._create_alarm( + 'alarm_on_instance_3', + 'deduced_alarm', + project_id='project_2', + metadata={'type': 'nova.instance', + 'name': 'instance_3', + 'resource_id': 'nbffhsdasdugf'}) + alarm_on_instance_4_vertex = self._create_alarm( + 'alarm_on_instance_4', + 'deduced_alarm', + metadata={'type': 'nova.instance', + 'name': 'instance_4', + 'resource_id': 'ngsuy76hgd87f'}) # create links edges = list() diff --git a/vitrage/tests/functional/base.py b/vitrage/tests/functional/base.py index 6e34c8290..78845b08f 100644 --- a/vitrage/tests/functional/base.py +++ b/vitrage/tests/functional/base.py @@ -22,11 +22,13 @@ from vitrage.tests.unit.entity_graph.base import TestEntityGraphUnitBase class TestFunctionalBase(TestEntityGraphUnitBase): - def _create_processor_with_graph(self, conf, processor=None): + def _create_processor_with_graph(self, conf, processor=None, + uuid=False): events = self._create_mock_events() if not processor: - processor = proc.Processor(conf, InitializationStatus()) + processor = proc.Processor(conf, InitializationStatus(), + uuid=uuid) for event in events: processor.process_event(event) diff --git a/vitrage/tests/functional/datasources/aodh/test_aodh.py b/vitrage/tests/functional/datasources/aodh/test_aodh.py index d6767bedd..6392e5b96 100644 --- a/vitrage/tests/functional/datasources/aodh/test_aodh.py +++ b/vitrage/tests/functional/datasources/aodh/test_aodh.py @@ -53,7 +53,7 @@ class TestAodhAlarms(TestDataSourcesBase): def test_aodh_alarms_validity(self): # Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), len(processor.entity_graph)) diff --git a/vitrage/tests/functional/datasources/cinder/test_cinder_volume.py b/vitrage/tests/functional/datasources/cinder/test_cinder_volume.py index 6ecdbe355..29696463b 100644 --- a/vitrage/tests/functional/datasources/cinder/test_cinder_volume.py +++ b/vitrage/tests/functional/datasources/cinder/test_cinder_volume.py @@ -52,7 +52,7 @@ class TestCinderVolume(TestDataSourcesBase): def test_cinder_volume_validity(self): # Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), len(processor.entity_graph)) diff --git a/vitrage/tests/functional/datasources/heat/test_heat_stack.py b/vitrage/tests/functional/datasources/heat/test_heat_stack.py index 09c0f436f..5b5e55765 100644 --- a/vitrage/tests/functional/datasources/heat/test_heat_stack.py +++ b/vitrage/tests/functional/datasources/heat/test_heat_stack.py @@ -52,7 +52,7 @@ class TestHeatStack(TestDataSourcesBase): def test_heat_stack_validity(self): # Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), len(processor.entity_graph)) diff --git a/vitrage/tests/functional/datasources/nagios/test_nagios.py b/vitrage/tests/functional/datasources/nagios/test_nagios.py index b204736bd..459f59180 100644 --- a/vitrage/tests/functional/datasources/nagios/test_nagios.py +++ b/vitrage/tests/functional/datasources/nagios/test_nagios.py @@ -51,7 +51,7 @@ class TestNagios(TestDataSourcesBase): def test_nagios_validity(self): # Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), len(processor.entity_graph)) diff --git a/vitrage/tests/functional/datasources/nova/test_nova_datasources.py b/vitrage/tests/functional/datasources/nova/test_nova_datasources.py index 67e11d1a2..141052ede 100644 --- a/vitrage/tests/functional/datasources/nova/test_nova_datasources.py +++ b/vitrage/tests/functional/datasources/nova/test_nova_datasources.py @@ -30,7 +30,7 @@ class TestNovaDatasources(TestDataSourcesBase): cls.load_datasources(cls.conf) def test_nova_datasources(self): - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), processor.entity_graph.num_vertices()) diff --git a/vitrage/tests/functional/datasources/static_physical/test_static_physical.py b/vitrage/tests/functional/datasources/static_physical/test_static_physical.py index 13970361e..26a971ce3 100644 --- a/vitrage/tests/functional/datasources/static_physical/test_static_physical.py +++ b/vitrage/tests/functional/datasources/static_physical/test_static_physical.py @@ -55,7 +55,7 @@ class TestStaticPhysical(TestDataSourcesBase): def test_static_physical_validity(self): # Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) transformers = processor.transformer_manager.transformers transformers[SWITCH] = transformers[STATIC_PHYSICAL_DATASOURCE] self.assertEqual(self._num_total_expected_vertices(), diff --git a/vitrage/tests/functional/entity_graph/consistency/test_consistency.py b/vitrage/tests/functional/entity_graph/consistency/test_consistency.py index 2056c6577..01a797264 100644 --- a/vitrage/tests/functional/entity_graph/consistency/test_consistency.py +++ b/vitrage/tests/functional/entity_graph/consistency/test_consistency.py @@ -74,7 +74,8 @@ class TestConsistencyFunctional(TestFunctionalBase): cls.conf.register_opts(cls.DATASOURCES_OPTS, group='datasources') cls.load_datasources(cls.conf) - cls.processor = Processor(cls.conf, cls.initialization_status) + cls.processor = Processor(cls.conf, cls.initialization_status, + uuid=True) cls.event_queue = queue.Queue() scenario_repo = ScenarioRepository(cls.conf) cls.evaluator = ScenarioEvaluator(cls.conf, @@ -93,7 +94,8 @@ class TestConsistencyFunctional(TestFunctionalBase): # Setup num_of_host_alarms = self.NUM_HOSTS - 2 num_instances_per_host = 4 - self._create_processor_with_graph(self.conf, processor=self.processor) + self._create_processor_with_graph(self.conf, processor=self.processor, + uuid=True) self._add_alarms() self._set_end_messages() self.assertEqual(self._num_total_expected_vertices() + @@ -167,7 +169,8 @@ class TestConsistencyFunctional(TestFunctionalBase): self.assertEqual(6, len(deleted_instance_vertices)) def _periodic_process_setup_stage(self, consistency_interval): - self._create_processor_with_graph(self.conf, processor=self.processor) + self._create_processor_with_graph(self.conf, processor=self.processor, + uuid=True) current_time = utcnow() # set all vertices to be have timestamp that consistency won't get diff --git a/vitrage/tests/functional/entity_graph/processor/test_processor.py b/vitrage/tests/functional/entity_graph/processor/test_processor.py index 37aafff19..032719def 100644 --- a/vitrage/tests/functional/entity_graph/processor/test_processor.py +++ b/vitrage/tests/functional/entity_graph/processor/test_processor.py @@ -34,7 +34,7 @@ class TestProcessorFunctional(TestFunctionalBase): cls.load_datasources(cls.conf) def test_create_entity_graph(self): - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) self.assertEqual(self._num_total_expected_vertices(), processor.entity_graph.num_vertices()) diff --git a/vitrage/tests/functional/entity_graph/states/test_datasource_info_mapper.py b/vitrage/tests/functional/entity_graph/states/test_datasource_info_mapper.py index 0ecb951c3..0c39d5cb0 100644 --- a/vitrage/tests/functional/entity_graph/states/test_datasource_info_mapper.py +++ b/vitrage/tests/functional/entity_graph/states/test_datasource_info_mapper.py @@ -17,7 +17,6 @@ from oslo_config import cfg from vitrage.common.constants import DatasourceAction as DSAction from vitrage.common.constants import GraphAction from vitrage.common.constants import VertexProperties as VProps -from vitrage.datasources.nova.instance.transformer import InstanceTransformer from vitrage.entity_graph.initialization_status import InitializationStatus from vitrage.entity_graph.mappings.operational_resource_state import \ OperationalResourceState @@ -38,7 +37,8 @@ class TestDatasourceInfoMapperFunctional(TestFunctionalBase): def test_state_on_update(self): # setup - processor = proc.Processor(self.conf, InitializationStatus()) + processor = proc.Processor(self.conf, InitializationStatus(), + uuid=True) event = self._create_event(spec_type='INSTANCE_SPEC', datasource_action=DSAction.INIT_SNAPSHOT) @@ -46,9 +46,9 @@ class TestDatasourceInfoMapperFunctional(TestFunctionalBase): processor.process_event(event) # test assertions - instance_transformer = InstanceTransformer({}, self.conf) - vitrage_id = instance_transformer._create_entity_key(event) - vertex = processor.entity_graph.get_vertex(vitrage_id) + entity = processor.transformer_manager.transform(event) + processor._find_and_fix_graph_vertex(entity.vertex, []) + vertex = processor.entity_graph.get_vertex(entity.vertex.vertex_id) self.assertEqual('ACTIVE', vertex[VProps.AGGREGATED_STATE]) self.assertEqual(OperationalResourceState.OK, vertex[VProps.OPERATIONAL_STATE]) diff --git a/vitrage/tests/functional/evaluator/test_action_executor.py b/vitrage/tests/functional/evaluator/test_action_executor.py index 73b72238b..89f7532a4 100644 --- a/vitrage/tests/functional/evaluator/test_action_executor.py +++ b/vitrage/tests/functional/evaluator/test_action_executor.py @@ -49,7 +49,7 @@ class TestActionExecutor(TestFunctionalBase): def test_execute_update_vertex(self): # Test Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) vertex_attrs = {VProps.TYPE: NOVA_HOST_DATASOURCE} host_vertices = processor.entity_graph.get_vertices( @@ -97,7 +97,7 @@ class TestActionExecutor(TestFunctionalBase): def test_execute_add_edge(self): # Test Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) vertex_attrs = {VProps.TYPE: NOVA_HOST_DATASOURCE} host_vertices = processor.entity_graph.get_vertices( @@ -147,7 +147,7 @@ class TestActionExecutor(TestFunctionalBase): def test_execute_add_vertex(self): # Test Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) vertex_attrs = {VProps.TYPE: NOVA_HOST_DATASOURCE} host_vertices = processor.entity_graph.get_vertices( @@ -159,7 +159,9 @@ class TestActionExecutor(TestFunctionalBase): props = { TFields.ALARM_NAME: 'VM_CPU_SUBOPTIMAL_PERFORMANCE', TFields.SEVERITY: 'CRITICAL', - VProps.STATE: AlarmProps.ACTIVE_STATE + VProps.STATE: AlarmProps.ACTIVE_STATE, + VProps.RESOURCE_ID: host[VProps.ID], + VProps.VITRAGE_ID: 'DUMMY_ID' } # Raise alarm action adds new vertex with type vitrage to the graph @@ -171,8 +173,6 @@ class TestActionExecutor(TestFunctionalBase): event_queue = queue.Queue() action_executor = ActionExecutor(event_queue) - expected_alarm_id = 'ALARM:vitrage:%s:%s' % (props[TFields.ALARM_NAME], - host.vertex_id) # Test Action action_executor.execute(action_spec, ActionMode.DO) processor.process_event(event_queue.get()) @@ -184,12 +184,7 @@ class TestActionExecutor(TestFunctionalBase): self.assertEqual(len(before_alarms) + 1, len(after_alarms)) self.assert_is_not_empty(after_alarms) - alarms = [alarm for alarm in after_alarms - if alarm.vertex_id == expected_alarm_id] - - # Expected exactly one alarm with expected id - self.assertEqual(1, len(alarms)) - alarm = alarms[0] + alarm = after_alarms[0] self.assertEqual(alarm.properties[VProps.CATEGORY], EntityCategory.ALARM) @@ -205,7 +200,7 @@ class TestActionExecutor(TestFunctionalBase): def test_execute_add_and_remove_vertex(self): # Test Setup - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) vertex_attrs = {VProps.TYPE: NOVA_HOST_DATASOURCE} host_vertices = processor.entity_graph.get_vertices( @@ -217,7 +212,8 @@ class TestActionExecutor(TestFunctionalBase): props = { TFields.ALARM_NAME: 'VM_CPU_SUBOPTIMAL_PERFORMANCE', TFields.SEVERITY: 'CRITICAL', - VProps.STATE: AlarmProps.ACTIVE_STATE + VProps.STATE: AlarmProps.ACTIVE_STATE, + VProps.RESOURCE_ID: host[VProps.ID] } action_spec = ActionSpecs(ActionType.RAISE_ALARM, targets, props) @@ -271,4 +267,6 @@ class TestActionExecutor(TestFunctionalBase): 'type': 'add_vertex', 'vitrage_entity_type': 'vitrage', 'severity': 'CRITICAL', + 'vitrage_id': 'mock_vitrage_id', + 'category': 'RESOURCE', 'sample_timestamp': '2016-03-17 11:33:32.443002+00:00'} diff --git a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py index 19a8794f1..438d2724e 100644 --- a/vitrage/tests/functional/evaluator/test_scenario_evaluator.py +++ b/vitrage/tests/functional/evaluator/test_scenario_evaluator.py @@ -40,6 +40,7 @@ from vitrage.utils.datetime import utcnow _TARGET_HOST = 'host-2' _TARGET_ZONE = 'zone-1' _NAGIOS_TEST_INFO = {'resource_name': _TARGET_HOST, + 'resource_id': _TARGET_HOST, DSProps.DATASOURCE_ACTION: DatasourceAction.SNAPSHOT} @@ -70,6 +71,7 @@ class TestScenarioEvaluator(TestFunctionalBase): event_queue, processor, evaluator = self._init_system() host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE, + _TARGET_HOST, _TARGET_HOST, processor.entity_graph) self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], @@ -98,6 +100,7 @@ class TestScenarioEvaluator(TestFunctionalBase): event_queue, processor, evaluator = self._init_system() host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE, + _TARGET_HOST, _TARGET_HOST, processor.entity_graph) self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], @@ -144,6 +147,7 @@ class TestScenarioEvaluator(TestFunctionalBase): event_queue, processor, evaluator = self._init_system() host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE, + _TARGET_HOST, _TARGET_HOST, processor.entity_graph) self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], @@ -183,6 +187,7 @@ class TestScenarioEvaluator(TestFunctionalBase): event_queue, processor, evaluator = self._init_system() host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE, + _TARGET_HOST, _TARGET_HOST, processor.entity_graph) self.assertEqual('AVAILABLE', host_v[VProps.AGGREGATED_STATE], @@ -478,21 +483,31 @@ class TestScenarioEvaluator(TestFunctionalBase): # test asserts self.assertEqual(num_orig_vertices + num_added_vertices + - num_deduced_vertices + num_nagios_alarm_vertices, + num_deduced_vertices + num_nagios_alarm_vertices + + # This is due to keeping alarm history : + # new alarm doesn't update same deleted alarm. + # Instead, it keeps the old one and creates a new one + 1, entity_graph.num_vertices()) self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges + - num_nagios_alarm_edges, entity_graph.num_edges()) + num_nagios_alarm_edges + 1, 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: 'vitrage', + VProps.IS_DELETED: True} + is_deleted = True + for counter in range(0, 1): + 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], is_deleted) + query = {VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: 'vitrage', VProps.IS_DELETED: False} + is_deleted = False query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'nagios'} port_neighbors = entity_graph.neighbors(port_vertex.vertex_id, @@ -517,21 +532,35 @@ class TestScenarioEvaluator(TestFunctionalBase): # test asserts self.assertEqual(num_orig_vertices + num_added_vertices + - num_deduced_vertices + num_nagios_alarm_vertices, + num_deduced_vertices + num_nagios_alarm_vertices + + # This is due to keeping alarm history : + # new alarm doesn't update same deleted alarm. + # Instead, it keeps the old one and creates a new one + 1, entity_graph.num_vertices()) self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges + - num_nagios_alarm_edges, entity_graph.num_edges()) + num_nagios_alarm_edges + 1, 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: 'vitrage', + VProps.IS_DELETED: True} + is_deleted = True + for counter in range(0, 1): + port_neighbors = entity_graph.neighbors(port_vertex.vertex_id, + vertex_attr_filter=query) + self.assertEqual(2, len(port_neighbors)) + for in_counter in range(0, 1): + self.assertEqual(port_neighbors[in_counter][VProps.CATEGORY], + EntityCategory.ALARM) + self.assertEqual(port_neighbors[in_counter][VProps.TYPE], + 'vitrage') + self.assertEqual(port_neighbors[in_counter][VProps.NAME], + 'simple_port_deduced_alarm') + self.assertEqual( + port_neighbors[in_counter][VProps.IS_DELETED], is_deleted) + + query = {VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: 'vitrage', VProps.IS_DELETED: False} + is_deleted = False query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'nagios'} port_neighbors = entity_graph.neighbors(port_vertex.vertex_id, @@ -551,21 +580,37 @@ class TestScenarioEvaluator(TestFunctionalBase): # test asserts self.assertEqual(num_orig_vertices + num_added_vertices + - num_deduced_vertices + num_nagios_alarm_vertices, + num_deduced_vertices + num_nagios_alarm_vertices + + # This is due to keeping alarm history : + # new alarm doesn't update same deleted alarm. + # Instead, it keeps the old one and creates a new one + # Since this is the second test, there are already two + # alarms of this type + 2, entity_graph.num_vertices()) self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges + - num_nagios_alarm_edges, entity_graph.num_edges()) + num_nagios_alarm_edges + 2, 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: 'vitrage', VProps.IS_DELETED: True} + is_deleted = True + for counter in range(0, 1): + port_neighbors = entity_graph.neighbors(port_vertex.vertex_id, + vertex_attr_filter=query) + self.assertEqual(2, len(port_neighbors)) + for in_counter in range(0, 1): + self.assertEqual(port_neighbors[in_counter][VProps.CATEGORY], + EntityCategory.ALARM) + self.assertEqual(port_neighbors[in_counter][VProps.TYPE], + 'vitrage') + self.assertEqual(port_neighbors[in_counter][VProps.NAME], + 'simple_port_deduced_alarm') + self.assertEqual( + port_neighbors[in_counter][VProps.IS_DELETED], is_deleted) + + query = {VProps.CATEGORY: EntityCategory.ALARM, + VProps.TYPE: 'vitrage', VProps.IS_DELETED: False} + is_deleted = False query = {VProps.CATEGORY: EntityCategory.ALARM, VProps.TYPE: 'nagios'} port_neighbors = entity_graph.neighbors(port_vertex.vertex_id, @@ -710,10 +755,14 @@ class TestScenarioEvaluator(TestFunctionalBase): processor.process_event(event_queue.get()) self.assertEqual(num_orig_vertices + num_added_vertices + - num_deduced_vertices + num_network_alarm_vertices, + num_deduced_vertices + num_network_alarm_vertices + + # This is due to keeping alarm history : + # new alarm doesn't update same deleted alarm. + # Instead, it keeps the old one and creates a new one + 1, entity_graph.num_vertices()) self.assertEqual(num_orig_edges + num_added_edges + num_deduced_edges + - num_network_alarm_edges, entity_graph.num_edges()) + num_network_alarm_edges + 1, entity_graph.num_edges()) query = {VProps.CATEGORY: EntityCategory.ALARM} network_neighbors = entity_graph.neighbors(network_vertex.vertex_id, @@ -725,15 +774,25 @@ class TestScenarioEvaluator(TestFunctionalBase): 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) + query = {VProps.CATEGORY: EntityCategory.ALARM, + VProps.IS_DELETED: True} + is_deleted = True + # Alarm History is saved. We are testing the deleted alarm and + # then we are testing the live alarm + for counter in range(0, 1): + 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], is_deleted) + + query = {VProps.CATEGORY: EntityCategory.ALARM, + VProps.IS_DELETED: False} + is_deleted = False def get_host_after_event(self, event_queue, nagios_event, processor, target_host): @@ -741,12 +800,13 @@ class TestScenarioEvaluator(TestFunctionalBase): while not event_queue.empty(): processor.process_event(event_queue.get()) host_v = self._get_entity_from_graph(NOVA_HOST_DATASOURCE, + target_host, target_host, processor.entity_graph) return host_v def _init_system(self): - processor = self._create_processor_with_graph(self.conf) + processor = self._create_processor_with_graph(self.conf, uuid=True) event_queue = queue.Queue() evaluator = ScenarioEvaluator(self.conf, processor.entity_graph, self.scenario_repository, event_queue, @@ -754,8 +814,11 @@ class TestScenarioEvaluator(TestFunctionalBase): return event_queue, processor, evaluator @staticmethod - def _get_entity_from_graph(entity_type, entity_name, entity_graph): + def _get_entity_from_graph(entity_type, entity_name, + entity_id, + entity_graph): vertex_attrs = {VProps.TYPE: entity_type, + VProps.ID: entity_id, VProps.NAME: entity_name} vertices = entity_graph.get_vertices(vertex_attr_filter=vertex_attrs) # assert len(vertices) == 1, "incorrect number of vertices" diff --git a/vitrage/tests/resources/mock_configurations/driver/driver_host_snapshot_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_host_snapshot_dynamic.json index ec9ae0b77..ad17d3a29 100644 --- a/vitrage/tests/resources/mock_configurations/driver/driver_host_snapshot_dynamic.json +++ b/vitrage/tests/resources/mock_configurations/driver/driver_host_snapshot_dynamic.json @@ -9,6 +9,9 @@ "service": "compute", "vitrage_entity_type": "nova.host", "zone": "zone0", + "id": "compute-0-0.local", + "category": "RESOURCE", + "vitrage_id": "compute-0-0.local", "vitrage_datasource_action": "init_snapshot", "vitrage_sample_date": "2015-12-01T12:46:41Z" } diff --git a/vitrage/tests/resources/mock_configurations/driver/driver_inst_snapshot_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_inst_snapshot_dynamic.json index 98efead8c..79c42b26f 100644 --- a/vitrage/tests/resources/mock_configurations/driver/driver_inst_snapshot_dynamic.json +++ b/vitrage/tests/resources/mock_configurations/driver/driver_inst_snapshot_dynamic.json @@ -72,6 +72,7 @@ "key_name": null, "OS-EXT-SRV-ATTR:hypervisor_hostname": "nyakar-devstack", "name": "vm2", + "category": "RESOURCE", "created": "2015-11-25T14:18:07Z", "tenant_id": "0683517e1e354d2ba25cba6937f44e79", "os-extended-volumes:volumes_attached": [], diff --git a/vitrage/tests/resources/mock_configurations/driver/driver_zone_snapshot_dynamic.json b/vitrage/tests/resources/mock_configurations/driver/driver_zone_snapshot_dynamic.json index 4bb3d6a47..15b6d54f9 100644 --- a/vitrage/tests/resources/mock_configurations/driver/driver_zone_snapshot_dynamic.json +++ b/vitrage/tests/resources/mock_configurations/driver/driver_zone_snapshot_dynamic.json @@ -12,6 +12,9 @@ "vitrage_entity_type": "nova.zone", "vitrage_datasource_action": "snapshot", "zoneName": "zone0", + "id": "zone0", + "vitrage_id": "zone0", + "category": "RESOURCE", "zoneState": { "available": "True" } diff --git a/vitrage/tests/unit/datasources/nova/test_nova_zone_transformer.py b/vitrage/tests/unit/datasources/nova/test_nova_zone_transformer.py index 7177847b2..5182e42fa 100644 --- a/vitrage/tests/unit/datasources/nova/test_nova_zone_transformer.py +++ b/vitrage/tests/unit/datasources/nova/test_nova_zone_transformer.py @@ -208,8 +208,6 @@ class NovaZoneTransformerTest(base.BaseTest): self.assertEqual(cluster_neighbor.vertex[VProps.VITRAGE_ID], cluster_neighbor.vertex.vertex_id) - self.assertEqual('RESOURCE:openstack.cluster:OpenStack Cluster', - cluster_neighbor.vertex[VProps.VITRAGE_ID]) self.assertEqual(False, cluster_neighbor.vertex[VProps.IS_DELETED]) self.assertEqual(EntityCategory.RESOURCE, diff --git a/vitrage/tests/unit/entity_graph/base.py b/vitrage/tests/unit/entity_graph/base.py index 8e29a6951..d665fae98 100644 --- a/vitrage/tests/unit/entity_graph/base.py +++ b/vitrage/tests/unit/entity_graph/base.py @@ -114,7 +114,8 @@ class TestEntityGraphUnitBase(base.BaseTest): # add instance entity with host if processor is None: - processor = proc.Processor(self.conf, InitializationStatus()) + processor = proc.Processor(self.conf, InitializationStatus(), + uuid=True) vertex, neighbors, event_type = processor.transformer_manager\ .transform(event) @@ -146,7 +147,7 @@ class TestEntityGraphUnitBase(base.BaseTest): return events_list[0] @staticmethod - def _create_alarm(vitrage_id, alarm_type, project_id=None): + def _create_alarm(vitrage_id, alarm_type, project_id=None, metadata=None): return graph_utils.create_vertex( vitrage_id, entity_id=vitrage_id, @@ -156,7 +157,8 @@ class TestEntityGraphUnitBase(base.BaseTest): is_deleted=False, sample_timestamp=None, is_placeholder=False, - project_id=project_id + project_id=project_id, + metadata=metadata ) @staticmethod diff --git a/vitrage/tests/unit/entity_graph/processor/test_processor.py b/vitrage/tests/unit/entity_graph/processor/test_processor.py index c40c5df9e..c24133ac6 100644 --- a/vitrage/tests/unit/entity_graph/processor/test_processor.py +++ b/vitrage/tests/unit/entity_graph/processor/test_processor.py @@ -51,7 +51,8 @@ class TestProcessor(TestEntityGraphUnitBase): def test_process_event(self): # check create instance event - processor = proc.Processor(self.conf, InitializationStatus()) + processor = proc.Processor(self.conf, InitializationStatus(), + uuid=True) event = self._create_event(spec_type=self.INSTANCE_SPEC, datasource_action=DSAction.INIT_SNAPSHOT) processor.process_event(event) @@ -145,10 +146,17 @@ class TestProcessor(TestEntityGraphUnitBase): new_edge = graph_utils.create_edge(vertex1.vertex_id, vertex2.vertex_id, 'backup') - new_neighbors = [Neighbor(None, new_edge)] + mock_neighbor = graph_utils.create_vertex( + "asdjashdkahsdashdalksjhd", + entity_id="wtw64768476", + entity_category="RESOURCE", + entity_type="nova.instance", + entity_state="AVAILABLE", + ) + new_neighbors = [Neighbor(mock_neighbor, new_edge)] # action - processor.update_relationship(None, new_neighbors) + processor.update_relationship(vertex1, new_neighbors) # test assertions self.assertEqual(3, processor.entity_graph.num_edges()) @@ -169,10 +177,10 @@ class TestProcessor(TestEntityGraphUnitBase): 'backup') processor.entity_graph.add_edge(new_edge) self.assertEqual(3, processor.entity_graph.num_edges()) - new_neighbors = [Neighbor(None, new_edge)] + new_neighbors = [Neighbor(vertex1, new_edge)] # action - processor.delete_relationship(None, new_neighbors) + processor.delete_relationship(vertex2, new_neighbors) # test assertions edge_from_graph = processor.entity_graph.get_edge(vertex1.vertex_id, diff --git a/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py b/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py index 05d12d1a2..2ca4e0402 100644 --- a/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py +++ b/vitrage/tests/unit/evaluator/recipes/test_set_state_recipe.py @@ -21,6 +21,8 @@ from vitrage.evaluator.template_fields import TemplateFields as TFields from vitrage.graph import Vertex from vitrage.tests import base +_SET_STATES_PARAM_LEN = 3 + class SetStateRecipeTest(base.BaseTest): @@ -47,7 +49,7 @@ class SetStateRecipeTest(base.BaseTest): self.assertEqual(UPDATE_VERTEX, action_steps[0].type) update_vertex_step_params = action_steps[0].params - self.assertEqual(2, len(update_vertex_step_params)) + self.assertEqual(_SET_STATES_PARAM_LEN, len(update_vertex_step_params)) vitrage_state = update_vertex_step_params[VProps.VITRAGE_STATE] self.assertEqual(self.props[TFields.STATE], vitrage_state) @@ -67,7 +69,7 @@ class SetStateRecipeTest(base.BaseTest): self.assertEqual(UPDATE_VERTEX, action_steps[0].type) update_vertex_step_params = action_steps[0].params - self.assertEqual(2, len(update_vertex_step_params)) + self.assertEqual(_SET_STATES_PARAM_LEN, len(update_vertex_step_params)) vitrage_state = update_vertex_step_params[VProps.VITRAGE_STATE] self.assertIsNone(vitrage_state) diff --git a/vitrage/tests/unit/graph/base.py b/vitrage/tests/unit/graph/base.py index ce0e25147..2f692daff 100644 --- a/vitrage/tests/unit/graph/base.py +++ b/vitrage/tests/unit/graph/base.py @@ -26,6 +26,7 @@ from oslo_log import log as logging from vitrage.common.constants import EdgeLabel as ELabel from vitrage.common.constants import EntityCategory +from vitrage.common.constants import VertexProperties as VProps from vitrage.common.exception import VitrageError from vitrage.datasources.nova.host import NOVA_HOST_DATASOURCE from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE @@ -75,7 +76,9 @@ v_alarm = graph_utils.create_vertex( vitrage_id=ALARM + '444444444444', entity_id='444444444444', entity_type=ALARM_ON_VM, - entity_category=ALARM) + entity_category=ALARM, + metadata={VProps.RESOURCE_ID: '333333333333', + VProps.NAME: 'anotheralarm'}) v_switch = graph_utils.create_vertex( vitrage_id=SWITCH + '1212121212', entity_id='1212121212', @@ -95,12 +98,14 @@ e_node_to_switch = graph_utils.create_edge( def add_connected_vertex(graph, entity_type, entity_subtype, entity_id, - edge_type, other_vertex, reverse=False): + edge_type, other_vertex, reverse=False, + metadata=None): vertex = graph_utils.create_vertex( vitrage_id=entity_subtype + str(entity_id), entity_id=entity_id, entity_category=entity_type, - entity_type=entity_subtype) + entity_type=entity_subtype, + metadata=metadata) edge = graph_utils.create_edge( source_id=other_vertex.vertex_id if reverse else vertex.vertex_id, target_id=vertex.vertex_id if reverse else other_vertex.vertex_id, @@ -136,7 +141,8 @@ class GraphTestBase(base.BaseTest): start = time.time() g = NXGraph(name, EntityCategory.RESOURCE + ':' + OPENSTACK_CLUSTER + ':' + - CLUSTER_ID) + CLUSTER_ID, + uuid=True) g.add_vertex(v_node) g.add_vertex(v_switch) g.add_edge(e_node_to_switch) @@ -158,7 +164,9 @@ class GraphTestBase(base.BaseTest): for j in range(num_of_alarms_per_host): add_connected_vertex(g, ALARM, ALARM_ON_HOST, cls.host_alarm_id, ELabel.ON, - host_to_add) + host_to_add, False, + {VProps.RESOURCE_ID: host_id, + VProps.NAME: host_id}) cls.host_alarm_id += 1 # Add Host Tests @@ -183,7 +191,9 @@ class GraphTestBase(base.BaseTest): for k in range(num_of_alarms_per_vm): add_connected_vertex(g, ALARM, ALARM_ON_VM, cls.vm_alarm_id, ELabel.ON, - vm_to_add) + vm_to_add, False, + {VProps.RESOURCE_ID: cls.vm_id - 1, + VProps.NAME: cls.vm_id - 1}) cls.vm_alarm_id += 1 end = time.time() diff --git a/vitrage/tests/unit/graph/test_graph.py b/vitrage/tests/unit/graph/test_graph.py index 07afadc8f..8bbed9731 100644 --- a/vitrage/tests/unit/graph/test_graph.py +++ b/vitrage/tests/unit/graph/test_graph.py @@ -103,7 +103,9 @@ class TestGraph(GraphTestBase): vitrage_id='123', entity_id='456', entity_category=NOVA_INSTANCE_DATASOURCE, - metadata={'some_meta': 'DATA'} + metadata={'some_meta': 'DATA', + 'type': 'nova.instance', + 'resource_id': 'sdg7849ythksjdg'} ) g.add_vertex(another_vertex) v = g.get_vertex(another_vertex.vertex_id) @@ -254,7 +256,8 @@ class TestGraph(GraphTestBase): v4 = v_alarm v5 = utils.create_vertex( vitrage_id='kuku', - entity_category=NOVA_HOST_DATASOURCE) + entity_type=NOVA_HOST_DATASOURCE, + entity_category=EntityCategory.RESOURCE) g = NXGraph('test_neighbors') g.add_vertex(v1) diff --git a/vitrage/tests/unit/graph/test_graph_algo.py b/vitrage/tests/unit/graph/test_graph_algo.py index 6a2511304..9518a0c24 100644 --- a/vitrage/tests/unit/graph/test_graph_algo.py +++ b/vitrage/tests/unit/graph/test_graph_algo.py @@ -21,7 +21,6 @@ Tests for `vitrage` graph driver algorithms from vitrage.common.constants import EdgeLabel from vitrage.common.constants import EdgeProperties as EProps -from vitrage.common.constants import VertexProperties as VProps from vitrage.datasources.heat.stack import HEAT_STACK_DATASOURCE from vitrage.datasources.neutron.network import NEUTRON_NETWORK_DATASOURCE from vitrage.graph.algo_driver.algorithm import Mapping diff --git a/vitrage_tempest_tests/tests/api/event/test_events.py b/vitrage_tempest_tests/tests/api/event/test_events.py index 862d4adf9..6a069d398 100644 --- a/vitrage_tempest_tests/tests/api/event/test_events.py +++ b/vitrage_tempest_tests/tests/api/event/test_events.py @@ -19,6 +19,8 @@ from datetime import datetime from oslo_log import log as logging from oslotest import base +import unittest + from vitrage.common.constants import EntityCategory from vitrage.common.constants import EventProperties as EventProps from vitrage.common.constants import VertexProperties as VProps @@ -39,22 +41,40 @@ class TestEvents(base.BaseTestCase): cls.vitrage_client = \ v_client.Client('1', session=keystone_client.get_session(cls.conf)) - def test_send_doctor_event(self): + def test_send_doctor_event_with_resource_id(self): """Sending an event in Doctor format should result in an alarm""" + details = { + 'hostname': 'host123', + 'source': 'sample_monitor', + 'cause': 'another alarm', + 'severity': 'critical', + 'status': 'down', + 'monitor_id': 'sample monitor', + 'resource_id': 'host123', + 'monitor_event_id': '456', + } + self._test_send_doctor_event(details) + + @unittest.skip("testing skipping") + def test_send_doctor_event_without_resource_id(self): + """Sending an event in Doctor format should result in an alarm""" + details = { + 'hostname': 'host123', + 'source': 'sample_monitor', + 'cause': 'another alarm', + 'severity': 'critical', + 'status': 'down', + 'monitor_id': 'sample monitor', + 'monitor_event_id': '456', + } + self._test_send_doctor_event(details) + + def _test_send_doctor_event(self, details): try: # post an event to the message bus event_time = datetime.now() event_time_iso = event_time.isoformat() event_type = 'compute.host.down' - details = { - 'hostname': 'host123', - 'source': 'sample_monitor', - 'cause': 'another alarm', - 'severity': 'critical', - 'status': 'down', - 'monitor_id': 'sample monitor', - 'monitor_event_id': '456', - } self.vitrage_client.event.post(event_time_iso, event_type, details) @@ -67,12 +87,15 @@ class TestEvents(base.BaseTestCase): alarm = api_alarms[0] event_time_tz = six.u(event_time.strftime('%Y-%m-%dT%H:%M:%SZ')) self._check_alarm(alarm, event_time_tz, event_type, details) + event_time = datetime.now() + event_time_iso = event_time.isoformat() + details['status'] = 'up' + self.vitrage_client.event.post(event_time_iso, event_type, details) except Exception as e: LOG.exception(e) raise finally: - # do what? LOG.warning('done') def _check_alarms(self): diff --git a/vitrage_tempest_tests/tests/api/topology/base.py b/vitrage_tempest_tests/tests/api/topology/base.py index 72f1773d5..866acfd6a 100644 --- a/vitrage_tempest_tests/tests/api/topology/base.py +++ b/vitrage_tempest_tests/tests/api/topology/base.py @@ -29,8 +29,7 @@ class BaseTopologyTest(BaseApiTest): def _rollback_to_default(self): self._delete_entities() api_graph = self.vitrage_client.topology.get( - limit=4, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=None, all_tenants=True) graph = self._create_graph_from_graph_dictionary(api_graph) entities = self._entities_validation_data() diff --git a/vitrage_tempest_tests/tests/api/topology/test_topology.py b/vitrage_tempest_tests/tests/api/topology/test_topology.py index 0ad2a9eda..826af0989 100644 --- a/vitrage_tempest_tests/tests/api/topology/test_topology.py +++ b/vitrage_tempest_tests/tests/api/topology/test_topology.py @@ -31,6 +31,7 @@ NOVA_QUERY = '{"and": [{"==": {"category": "RESOURCE"}},' \ '{"==": {"type": "nova.instance"}},' \ '{"==": {"type": "nova.host"}},' \ '{"==": {"type": "nova.zone"}}]}]}' +CLUSTER_VERTEX_ID = 'RESOURCE:openstack.cluster:OpenStack Cluster' class TestTopology(BaseTopologyTest): @@ -277,7 +278,7 @@ class TestTopology(BaseTopologyTest): # Calculate expected results api_graph = self.vitrage_client.topology.get( limit=2, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=CLUSTER_VERTEX_ID, all_tenants=True) graph = self._create_graph_from_graph_dictionary(api_graph) entities = self._entities_validation_data( @@ -310,7 +311,7 @@ class TestTopology(BaseTopologyTest): # Calculate expected results api_graph = self.vitrage_client.topology.get( limit=3, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=CLUSTER_VERTEX_ID, all_tenants=True) graph = self._create_graph_from_graph_dictionary(api_graph) entities = self._entities_validation_data( @@ -348,14 +349,13 @@ class TestTopology(BaseTopologyTest): # Calculate expected results self.vitrage_client.topology.get( limit=2, - root='RESOURCE:openstack.cluster:OpenStack Cluster', + root=None, all_tenants=True) except ClientException as e: self.assertEqual(403, e.code) self.assertEqual( - str(e), + str(e.message), "Graph-type 'graph' requires a 'root' with 'depth'") - raise finally: self._rollback_to_default()