Api performance enhancements

- Topology code refactored to be more efficient
 - Backend APIs returning large data sets compress the response
 - Garbage Collector configured to collect when there are few
   collectable objects, rather then wait till there are many.
 - Garbage Collector called after every API call.

Change-Id: Ieda233932aff7e6621845544d94f73960ece834c
Depends-On: I5a908238cfd02616bd4a75470057157338530917
This commit is contained in:
Idan Hefetz 2018-11-22 16:55:00 +00:00
parent 5409f4adbe
commit a21a32ccfa
15 changed files with 135 additions and 172 deletions

View File

@ -38,6 +38,7 @@ APPCONFIGS = {}
def setup_app(root, conf=None): def setup_app(root, conf=None):
app_hooks = [hooks.ConfigHook(conf), app_hooks = [hooks.ConfigHook(conf),
hooks.TranslationHook(), hooks.TranslationHook(),
hooks.GCHook(),
hooks.RPCHook(conf), hooks.RPCHook(conf),
hooks.ContextHook(), hooks.ContextHook(),
hooks.DBHook(conf)] hooks.DBHook(conf)]

View File

@ -13,7 +13,6 @@
# under the License. # under the License.
import json
from oslo_log import log from oslo_log import log
from oslo_utils.strutils import bool_from_string from oslo_utils.strutils import bool_from_string
import pecan import pecan
@ -23,7 +22,7 @@ from vitrage.api.controllers.rest import RootRestController
from vitrage.api.policy import enforce from vitrage.api.policy import enforce
from vitrage.common.constants import TenantProps from vitrage.common.constants import TenantProps
from vitrage.common.constants import VertexProperties as Vprops from vitrage.common.constants import VertexProperties as Vprops
from vitrage.common.utils import decompress_obj
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -53,7 +52,7 @@ class BaseAlarmsController(RootRestController):
enforce("list alarms", pecan.request.headers, enforce("list alarms", pecan.request.headers,
pecan.request.enforcer, {}) pecan.request.enforcer, {})
alarms_json = \ alarms = \
pecan.request.client.call(pecan.request.context, pecan.request.client.call(pecan.request.context,
'get_alarms', 'get_alarms',
vitrage_id=vitrage_id, vitrage_id=vitrage_id,
@ -71,7 +70,7 @@ class BaseAlarmsController(RootRestController):
) )
try: try:
alarms_list = json.loads(alarms_json)['alarms'] alarms_list = decompress_obj(alarms)['alarms']
return alarms_list return alarms_list
except Exception: except Exception:

View File

@ -19,7 +19,7 @@ from pecan.core import abort
from vitrage.api.controllers.rest import RootRestController from vitrage.api.controllers.rest import RootRestController
from vitrage.api.policy import enforce from vitrage.api.policy import enforce
from vitrage.common.utils import decompress_obj
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -55,13 +55,12 @@ class ResourcesController(RootRestController):
LOG.info('get_resources with type: %s, all_tenants: %s', LOG.info('get_resources with type: %s, all_tenants: %s',
resource_type, all_tenants) resource_type, all_tenants)
try: try:
resources_json = \ resources = \
pecan.request.client.call(pecan.request.context, pecan.request.client.call(pecan.request.context,
'get_resources', 'get_resources',
resource_type=resource_type, resource_type=resource_type,
all_tenants=all_tenants) all_tenants=all_tenants)
LOG.info(resources_json) resources = decompress_obj(resources)['resources']
resources = json.loads(resources_json)['resources']
return resources return resources
except Exception: except Exception:
LOG.exception('Failed to get resources.') LOG.exception('Failed to get resources.')

View File

@ -9,6 +9,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.
import gc
from vitrage.api.controllers.v1 import alarm from vitrage.api.controllers.v1 import alarm
from vitrage.api.controllers.v1 import event from vitrage.api.controllers.v1 import event
@ -20,6 +21,9 @@ from vitrage.api.controllers.v1 import webhook
class V1Controller(object): class V1Controller(object):
gc.set_threshold(1, 1, 1)
topology = topology.TopologyController() topology = topology.TopologyController()
resources = resource.ResourcesController() resources = resource.ResourcesController()
alarm = alarm.AlarmsController() alarm = alarm.AlarmsController()

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import json import json
import networkx as nx import networkx as nx
@ -27,6 +26,7 @@ from vitrage.api.policy import enforce
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
# noinspection PyProtectedMember # noinspection PyProtectedMember
from vitrage.common.utils import decompress_obj
from vitrage.datasources.transformer_base import CLUSTER_ID from vitrage.datasources.transformer_base import CLUSTER_ID
@ -77,8 +77,7 @@ class TopologyController(RootRestController):
query=query, query=query,
root=root, root=root,
all_tenants=all_tenants) all_tenants=all_tenants)
LOG.debug(graph_data) graph = decompress_obj(graph_data)
graph = json.loads(graph_data)
if graph_type == 'graph': if graph_type == 'graph':
return graph return graph
if graph_type == 'tree': if graph_type == 'tree':

View File

@ -9,7 +9,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.
import gc
import oslo_messaging import oslo_messaging
from oslo_context import context from oslo_context import context
@ -89,3 +89,9 @@ class DBHook(hooks.PecanHook):
def before(self, state): def before(self, state):
state.request.storage = self.storage state.request.storage = self.storage
class GCHook(hooks.PecanHook):
def after(self, state):
gc.collect()

View File

@ -22,6 +22,7 @@ from vitrage.common.constants import EntityCategory as ECategory
from vitrage.common.constants import HistoryProps as HProps from vitrage.common.constants import HistoryProps as HProps
from vitrage.common.constants import TenantProps from vitrage.common.constants import TenantProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.utils import compress_obj
from vitrage.datasources.alarm_properties import AlarmProperties as AProps from vitrage.datasources.alarm_properties import AlarmProperties as AProps
from vitrage.entity_graph.mappings.operational_alarm_severity import \ from vitrage.entity_graph.mappings.operational_alarm_severity import \
OperationalAlarmSeverity OperationalAlarmSeverity
@ -54,7 +55,8 @@ class AlarmApis(EntityGraphApisBase):
kwargs.get('filter_vals', []).append(vitrage_id) kwargs.get('filter_vals', []).append(vitrage_id)
alarms = self._get_alarms(*args, **kwargs) alarms = self._get_alarms(*args, **kwargs)
return json.dumps({'alarms': [v.payload for v in alarms]}) data = {'alarms': [v.payload for v in alarms]}
return compress_obj(data, level=1)
# TODO(annarez): add db support # TODO(annarez): add db support
def show_alarm(self, ctx, vitrage_id): def show_alarm(self, ctx, vitrage_id):

View File

@ -21,7 +21,8 @@ from vitrage.api_handler.apis.base import RESOURCES_ALL_QUERY
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import TenantProps from vitrage.common.constants import TenantProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.utils import compress_obj
from vitrage.common.utils import timed_method
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -34,6 +35,7 @@ class ResourceApis(EntityGraphApisBase):
self.entity_graph = entity_graph self.entity_graph = entity_graph
self.conf = conf self.conf = conf
@timed_method(log_results=True)
def get_resources(self, ctx, resource_type=None, all_tenants=False): def get_resources(self, ctx, resource_type=None, all_tenants=False):
LOG.debug('ResourceApis get_resources - resource_type: %s,' LOG.debug('ResourceApis get_resources - resource_type: %s,'
'all_tenants: %s', str(resource_type), all_tenants) 'all_tenants: %s', str(resource_type), all_tenants)
@ -55,8 +57,8 @@ class ResourceApis(EntityGraphApisBase):
query['and'].append(type_query) query['and'].append(type_query)
resources = self.entity_graph.get_vertices(query_dict=query) resources = self.entity_graph.get_vertices(query_dict=query)
return json.dumps({'resources': [resource.properties data = {'resources': [r.properties for r in resources]}
for resource in resources]}) return compress_obj(data, level=1)
def show_resource(self, ctx, vitrage_id): def show_resource(self, ctx, vitrage_id):

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 networkx.algorithms.shortest_paths.generic import shortest_path
from oslo_log import log from oslo_log import log
from osprofiler import profiler from osprofiler import profiler
@ -24,7 +25,8 @@ from vitrage.common.constants import EntityCategory
from vitrage.common.constants import TenantProps from vitrage.common.constants import TenantProps
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.exception import VitrageError from vitrage.common.exception import VitrageError
from vitrage.datasources.nova.instance import NOVA_INSTANCE_DATASOURCE from vitrage.common.utils import compress_obj
from vitrage.common.utils import timed_method
from vitrage.datasources import OPENSTACK_CLUSTER from vitrage.datasources import OPENSTACK_CLUSTER
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -38,13 +40,13 @@ class TopologyApis(EntityGraphApisBase):
self.entity_graph = entity_graph self.entity_graph = entity_graph
self.conf = conf self.conf = conf
@timed_method(log_results=True)
def get_topology(self, ctx, graph_type, depth, query, root, all_tenants): def get_topology(self, ctx, graph_type, depth, query, root, all_tenants):
LOG.debug("TopologyApis get_topology - root: %s, all_tenants=%s", LOG.debug("TopologyApis get_topology - root: %s, all_tenants=%s",
str(root), all_tenants) str(root), all_tenants)
project_id = ctx.get(TenantProps.TENANT, None) project_id = ctx.get(TenantProps.TENANT, None)
is_admin_project = ctx.get(TenantProps.IS_ADMIN, False) is_admin_project = ctx.get(TenantProps.IS_ADMIN, False)
ga = self.entity_graph.algo
LOG.debug('project_id = %s, is_admin_project %s', LOG.debug('project_id = %s, is_admin_project %s',
project_id, is_admin_project) project_id, is_admin_project)
@ -63,7 +65,8 @@ class TopologyApis(EntityGraphApisBase):
{'==': {VProps.PROJECT_ID: None}}]} {'==': {VProps.PROJECT_ID: None}}]}
current_query = {'and': [query, project_query]} current_query = {'and': [query, project_query]}
graph = ga.graph_query_vertices(root_id, graph = self.entity_graph.algo.graph_query_vertices(
root_id,
query_dict=current_query, query_dict=current_query,
depth=depth, depth=depth,
edge_query_dict=EDGE_QUERY) edge_query_dict=EDGE_QUERY)
@ -71,21 +74,21 @@ class TopologyApis(EntityGraphApisBase):
else: else:
if all_tenants: if all_tenants:
q = query if query else TOPOLOGY_AND_ALARMS_QUERY q = query if query else TOPOLOGY_AND_ALARMS_QUERY
graph = ga.create_graph_from_matching_vertices( graph = \
self.entity_graph.algo.create_graph_from_matching_vertices(
query_dict=q, query_dict=q,
edge_attr_filter={VProps.VITRAGE_IS_DELETED: False}) edge_attr_filter={VProps.VITRAGE_IS_DELETED: False})
else: else:
graph = self._get_topology_for_specific_project( graph = self._get_topology_for_specific_project(
ga,
query, query,
project_id, project_id,
is_admin_project, is_admin_project,
root_id) root_id)
return graph.json_output_graph() data = graph.json_output_graph(raw=True)
return compress_obj(data, level=1)
def _get_topology_for_specific_project(self, def _get_topology_for_specific_project(self,
ga,
query, query,
project_id, project_id,
is_admin_project, is_admin_project,
@ -95,7 +98,6 @@ class TopologyApis(EntityGraphApisBase):
Finds all the entities which has project_id. In case the tenant is Finds all the entities which has project_id. In case the tenant is
admin then project_id can also be None. admin then project_id can also be None.
:type ga: NXAlgorithm
:type query: dictionary :type query: dictionary
:type project_id: string :type project_id: string
:type is_admin_project: boolean :type is_admin_project: boolean
@ -118,25 +120,24 @@ class TopologyApis(EntityGraphApisBase):
default_query = {'or': [resource_query, alarm_query]} default_query = {'or': [resource_query, alarm_query]}
q = default_query q = default_query
tmp_graph = ga.create_graph_from_matching_vertices(query_dict=q) vertices_ids = self.entity_graph.get_vertices_ids(query_dict=q)
graph = self._create_graph_of_connected_components(ga, tmp_graph, root) vertices_ids = self._all_paths_from_node(self.entity_graph,
source_node=root,
targets=vertices_ids)
graph = self.entity_graph.algo.subgraph(vertices_ids).copy()
edge_query = {EProps.VITRAGE_IS_DELETED: False} edge_query = {EProps.VITRAGE_IS_DELETED: False}
self._remove_unnecessary_elements(ga, self._remove_unnecessary_elements(
graph, graph, project_id, is_admin_project, edge_attr_filter=edge_query)
project_id,
is_admin_project,
edge_attr_filter=edge_query)
return graph return graph
def _remove_unnecessary_elements(self, def _remove_unnecessary_elements(self,
ga,
graph, graph,
project_id, project_id,
is_admin_project, is_admin_project,
edge_attr_filter): edge_attr_filter):
# delete non matching edges # delete non matching edges
ga._apply_edge_attr_filter(graph, edge_attr_filter) self.entity_graph.algo.apply_edge_attr_filter(graph, edge_attr_filter)
self._remove_alarms_of_other_projects(graph, self._remove_alarms_of_other_projects(graph,
project_id, project_id,
@ -173,44 +174,19 @@ class TopologyApis(EntityGraphApisBase):
if cond1 or cond2: if cond1 or cond2:
graph.remove_vertex(alarm) graph.remove_vertex(alarm)
def _create_graph_of_connected_components(self, ga, tmp_graph, root): @staticmethod
return ga.subgraph(self._topology_for_unrooted_graph(ga, def _all_paths_from_node(graph, source_node, targets):
tmp_graph, """Find all nodes on a (shortest) path from source to targets
root)).copy()
def _topology_for_unrooted_graph(self, ga, subgraph, root): Return all the node ids that are either in targets
"""Finds topology for unrooted subgraph or are in a path from source node to any of targets
1. Finds all the connected component subgraphs in subgraph.
2. For each component, finds the path from one of the VMs (if exists)
to the root entity.
3. Unify all the entities found and return them
:type ga: NXAlgorithm
:type subgraph: networkx graph
:type root: string
:rtype: list :rtype: list
""" """
vertices_ids = targets
entities = [] paths = shortest_path(graph._g, source=source_node)
vertices_ids.update(*[set(paths.get(n, [])) for n in targets])
root_vertex = \ return vertices_ids
self.entity_graph.get_vertex(root)
local_connected_component_subgraphs = \
ga.connected_component_subgraphs(subgraph)
for component_subgraph in local_connected_component_subgraphs:
entities += list(component_subgraph.nodes())
instance_in_component_subgraph = \
self._find_instance_in_graph(component_subgraph)
if instance_in_component_subgraph:
paths = ga.all_simple_paths(root_vertex.vertex_id,
instance_in_component_subgraph)
for path in paths:
entities += path
return set(entities)
def _default_root_id(self): def _default_root_id(self):
tmp_vertices = self.entity_graph.get_vertices( tmp_vertices = self.entity_graph.get_vertices(
@ -221,13 +197,3 @@ class TopologyApis(EntityGraphApisBase):
if len(tmp_vertices) > 1: if len(tmp_vertices) > 1:
raise VitrageError("Multiple root vertices found") raise VitrageError("Multiple root vertices found")
return tmp_vertices[0].vertex_id return tmp_vertices[0].vertex_id
@staticmethod
def _find_instance_in_graph(graph):
for node, node_data in graph.nodes(data=True):
if node_data[VProps.VITRAGE_CATEGORY] == \
EntityCategory.RESOURCE \
and node_data[VProps.VITRAGE_TYPE] == \
NOVA_INSTANCE_DATASOURCE:
return node
return None

View File

@ -16,18 +16,25 @@
# 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.
import base64
from collections import defaultdict from collections import defaultdict
import copy import copy
import hashlib import hashlib
import itertools import itertools
import random import random
import six import six
from six.moves import cPickle
import threading import threading
import time
import zlib
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log
import cProfile import cProfile
LOG = log.getLogger(__name__)
def recursive_keypairs(d, separator='.'): def recursive_keypairs(d, separator='.'):
# taken from ceilometer and gnocchi # taken from ceilometer and gnocchi
@ -116,3 +123,35 @@ def fmt(docstr):
docstr = docstr.strip() docstr = docstr.strip()
return docstr return docstr
def timed_method(log_results=False, warn_above_sec=-1):
def _decorator(function):
def wrapper(*args, **kwargs):
t1 = time.time()
result = function(*args, **kwargs)
t2 = time.time()
if warn_above_sec > 0 and warn_above_sec < t2 - t1:
LOG.warning(
'Function %s runtime crossed limit %s seconds.',
function.__name__, t2 - t1)
elif log_results:
LOG.info('Function %s timed %s', function.__name__, t2 - t1)
return result
return wrapper
return _decorator
def compress_obj(obj, level=9):
str_data = cPickle.dumps(obj)
data = base64.b64encode(zlib.compress(str_data, level))
return data
def decompress_obj(blob):
decoded_blob = base64.standard_b64decode(blob)
str_data = zlib.decompress(decoded_blob)
obj = cPickle.loads(str_data)
del decoded_blob
del str_data
return obj

View File

@ -79,29 +79,8 @@ class GraphAlgorithm(object):
""" """
pass pass
@staticmethod
def connected_component_subgraphs(subgraph):
"""Generate connected components as subgraphs.
:type subgraph: NetworkX graph.
:rtype: list of NXGraphs
"""
pass
def all_simple_paths(self, source, target):
"""Generate all simple paths in the graph G from source to target.
A simple path is a path with no repeated nodes.
:type source: Starting node for path
:type target: Ending node for path
:rtype: lists of simple paths
"""
pass
@abc.abstractmethod @abc.abstractmethod
def create_graph_from_matching_vertices(self, def create_graph_from_matching_vertices(self,
vertex_attr_filter=None,
query_dict=None, query_dict=None,
edge_attr_filter=None): edge_attr_filter=None):
"""Generate graph using the query """Generate graph using the query
@ -109,7 +88,6 @@ class GraphAlgorithm(object):
Finds all the vertices in the graph matching the query, and returns Finds all the vertices in the graph matching the query, and returns
a subgraph consisted from the vertices a subgraph consisted from the vertices
:type vertex_attr_filter: dictionary
:type query_dict: dictionary :type query_dict: dictionary
:type edge_attr_filter: dictionary :type edge_attr_filter: dictionary
:rtype: NXGraph :rtype: NXGraph

View File

@ -12,9 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from networkx.algorithms import components
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.common.constants import EdgeProperties as EProps
@ -23,8 +20,6 @@ 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 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 Vertex
from vitrage.graph.filter import check_filter from vitrage.graph.filter import check_filter
from vitrage.graph.query import create_predicate from vitrage.graph.query import create_predicate
@ -84,10 +79,11 @@ class NXAlgorithm(GraphAlgorithm):
e_result.extend(e_list) e_result.extend(e_list)
nodes_q.extend([(v_id, curr_depth + 1) for v_id, data in n_list]) nodes_q.extend([(v_id, curr_depth + 1) for v_id, data in n_list])
graph = self._create_new_graph( graph = self._create_new_graph(graph.name)
graph.name, for v_id, data in n_result:
vertices=self._vertex_result_to_list(n_result), graph._g.add_node(v_id, **data)
edges=self._edge_result_to_list(e_result)) for source_id, target_id, label, data in e_result:
graph._g.add_edge(source_id, target_id, label, **data)
return graph return graph
@ -128,32 +124,15 @@ class NXAlgorithm(GraphAlgorithm):
validate) validate)
def create_graph_from_matching_vertices(self, def create_graph_from_matching_vertices(self,
vertex_attr_filter=None,
query_dict=None, query_dict=None,
edge_attr_filter=None): edge_attr_filter=None):
if query_dict: vertices_ids = self.graph.get_vertices_ids(query_dict=query_dict)
vertices = self.graph.get_vertices(query_dict=query_dict)
elif vertex_attr_filter:
vertices = self.graph.get_vertices(
vertex_attr_filter=vertex_attr_filter)
else:
vertices = self.graph.get_vertices()
vertices_ids = [vertex.vertex_id for vertex in vertices]
graph = self._create_new_graph('graph') graph = self._create_new_graph('graph')
graph._g = self.graph._g.subgraph(vertices_ids).copy() graph._g = self.graph._g.subgraph(vertices_ids)
# delete non matching edges # delete non matching edges
if edge_attr_filter: if edge_attr_filter:
self._apply_edge_attr_filter(graph, edge_attr_filter) self.apply_edge_attr_filter(graph, edge_attr_filter)
LOG.debug('match query, find graph: nodes %s, edges %s',
str(list(graph._g.nodes(data=True))),
str(list(graph._g.edges(data=True))))
LOG.debug('match query, real graph: nodes %s, edges %s',
str(list(self.graph._g.nodes(data=True))),
str(list(self.graph._g.edges(data=True))))
return graph return graph
@ -162,15 +141,6 @@ class NXAlgorithm(GraphAlgorithm):
subgraph._g = self.graph._g.subgraph(entities) subgraph._g = self.graph._g.subgraph(entities)
return subgraph return subgraph
def connected_component_subgraphs(self, subgraph):
return components.connected_component_subgraphs(
subgraph._g.to_undirected(), copy=False)
def all_simple_paths(self, source, target):
return simple_paths.all_simple_paths(self.graph._g,
source=source,
target=target)
def _filtered_subgraph_matching(self, def _filtered_subgraph_matching(self,
ge_v_id, ge_v_id,
sge_v_id, sge_v_id,
@ -191,21 +161,6 @@ class NXAlgorithm(GraphAlgorithm):
return [] 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 @staticmethod
def _list_union(list_1, list_2): def _list_union(list_1, list_2):
"""Union of list that aren't hashable """Union of list that aren't hashable
@ -223,7 +178,7 @@ class NXAlgorithm(GraphAlgorithm):
return list_1 return list_1
@staticmethod @staticmethod
def _apply_edge_attr_filter(graph, edge_attr_filter): def apply_edge_attr_filter(graph, edge_attr_filter):
edges = graph._g.edges(data=True, keys=True) edges = graph._g.edges(data=True, keys=True)
edges_to_remove = [(u, v, k) for (u, v, k, d) in edges edges_to_remove = [(u, v, k) for (u, v, k, d) in edges
if not check_filter(d, edge_attr_filter)] if not check_filter(d, edge_attr_filter)]

View File

@ -250,6 +250,17 @@ class NXGraph(Graph):
else: else:
return [] return []
def get_vertices_ids(self, query_dict):
if not query_dict:
return list(self._g.nodes())
vertices_ids = set()
match_func = create_predicate(query_dict)
for node, node_data in self._g.nodes(data=True):
if match_func(node_data):
vertices_ids.add(node)
return vertices_ids
def get_vertices_by_key(self, key_values_hash): def get_vertices_by_key(self, key_values_hash):
if key_values_hash in self.key_to_vertex_ids: if key_values_hash in self.key_to_vertex_ids:

View File

@ -18,6 +18,7 @@
from datetime import datetime from datetime import datetime
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
from mock import mock from mock import mock
from vitrage.common.utils import compress_obj
from vitrage.storage.sqlalchemy import models from vitrage.storage.sqlalchemy import models
from vitrage.tests.functional.api.v1 import FunctionalTest from vitrage.tests.functional.api.v1 import FunctionalTest
@ -54,7 +55,7 @@ class NoAuthTest(FunctionalTest):
def test_noauth_mode_get_topology(self): def test_noauth_mode_get_topology(self):
with mock.patch('pecan.request') as request: with mock.patch('pecan.request') as request:
request.client.call.return_value = '{}' request.client.call.return_value = compress_obj({})
params = dict(depth=None, graph_type='graph', query=None, params = dict(depth=None, graph_type='graph', query=None,
root=None, root=None,
all_tenants=False) all_tenants=False)
@ -66,7 +67,7 @@ class NoAuthTest(FunctionalTest):
def test_noauth_mode_list_alarms(self): def test_noauth_mode_list_alarms(self):
with mock.patch('pecan.request') as request: with mock.patch('pecan.request') as request:
request.client.call.return_value = '{"alarms": []}' request.client.call.return_value = compress_obj({"alarms": []})
params = dict(vitrage_id='all', all_tenants=False) params = dict(vitrage_id='all', all_tenants=False)
data = self.get_json('/alarm/', params=params) data = self.get_json('/alarm/', params=params)
@ -95,7 +96,7 @@ class NoAuthTest(FunctionalTest):
def test_noauth_mode_list_resources(self): def test_noauth_mode_list_resources(self):
with mock.patch('pecan.request') as request: with mock.patch('pecan.request') as request:
request.client.call.return_value = '{"resources": []}' request.client.call.return_value = compress_obj({"resources": []})
params = dict(resource_type='all', all_tenants=False) params = dict(resource_type='all', all_tenants=False)
data = self.get_json('/resources/', params=params) data = self.get_json('/resources/', params=params)

View File

@ -26,6 +26,7 @@ from vitrage.common.constants import EdgeLabel
from vitrage.common.constants import EdgeProperties from vitrage.common.constants import EdgeProperties
from vitrage.common.constants import EntityCategory from vitrage.common.constants import EntityCategory
from vitrage.common.constants import VertexProperties as VProps from vitrage.common.constants import VertexProperties as VProps
from vitrage.common.utils import decompress_obj
from vitrage.datasources import NOVA_HOST_DATASOURCE from vitrage.datasources import NOVA_HOST_DATASOURCE
from vitrage.datasources import NOVA_INSTANCE_DATASOURCE from vitrage.datasources import NOVA_INSTANCE_DATASOURCE
from vitrage.datasources import NOVA_ZONE_DATASOURCE from vitrage.datasources import NOVA_ZONE_DATASOURCE
@ -60,7 +61,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
# Action # Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False) alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False)
alarms = json.loads(alarms)['alarms'] alarms = decompress_obj(alarms)['alarms']
# Test assertions # Test assertions
self.assertThat(alarms, matchers.HasLength(3)) self.assertThat(alarms, matchers.HasLength(3))
@ -74,7 +75,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
# Action # Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False) alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=False)
alarms = json.loads(alarms)['alarms'] alarms = decompress_obj(alarms)['alarms']
# Test assertions # Test assertions
self.assertThat(alarms, matchers.HasLength(2)) self.assertThat(alarms, matchers.HasLength(2))
@ -105,7 +106,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
# Action # Action
alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=True) alarms = apis.get_alarms(ctx, vitrage_id='all', all_tenants=True)
alarms = json.loads(alarms)['alarms'] alarms = decompress_obj(alarms)['alarms']
# Test assertions # Test assertions
self.assertThat(alarms, matchers.HasLength(5)) self.assertThat(alarms, matchers.HasLength(5))
@ -200,7 +201,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
query=None, query=None,
root=None, root=None,
all_tenants=False) all_tenants=False)
graph_topology = json.loads(graph_topology) graph_topology = decompress_obj(graph_topology)
# Test assertions # Test assertions
self.assertThat(graph_topology['nodes'], matchers.HasLength(8)) self.assertThat(graph_topology['nodes'], matchers.HasLength(8))
@ -222,7 +223,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
query=None, query=None,
root=None, root=None,
all_tenants=False) all_tenants=False)
graph_topology = json.loads(graph_topology) graph_topology = decompress_obj(graph_topology)
# Test assertions # Test assertions
self.assertThat(graph_topology['nodes'], matchers.HasLength(7)) self.assertThat(graph_topology['nodes'], matchers.HasLength(7))
@ -244,7 +245,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
query=None, query=None,
root=None, root=None,
all_tenants=True) all_tenants=True)
graph_topology = json.loads(graph_topology) graph_topology = decompress_obj(graph_topology)
# Test assertions # Test assertions
self.assertThat(graph_topology['nodes'], matchers.HasLength(12)) self.assertThat(graph_topology['nodes'], matchers.HasLength(12))
@ -260,7 +261,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
ctx, ctx,
resource_type=None, resource_type=None,
all_tenants=False) all_tenants=False)
resources = json.loads(resources)['resources'] resources = decompress_obj(resources)['resources']
# Test assertions # Test assertions
self.assertThat(resources, matchers.HasLength(5)) self.assertThat(resources, matchers.HasLength(5))
@ -276,7 +277,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
ctx, ctx,
resource_type=None, resource_type=None,
all_tenants=False) all_tenants=False)
resources = json.loads(resources)['resources'] resources = decompress_obj(resources)['resources']
# Test assertions # Test assertions
self.assertThat(resources, matchers.HasLength(2)) self.assertThat(resources, matchers.HasLength(2))
@ -292,7 +293,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
ctx, ctx,
resource_type=NOVA_HOST_DATASOURCE, resource_type=NOVA_HOST_DATASOURCE,
all_tenants=False) all_tenants=False)
resources = json.loads(resources)['resources'] resources = decompress_obj(resources)['resources']
# Test assertions # Test assertions
self.assertThat(resources, IsEmpty()) self.assertThat(resources, IsEmpty())
@ -308,7 +309,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
ctx, ctx,
resource_type=NOVA_INSTANCE_DATASOURCE, resource_type=NOVA_INSTANCE_DATASOURCE,
all_tenants=False) all_tenants=False)
resources = json.loads(resources)['resources'] resources = decompress_obj(resources)['resources']
# Test assertions # Test assertions
self.assertThat(resources, matchers.HasLength(2)) self.assertThat(resources, matchers.HasLength(2))
@ -324,7 +325,7 @@ class TestApis(TestEntityGraphUnitBase, TestConfiguration):
ctx, ctx,
resource_type=None, resource_type=None,
all_tenants=True) all_tenants=True)
resources = json.loads(resources)['resources'] resources = decompress_obj(resources)['resources']
# Test assertions # Test assertions
self.assertThat(resources, matchers.HasLength(7)) self.assertThat(resources, matchers.HasLength(7))